• Spring Boot AOP 实践


    面向切面的程序设计。嗯..

    https://img2022.cnblogs.com/blog/1106277/202203/1106277-20220304134035668-2139042219.jpg

    其实,面向切面编程(Aspect-oriented programming,AOP,又译作面向方面的程序设计剖面导向程序设计)和 OOP 一样都是计算机科学中的一种程序设计思想。例如:日志收集功能。传统的 OOP 虽然也能实现,但 AOP 思想为我们打开了另一扇窗。AOP 将项目的日志收集功能拆分出来成为一个关注点(Concern)叫切面也可以 (Aspect),使用的时候通过代理模式织入(Weaving)即可。织入后就会在关注点上搞一些动作,这个过程就是通知 (Advice) 我们通过 AOP 来实现一个日志收集系统。

    我们可以通过 Springboot + AOP 实现一个简单日志采集功能

    • 引入 Pom 坐标

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
          <exclusions>
              <exclusion>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-logging</artifactId>
              </exclusion>
          </exclusions>
      </dependency>
      
      <!-- 日志 -->
      <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-log4j2 -->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-log4j2</artifactId>
      </dependency>
      
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-aop</artifactId>
      </dependency>
      
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
          <exclusions>
              <exclusion>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-logging</artifactId>
              </exclusion>
          </exclusions>
      </dependency>
      
    • 先配置一下日志的格式,在 resource 中添加一个 log4j2-spring.xml 文件写入以下内容,如果是IDEA 就在 vm option 中添加 Dlog4j.skipJansi=false 来展示色彩

      <?xml version="1.0" encoding="UTF-8"?>
      <Configuration status="info" name="log4j2-name" monitorInterval="5">
          <Properties>
              <Property name="console.pattern">------------- %n 执行时间:%style{%d{yyyy-MM-dd HH:mm:ss.SSS}}{Magenta}%n 消息类型:%highlight{%p}%n 当前线程:%style{%t}{Cyan}%n 消息所在类: %style{%c}{Yellow}%n 返回的消息:%m%n</Property>
          </Properties>
      
          <Appenders>
              <Console name="console-appender">
                  <PatternLayout pattern="${console.pattern}"/>
              </Console>
      
          </Appenders>
          <Loggers>
              <Logger name="org.springframework.boot" level="error"/>
              <Logger name="org.apache" level="error"/>
              <Root level="info">
                  <AppenderRef ref="console-appender"/>
              </Root>
          </Loggers>
      </Configuration>
      
    • 配置 Aspect

      /**
       * 使用 Aspect注解 开启
       */
      @Aspect
      @Component
      public class LogAspect {
          private Logger logger = LogManager.getLogger(LogAspect.class);
      
          /**
           * 配置切入点
           */
          @Pointcut("execution(public * com.example.demo..*.*(..))*)")
          public void pointcut() {
      
          }
      
          /**
           * 前置通知
           *
           * @param joinPoint
           */
          @Before("pointcut()")
          public void doBeforeAccessCheck(JoinPoint joinPoint) {
              logger.info("Before方法进入");
          }
      
          /**
           * 后置通知 (不管执行了与否)
           *
           * @param joinPoint
           */
          @After("pointcut()")
          public void doAfterAccessCheck(JoinPoint joinPoint) {
              logger.info("After方法进入");
          }
      
          /**
           * 代码必须正常返回之后才会通知
           *
           * @param joinPoint
           * @param jsonReturn
           */
          @AfterReturning(value = "pointcut()", returning = "jsonReturn")
          public void doAfterReturn(JoinPoint joinPoint, Object jsonReturn) {
              logger.info("AfterReturning方法进入");
          }
      
          /**
           * 代码抛出异常之后才会通知
           *
           * @param joinPoint
           * @param throwing
           */
          @AfterThrowing(value = "pointcut()", throwing = "throwing")
          private void doAfterThrowing(JoinPoint joinPoint, Exception throwing) {
              logger.info("AfterThrowing方法进入");
          }
      
          /**
           * 环绕通知 控制目标代码是否执行,可以在执行前后、抛异常后执行任意拦截代码
           *
           * @param pjp
           * @return
           * @throws Throwable
           */
          @Around(value = "pointcut()", argNames = "pjp")
          private Object doAround(ProceedingJoinPoint pjp) throws Throwable {
              Long startTime = System.currentTimeMillis();
              Object proceed = pjp.proceed();
              Long endTime = System.currentTimeMillis();
              logger.info("Around方法进入,用时{}", endTime - startTime);
              return proceed;
          }
      
      }
      
    • 定义一个 controller

      @RestController
      public class IndexController {
      
          @GetMapping("")
          public String index() {
              return "访问了首页";
          }
      
          @GetMapping("error")
          public User errorIndex() throws Exception {
              throw new Exception("访问了错误的首页");
          }
      }
      
    • 启动服务访问首页 *GET* [http://localhost:8080/](http://localhost:8080/) 即可看到日志信息

      ------------- 
       执行时间:2022-03-04 13:24:59.858
       消息类型:INFO
       当前线程:http-nio-8080-exec-1
       消息所在类: com.example.demo.aspects.LogAspect
       返回的消息:Before方法进入
      ------------- 
       执行时间:2022-03-04 13:25:00.875
       消息类型:INFO
       当前线程:http-nio-8080-exec-1
       消息所在类: com.example.demo.aspects.LogAspect
       返回的消息:AfterReturning方法进入
      ------------- 
       执行时间:2022-03-04 13:25:00.876
       消息类型:INFO
       当前线程:http-nio-8080-exec-1
       消息所在类: com.example.demo.aspects.LogAspect
       返回的消息:After方法进入
      ------------- 
       执行时间:2022-03-04 13:25:00.878
       消息类型:INFO
       当前线程:http-nio-8080-exec-1
       消息所在类: com.example.demo.aspects.LogAspect
       返回的消息:Around方法进入,用时1019
      

    这样就实现了一个简单的日志系统。我们还可以使用反射来更加灵活自由的实现我们的日志系统。

    • 创建一个 Log 的注解。
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Log {
    
        String title() default "标题";
    
    }
    
    • 修改 LogAspect 的代码为下面的内容
    /**
     * 使用 Aspect 开启
     */
    @Aspect
    @Component
    public class LogAspect {
        private Logger logger = LogManager.getLogger(LogAspect.class);
    
        @AfterReturning(pointcut = "@annotation(logAnnotation)", returning = "jsonResult")
        void afterLogReturn(JoinPoint joinPoint, Log logAnnotation, Object jsonResult) {
            logger.info("一个Log注解的通知:{}", logAnnotation.msg());
        }
    }
    
    • IndexController 中添加 Log 注解
    @RestController
    public class IndexController {
    
        @GetMapping("")
        @Log(msg = "首页的日志记录")
        public String index() throws InterruptedException {
            Thread.sleep(1000);
            return "访问了首页";
        }
    
        @GetMapping("error")
        @Log(msg = "错误页的日志记录")
        public User errorIndex() throws Exception {
            throw new Exception("访问了错误的首页");
        }
    }
    
    • 启动服务访问首页 *GET* [http://localhost:8080/](http://localhost:8080/) 即可看到日志信息
    执行时间:2022-03-04 13:37:37.431
    消息类型:INFO
    当前线程:http-nio-8080-exec-2
    消息所在类: com.example.demo.aspects.LogAspect
    返回的消息:一个Log注解的通知:首页的日志记录
    

    看也是可以拿到的,这样就有两种解决手段了,到此 AOP 的简单实践就完毕了。

    参考文献:

    [1] https://baike.baidu.com/item/织入/4602338

    [2] https://zh.wikipedia.org/wiki/面向切面的程序设计

    [3] https://openhome.cc/Gossip/Spring/Pointcut.html [Pointcut 表示式]

  • 相关阅读:
    ROS2概述和实践入门
    ROS2与FastRTPSROS2与FastRTPS
    RoboWare Studio已经停更
    ubuntu20.04 Roboware安装遇到的问题
    Ubuntu16.04安装ROS2
    拥抱ROS2系列:ROS2概述和实践入门(一)
    clash for Linux安装使用
    Ubuntu 安装RoboWare
    Ubuntu 18.04 安装VSCode
    开源ImageFilter库For IOS源码发布
  • 原文地址:https://www.cnblogs.com/l5gw/p/15963953.html
Copyright © 2020-2023  润新知