浅析:AOP的使用

示例代码

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.junit.runner.RunWith;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Aspect
class AopAdviceConfig {

    @Before("@annotation(Test)")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("我是前置通知....");
    }

    @Around("@annotation(Test)")
    public String around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("开启....");
        Object n = joinPoint.proceed();
        System.out.println("关闭...." + n);
        return "!!!";
    }

    @After("@annotation(Test)")
    public void afterAdvice() throws Throwable {
        System.out.println("后置事务……");
    }

    @AfterReturning(value = "@annotation(Test)", returning = "r")
    public Object afterReturning(Object r) {
        System.out.println(r);
        return r + "!!";
    }

    @AfterThrowing(value = "@annotation(Test)")
    public void afterThrowing() {
        System.out.println("目标方法报错");
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Test{

}

//定义一个接口
interface AspectJService {

    /**
     * 测试前置通知
     */
    int beforeAdvice();

    /**
     * 测试后置通知
     * @return
     */
    String afterAdvice();
}
//实现类
class AspectJServiceImpl implements AspectJService {

    @Override
    public int beforeAdvice() {
        System.out.println("测试前置通知,我是第一个Service。。。。。。");
        return 1;
    }

    /**
     * 测试后置通知
     * @return
     */
    @Override
    @Test
    public String afterAdvice() {
        System.out.println("测试AspectJ后置通知。。。。");
        System.out.println("continue");
        return "true";
    }
}

@SpringBootTest
@RunWith(SpringRunner.class)
public class AopTest1 {

    public static void main(String[] args) throws NoSuchMethodException {
        //进行方法调用
        System.out.println(new AopTest1().test1());
    }

    public Object test1() {
        //手工创建一个实例
        AspectJService aspectJService = new AspectJServiceImpl();
        //使用AspectJ语法 自动创建代理对象
        AspectJProxyFactory aspectJProxyFactory = new AspectJProxyFactory(aspectJService);
        //添加切面和通知类
        aspectJProxyFactory.addAspect(AopAdviceConfig.class);
        //创建代理对象
        AspectJService proxyService = aspectJProxyFactory.getProxy();
        Object o = proxyService.afterAdvice();
        return o;
    }
}

输出结果

开启....
我是前置通知....
测试AspectJ后置通知。。。。
continue
true
后置事务……
关闭....true
!!!

执行优先级:

根据上面的代码的输出结果可以看出,如果一个类被多个AOP注解增强,按照Around》Before》目标类》【AfterThrowing》AfterReturning 】》After》Around执行

afterThrowing与afterReturning执行注意事项

afterThrowing与afterReturning冲突,因为after Returning只有在正常执行是织入,而afterThrowing则在出异常后织入。
关于Around注解的目标类带返回值报错问题:Null return value from advice does not match primitive return type for:
因为Aop的底层是采用的ReflectionUtils.accessibleConstructor()根据Class对象创建对应的实例的,而如果使用基本类作为返回值类型,这个方法是会报错的。所以如果有返回类型,可以采用包装类。

注解使用

Around:相当于是一个顺序执行,在执行ProceedingJoinPoint#proceed方法后,会按顺序触发其他的AOP注解直到执行完成才会进行执行之后的方法。

  • 在增强的方法上@Around("execution(* 包名.*(..))")或使用切点@Around("pointcut()")
  • 接收参数类型为ProceedingJoinPoint,必须有这个参数在切面方法的入参第一位
  • 返回值可为任意包装类
  • 需要执行ProceedingJoinPoint对象的proceed方法,在这个方法前与后面做环绕处理,可以决定何时执行与完全阻止方法的执行
  • 返回proceed方法的返回值
  • @Around相当于@Before和@AfterReturning功能的总和
  • 可以改变方法参数,在proceed方法执行的时候可以传入Object[]对象作为参数,作为目标方法的实参使用。
  • 如果传入Object[]参数与方法入参数量不同或类型不同,会抛出异常
  • 通过改变proceed()的返回值来修改目标方法的返回值

Before:在目标方法执行之前执行。

  • 在增强的方法上@Before("execution(* 包名.*.*(..))")
  • 上述表达式可使用pointcut或切入表达式,效果一致,之后不再赘述
  • 切点方法没有形参与返回值

After:在目标方法执行完成后执行。

  • 用法同@Before

AfterThrowing:在目标方法执行完,如果被After增强的话就在After执行完毕后执行

  • @AfterReturning类似,同样有一个切点和一个定义参数名的参数——throwing
  • 同样可以通过切面方法的入参进行限制切面方法的执行,e.g. 只打印IOException类型的异常, 完全不限制可以使用Throwable类型
  • pointcut使用同@AfterReturning
  • 还有一个默认的value参数,如果指定了pointcut则会覆盖value的值
  • 如果目标方法中的异常被try catch块捕获,此时异常完全被catch块处理,如果没有另外抛出异常,那么还是会正常运行,不会进入AfterThrowing切面方法

AfterReturning:在目标方法执行完,如果被After增强的话就在After执行完毕后执行。如果目标方法报错不会织入,

  • 和上边的方法不同的地方是该注解除了切点,还有一个返回值的对象名
  • 不同的两个注解参数:returning与pointcut,其中pointcut参数可以为切面表达式,也可为切点
  • returning定义的参数名作为切面方法的入参名,类型可以指定。如果切面方法入参类型指定Object则无限制,如果为其它类型,则当且仅当目标方法返回相同类型时才会进入切面方法,否则不会
  • 还有一个默认的value参数,如果指定了pointcut则会覆盖value的值
  • 与@After类似,但@AfterReturning只有方法成功完成才会被织入,而@After不管结果如何都会被织入
  • 不能改变返回的值
posted @ 2021-06-23 21:32  有间猫  阅读(160)  评论(0编辑  收藏  举报