1.AspectJ介绍及Pointcut注解应用
(1)AspectJ
- @AspectJ的风格类似纯java注解的普通java类
- Spring可以使用AspectJ来做切入点解析
- AOP的运行时仍旧是纯的Spring AOP,对AspectJ的编译器或者织入无依赖性
(2)Spring中配置@AspectJ
- 对@AspectJ支持可以使用XML或者Java风格的配置
- 确保AspectJ的aspectjweaver.jar库包含在应用程序(版本1.6.8或者更高版本)的classpath中
java注解方式:
@Configuration @EnableAspectJAutoProxy public class AppConfig{}
xml方式:
<aop:aspectj-autoproxy/>
(3)@Aspect注解的使用
- @AspectJ切面使用@Aspect注解配置,任何拥有@Aspect的bean将被Spring自动识别并应用
- 用@Aspect注解的类可以有方法和字段,他们也可能包括切入点(pointcut),通知(advice)和引入(introduction)声明
- @Aspect注解是不能够通过类路径自动检测发现的,所以需要配合使用@Component注释或者在xml配置bean
1)在xml中配置bean
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect"><!--configure properties of aspect here as nomal--> </bean>
2)使用@Component注释
package org.xyz; import org.sapectj.lang.annotation.Aspect;@Aspect public class NotVeryUsefulAspect{ }
- 一个类中的@Aspect注解标识它为一个切面,并且将自己从自动代理中排除,否则会出现死循环
(4)pointcut
- 一个切入点通过一个普通的方法定义来提供,并且切入点表达式使用@Pointcut注解,方法返回类型必须为void
- 定义一个名为‘anyOldTransfer’,这个切入点将匹配任何名为“transfer”的方法的执行
@Pointcut("execution(* transfer(..))")//the pointcut expression private void anyOldTransfer(){}//the pointcut signature
- 切入点支持的定义方式
- execution:匹配方法执行的连接点
- within:限定匹配特定类型的连接点
- this:限定匹配特定连接点的bean引用是指定类型的实例
- target:限定匹配特定连接点的目标对象是指定类型的实例
- args:限定匹配特定连接点的参数是指定类型的实例
- @target:限定匹配特定连接点的类执行对象的具有给定类型的注解
- @args:限定匹配特定连接点实际传入参数的类型具有给定类型的注解‘
- @within:限定匹配到具有给定的注释类型的连接点
- @annotation:限定匹配特定连接点的主体具有给定的注解
(5)组合pointcut
- 切入点表达式可以通过&&、||、!进行组合,也可以通过名字引入切入点表达式
- 通过组合,可以建立更加复杂的切入点表达式
@Pointcut("execution(public *(..))") private void anyPublicOperation(){}@Pointcut("within(com.xyz.someapp.trading..)") private void inTrading(){}@Pointcut("anyPublicOperation() && inTrading()") private void tradingOperation(){}
(6)定义良好的pointcuts
- AspectJ是编译期的AOP
- 检查代码并匹配连接点与切入点的代价是昂贵的
- 一个好的切入点应该包括以下几点
- 选择特定类型的连接点,如:execution,get,set,call,handler
- 确定连接点范围,如:within,withincode
- 匹配上下文信息,如:this,target,@annotation
2.Advice定义及实例
(1)Before advice
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component;@Component @Aspect public class ExampleAspect{//在执行com.xyz.aop.aspectj包下以Biz结尾的类的所有方法时匹配Advice@Before("*execution(* com.xyz.aop.aspectj.biz.*Biz.*(..))")public void before(){//.. } }
(2)After returning advice
@Aspect public class AfterReturningExample{@AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")public void doAccessCheck(){//... } }
- 有时候需要在通知体内得到返回的实际值,可以使用@AfterReturning绑定返回值的形式
@Aspect public class AfterReturningExample{@AfterReturning(pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",returning="retVal")public void doAccessCheck(Object retVal){//... } }
(3)After throwing advice
@Aspect public class AfterThrowingExample{@AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")public void doRecoveryActions(){//... } }
- 有时候需要在通知体内得到返回的实际值,可以使用@AfterReturning绑定的返回值的形式
@Aspect public class AfterThrowingExample{@AfterThrowing(pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",throwing="ex")public void doRecoveryActions(DataAccessException ex){//... } }
(4)After (finally) advice
- 最终通知必须准备处理正常和异常两种返回情况,它通常用于释放资源
@Aspect public class AfterFinallyExample{@After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")public void doReleaseLock(){//... } }
(5)Around advice
- 环绕通知使用@Around注解来声明,通知方法的第一个参数必须是ProceedingJoinPoint类型
- 在通知内部调用ProceedingJoinPoint的proceed()方法会导致执行真正的方法,传入一个Object[]对象,数组中的值将被作为参数传递给方法
@Aspect public class AroundExample{@Around("com.xyz.myapp.SystemArchitecture.businessService()")public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable{//start stopwatchObject retVal=pjp.proceed();//stop stopwatchreturn retVal;} }
3.Advice扩展
(1)给advice传递参数
方式一:
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)") public void validateAccount(Account account){//... }
方式二:
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)") private void accountDataAccessOperation(Account account){}@Before("accountDataAccessOperation(account)") //Account account:这里的方法参数可以是任何类的对象 public void validateAccount(Account account){//... }
方式三:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Auditable{Auditable value(); }
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()&&@annotation(auditable)") public void audit(Auditable auditable){AuditCode code=auditable.value();//... }
(2)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<MyType> param){//Advice implementation }
(3)Advice参数名称
- 通知和切入点注解有一个额外的“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,JoinPoint.StaticPart,那么可以忽略它
@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 }
(4)Introductions
- 允许一个切面声明一个通知对象实现指定接口,并且提供了一个接口实现类来代表这些对象
- introduction使用@DeclareParents进行注解,这个注解用来定义匹配的类型拥有一个新的parent
- 例如:给定一个接口UsageTracked,并且该接口拥有DefaultUsageTracked的实现,接下来的切面生命了所有的service接口的实现都实现了UsageTracked接口
@Aspect public class UsageTracking{@DeclareParents(value="com.xyz.myapp.service.*+",defaultImpl=DefaultUsageTracked.class)public static UsageTracked mixin;@Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)")public void recordUsage(UsageTracked usageTracked){usageTracked.incrementUseCount();} }
(5)切面实例化模型
- 这是一个高级主题
- “perthis”切面通过制定@Aspect注解perthis子句实现
- 每个独立的service对象执行时都会创建一个切面实例
- service对象的每个方法在第一次执行的时候创建切面实例,切面在service对象失效的同时失效
@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())") public class MyAspect{private int someState;@Before(com.xyz.myapp.SystemArchitecture.businessService())public void recordServiceUsage(){//... } }