SpringBoot學習之整合AOP

Charles Yan發表於2020-10-11

基於SpringBoot整合Aop記錄日誌

  • SpringBoot各版本參考文件

    https://docs.spring.io/spring-boot/docs/

  • 查詢引入依賴
    SpringBoot查詢Aop依賴

  • 引入依賴

    
    
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
      </dependency>
    
    
    
  • 切面類

    
    
      package link.lycreate.springbooteasyexceldemo.aspect;
    
      import lombok.extern.slf4j.Slf4j;
      import org.aspectj.lang.JoinPoint;
      import org.aspectj.lang.ProceedingJoinPoint;
      import org.aspectj.lang.annotation.*;
      import org.aspectj.lang.reflect.MethodSignature;
      import org.springframework.stereotype.Component;
      import org.springframework.web.context.request.RequestAttributes;
      import org.springframework.web.context.request.RequestContextHolder;
      import org.springframework.web.context.request.ServletRequestAttributes;
    
      import javax.servlet.http.HttpServletRequest;
      import java.lang.reflect.Method;
      import java.util.Arrays;
    
      /**
      * @ClassName LogAspect
      * @Description TODO 日誌切面類$
      * @Author charlesYan
      * @Date 2020/10/9 12:53
      * @Version 1.0
      **/
      @Aspect  // 宣告是一個切面元件
      @Component // 加入到IOC容器
      @Slf4j  // 等同於 private final Logger logger = LoggerFactory.getLogger(XXX.class);
      public class LogAspect {
    
          /**
          * @Author charlesYan
          * @Description // 指定切入點表示式,攔截那些方法,即為哪些類生成代理物件
          *      第一*表示匹配任何返回值的方法
          *      第二*表示匹配controller包下的所有類
          *      第三*表示匹配類下的所有方法
          *      ..表示任何個數引數,和如何型別的引數
          **/
          //@Pointcut("execution(* link.lycreate.springbooteasyexceldemo.controller.*.*(..))")
          @Pointcut("@annotation(link.lycreate.springbooteasyexceldemo.aspect.LogFilter)") //在所有標記指定註解的方法上攔截
          public void logPointCut(){}
    
          /**
          * @Author charlesYan
          * @Description //前置通知:在目標方法執行前呼叫
          **/
          @Before("logPointCut()")
          public void before(JoinPoint joinPoint){
              System.out.println("---------------Before Begin CurrentTime = " + System.currentTimeMillis());
              /*獲取當前請求的HttpServletRequest*/
              RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
              HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
    
              log.info("URL-->"+request.getRequestURL().toString());
              log.info("IP-->"+request.getRemoteAddr());
              log.info("HTTP_Method-->"+request.getMethod());
              log.info("Request_args-->"+ Arrays.toString(joinPoint.getArgs()));
    
              System.out.println("---------------Before End CurrentTime = " + System.currentTimeMillis());
    
          }
    
          /**
          * @Author charlesYan
          * @Description //返回通知 在目標方法執行後呼叫,若目標方法出現異常,則不執行
          **/
          @AfterReturning(value = "logPointCut()",returning = "obj")
          public void afterReturning(JoinPoint joinPoint,Object obj){
              System.out.println("---------------AfterReturn CurrentTime = " + System.currentTimeMillis());
    
          }
    
          /**
          * @Author charlesYan
          * @Description //後置通知:無論目標方法在執行過程中是否出現異常都會在它之後呼叫
          **/
          @After("logPointCut()")
          public void after(JoinPoint joinPoint){
              System.out.println("---------------After CurrentTime = " + System.currentTimeMillis());
          }
    
          /**
          * @Author charlesYan
          * @Description //異常通知:目標方法丟擲異常時執行
          **/
          @AfterThrowing(value = "logPointCut()", throwing = "ex")
          public void afterThrowing(JoinPoint joinPoint,Exception ex){
              System.out.println("---------------AfterThrowing CurrentTime = " + System.currentTimeMillis());
          }
    
          /**
          * @Author charlesYan
          * @Description //環繞通知:是前面四個通知的結合體
          *         需要在方法之前執行,可以寫在joinPoint.procedd();之前
          *         需要在方法之後執行,可以寫在joinPoint.procedd();之後
          **/
          @Around("logPointCut()")
          public void around(ProceedingJoinPoint joinPoint) throws Throwable {
    
              // 獲取目標方法的名稱
              String methodName = joinPoint.getSignature().getName();
              // 獲取方法傳入引數
              Object[] params = joinPoint.getArgs();
              MethodSignature signature = (MethodSignature) joinPoint.getSignature();
              Method method = signature.getMethod();
              // 獲取方法上LogFilter註解
              LogFilter logFilter = method.getAnnotation(LogFilter.class);
              String value = logFilter.value() ;
              log.info("模組描述:"+value);
              System.out.println("---------------Around Before CurrentTime = " + System.currentTimeMillis() + " method name:" + methodName + " args:" + params[0]);
              // 執行源方法
              joinPoint.proceed();
              System.out.println("---------------Around After CurrentTime = " + System.currentTimeMillis() + " method name:" + methodName + " args:" + params[0]);
          }
      }
    
    
    
    
  • 自定義註解

    
    
      /**
      * @ClassName LogFilter
      * @Description TODO 自定義日誌註解類$
      * @Author charlesYan
      * @Date 2020/10/11 17:59
      * @Version 1.0
      **/
    
      @Target(ElementType.METHOD)//Target註解決定LogFilter註解可以加在哪些成分上,如加在類身上,或者屬性身上,或者方法身上等成分
      @Retention(RetentionPolicy.RUNTIME)//Retention註解括號中的"RetentionPolicy.RUNTIME"意思是讓LogFilter這個註解的生命週期一直程式執行時都存在
      @Documented
      public @interface LogFilter {
    
          String value() default "";
      }
    
    
    
    
  • 請求方法

    
      @LogFilter("儲存請求日誌")
      @RequestMapping(path = "/saveRequestLog",method = RequestMethod.POST)
      public String saveRequestLog(@RequestParam("name")String name){
          return "請求成功:" + name;
      }
    
    
    
  • 測試方式
    切面測試請求

