springboot+mybatis+dubbo+aop日誌第三篇

AOP稱爲面向切面編程,在程序開發中主要用來解決一些系統層面上的問題,好比日誌,事務,權限等等。java

Spring AOP模塊提供截取攔截應用程序的攔截器,例如,當執行方法時,能夠在執行方法以前或以後添加額外的功能.web

一 AOP的基本概念

(1)Aspect(切面):一般是一個類,裏面能夠定義切入點和通知spring

(2)JointPoint(鏈接點):程序執行過程當中明確的點,通常是方法的調用express

(3)Advice(通知):AOP在特定的切入點上執行的加強處理,有before,after,afterReturning,afterThrowing,aroundapache

(4)Pointcut(切入點):就是帶有通知的鏈接點,在程序中主要體現爲書寫切入點表達式編程

(5)AOP代理:AOP框架建立的對象,代理就是目標對象的增強。Spring中的AOP代理可使JDK動態代理,也能夠是CGLIB代理,前者基於接口,後者基於子類json

二 AOP經常使用註解

  • @JoinPoint數組

     切入點(PointCut)是一組一個或多個鏈接點,在其中應該執行的通知。 您可使用表達式或模式指定切入點,咱們將在AOP示例中看到。 在Spring中切入點有助於使用特定的鏈接點來應用通知。請考慮如下示例:app

    @Pointcut("execution(* com.demo.controller.*.*(..))")
    @Pointcut("execution(* com.demo.StudentServiceImpl.getName(..))")

    語法框架

    @Aspectpublic class Logging {   

    //切全部的controller包下表全部方法
    @Pointcut("execution(* com.demo.controller.*.*(..))")
    private void controllerPoint(){}

        @Aspect - 將類標記爲包含通知方法的類。

        @Pointcut - 將函數標記爲切入點

        execution( expression ) - 涵蓋應用通知的方法的表達式。

  • @Before Advice

    @Before是一種通知類型(前置),能夠確保在方法執行以前運行通知。 如下是@Before通知(advice)的語法:

    @Pointcut("execution(* com.demo.controller.*.*(..))")
    private void controllerPoint(){}
    @Before(
    "controllerPoint()")
    public void beforeAdvice(){
    System.out.println("在方法執行以前輸出");
    }
  • @After Advice

    @After是一種通知類型(後置),可確保在方法執行後運行通知。 如下是@After通知類的語法:

    @Pointcut("execution(* com.demo.controller.*.*(..))")p
    rivate
    void controllerPoint(){}
    @After(
    "controllerPoint()")

    public void afterAdvice(){
    System.out.println("方法執行後輸出。");
    }
  • @After Returning Advice

    @AfterReturning是一種通知類型,可確保方法執行成功後運行通知。 如下是@AfterReturning通知的語法:

    @AfterReturning(pointcut="execution(* com.demo.controller.*.*(..))", returning="retVal")
    public void afterReturningAdvice(JoinPoint jp, Object retVal){
    System.out.println("Method Signature: " + jp.getSignature());
    System.out.println("Returning:" + retVal.toString() );
    }
     - 要返回的變量的名稱。ps:returning
  • @AfterThrowing

    @AfterThrowing是一種通知類型,能夠確保在方法拋出異常時運行一個通知

    @AfterThrowing(pointcut="execution(* com.demo.controller.*.*(..))", throwing= "error")
    public void afterThrowingAdvice(JoinPoint jp, Throwable error){
    System.out.println("Method Signature: " + jp.getSignature());
    System.out.println("Exception: "+error);
    }
    ps: - 返回的異常名稱throwing
  • @Around

    @Around是一種環繞通知,經過環繞通知,咱們能夠在一個方法內完成前置、後置、異常(@AfterThrowing)等通知所實現的功能。

    @Around("controllerPoint()")

    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
    System.out.println("方法開始");
    //執行方法
    Object result=jp.proceed(args);
    System.out.println("方法結束");
    return result.toString();
    }
     

三 使用AOP記錄每一個servie方法執行日誌

package com.example.dubbo.demo.service.aop;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;

@Component
@Aspect
public class LogAspect {
    
    private final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    //定義切點
    @Pointcut("execution(public * com.example.dubbo.demo.service.impl..*.*(..))")
    public void serviceLog(){}
    
    @Around("serviceLog()")
    public Object logBefore(ProceedingJoinPoint pj) throws Throwable{
        //接口請求的開始時間
        Long startTimeMillis = System.currentTimeMillis();
        JSONObject paramJson = this.printMethodParams(pj,String.valueOf(startTimeMillis));
        logger.info("請求前:{}",paramJson.toString());
        Object retVal = pj.proceed();
        
        JSONObject returnJson = new JSONObject();
        returnJson.put("class_name",paramJson.get("class_name"));
        returnJson.put("method_name",paramJson.get("method_name"));
        returnJson.put("class_name_method",paramJson.get("class_name_method"));
        returnJson.put("return_name",retVal);
        Long endTimeMillis = System.currentTimeMillis();
        returnJson.put("endTimeMillis",endTimeMillis);
        returnJson.put("times",endTimeMillis - startTimeMillis);
        logger.info("請求後:"+returnJson.toString());
        
        return retVal;
        
    }
    
