談談 Java 中自定義註解及使用場景

2021-02-20 Java程式設計師社區

Java自定義註解一般使用場景為:自定義註解+攔截器或者AOP,使用自定義註解來自己設計框架,使得代碼看起來非常優雅。本文將先從自定義註解的基礎概念說起,然後開始實戰,寫小段代碼實現自定義註解+攔截器,自定義註解+AOP。

一. 什麼是註解(Annotation)

Java註解是什麼,以下是引用自維基百科的內容

Java註解又稱Java標註,是JDK5.0版本開始支持加入原始碼的特殊語法元數據。

Java語言中的類、方法、變量、參數和包等都可以被標註。和Javadoc不同,Java標註可以通過反射獲取標註內容。在編譯器生成類文件時,標註可以被嵌入到字節碼中。Java虛擬機可以保留標註內容,在運行時可以獲取到標註內容。當然它也支持自定義Java標註。

二. 註解體系圖

元註解:java.lang.annotation中提供了元註解,可以使用這些註解來定義自己的註解。主要使用的是Target和Retention註解

註解處理類:既然上面定義了註解,那得有辦法拿到我們定義的註解啊。java.lang.reflect.AnnotationElement接口則提供了該功能。註解的處理是通過java反射來處理的。

如下,反射相關的類Class, Method, Field都實現了AnnotationElement接口。

因此,只要我們通過反射拿到Class, Method, Field類,就能夠通過getAnnotation(Class<T>)拿到我們想要的註解並取值。

搜索Java知音公眾號,回復「後端面試」,送你一份Java面試題寶典

三. 常用元註解

Target:描述了註解修飾的對象範圍,取值在java.lang.annotation.ElementType定義,常用的包括:

Retention: 表示註解保留時間長短。取值在java.lang.annotation.RetentionPolicy中,取值為:

CLASS:隨源文件一起編譯在class文件中,運行時忽略

只有定義為RetentionPolicy.RUNTIME時,我們才能通過註解反射獲取到註解。

所以,假設我們要自定義一個註解,它用在欄位上,並且可以通過反射獲取到,功能是用來描述欄位的長度和作用。

@Target(ElementType.FIELD)  //  註解用於欄位上
@Retention(RetentionPolicy.RUNTIME)  // 保留到運行時,可通過註解獲取
public @interface MyField {
    String description();
    int length();
}

四. 示例-反射獲取註解

先定義一個註解:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyField {
    String description();
    int length();
}

通過反射獲取註解

public class MyFieldTest {

    //使用我們的自定義註解
    @MyField(description = "用戶名", length = 12)
    private String username;

    @Test
    public void testMyField(){

        // 獲取類模板
        Class c = MyFieldTest.class;

        // 獲取所有欄位
        for(Field f : c.getDeclaredFields()){
            // 判斷這個欄位是否有MyField註解
            if(f.isAnnotationPresent(MyField.class)){
                MyField annotation = f.getAnnotation(MyField.class);
                System.out.println("欄位:[" + f.getName() + "], 描述:[" + annotation.description() + "], 長度:[" + annotation.length() +"]");
            }
        }

    }
}

運行結果

應用場景一:自定義註解+攔截器 實現登錄校驗

接下來,我們使用springboot攔截器實現這樣一個功能,如果方法上加了@LoginRequired,則提示用戶該接口需要登錄才能訪問,否則不需要登錄。

首先定義一個LoginRequired註解

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

然後寫兩個簡單的接口,訪問sourceA,sourceB資源

@RestController
public class IndexController {

    @GetMapping("/sourceA")
    public String sourceA(){
        return "你正在訪問sourceA資源";
    }

    @GetMapping("/sourceB")
    public String sourceB(){
        return "你正在訪問sourceB資源";
    }

}

沒添加攔截器之前成功訪問


實現spring的HandlerInterceptor 類先實現攔截器,但不攔截,只是簡單列印日誌,如下:

public class SourceAccessInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("進入攔截器了");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

實現spring類WebMvcConfigurer,創建配置類把攔截器添加到攔截器鏈中

@Configuration
public class InterceptorTrainConfigurer implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SourceAccessInterceptor()).addPathPatterns("/**");
    }
}

攔截成功如下

在sourceB方法上添加我們的登錄註解@LoginRequired

@RestController
public class IndexController {

    @GetMapping("/sourceA")
    public String sourceA(){
        return "你正在訪問sourceA資源";
    }

