十:SpringBoot-配置AOP切面編程,解決日誌記錄業務

1、AOP切面編程

在軟件業,AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP(面向對象編程)的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。

1.1 AOP編程特點

  1. AOP採取橫向抽取機制,取代了傳統縱向繼承體系重複性代碼
  2. 經典應用:事務管理、性能監視、安全檢查、緩存 、日誌等
  3. aop底層將採用代理機制進行實現
  4. 接口 + 實現類 :spring採用 jdk 的動態代理Proxy
  5. 實現類:spring 採用 cglib字節碼增強

1.2 AOP中術語和圖解

  1. target:目標類
    需要被代理的類。例如:UserService
  2. Joinpoint:連接點
    所謂連接點是指那些可能被攔截到的方法。例如:所有的方法
  3. PointCut:切入點
    已經被增強的連接點。例如:addUser()
  4. advice:通知/增強
    增強代碼。例如:after、before
  5. Weaving:織入
    指把增強advice應用到目標對象target來創建新的代理對象proxy的過程.
  6. proxy 代理類
  7. Aspect(切面): 是切入點pointcut和通知advice的結合
    一個線是一個特殊的面。
    一個切入點和一個通知,組成成一個特殊的面。

2、SpringBoot整合AOP

2.1 核心依賴

<!-- AOP依賴 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.2 編寫日誌記錄註解

package com.boot.aop.config;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogFilter {
    String value() default "" ;
}

2.3 編寫日誌記錄的切面代碼

這裏分爲兩種情況處理,一種正常的請求日誌,和系統異常的錯誤日誌。
核心註解兩個。@Aspect和@Component。

package com.boot.aop.config;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

@Aspect
@Component
public class LogAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(LogAspect.class) ;

    @Pointcut("@annotation(com.boot.aop.config.LogFilter)")
    public void logPointCut (){

    }
    @Around("logPointCut()")
    public Object around (ProceedingJoinPoint point) throws Throwable {
        Object result = null ;
        try{
            // 執行方法
            result = point.proceed();
            // 保存請求日誌
            saveRequestLog(point);
        } catch (Exception e){
            // 保存異常日誌
            saveExceptionLog(point,e.getMessage());
        }
        return result;
    }
    private void saveExceptionLog (ProceedingJoinPoint point,String exeMsg){
        LOGGER.info("捕獲異常:"+exeMsg);
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        LOGGER.info("請求路徑:"+request.getRequestURL());
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        LOGGER.info("請求方法:"+method.getName());
        // 獲取方法上LogFilter註解
        LogFilter logFilter = method.getAnnotation(LogFilter.class);
        String value = logFilter.value() ;
        LOGGER.info("模塊描述:"+value);
        Object[] args = point.getArgs();
        LOGGER.info("請求參數:"+ JSONObject.toJSONString(args));
    }
    private void saveRequestLog (ProceedingJoinPoint point){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        LOGGER.info("請求路徑:"+request.getRequestURL());
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        LOGGER.info("請求方法:"+method.getName());
        // 獲取方法上LogFilter註解
        LogFilter logFilter = method.getAnnotation(LogFilter.class);
        String value = logFilter.value() ;
        LOGGER.info("模塊描述:"+value);
        Object[] args = point.getArgs();
        LOGGER.info("請求參數:"+ JSONObject.toJSONString(args));
    }
}

2.4 請求日誌測試

@LogFilter("保存請求日誌")
@RequestMapping("/saveRequestLog")
public String saveRequestLog (@RequestParam("name") String name){
    return "success:"+name ;
}

切面類信息打印

/**
 * 請求路徑:http://localhost:8011/saveRequestLog
 * 請求方法:saveRequestLog
 * 模塊描述:保存請求日誌
 * 請求參數:["cicada"]
 */

2.5 異常日誌測試

@LogFilter("保存異常日誌")
@RequestMapping("/saveExceptionLog")
public String saveExceptionLog (@RequestParam("name") String name){
    int error = 100 / 0 ;
    System.out.println(error);
    return "success:"+name ;
}

切面類信息打印

/**
 * 捕獲異常:/ by zero
 * 請求路徑:http://localhost:8011/saveExceptionLog
 * 請求方法:saveExceptionLog
 * 模塊描述:保存異常日誌
 * 請求參數:["cicada"]
 */
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章