Spring核心技术--AOP
来源:互联网 发布:java 字节码 汇编 编辑:程序博客网 时间:2024/05/16 15:45
Spring核心技术–AOP
在OOP中, 模块化的单位是class, 在AOP中, 模块化的单位是aspect.
Spring IoC容器并不直接与AOP模块耦合, AOP模块是作为一个中间件方案提供给IoC容器使用的.
声明式事务管理就是AOP在Spring框架中的一个典型实现, 另外, 池化也是一个典型的应用.
对于Spring AOP编程来说, 推荐使用能够完成需求功能的最小Advice种类进行实现. 例如, 如果你想简单实现用返回值更新缓存, 那么应该使用@After Returning
Advice而不是@Around
Advice. 虽然Around
advice更加强大, 也能完成相同的功能, 但是也更容易出错.
在Spring中, Pointcut + Advice = Spring AOP基本组件
Spring AOP的能力和局限性
只能用于方法级别的连接点, 不支持属性级别的拦截.
如果想实现属性级别的拦截, 考虑使用AspectJ.
Spring AOP与AspectJ并不是相互替代的关系, Spring AOP也不是用来取代AspectJ或者是其他AOP框架, Spring AOP只是为了更好的为Spring IoC容器提供一些通用问题的解决方案. 你可以同时使用AspectJ作为Spring AOP的补充.
注意: Spring AOP的使用借鉴了AspectJ的一些语法(例如使用了一些AspectJ同名的注解, 和切点解释, 匹配的方法, spring-aop模块默认已经添加了依赖, 无需自己手动整合), 实际实现与AspectJ本身大相径庭.
AOP Proxy类型
Spring AOP的实现方式是proxy-based AOP. 默认使用JDK动态代理作为AOP Proxy的实现方式. JDK动态代理能够代理被代理对象的所有接口.
Spring AOP也能使用CGLIB代理, 当一个business object中想被代理的某非final方法不是接口中的方法的时候, Spring AOP就会使用GCLIB的方式进行代理.
注意: 基于proxy的aop都只能够拦截被代理对象的外部调用, 也就是下例的方式不会被advice拦截:
public class SimplePojo implements Pojo { public void foo() { // this next method invocation is a direct call on the 'this' reference // 当proxy对象调用实际的被代理对象的foo方法时, this指针已经指向被代理对象, 所以bar方法不会被advice拦截 this.bar(); } public void bar() { // some logic... }}
这种情况下只能使用AspectJ的方式通过weaving的方式实现AOP.
使用AspectJ风格进行Pointcut定义
在切点定义中, 可用的切点类型主要有以下几种.
注意: 在Spring AOP中, this指的是代理类的instance, target才是被代理类的instance.
组合切点
使用’&&’, ‘||’和’!’进行切点定义的组合, 实现复杂的切点定义.
下面是一个例子
// 匹配任意公有方法的切点@Pointcut("execution(public * *(..))")private void anyPublicOperation() {}// 匹配任意trading包中方法的切点@Pointcut("within(com.xyz.someapp.trading..*)")private void inTrading() {}// 组合切点: 匹配trading包中任意共有方法的切点@Pointcut("anyPublicOperation() && inTrading()")private void tradingOperation() {}
按层定义可复用的切点定义
下面是一个符合这个规范的例子:
package com.xyz.someapp;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;@Aspectpublic class SystemArchitecture { /** * A join point is in the web layer if the method is defined * in a type in the com.xyz.someapp.web package or any sub-package * under that. */ @Pointcut("within(com.xyz.someapp.web..*)") public void inWebLayer() {} /** * A join point is in the service layer if the method is defined * in a type in the com.xyz.someapp.service package or any sub-package * under that. */ @Pointcut("within(com.xyz.someapp.service..*)") public void inServiceLayer() {} /** * A join point is in the data access layer if the method is defined * in a type in the com.xyz.someapp.dao package or any sub-package * under that. */ @Pointcut("within(com.xyz.someapp.dao..*)") public void inDataAccessLayer() {} /** * A business service is the execution of any method defined on a service * interface. This definition assumes that interfaces are placed in the * "service" package, and that implementation types are in sub-packages. * * If you group service interfaces by functional area (for example, * in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then * the pointcut expression "execution(* com.xyz.someapp.service.*.*(..))" * could be used instead. * * Alternatively, you can write the expression using the 'bean' * PCD, like so "bean(*Service)". (This assumes that you have * named your Spring service beans in a consistent fashion.) */ @Pointcut("execution(* com.xyz.someapp..service.*.*(..))") public void businessService() {} /** * A data access operation is the execution of any method defined on a * dao interface. This definition assumes that interfaces are placed in the * "dao" package, and that implementation types are in sub-packages. */ @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))") public void dataAccessOperation() {}}
一些切点定义的例子
这里我们以execution
类型的切点定义为例, 通用的模式是:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
其中, 必选字段是
- ret-type-pattern
- name-pattern
- param-pattern
表达式支持’*’, ‘..’等通配符, 下面是一些符合上述定义的例子:
// 所有public方法execution(public * *(..))// 所有以set开头的方法execution(* set*(..))// AccoutService接口中定义的所有方法execution(* com.xyz.service.AccountService.*(..))// service包中定义的所有方法execution(* com.xyz.service.*.*(..))// service及其子包中定义的所有方法execution(* com.xyz.service..*.*(..))// service包中定义的所有方法within(com.xyz.service.*)// service及其子包中定义的所有方法within(com.xyz.service..*)// 所有实现了AccountService接口的代理类instance的所有方法this(com.xyz.service.AccountService)// 所有实现了AccountService接口的被代理类instance的所有方法target(com.xyz.service.AccountService)// 所有接受单个java.io.Serializable类型参数的方法(这个接口只要是参数的接口之一即可)// 注意: 如果是execution(* *(java.io.Serializable)), 要求参数类型声明必须严格是java.io.Serializable. args只要是Serializable的就可以了, 匹配面更广args(java.io.Serializable)
如何定义好的切点
我们定义的任何切点都会在运行时被AspectJ重写和优化, 但即使如此, 我们也应该通过更明确的显式定义, 加速AspectJ切点匹配的时间和空间消耗.
前面介绍过的切点基本上可以分为三类:
- 按类型定义的切点: 如
execution
类型. - 按范围定义的切点: 如
within
类型. - 上下文相关的切点: 如
this
,target
.
一个好的切点定义应该尽量包括1, 2两种类型的定义, 再退一步, 至少要包含第2种定义, 因为within
类型能够快速抛弃掉不符合的大量切点, 极大加速运行时解析速度.
使用AspectJ风格进行Advice定义
一个Advice与一个Pointcut绑定, 构成AOP的基本组件.
以下是一些不同类型的Advice定义:
@AfterReturning( pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", returning="retVal") // returning能够对被代理类的对象的方法执行返回结果进行绑定public void doAccessCheck(Object retVal) { // ...}@AfterThrowing( pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", throwing="ex") // 可以根据方法参数指定拦截的异常类型public void doRecoveryActions(DataAccessException ex) { // ...}// 使用@Around advice, 一般是有@Before和@After共享状态的需要时启用// 下面是一个使用@Around实现的方法执行耗时计算.// Spring的缓存机制也是使用这种Advice实现的.@Around("com.xyz.myapp.SystemArchitecture.businessService()")public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch // 执行方法(甚至可以不执行, 决定权完全掌握在应用编写者手中!) Object retVal = pjp.proceed(); // stop stopwatch return retVal;}
在Advice中获取连接点(方法)状态
在Spring AOP中, 仅支持方法作为连接点, 所以Advice获取的总是方法的状态(包括参数, 方法签名, 类名等等).
Spring中要想获取连接点的状态, 必须将连接点作为Advice方法的第一个参数传入, 主要用两个类型描述:
org.aspectj.lang.JoinPoint
ProceedingJoinPoint
:@Around
Advice使用
上述类型有很多实用的方法, 如:
getArgs()
getThis()
getTarget()
getSignature()
toString
获取和使用连接点(方法)参数
下面是一个例子:
@Around("execution(List<Account> find*(..)) && " + "com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " + "args(accountHolderNamePattern)")public Object preProcessQueryPattern(ProceedingJoinPoint pjp, String accountHolderNamePattern) throws Throwable { String newPattern = preProcess(accountHolderNamePattern); return pjp.proceed(new Object[] {newPattern});}
Advice执行顺序
当多个Advice作用于同一个连接点的时候, 我们可以通过
Order
来显式指定Advice优先级.
举个例子:
@Before
: 优先级高的, 先运行;@After
: 优先级高的, 后运行;
使用AspectJ风格进行introductions定义
introductions允许一个aspect为连接点方法对应的this对象(proxy对象)引入一个新的接口, 并给出默认的实现, 作为proxy对象的增强实现.
简单的说, 就是能够动态为this对象(proxy对象)扩展某接口的功能.
下面是一个用例:
@Aspectpublic class UsageTracking { // @DeclareParents为value对应的所有类型的代理对象引入了新的接口实现, 也就是说对于指定类型, 我们可以使用UsageTracked usageTracked = (UsageTracked) context.getBean("myService"); 去获取它们了. 注意: 它们的类型声明中本没有实现UsageTracked接口 @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class) public static UsageTracked mixin; @Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)") public void recordUsage(UsageTracked usageTracked) { usageTracked.incrementUseCount(); }}
案例解析
案例1: 使用AOP实现失败自动重试
需求说明: 对于一些会由于并发问题(例如死锁)导致的服务执行失败, 如果操作是幂等性的, 我们希望程序能够透明的重试, 而不是抛给用户一个PessimisticLockingFailureException
.
// 定义一个幂等操作标识, 用于辅助切面识别@Retention(RetentionPolicy.RUNTIME)public @interface Idempotent { // marker annotation}@Aspect // 实现Ordered接口能够让我们的类中定义一个order属性, 执行的时候, 会按照order属性的顺序进行执行(值越大,优先级越低, 相同值的执行顺序随机. 与servlet执行顺序策略相似)public class ConcurrentOperationExecutor implements Ordered { private static final int DEFAULT_MAX_RETRIES = 2; private int maxRetries = DEFAULT_MAX_RETRIES; private int order = 1; public void setMaxRetries(int maxRetries) { this.maxRetries = maxRetries; } public int getOrder() { return this.order; } public void setOrder(int order) { this.order = order; } @Around("com.xyz.myapp.SystemArchitecture.businessService() && " + "@annotation(com.xyz.myapp.service.Idempotent)") public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { int numAttempts = 0; PessimisticLockingFailureException lockFailureException; do { numAttempts++; try { return pjp.proceed(); } catch(PessimisticLockingFailureException ex) { // 如果出现了并发问题, 先捕捉, 不抛出, 尝试重试 lockFailureException = ex; } } while(numAttempts <= this.maxRetries); // 如果还可以重试 // 达到了最大重试次数, 抛出异常 throw lockFailureException; }}@Configuratinpublic class AspectConfig { // 手动进行Aspect的初始化 @Bean public ConcurrentOperationExecutor config() { return new ConcurrentOperationExecutor(3, 100); }}
小结
以上.
参考链接:
- Spring官方文档–AOP编程
- Spring核心技术--AOP
- Spring核心技术之AOP
- Spring核心技术Ioc和AOP
- Spring核心技术之Ioc和AOP
- Spring核心技术之Ioc和AOP
- Spring核心技术阐述(IOC、DI、AOP)
- Spring核心技术阐述(IOC、DI、AOP)
- Spring核心技术之Ioc和AOP
- Spring核心技术之Ioc和AOP
- Spring 核心技术——AOP 简介
- Spring核心技术
- Spring核心技术
- Spring核心技术
- Spring核心技术
- Spring核心技术
- Spring核心技术原理-(2)-通过Web开发演进过程了解一下为什么要有Spring AOP?
- Spring核心技术Spring容器
- AOP: Spring3核心技术之AOP配置
- linux 各类文件解压方法
- 硬解码和软解码
- es6学习随笔
- 事物的四大特性
- js日历插件--laydate
- Spring核心技术--AOP
- iOS 2D绘图详解(Quartz 2D)之路径(点,直线,虚线,曲线,圆弧,椭圆,矩形)
- Anroid ListView分组和悬浮Header实现
- cas 4.2.x 单点登出
- mysql死锁分析
- win2012 r2 + IIS8.5 伪静态设置脚本映射
- java用命令行编译运行时带有包和使用外部jar包的情况
- aria-label及aria-labelledby应用
- poj