    @LoginRequired
    @GetMapping("/sourceB")
    public String sourceB(){
        return "你正在訪問sourceB資源";
    }

}

簡單實現登錄攔截邏輯

@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("進入攔截器了");

        // 反射獲取方法上的LoginRequred註解
        HandlerMethod handlerMethod = (HandlerMethod)handler;
        LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class);
        if(loginRequired == null){
            return true;
        }

        // 有LoginRequired註解說明需要登錄,提示用戶登錄
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().print("你訪問的資源需要登錄");
        return false;
    }

運行成功,訪問sourceB時需要登錄了,訪問sourceA則不用登錄

應用場景二:自定義註解+AOP 實現日誌列印

先導入切面需要的依賴包

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

定義一個註解@MyLog

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

定義一個切面類,見如下代碼注釋理解:

@Aspect // 1.表明這是一個切面類
@Component
public class MyLogAspect {

    // 2. PointCut表示這是一個切點,@annotation表示這個切點切到一個註解上,後面帶該註解的全類名
    // 切面最主要的就是切點,所有的故事都圍繞切點發生
    // logPointCut()代表切點名稱
    @Pointcut("@annotation(me.zebin.demo.annotationdemo.aoplog.MyLog)")
    public void logPointCut(){};

    // 3. 環繞通知
    @Around("logPointCut()")
    public void logAround(ProceedingJoinPoint joinPoint){
        // 獲取方法名稱
        String methodName = joinPoint.getSignature().getName();
        // 獲取入參
        Object[] param = joinPoint.getArgs();

        StringBuilder sb = new StringBuilder();
        for(Object o : param){
            sb.append(o + "; ");
        }
        System.out.println("進入[" + methodName + "]方法,參數為:" + sb.toString());

        // 繼續執行方法
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println(methodName + "方法執行結束");

    }
}

在步驟二中的IndexController寫一個sourceC進行測試,加上我們的自定義註解:

    @MyLog
    @GetMapping("/sourceC/{source_name}")
    public String sourceC(@PathVariable("source_name") String sourceName){
        return "你正在訪問sourceC資源";
    }

啟動springboot web項目,輸入訪問地址

