1. 程式人生 > 程式設計 >自定義註解加AOP怎麼玩?

自定義註解加AOP怎麼玩?

原文地址

前言

註解是在JDK1.5之後引入的新特性位於 java.lang.annotation ,註解其實就是對程式碼進行一種特殊的標記,這些標記可以在編譯,類載入和執行時被讀取,並執行相應的處理。本文主要分析如何自定義註解和註解的一些基礎知識,然後在配合這AOP在實際運用中玩出新花樣。

本文分為三部分

  • 註解分析
  • 自定義註解
    • 編譯時註解
    • 執行時註解
  • 整合AOP

原本可以分為兩篇文章,但是想來想去還是寫一篇。趁熱打鐵。

註解分析

註解怎麼執行的

想要自定義註解就要知道註解是怎麼構成的,結合著專案中常用的註解來分析一下註解到底是怎麼工作的。

看一下@Override註解 其主要作用是編譯時進行格式檢查。點進去看一下@Override

實現。

點進去發現裡面是空的除了兩個元註解什麼都沒有,那麼它到底是怎麼實現的呢

其實@Override可以理解為是一個標籤,它並沒有實際的邏輯處理,而實現邏輯的就是註解的使用者。它本質就是一個 『標記式註解』,僅被編譯器可知 。

舉個例子你的老闆讓你整理一下重要的檔案,但是檔案太多了你肯定需要把一下重要的檔案給標記出來,然後你交給你老闆的時候,老闆會怎麼做?老闆當然是看到有標記的檔案就去檢查一下。

結合著上面的例子使用@Override註解的就是你,你的老闆就是 JVM虛擬機器器,在編譯的時候就是你的老闆進行檢查的時候,JVM發現了這個註解(標記)則就會進行處理 其處理機制主要是JVM內部處理。

總結下來就是:

定義註解,掃描註解,執行邏輯

元註解

在自定義註解之前我們要知道幾個JDK為我們提供的“元註解”,元註解就是定義註解的註解,下面看看都有什麼作用。

元註解一共有四個,都可以在 java.lang.annotation 下找到

  • @Target
  • @Retention
  • @Documented
  • @Inherited

@Target

@Target 註解主要用於定義註解使用的位置,被描述的註解可以用在什麼地方 。@Target的引數是ElementType列舉類,下面詳解都有什麼作用。

列舉?
作用?
ElementType.PACKAGE 註解用在包
ElementType.TYPE 註解作用於型別(類,介面,註解,列舉)
ElementType.ANNOTATION_TYPE 註解作用於註解
ElementType.CONSTRUCTOR 註解作用於構造方法
ElementType.METHOD 註解作用於方法
ElementType.PARAMETER 註解作用於方法引數
ElementType.FIELD 註解作用於屬性
ElementType.LOCAL_VARIABLE 註解作用於區域性變數

@Target 如果不設定範圍的話預設可以作用於所有目標上面

看一下@Target 的原始碼

看一下里面有一個Value引數,它的返回值為ElementType[],ElementType就是上面的列舉類。

@Retention

@Retention註解的作用就是指定註解的生命週期。比如在編譯時可以處理執行時可以處理等。它的列舉類為RetentionPolicy

列舉?
作用?
RetentionPolicy.SOURCE 原始碼中保留,編譯期可以處理
RetentionPolicy.CLASS Class檔案中保留,Class載入時可以處理
RetentionPolicy.RUNTIME 執行時保留,執行中可以處理

@Retention的預設值為 RetentionPolicy.CLASS即在Class載入時處理

@Retention原始碼

@Documented

@Documented註解的話就比較簡單,主要作用就是描述註解檔案化。就是在 在生成javadoc的時候,是不包含註釋的,但是如果註解被@Documented修飾,則生成的檔案就包含該註解。 此註解在以後版本可能會被刪除這裡就不詳細的看了。

@Inherited

@Inherited 註解修飾的註解時具有可繼承性的,就是說我們用 @Inherited 修飾了一個類,那麼這個類的子類也會預設繼承此註解。

原始碼

自定義註解

上面介紹了註解的元註解,那現在就開始實戰自定義註解。

GIT專案地址: https://github.com/scramblecode/project-demos

和往常套路一樣先建立專案,上面是本文章的示例可以下載下來看。

首先先寫一個簡單的例子。然後實戰在SpringBoot中使用自定義註解加攔截器獲取到請求引數。

簡單定義註解

這裡介紹兩個例子 一個是編譯時註解,第二個例子是執行時註解。最後在配合著SpringBoot+AOP寫一個專案中非常實用的例子

編譯時註解

建立編譯時註解我們首先要建立一個依賴專案作為註解處理器。

首先先建立一個註解介面,使用IDEA建立可以選擇建立註解。

在建立一個DataTest註解,這裡定義註解的目的就是如果使用了該註解在編譯時打印出Hello World!

然後編寫註解處理器,這裡使用的AbstractProcessor,本文只限簡單使用,如果有機會寫一篇文章研究AbstractProcessor

具體程式碼

然後需要建立META-INF檔案,這裡推薦使用谷歌的 auto-service 可以自動生成 META-INF/services/javax.annotation.processing.Processor

加入依賴即可

<dependency>
    <groupId>com.google.auto.service</groupId>
    <artifactId>auto-service</artifactId>
    <version>1.0-rc5</version>
</dependency>複製程式碼

定義完後在你的主專案引入註解處理器。在POM檔案中加入本地註解處理器的依賴

新增完成之後建立一個簡單的類,然後加上@DataTest註解

執行開始編譯,就會發現控制檯輸出以下資訊。

編譯時註解可以寫一些生成工具比如lombok這種生成程式碼的工具可以使用。

執行時註解

簡單建立一個註解來獲取被註解標識的名稱和包路徑。

首先建立註解,定義為執行時註解目標為類屬性等。

使用註解

@GetClassName(value = "測試註解")
public class Student {
}複製程式碼

然後建立一個註解處理類,執行

控制檯輸出。

整合AOP

在Web開發中經常要輸出日誌,然後還有介面的執行時間。現在我們就用自定義註解加AOP實現這種功能。

首先把專案完善一下,增加一個測試介面

然後建立log註解。

然後定義切面類

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

    @Pointcut("@annotation(com.lqcoder.annotationdemo.annotation.OutputLog)")
    public void weblog(){

    }

    @Around("weblog()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        List<Object> logArgs = Arrays.stream(point.getArgs())
                .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
                .collect(Collectors.toList());
        try {
            logger.info("請求url={},請求引數={}",request.getRequestURI(),JSON.toJSONString(logArgs));
        } catch (Exception e) {
            logger.error("請求引數獲取異常",e);
        }
        Object result = point.proceed();
        //執行時長(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        try {
            logger.info("請求耗時={}ms,返回結果={}",time,JSON.toJSONString(result));
        } catch (Exception e) {
            logger.error("返回引數獲取異常",e);
        }
        return result;
    }

}複製程式碼

定義好之後重啟專案,然後呼叫一下介面

執行結果可以看到已經生效。

本文結束。