Spring对AspectJ的支持

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 stopwatch
        Object retVal=pjp.proceed();
        //stop stopwatch
        return 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(){
        //...
    }
}

猜你喜欢

转载自www.cnblogs.com/chanaichao/p/9267173.html
今日推荐