Guns——AOP日誌框架(一)

1.簡單回顧AOP

  • @Before 前置通知(Before advice) :在某連接點(JoinPoint)——核心代碼(類或者方法)之前執行的通知,但這個通知不能阻止連接點前的執行。爲啥不能阻止線程進入核心代碼呢?因爲@Before註解的方法入參不能傳ProceedingJoinPoint,而只能傳入JoinPoint。要知道從aop走到核心代碼就是通過調用ProceedingJionPoint的proceed()方法。而JoinPoint沒有這個方法。
  • 這裏牽扯區別這兩個類:Proceedingjoinpoint 繼承了 JoinPoint 。是在JoinPoint的基礎上暴露出 proceed 這個方法。proceed很重要,這個是aop代理鏈執行的方法。暴露出這個方法,就能支持 aop:around 這種切面(而其他的幾種切面只需要用到JoinPoint,這跟切面類型有關), 能決定是否走代理鏈還是走自己攔截的其他邏輯。建議看一下 JdkDynamicAopProxy的invoke方法,瞭解一下代理鏈的執行原理。這樣你就能明白 proceed方法的重要性。
  • @After 後通知(After advice) :當某連接點退出的時候執行的通知(不論是正常返回還是異常退出)。
  • @AfterReturning 返回後通知(After return advice) :在某連接點正常完成後執行的通知,不包括拋出異常的情況。
  • @Around 環繞通知(Around advice) :包圍一個連接點的通知,類似Web中Servlet規範中的Filter的doFilter方法。可以在方法的調用前後完成自定義的行爲,也可以選擇不執行。這時aop的最重要的,最常用的註解。用這個註解的方法入參傳的是ProceedingJionPoint pjp,可以決定當前線程能否進入核心方法中——通過調用pjp.proceed();
  • @AfterThrowing 拋出異常後通知(After throwing advice) : 在方法拋出異常退出時執行的通知。

2.spring boot AOP使用流程

  • 首先將一個類聲明成切面類
@Aspect
@Component
public class LogAop {}
  • 接着定義切點,在此使用註解切點
@Pointcut(value = "@annotation(cn.stylefeng.guns.core.common.annotion.BussinessLog)")
    public void cutService() { }
  •  接着定義建言
    @Around("cutService()")
    public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {
        //先執行業務
        Object result = point.proceed();
        try {
            handle(point);
        } catch (Exception e) {
            log.error("日誌記錄出錯!", e);
        }
        return result;
    }
  • 最後在需要織入建言的方法上加入註解即可
    @RequestMapping(value = "/add")
    @BussinessLog(value = "添加角色", key = "name", dict = RoleDict.class)
    public ResponseData add(@Valid Role role, BindingResult result) {
        if (result.hasErrors()) {
            throw new ServiceException(BizExceptionEnum.REQUEST_NULL);
        }
        role.setId(null);
        this.roleService.insert(role);
        return SUCCESS_TIP;
    }

3.日誌框架的基本功能

  • 對用戶的登陸狀態進行記錄(成功、失敗、退出)------直接在相應登陸控制器上記錄日誌
  • 對異常操作進行記錄(自定義異常和運行時異常)------直接在全局異常攔截控制器上記錄日誌
  • 對業務操作進行記錄(增刪改查)------使用AOP方式記錄日誌

4.日誌框架實現

注意:  在實際記錄日誌時候,需要開啓線程記錄,因爲記錄操作會非常多防止堵塞主線程

  • 第一種方法如guns一樣自定義線程池
  //異步操作記錄日誌的線程池
    private ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(10);
  • 第二種方法使用spring的線程池,需要配置相關的配置類,之後在需要執行的方法打上@Async上打上註解即可異步調用
@Async
public void async(){}

5.分析AOP的主要邏輯

  • 可以看到主要方法在handle(),並且是在執行具體業務邏輯之後纔開始執行aop的邏輯
@Around("cutService()")
    public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {
        //先執行業務
        Object result = point.proceed();
        try {
            handle(point);
        } catch (Exception e) {
            log.error("日誌記錄出錯!", e);
        }
        return result;
    }
  •  首先獲取的是攔截方法名
 private void handle(ProceedingJoinPoint point) throws Exception {

        //獲取攔截的方法名
        Signature sig = point.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("該註解只能用於方法");
        }
        msig = (MethodSignature) sig;
        Object target = point.getTarget();
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        String methodName = currentMethod.getName();
  •  下方是一些常用操作
//攔截的實體類
Object target = point.getTarget();
//攔截的方法名稱
String methodName = point.getSignature().getName();
//攔截的方法參數
Object[] args = point.getArgs();
//攔截的放參數類型
Class[] parameterTypes = ((MethodSignature)point.getSignature()).getMethod().getParameterTypes();
Method m = null;
try {
//通過反射獲得攔截的method
m = target.getClass().getMethod(methodName, parameterTypes);
//如果是橋則要獲得實際攔截的method
if(m.isBridge()){
  for(int i = 0; i < args.length; i++){
    //獲得泛型類型
    Class genClazz = GenericsUtils.getSuperClassGenricType(target.getClass());
    //根據實際參數類型替換parameterType中的類型
    if(args[i].getClass().isAssignableFrom(genClazz)){
     parameterTypes[i] = genClazz;
    }
}
//獲得parameterType參數類型的方法
m = target.getClass().getMethod(methodName, parameterTypes);
  • 獲取註解上的值寫入日誌 ,包括業務內容value、主鍵key、字典類dict
  //獲取操作名稱
        BussinessLog annotation = currentMethod.getAnnotation(BussinessLog.class);
        String bussinessName = annotation.value();
        String key = annotation.key();
        Class dictClass = annotation.dict();
 @RequestMapping(value = "/add")
    @BussinessLog(value = "添加角色", key = "name", dict = RoleDict.class)
    @Permission(Const.ADMIN_NAME)
    @ResponseBody
    public ResponseData add(@Valid Role role, BindingResult result) {
        if (result.hasErrors()) {
            throw new ServiceException(BizExceptionEnum.REQUEST_NULL);
        }
        role.setId(null);
        this.roleService.insert(role);
        return SUCCESS_TIP;
    }

 


---------------------
參考原文:https://blog.csdn.net/zhanglf02/article/details/78132304

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章