1.启用@AspectJ,需要下载aspectjweaver.jar
<!-- 默认启用动态代理 -->
<aop:aspectj-autoproxy/>
<!-- 注解启用CGliB -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!--XML方式启用CGLIB -->
<aop:config proxy-target-class="true">
<!-- other beans defined here... -->
</aop:config>
2.声明一个切面(Aspect)
/** 注解的方式声明 **/ package org.xyz; import org.aspectj.lang.annotation.Aspect; @Aspect @Component public class MyAspect{ }
添加@Component注解是为了让Spring自动搜索到并进行管理,当然还需要告诉Spring搜索路径:
<context:component-scan base-package="org.xyz"/>
<!-- XML方式声明,不需要@Aspect和@Component注解 --> <bean id="masp" class="org.xyz.MyAspect"> <!-- 配置切面属性 --> </bean> <aop:config> <aop:aspect id="myAspect" ref="masp"> ... ... </aop:aspect> </aop:config>
3.声明一个切点(Pointcut)
Spring AOP只支持在方法上定义连接点,所以只需考虑如何让切点匹配到目标方法,声明一个切点需要2步:一个包含名称的签名及参数(方法返回值必须为void);一个切点表达式。切点表达式使用@Pointcut注解表示。
@Pointcut("execution(* com.xyz.myapp.service..(..))") public void anyOldTransfer(){ } /** * anyOldTransfer即为切点签名 * execution为切点表达式,这里表示任意返回值,service包下(包括子包)任意形参的接口实现类方法 */
<!-- XML方式配置 --> <aop:config> <aop:aspect id="myAspect" ref="masp"> <aop:pointcut id="anyOldTransfer" expression="execution(* com.xyz.myapp.service..(..))"/> </aop:aspect> </aop:config>
4.声明一个通知(Advice)
@Aspect public class AspectExample(){ @Before("execution(* com.xyz.myapp.dao..(..)") public void beforeTest(){ } @After("execution(* com.xyz.myapp.dao..(..)") public void afterTest(){ } @AfterReturning("execution(* com.xyz.myapp.dao..(..)") public void afterReturnTest(){ } /** 将返回值传递给切点 */ @AfterReturning("execution(* com.xyz.myapp.dao..(..)",returning="retVal") public void afterReturningTest(Object retVal){ } @AfterThrowing("execution(* com.xyz.myapp.dao..(..)") public void afterThrowingTest(){ } /** 捕捉指定异常 */ @AfterThrowing("execution(* com.xyz.myapp.dao..(..)",throwing="ex") public void afterThrowingTest(DataAccessException ex){ } @Around("execution(* com.xyz.myapp.dao..(..)") public Object aroundTest(ProceedingJoinPoint pjp) throws Throwable{ //前处理 Object retVal = pjp.proceed(); //后处理 return retVal; } }
<!-- 使用注解的方式声明 --> <aop:aspect id="beforeExample" ref="aBean"> <aop:pointcut id="dataAccessOperation" expression="execution(* com.xyz.myapp.dao..(..))" /> <!-- Before --> <aop:before pointcut-ref="dataAccessOperation" method="doAccessCheck"/> <!-- After returning --> <aop:after-returning pointcut-ref="dataAccessOperation" returning="retVal" method="doAccessCheck"/> <!-- After throwing--> <aop:after-throwing pointcut-ref="dataAccessOperation" throwing="dataAccessEx" method="doRecoveryActions"/> <!-- After --> <aop:after pointcut-ref="dataAccessOperation" method="doReleaseLock"/> <!-- Around --> <aop:around pointcut-ref="dataAccessOperation" method="doBasicProfiling"/> </aop:aspect>
访问当前JoinPoint
任何Advice类型方法都可以声明第一个形参为org.aspectj.lang.JoinPoint(Around的为ProceedingJoinPoint,JoinPoint的子类)
JoinPoint接口提供了:getArgs()获取方法形参,getThis()获取代理对象,getTarget()获取目标对象
将调用方法参数传递到advice
后置的上面已经给出实例,下面看看前置的
@Before("execution(* com.xyz.myapp.dao..(..) && args(account,..)") public void beforeTest(Account account){ }
自定义注解使用
//定义注解 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Auditable { AuditCode value(); } //获取注解 @Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)") public void audit(Auditable auditable) { AuditCode code = auditable.value(); // ... }
Advice参数和泛型
Spring AOP还可以处理带泛型的类和方法参数
public interface Sample<T> { void sampleGenericMethod(T param); void sampleGenericCollectionMethod(Collection<T> param); } @Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)") public void beforeSampleMethod(MyType param) { // Advice implementation } /** 对于集合的泛型形参要用?代替,真正类型由调用者自行转换 */ @Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)") public void beforeSampleMethod(Collection<?> param) { // Advice implementation }
参数名称确定
这里的参数名称主要指目标方法的形参名称和Advice方法的形参名称如何确定,Spring AOP通过以下方式来确定参数名称:
- 如果明确指定了参数名称,就使用指定的参数名称;如何指定呢?advice和pointcut注解有一个可选的"argNames"属性可以用于指定参数名称,如
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames="bean,auditable") public void audit(Object bean, Auditable auditable) { AuditCode code = auditable.value(); // ... use code and bean }
- 如果第一个参数是
JoinPoint
,ProceedingJoinPoint
, orJoinPoint.StaticPart
类型,"argNames"属性中不需要包含他们的命名,如@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames="bean,auditable") public void audit(JoinPoint jp, Object bean, Auditable auditable) { AuditCode code = auditable.value(); // ... use code, bean, and jp }
- 如果你不需要再advice方法中获取目标方法的参数,可以省略"argNames"属性
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()") public void audit(JoinPoint jp) { // ... use jp }
//或者直接使用aspectJ表达式中的arg
@Before("execution(* x.y.service.FooService.getFoo(String,int) && arg(name,age))") public void audit(JoinPoint jp,String name,int age) { // ... use jp }
参数处理
如果你想在调用目标方法之前处理下传入的参数,可以这样做:
@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}); } /** * 这里要注意形参的顺序,第一个传入的也要作为第一个传进proceed方法中 */
Advice 顺序
当多个连接点重合时,如何进行有序的执行呢?Spring AOP遵循AspectJ确定的相同的优先级规则作为advice的执行顺序。
在进入时,优先级越高的越先执行,如两个before advice,优先级高的先执行
在退出时,优先级越高的越后执行,如两个after advice,优先级高的后执行
当两个advice定义在不同的切面(Aspect)上且都需要运行在相同的连接点,这种情况下除非你指定顺序,否则执行顺序是不确定的。那如何指定执行顺序呢?
Aspect 类实现 org.springframework.core.Ordered
接口或添加Order注解,从Ordered的getValue()
返回的值越小优先级越高
当两个advice定义在相同的切面(Aspect)上且都需要运行在相同的连接点上,这种情况因为Ordered接口也没办法定义顺序了,那建议对advice进行合并或对aspect进行重构。
@Aspect 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()") 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; } }
<aop:aspectj-autoproxy/> <bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor"> <property name="maxRetries" value="3"/> <property name="order" value="100"/> </bean>
Spring AOP与AspectJ 如何选择?
如果你只是在Spring管理的bean上(如controller,service,dao)执行advice,并且没有很复杂的参数传递(如将目标方法的参数传递到Aspect类中),那Spring AOP是最佳的选择
如果需要在非Spring管理的对象(如domain对象,程序中显示创建的对象)上执行advice,或有复杂的参数传递,建议使用AspectJ
是否该启用CGLIB?
如果你需代理目标都有实现的接口,那就无需启用CGLIB了,通常我们service,dao层都有接口,如果只是代理这些实现类,使用Java 动态代理即可
如果你代理的目标没有实现的接口,那需要启用CGLIB,但这里要注意3点:
1.final方法是不能被代理的,因为CGLIB无法重写final方法
2.从Spring 3.2开始CGLIB就集成到Spring core包中,因此无需导入CGLIB包了
3.使用CGLIB作为代理时,代理对象的构造器会执行2次,但一般代理构造器中并无逻辑处理,所以调用2次也不会有什么影响