Spring boot自定義註解方式實現日誌記錄

Annontation是Java5開始引入的新特性,中文名稱叫註解html

1、常見註解

1.1 JDK自帶註解

  • @Override:表示覆蓋或重寫父類的方法。 
  • @Deprecated:表示該方法已通過時了。
  • @suppressWarnings:表示忽略指定警告。

1.2 第三方註解

好比Springjava

  • @Component:組件,沒有明確的角色。
  • @Service 在業務邏輯層(service層)使用。
  • @Autowired 自動裝配。

Spring、Mybatis使用了大量的註解,這些註解可以讓人讀懂別人寫的代碼,特別是框架相關的代碼;同時讓編程更加簡潔,代碼更加清晰;也讓別人高看一眼。spring

既然自定義註解有這麼多好處,那咱們如何去自定義註解?何時用自定義註解?編程

2、自定義註解

2.1 使用場景

好比公司有個後臺管理系統,每一個接口都要去記錄它的操做日誌,若是按照之前的思惟咱們就每一個接口裏面去寫相應的操做日誌記錄代碼。這種方式確定很差,這時咱們想到了spring的aop。其實自定義註解的實現本質上是spring aop對自定義註解的支持。json

那麼在瞭解自定義註解以前,咱們先了解下元註解,即註解的註解。app

2.2 元註解

以Log.java爲例:框架

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {

    /**
     * 模塊
     * @return
     */
    String title() default "";

    /**
     * 功能
     * @return
     */
    String action() default "";

}
  • 使用@interface關鍵字定義註解。
  • 成員以無參無異常方式聲明,能夠用default爲成員指定一個默認值。
  • 類上面的代碼稱之爲元註解。

下面咱們分別介紹下元註解:ide

@Target 是註解的做用域 :表示該註解能夠用於一個類中的那些屬性及方法上,若是做用域類型有多個用英文逗號分隔。spring-boot

public enum ElementType {
    /** 用於描述類、接口(包括註解類型) 或enum聲明 */
    TYPE,

    /** 成員變量、對象、屬性(包括enum實例)  */
    FIELD,

    /** 用於描述方法 */
    METHOD,

    /** 用於描述參數 */
    PARAMETER,

    /** 用於描述構造器 */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

@Retention:表示該註解的生命週期。測試

public enum RetentionPolicy {
    /**
     * 在編譯階段丟棄。這些註解在編譯結束以後就再也不有任何意義,因此它們不會寫入字節碼。
     */
    SOURCE,

    /**
     * 在類加載的時候丟棄。在字節碼文件的處理中有用。註解默認使用這種方式
     */
    CLASS,

    /**
     * 始終不會丟棄,運行期也保留該註解,所以可使用反射機制讀取該註解的信息。咱們自定義的註解一般使用這種方式。 
     */
    RUNTIME
}

@Inherited:此註解是標識性的元註解,表示當前註解能夠由子註解來繼承。

@Documented:表示生成javadoc的時候會包含註解。

3、實現日誌記錄功能

3.1 添加依賴

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

3.2 使用自定義註解

@Log(title = "測試", action = "獲取")

@RestController
@RequestMapping(value = "/v1/demo")
public class DemoController {

    @Log(title = "測試", action = "獲取")
    @GetMapping(value = "/{id}")
    public BaseResponse selectCouponById(@PathVariable("id") Long id) {
        System.out.println("controller");
        return ResultResponse.success(StatusResultEnum.SUCCESS, id);
    }

}

3.3 註解類切面

這個是最主要的類,可使用自定義註解或針對包名實現AOP加強。

1)這裏實現了對自定義註解的環繞加強切點,對使用了自定義註解的方法進行AOP切面處理;

2)對方法運行時間進行監控;

3)對方法名,參數名,參數值,對日誌描述的優化處理;

在方法上增長@Aspect 註解聲明切面,使用@Pointcut 註解定義切點,標記方法。

使用切點加強的時機註解:@Before,@Around,@AfterReturning,@AfterThrowing,@After

@Aspect
做用是把當前類標識爲一個切面供容器讀取

@Pointcut
定義一個切點

@Before
標識一個前置加強方法,至關於BeforeAdvice的功能

@After
final加強,無論是拋出異常或者正常退出都會執行 

@component
把普通pojo實例化到spring容器中,至關於配置文件中的<bean id="" class=""/>

package com.lian.demo.aop;

import com.alibaba.fastjson.JSONObject;
import com.lian.demo.common.annotation.Log;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 操做日誌記錄處理
 */
@Aspect
@Component
@Slf4j
public class LogAspect {

    /**
     * 配置織入點
     */
    @Pointcut("@annotation(com.lian.demo.common.annotation.Log)")
    public void logPointCut() {
    }

    /**
     * 環繞加強,至關於MethodInterceptor
     */
    @Around("logPointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("Around==========");

        Object res = null;
        long time = System.currentTimeMillis();
        try {
            res =  joinPoint.proceed();
            time = System.currentTimeMillis() - time;
            log.info("res=========={}", JSONObject.toJSONString(res));
            return res;
        } catch (Throwable e) {
            log.error("LogAspect 操做失敗:", e.getMessage());
            e.printStackTrace();
        }
    }

    @Before("logPointCut()")
    public void before(JoinPoint joinPoint) {
        Log logBean = this.getAnnotationLog(joinPoint);
        log.info("打印: {} 開始前", logBean.title());
    }

    /**
     * 後置最終通知,final加強,無論是拋出異常或者正常退出都會執行
     */
    @After("logPointCut()")
    public void after(JoinPoint jp){
        log.info("方法最後執行");
    }

    /**
     * 處理完請求,返回內容
     * @param ret
     */
    @AfterReturning(returning = "ret", pointcut = "logPointCut()")
    public void doAfterReturning(Object ret) {
        log.info("方法的返回值 : {}", ret);
        // TODO 業務代碼
//        addOperationLog(joinPoint,res,time);
    }

    /**
     * 後置異常通知
     * 
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        log.error("方法異常時執行 : {}", e);
    }

    /**
     * 是否存在註解,若是存在就獲取
     */
    private Log getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null) {
            return method.getAnnotation(Log.class);
        }

        return null;
    }

}

3.4 題外話

經過反射獲取註解信息

public class Test {

    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("com.lian.demo.controller.DemoController");
            // 獲取全部的方法
            Method[] method = clazz.getMethods();
            for(Method m : method) {
                // 判斷方法上是否使用了自定義註解Log
                boolean isExist = m.isAnnotationPresent(Log.class);
                if (isExist) {
                    Log log = m.getAnnotation(Log.class);
                    System.out.println(log.title());
                }
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}

參考

https://www.cnblogs.com/wenjunwei/p/9639909.html

https://blog.csdn.net/u013825231/article/details/80468167

https://www.cnblogs.com/Qian123/p/5256084.html#_label2

相關文章
相關標籤/搜索