     @AfterThrowing(pointcut = "serviceLog()", throwing = "e")//切點在webpointCut()
     public void handleThrowing(JoinPoint joinPoint, Exception e) throws IOException {
         Long startTimeMillis = System.currentTimeMillis();
         JSONObject paramJson = this.printMethodParams(joinPoint,String.valueOf(startTimeMillis));
         //獲取錯誤詳細信息
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
         e.printStackTrace(pw);
         paramJson.put("errorMsg", e.getMessage());
         paramJson.put("StackTrace", sw.toString());
         logger.info("請求錯誤:"+paramJson.toString());
         pw.flush();   
         sw.flush();   
    }

    /**
     * 打印類method的名稱以及參數
     * @param point 切面
     */
    public JSONObject printMethodParams(JoinPoint point,String startTimeMillis){
        if(point == null){
            return new JSONObject();
        }
        /**
         * Signature 包含了方法名、申明類型以及地址等信息
         */
        String class_name = point.getTarget().getClass().getName();
        String method_name = point.getSignature().getName();
    
        logger.info("class_name = {},startTimeMillis:"+startTimeMillis,class_name);
        logger.info("method_name = {},startTimeMillis:"+startTimeMillis,method_name);
 
        JSONObject paramJson = new JSONObject();
        paramJson.put("class_name",class_name);
        paramJson.put("method_name",method_name);
        paramJson.put("startTimeMillis",startTimeMillis);
        paramJson.put("class_name_method", String.format("%s.%s", class_name,method_name));
        /**
         * 獲取方法的參數值數組。
         */
        Object[] method_args = point.getArgs();
 
        try {
             //獲取方法參數名稱
            String[] paramNames = getFieldsName(class_name, method_name);
 
            //打印方法的參數名和參數值
            String param_name = logParam(paramNames,method_args);
            paramJson.put("param_name",JSONObject.parse(param_name));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return paramJson;
    }
 
    /**
     * 使用javassist來獲取方法參數名稱
     * @param class_name    類名
     * @param method_name   方法名
     * @return
     * @throws Exception
     */
    private String[] getFieldsName(String class_name, String method_name) throws Exception {
        Class<?> clazz = Class.forName(class_name);
        String clazz_name = clazz.getName();
        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(clazz);
        pool.insertClassPath(classPath);
 
        CtClass ctClass = pool.get(clazz_name);
        CtMethod ctMethod = ctClass.getDeclaredMethod(method_name);
        MethodInfo methodInfo = ctMethod.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        if(attr == null){
            return null;
        }
        String[] paramsArgsName = new String[ctMethod.getParameterTypes().length];
        // 若是是靜態方法,則第一就是參數
        // 若是不是靜態方法,則第一個是"this",而後纔是方法的參數
        // 我接口中沒有寫public修飾詞,致使個人數組少一位參數,因此再日後一位,本來應該是   XX ? 0 : 1
        int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
        for (int i=0;i<paramsArgsName.length;i++){
            paramsArgsName[i] = attr.variableName(i+pos);
        }
        return paramsArgsName;
    }
 
 
    /**
     * 判斷是否爲基本類型:包括String
     * @param clazz clazz
     * @return  true:是;     false:不是
     */
    private boolean isPrimite(Class<?> clazz){
        if (clazz.isPrimitive() || clazz == String.class){
            return true;
        }else {
            return false;
        }
    }
 
 
    /**
     * 打印方法參數值  基本類型直接打印,非基本類型須要重寫toString方法
     * @param paramsArgsName    方法參數名數組
     * @param paramsArgsValue   方法參數值數組
     */
    private String logParam(String[] paramsArgsName,Object[] paramsArgsValue){
        StringBuffer buffer = new StringBuffer();
        if(ArrayUtils.isEmpty(paramsArgsName) || ArrayUtils.isEmpty(paramsArgsValue)){
            buffer.append("{\"noargs\":\"該方法沒有參數\"}");
            return buffer.toString();
        }
        for (int i=0;i<paramsArgsName.length;i++){
            //參數名
            String name = paramsArgsName[i];
            //參數值
            Object value = paramsArgsValue[i];
            buffer.append("\""+name+"\":");
            if(isPrimite(value.getClass())){
                buffer.append("\""+value+"\",");
            }else {
                buffer.append(JSON.toJSONString(value)+",");
            }
        }
        return "{"+buffer.toString().substring(0,buffer.toString().length()-1)+"}";
    }

}

 最後須要再 application.properties文件中添加開啓aop的配資

 spring.aop.auto=true

 

做者: Eric.Chen
出處: https://www.cnblogs.com/lc-chenlong
若是喜歡做者的文章,請關注「寫代碼的猿」訂閱號以便第一時間得到最新內容。本文版權歸做者全部,歡迎轉載
相關文章
相關標籤/搜索