• 浅析: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不管结果如何都会被织入
    • 不能改变返回的值
  • 相关阅读:
    CXF调用webservice客户端
    CXF 需要的jar包下载
    跨域解决
    eclipce集成activiti
    MyEclipse 2017 CI 7安装使用
    BaiduPCS-Go的安装及使用
    织梦后台添加友链,前台不显示|修改友情链接的显示行数
    3种方法判断手机浏览器跳转WAP手机网站
    织梦DEDE后台定时分时段自动更新发布文章插件
    织梦DedeCMS信息发布员发布文章默认自动审核更新并生成HTML页面
  • 原文地址:https://www.cnblogs.com/itcod/p/14924548.html
Copyright © 2020-2023  润新知