阐述 Spring AOP 的底层原理

 2022-09-01
原文地址:https://blog.51cto.com/u_208751/5637789

AOP的概念

  • AOP全称Aspect -Oriented Proramming,中文称为面向切面编程:
  • 202209012346357621.png
AOP 中的基础要素:
  • Join point(连接点):程序能被拦截的某些目标,比如某个执行方法。

  • Advice(通知):是指在特定的连接点(Join point)要做的动作。通知分为方法执行前通知(Before Advice),方法执行后通知(After Advice),环绕通知(Around Advice)等。。

  • Pointcut(切点): 用来切入Join point(连接点)的特定表述语言,比如拦截某个类的某些执行方法

  • Aspect(切面): 各种Advice(通知)与Pointcut(切点)相互组成的实体

  • Weaving(织入): 将Advice(通知)嵌入OOP程序中的过程

202209012346374042.png

Java平台AOP的实现

  • AOP是一种编程理念,需要具体的实现机制,Java平台上的实现主要分为以下几类:
  1. 动态代理: 功能模块类需实现接口(Spring AOP默认采用这种机制)。
  2. 动态字节码增强:在运行期间,通过生成子类将切面逻辑动态加入到这些子类中。
  3. Java代码生成:比较古老的方式,基本已经淘汰。
  4. 自定义类加载器:在加载class文件期间,将切面逻辑添加到功能模块的类中。
  5. AOL扩展:用扩展语言实现AOP。

Spring的AOP框架

  • Spring的AOP采用JDK动态代理机制和CJLIB字节码生成技术实现 :
  • 202209012346384433.png

@ AspectJ形式的Spring AOP

  • AspectJ的acj编译器把aspect类编译成class字节码后,在java目标类再进行编译时织入,最终形成完整的Java类字节码文件。

  • 202209012346396714.png

  • 202209012346408465.png

Spring AOP 应用案例
  • 日志处理案例
    /**
    * 日志AOP
    */
    @Aspect
    public class LogAspect {
      // 日志服务类
        private final LogService logService;
    
        ThreadLocal<Long> currentTime = new ThreadLocal<>();
      // 构造方法注入
        public LogAspect(LogService logService) {
            this.logService = logService;
        }
    
        /**
         * 配置切入点
         */
        @Pointcut("@annotation(demo.aspectJ.annotation.Log)")
        public void logPointcut() {
            // 该方法无方法体,主要为了让同类中其他方法使用此切入点
        }
    
        /**
         * 配置环绕通知,使用在方法logPointcut()上注册的切入点
         *
         * @param joinPoint join point for advice
         */
        @Around("logPointcut()")
        public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
            Object result;
            currentTime.set(System.currentTimeMillis());
            result = joinPoint.proceed();
            Log log = new Log("INFO",System.currentTimeMillis() - currentTime.get());
            currentTime.remove();
            HttpServletRequest request = RequestHolder.getHttpServletRequest();
            logService.save(getUsername(), StringUtils.getBrowser(request), StringUtils.getIp(request),joinPoint, log);
            return result;
        }
    
        /**
         * 配置异常通知
         *
         * @param joinPoint join point for advice
         * @param e exception
         */
        @AfterThrowing(pointcut = "logPointcut()", throwing = "e")
        public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
            Log log = new Log("ERROR",System.currentTimeMillis() - currentTime.get());
            currentTime.remove();
            log.setExceptionDetail(ThrowableUtil.getStackTrace(e).getBytes());
            HttpServletRequest request = RequestHolder.getHttpServletRequest();
            logService.save(getUsername(), StringUtils.getBrowser(request), StringUtils.getIp(request), (ProceedingJoinPoint)joinPoint, log);
        }
        ...
    }
    
      @Log("用户登录")
        public ResponseEntity<Object> login(@Validated @RequestBody UserDTO user, HttpServletRequest request) throws Exception {
            // 登录过程
            ...
      }