在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基本组件
只能用于方法级别的连接点, 不支持属性级别的拦截.
如果想实现属性级别的拦截, 考虑使用AspectJ.
Spring AOP与AspectJ并不是相互替代的关系, Spring AOP也不是用来取代AspectJ或者是其他AOP框架, Spring AOP只是为了更好的为Spring IoC容器提供一些通用问题的解决方案. 你可以同时使用AspectJ作为Spring AOP的补充.
注意: Spring AOP的使用借鉴了AspectJ的一些语法(例如使用了一些AspectJ同名的注解, 和切点解释, 匹配的方法, spring-aop模块默认已经添加了依赖, 无需自己手动整合), 实际实现与AspectJ本身大相径庭.
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.
在切点定义中, 可用的切点类型主要有以下几种.
切点类型 | 简介 |
---|---|
execution | (最常用)匹配任意符合的方法 |
within | 匹配指定类型中的方法 |
this | 仅匹配动态代理类中的方法 |
target | 仅匹配被代理类中的方法 |
args | 仅匹配参数的运行时类型符合指定类型的方法 |
@target | 仅匹配含有指定注解的被代理类的方法 |
@args | 仅匹配参数运行时类型含有指定注解的方法 |
@within | 匹配含有指定注解的方法 |
@annotaion | 匹配标有指定注解的方法 |
注意: 在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;
@Aspect
public 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?)
其中, 必选字段是
表达式支持’*’, ‘..’等通配符, 下面是一些符合上述定义的例子:
// 所有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
类型能够快速抛弃掉不符合的大量切点, 极大加速运行时解析速度.
一个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;
}
在Spring AOP中, 仅支持方法作为连接点, 所以Advice获取的总是方法的状态(包括参数, 方法签名, 类名等等).
Spring中要想获取连接点的状态, 必须将连接点作为Advice方法的第一个参数传入, 主要用两个类型描述:
org.aspectj.lang.JoinPoint
ProceedingJoinPoint
: @Around
Advice使用上述类型有很多实用的方法, 如:
getArgs()
getThis()
getTarget()
getSignature()
toString
下面是一个例子:
@Around("execution(List 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作用于同一个连接点的时候, 我们可以通过
Order
来显式指定Advice优先级.
举个例子:
@Before
: 优先级高的, 先运行;@After
: 优先级高的, 后运行;introductions允许一个aspect为连接点方法对应的this对象(proxy对象)引入一个新的接口, 并给出默认的实现, 作为proxy对象的增强实现.
简单的说, 就是能够动态为this对象(proxy对象)扩展某接口的功能.
下面是一个用例:
@Aspect
public 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;
}
}
@Configuratin
public class AspectConfig {
// 手动进行Aspect的初始化
@Bean
public ConcurrentOperationExecutor config() {
return new ConcurrentOperationExecutor(3, 100);
}
}
以上.
参考链接: