1. 註解是什麼
首先,我們先來康康註解在百度百科上的解釋
而在 Java 中,簡單通俗的講,就是一個標籤,對類、方法、變數的一個解釋說明,在早些年,我們通常使用 xml 去對我們的程式碼進行增強的解釋,但是格式繁雜,程式碼可讀性差,維護起來很困難,在 Java SE 5.0 以後,註解的出現為這種情況得到了改善,越來越多的開源專案開始使用註解,拋棄了 xml 。
xml 就像一段程式碼的補充解釋和說明,是一段單獨的文件,比如我們 Spring 專案中使用 xml 配置 Bean 的作用域,而註解是寫在程式碼旁邊,對程式碼進行標記和進行進一步的解釋。
- xml 配置 Bean
<bean name="user" class="shanhe.show.User" scope="prototype"
</bean>
- 註解配置 Bean
@Bean
public class User{}
2. 註解該怎麼用
我們使用註解的方法非常的簡單,可以分別這樣去用
類
@Data
public class User{}
方法
@Override
public String print(){}
變數
@Notnull
private String id;
引數
String getIdByName(@Param("name") String name);
其使用的簡單和方便其實也是我們選擇使用的註解的最大原因:)
3. 自定義註解
要想真正的理解註解的實現原理,首先我們要學會自己去實現一個自定義的註解,當我們得到一個自己的註解之後,相信可以對註解的理解有更為深刻的認識。
自定義註解之前,我們先來認識幾個定義註解的註解——元註解。
@Target
@Retention
@Docuemented
Inherited
通過這四個元註解,我們就可以去自定義一個我們想要的註解,首先來分別解釋一下,這四個元註解在構建自定義註解的時候起到的作用
@Target
正如它的名字那樣,它是用於限定這個自定義註解能夠應用的 Java 元素,在這個註解中維護著一個列舉類:
public enum ElementType {
/** 類,介面(包括註解型別)或列舉的宣告 */
TYPE,
/** 屬性的宣告 */
FIELD,
/** 方法的宣告 */
METHOD,
/** 方法形式引數宣告 */
PARAMETER,
/** 構造方法的宣告 */
CONSTRUCTOR,
/** 區域性變數宣告 */
LOCAL_VARIABLE,
/** 註解型別宣告 */
ANNOTATION_TYPE,
/** 包的宣告 */
PACKAGE
}
其使用的方法如下
@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface AnnotionDemo {
String value();
}
@Retention
註解是對自定義註解的生命週期進行限定,分為了原始檔、編譯期、執行期這三類,同樣的,它也有一個好搭檔——列舉類去維護這三個階段。
public enum RetentionPolicy {
/**
* 註解將被編譯器忽略掉
*/
SOURCE,
/**
* 註解將被編譯器記錄在class檔案中,但在執行時不會被虛擬機器保留,這是一個預設的行為
*/
CLASS,
/**
* 註解將被編譯器記錄在class檔案中,而且在執行時會被虛擬機器保留,因此它們能通過反射被讀取到
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
我們實際開發中使用自定義註解的時候,最常使用的還是RUNTIME
型別,我們可以通過另外一個神器——反射去獲取到我們自定義註解的相關內容,從而對這些不同的內容進行不同的判斷,後面專案實戰部分,會舉例說明實際使用的方法
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotionDemo {
String value();
}
@Document
註解相對來說就比較簡單了,它只是用來指定自定義註解能否跟隨著它被使用的 Java 檔案一起生成到 JavaDoc 中,就目前來看,這個元註解對於我們的作用並不是很大。
@Inherited
的使用則是有條件限制,只有當ElementType
為TYPE
的時候才會生效,而它的作用就是將父類的作用域擴充到子類中,是子類可以去繼承原本處於父類上的註解。
所以綜上所述,我們就可以運用元註解去自定義出一個屬於我們自己的註解:
@Document
@Inherited
@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotionDemo {
String value();
}
4. 註解實現原理
首先,在 Java 的文件中,我找到了這樣的一句話:
The direct superinterface of every annotation type is java.lang.annotation.Annotation
意思就說,我們不管是自定義的註解也好,JDK中原生的註解也好,都是繼承自Annotation
這個介面的,也就是說我們上面自定義的註解經過了編譯器編譯後,大概是這個樣子的
@Document
@Inherited
@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public interface AnnotionDemo extends Annotation {
String value();
}
那麼我們是使用註解的時候,怎麼去給註解中的value
賦值呢?
我們使用該註解後,通過反編譯後的程式碼,我們可以找到(這裡就不貼出反編譯後的程式碼了,浪費空間,大家知道怎麼回事就好~)在堆記憶體中有一個代理物件,大概長下面這樣
public final class com.sun.proxy.$Proxy1 extends java.lang.reflect.Proxy implements java.lang.annotation.Annotation
然後在這個代理類中去完成了對value
的賦值,而執行這一系列動作的是一個叫做AnnotationInvocationHandler
的東西,它完成了在執行期間生成動態代理物件的操作,整體的流程大概是這樣的
5.在專案中我們如何去使用註解?
下面,我們通過一個實際應用的一個小?去看一下
我們在使用系統的時候,通常會有許可權的控制,在專案中,我們會在 gateway 中去設定過濾器,通過過濾請求之中的token,獲取對應的使用者資訊,從而拿到使用者的許可權,完成對許可權的控制,但是有一些介面是處於非登入狀態(即沒有token的時候)也需要去訪問的,而這些介面並非固定一成不變的,這個時候,我們就需要一個標誌,也就是註解去註明,哪些介面的訪問是無需進行許可權的,相當於頒發了一個綠牌,可以跳過許可權的控制。
自定義@Pass
註解
/**
* 既可以作用於方法上,也可以作用於類上,作用於類上時,該類下的所有介面均可跳過
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pass {
boolean required() default true;
}
處理方式
// 如果不是對映到方法直接通過
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
//檢查是否有pass註釋,有則跳過認證
if (method.isAnnotationPresent(Pass.class)) {
Pass pass = method.getAnnotation(Pass.class);
if (pass.required()) {
return true;
}
}
在攔截器中獲取Method方法,通過反射去獲取註解中的值,這樣就可以跳過過濾直接去訪問介面啦,具體使用方法如下:
@Pass
@GetMapping("hello")
public String hello(){
return "hello";
}
相信我聰明的讀者一定可以舉一反三,使用註解去巧妙的實現更多的功能,本次註解的相關內容就到此為止了~
如果你有學到,請給我點贊+關注,原創不易,且看且珍惜。