• 基于注解@Aspect实现Spring AOP


    摘要:基于注解@Aspect实现Spring AOP切面编程。

    基于注解@Aspect实现Spring AOP

      Spring AOP使用的是动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象在特定的切点做了增强处理,包含了目标对象的全部方法,并且回调原对象的方法。
         
      Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类实现一个接口,她的核心是InvocationHandler接口和Proxy类。如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类。注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

      如果想要实现拦截private方法,可以使用原生 AspectJ 编译时织入。Spring使用纯Spring AOP(只能拦截public/protected/包)都是无法被拦截private方法的,因为子类无法覆盖;包级别能被拦截的原因是,如果子类和父类在同一个包中是能覆盖的。cglib动态代理可以拦截 public/protected/包级别方法(即这些方法都是能代理的)。因为AOP底层是动态代理,jdk动态代理是代理接口,私有方法必然不会存在在接口里,所以就不会被拦截;cglib是生成被代理类的子类,private的方法照样不会出现在子类里,也不能被拦截。关于Spring AOP的更多基本概念请参考《Spring AOP 面向切面编程之AOP是什么》。

      AspectJ是一个面向切面的框架,它扩展了java语言,定义了AOP语法,能够在编译期提供代码的织入。这里创建一个基于注解@Aspect的demo,实现AOP切面。定义测试API getUserById:

        @Autowired
        private UserService userService;
        
        @GetMapping(value ="/getUserById", produces = "application/json; charset=utf-8")
        public User getUserById(Long userId) {
            User user = userService.getUserById(userId);      
            return user;
        }
    

      编写实现类:

    package com.eg.wiener.service;
    
    import com.eg.wiener.dto.User;
    
    public interface UserService {
        User getUserById(Long userId);
    }
    
     === 我是分割线 === 
     
    package com.eg.wiener.service.impl;
    
    import com.eg.wiener.dto.User;
    import com.eg.wiener.service.UserService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    
    import java.util.UUID;
    
    @Service
    public class UserServiceImpl implements UserService {
        private static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
    
        @Override
        public User getUserById(Long userId) {
            logger.info("--------**测试bean增强**-----------");
            User user = new User();
            user.setId(userId);
            user.setAddress("测试地址是 " + UUID.randomUUID().toString());
            logger.info("类信息: {}", user.getClass());
            return user;
        }
    }
    

      在切面UserServiceAspect中使用@Pointcut注解声明切点表达式,命名为userPointcut(),然后在各类通知类型中引用此切点表达式。

    package com.eg.wiener.aop;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class UserServiceAspect {
        private static Logger logger = LoggerFactory.getLogger(UserServiceAspect.class);
        private final String POINT_CUT = "execution(* com.eg.wiener.service.UserService.getUserById(..))";
        
        @Pointcut(POINT_CUT)
        public void userPointcut() {
        }
    
        @Before("userPointcut()")
        public void beforeTest() {
            System.out.println("before ...");
        }
    
        @After("userPointcut()")
        public void afterTest() {
            System.out.println("After ...");
        }
    
        @AfterReturning("userPointcut()")
        public void afterReturningTest() {
            System.out.println("AfterReturning ...");
        }
    
        @Around("userPointcut()")
        public Object aroundTest(ProceedingJoinPoint pj) {
            long start = System.currentTimeMillis();
            Object obj = new Object();
            try {
                System.out.println("Around start ...");
                obj = pj.proceed(pj.getArgs());
                System.out.println("Around end ...耗时:" + 
    (System.currentTimeMillis() - start));
            } catch (Exception e) {
                logger.error("------ e -----,", e);
            } catch (Throwable throwable) {
                logger.error("----- throwable ------,", throwable);
            }
            return obj;
        }
    }
    

      POINT_CUT具体到了函数getUserById,表示切入点就是userService中的函数getUserById,不拦截其它接口。org.aspectj.lang.ProceedingJoinPoint为JoinPoint的子类,重点看一下其中的如下两个方法:

    public Object proceed() throws Throwable;
    public Object proceed(Object[] args) throws Throwable;

    执行proceed() 意为调用下一个advice或者执行目标方法,返回结果为目标方法返回值,因此,如有必要,可以在这里修改方法的返回值。proceed(Object[] args)中的args为目标方法的参数,可以通过修改参数改变方法入参。

      在编写环绕通知的时候,如果被增强函数有返回值,我们需要返回执行结果;否则,API无法返回我们的业务数据,导致竹篮打水一场空。启动项目,请求getUserById,控制台打印结果如下:

    Around start ...
    before ...
    2021-02-17 10:55:21.259 INFO 9000 --- [nio-8087-exec-4] c.e.wiener.service.impl.UserServiceImpl : --------测试bean增强-----------
    2021-02-17 10:55:21.259 INFO 9000 --- [nio-8087-exec-4] c.e.wiener.service.impl.UserServiceImpl : 类信息: class com.eg.wiener.dto.User
    AfterReturning ...
    After ...
    Around end ...耗时:1

      可以看到五种通知类型和具体实现类的执行顺序为:around、before、实现类、afterReturning、after和around。

      使用环绕通知监控API执行时间。在环绕通知中记录API执行完毕所耗费的时间,可以方便的监控和统计哪些API性能低,需要重构,以保证系统稳健运行。

      既然AOP已经生效了,那么,问题来了,究竟AspectJ是如何在没有修改UserServiceImpl类的情况下为UserServiceImpl类增加新功能的呢?这是注解@AspectJ在内存中临时为方法生成一个AOP对象,这个AOP对象在特定的切点做了增强处理,包含了目标对象的全部方法,并且回调原对象的方法,即运行的就是经过增强之后的AOP对象。

    小结

      AspectJ在编译时就增强了目标对象,Spring AOP的动态代理则是在每次运行时动态的增强,生成AOP代理对象,区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

    Reference


      读后有收获,小礼物走一走,请作者喝咖啡。

    赞赏支持

  • 相关阅读:
    JavaSE学习(二):进制转换—数据类型转换—Java运算符
    JavaSE学习(五):数组及其基础操作
    iOS工作中的经验总结—马甲包审核以及常见审核问题!!!(干货)
    月薪过万不是梦!2018年最全/最新Python面试题(整理汇总)
    Python:爬虫技巧总结!
    【转】maven学习(下) 利用Profile构建不同环境的部署包
    【转】maven学习(上) 基本入门用法
    Java从控制台获取数据的方法
    【转】LinkedHashMap实现由插入集合的顺序输出
    浅谈String/StringBuffer/StringBuilder字符串的拼接
  • 原文地址:https://www.cnblogs.com/east7/p/14454033.html
Copyright © 2020-2023  润新知