相關焦點

  • java的註解是個什麼玩意
    在使用json時,大部分情況下會定義一個實體類,類中有各種欄位來描述json中的key;在使用xml時,也會定義一個DTD來描述xml中可以有哪些key。註解的作用也是類似,註解就是描述一組數據、定義數據類型的一個對象。
  • 詳解Java註解機制
    ,如Spring中就大量使用了反射和註解,實現了諸如Bean容器管理機制等操作,SpringMvc框架中大量使用了註解,實現了servlet容器的簡易操作等,現在我們開始詳細的學習Java中的註解機制註解是什麼日常開發中經常提到註解,那麼註解是什麼呢?
  • Spring Boot自定義 Servlet Filter 的兩種方式
    作者 | 碼農小胖哥針對Java開發者的持續交付完整實施指南 | 內含福利1.前言有些時候我們需要在 Spring Boot Servlet Web 應用中聲明一些自定義的 Servlet Filter 來處理一些邏輯
  • SpringBoot使用@Transactional註解配置事務
    聲明式事務管理使業務代碼邏輯不受汙染, 因此在實際使用中聲明式事務用的比較多。聲明式事務有兩種方式,一種是在配置文件(xml)中做相關的事務規則聲明,另一種是基於@Transactional註解的方式。@Transactional可以作用於接口、接口方法、類以及類方法上。
  • 關於Java中枚舉Enum的深入剖析
    Java中的枚舉類型為Enum,本文將對枚舉進行一些比較深入的剖析。什麼是EnumEnum是自Java 5 引入的特性,用來方便Java開發者實現枚舉應用。一個簡單的Enum使用如下。枚舉原理是什麼Java中Enum的本質其實是在編譯時期轉換成對應的類的形式。首先,為了探究枚舉的原理,我們先簡單定義一個枚舉類,這裡以季節為例,類名為Season,包含春夏秋冬四個枚舉條目.
  • 談談Java接口Result設計
    當我們討論要用Result代替Exception時,經常會以這是HSF接口為由,因為性能開銷等等。我們常說HSF這種RPC框架,設計的目的就是為了看起來像本地調用。那麼,這個「看起來像本地調用」到底指的是哪方面像呢?顯然,編碼時像,運行時不像。所以我們寫調用HSF接口的代碼時,感覺像在調用本地方法,那麼我們的編碼直覺和習慣也都應該是符合java的規範的。因此
  • Java工程師成神之路~
    集合類常用集合類的使用、ArrayList和LinkedList和Vector的區別 、SynchronizedList和Vector的區別、HashMap、HashTable、ConcurrentHashMap區別、Java 8中stream相關用法、apache集合處理工具類的使用、不同版本的JDK中HashMap的實現的區別以及原因枚舉枚舉的用法
  • 超詳細 Spring @RequestMapping 註解使用技巧
    如果請求參數和處理方法參數的名稱一樣的話,@RequestParam 註解的 value 這個參數就可省掉了, 如代碼的第11行所示。@RequestParam 註解的 required 這個參數定義了參數值是否是必須要傳的。
  • Java對象轉換方案分析與mapstruct實踐
    隨著系統模塊分層不斷細化,在Java日常開發中不可避免地涉及到各種對象的轉換,如:DO、DTO、VO等等,編寫映射轉換代碼是一個繁瑣重複且還易錯的工作
  • Java:由淺入深揭開 AOP 實現原理
    文章中用到的程式語言為kotlin,需要的可以在IDEA中直接轉為java。這篇文章將會按照如下目錄展開:AOP簡介代碼中實現舉例AOP實現原理部分源碼解析1. AOP簡介相信大家或多或少的了解過AOP,都知道它是面向切面編程,在網上搜索可以找到很多的解釋。
  • Java8 中的函數式接口
    也就是我們數據中,怎麼從 A 到 B 的這部分邏輯。@FunctionalInterface註解如果你想自己定義個新的函數式接口,強烈建議你加上@FunctionalInterface 註解。可以更好地揭示我們定義這個接口的意思,同時也可以讓編譯器幫助我們檢查接口定義的正確與否。
  • 2019Java視頻教程-Spring Boot實戰
    今天分享一套千鋒教育很火的Spring Boot教程視頻,內容不錯,可以看一看。
  • Java筆試面試-SpringMVC 核心
    ,其值必須在可接受的範圍內@Future被註解的元素必須是日期,檢查給定的日期是否比現在晚@Max(value)被註解的元素必須為一個數字,其值必須小於等於指定的最大值@Min(value)被註解的元素必須為一個數字,其值必須大於等於指定的最小值@NotNull被註解的元素必須不為 null@Null被註解的元素必須為 null@Past(java.util.Date/Calendar)被註解的元素必須過去的日期
  • 技術分享 | Java代審3:Java Web 過濾器 - filter
    filter 的配置類似於 Servlet,由 filter 和 filter-mapping 兩組標籤組成,和 Servlet 一樣,如果版本大於 3.0 也可以使用註解的方式來配置 filter <filter><!
  • 一個註解搞定 SpringBoot 接口防刷,還有誰不會?
    作者:CS打贏你blog.csdn.net/weixin_42533856/article/details/82593123說明:使用了註解的方式進行對接口防刷的功能
  • 一個註解搞定接口防刷!還有誰不會?
    說明:使用了註解的方式進行對接口防刷的功能
  • Java類加載器 — classloader 的原理及應用
    自定義加載器(Custom Classloader)通常是我們為了某些特殊目的實現的自定義加載器,後面我們得會詳細介紹到它的作用以及使用場景。通過靈活定義classloader的加載機制,我們可以完成很多事情,例如解決類衝突問題,實現熱加載以及熱部署,甚至可以實現jar包的加密保護。接下來,我們會針對這些特殊場景進行逐一介紹。
  • 談談Java內存管理
    背景知識根據網絡可以找到的資料以及筆者能夠打聽到的消息,目前國內外著名的幾個大型網際網路公司的語言選型概括如下:Google: C/C++ Go Python Java JavaScript,不得不提的是Google貢獻給java社區的guava包質量非常高,非常值得學習和使用。
  • 2017不能錯過的 Java 庫及史上最全的 Java 新手問題匯總
    它使用起來非常簡便,功能豐富,而且可以自擴展來進一步提高性能、減少資源開銷。MBassador 高性能的最關鍵原因在於一種特殊的數據結構,這種數據結構可最大限度減小鎖競爭。基於註解同步/異步事件發布可配置引用類型事件過濾事件包裹Handler優先級自定義錯誤處理擴展性// Define your listenerclass SimpleFileListener{    @Handler    public void handle(File