Spring-AOP-学习笔记(2)-AspectJ

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
}
  • 如果第一个参数是JoinPointProceedingJoinPoint, or JoinPoint.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次也不会有什么影响

原文地址:https://www.cnblogs.com/manliu/p/5986498.html