面試官:註解五問你怕了嗎?
- 2020 年 11 月 3 日
- 筆記
- Java Grammar, Java Grammer, 面試
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";
}
相信我聰明的讀者一定可以舉一反三,使用註解去巧妙的實現更多的功能,本次註解的相關內容就到此為止了~
如果你有學到,請給我點贊+關注,原創不易,且看且珍惜。