報錯資訊

方法引數未宣告

  • 報錯資訊

    
      Caused by: java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut
    
    
    
  • 截圖資訊
    SpringBoot整合AOP報錯一

  • 錯誤程式碼

    
      /**
      * @Author charlesYan
      * @Description //返回通知 在目標方法執行後呼叫,若目標方法出現異常,則不執行
      **/
      @AfterReturning("logPointCut()")
      public void afterReturning(JoinPoint joinPoint,Object obj){
          System.out.println("---------------AfterReturn CurrentTime = " + System.currentTimeMillis());
    
      }
    
    
  • 正確程式碼

    
      /**
      * @Author charlesYan
      * @Description //返回通知 在目標方法執行後呼叫,若目標方法出現異常,則不執行
      **/
      @AfterReturning(value = "logPointCut()", returning = "obj")
      public void afterReturning(JoinPoint joinPoint,Object obj){
          System.out.println("---------------AfterReturn CurrentTime = " + System.currentTimeMillis());
    
      }
    
    
    
  • 原因分析

    
      新增的方法引數未賦值
    
    
    

總結

Aop切面通知執行順序

  • 例圖
    AOP切面類通知執行順序

通知註解中的value屬性補充

  • 自定義註解

    
      //註解實體類
      package com.trip.demo;
    
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
    
    
      @Retention(RetentionPolicy.RUNTIME)
      @Target({ ElementType.METHOD })
      public @interface SMSAndMailSender {
    
          /*簡訊模板String格式化串*/
          String value() default "";
    
          String smsContent() default "";
    
          String mailContent() default "";
          /*是否啟用傳送功能*/
          boolean isActive() default true;
          /*主題*/
          String subject() default "";
    
      }
    
    
    
    
  • 切面類中的切面方法

    
      /**
      * 在所有標記了@SMSAndMailSender的方法中切入
      * @param joinPoint
      * @param obj
      */
      @AfterReturning(value="@annotation(com.trip.demo.SMSAndMailSender)", returning="obj")
      public void afterReturning(JoinPoint joinPoint,Object obj){
          System.out.println("---------------AfterReturn CurrentTime = " + System.currentTimeMillis());
    
      }
    
    

參考連結

  • SpringBoot整合Aop

    https://www.cnblogs.com/fernfei/p/12092510.html

  • SpringBoot整合aop

    https://www.cnblogs.com/myitnews/p/11848159.html

  • SpringBoot 通過自定義註解實現AOP切面程式設計例項

    https://www.cnblogs.com/lingyejun/p/9941350.html

  • SpringBoot2.0 基礎案例(11):配置AOP切面程式設計,解決日誌記錄業務

    https://my.oschina.net/cicadasmile/blog/3073069

  • 淺談Spring AOP 面向切面程式設計 最通俗易懂的畫圖理解AOP、AOP通知執行順序~

    https://www.cnblogs.com/ChromeT/p/11823735.html

  • @interface 註解詳解

    https://blog.csdn.net/u010882691/article/details/82427520

相關文章