【Java 注解】Java注解-最通俗易懂的講解
原文:https://blog.csdn.net/weixin_43888891/article/details/126963074
====================================================
一、概念
1.1. 什么是注解?
Java注解(Annotation),也叫元數據。一種代碼級別的說明。它是JDK1.5及以后版本引入的一個特性,與類、接口、枚舉是在同一個層次。它可以聲明在包、類、字段、方法、局部變量、方法參數等的前面,用來對這些元素進行說明,注釋。
如果要對于元數據的作用進行分類,還沒有明確的定義,不過我們可以根據它所起的作用,大致可分為三類:
① 編寫文檔:通過代碼里標識的元數據生成文檔【生成文檔doc文檔】
② 代碼分析:通過代碼里標識的元數據對代碼進行分析【使用反射】
③ 編譯檢查:通過代碼里標識的元數據讓編譯器能夠實現基本的編譯檢查【Override】
注解是以‘@注解名’在代碼中存在的,根據注解參數的個數,我們可以將注解分為:標記注解、單值注解、完整注解三類。它們都不會直接影響到程序的語義,只是作為注解(標識)存在,我們可以通過反射機制編程實現對這些元數據(用來描述數據的數據)的訪問。
首先一定要明白:注解他本身是沒有任何作用的,比如@RequestMapping,在controller層@RequestMapping基本上可以說是必見的,我們都知道他的作用是請求映射,通過他來設置請求的url地址,
舉例:將@RequestMapping("config")寫在方法上,然后我們就可以通過url地址訪問到這個方法了,但是記住這并不是@RequestMapping注解的功能,SpringMVC會通過反射將注解當中設置的屬性值config拿到,然后將url和你聲明注解的方法進行綁定。
記?。?strong>注解只是用來標記,而這個注解真正的功能都是由框架通過反射來實現的。
1.2. 什么是元數據?
元數據是一個非常廣泛的概念,元數據的定義也非常簡單,只要是描述數據的數據都是元數據。很簡單,一個數字170,單看數據你肯定不知道這個數據代表什么,這就需要元數據支持,你才能明白數據代表的事實是什么。它可能是一個人的身高,也可能指一個人的體重,這需要數據對應的標題、單位等信息來描述這個數據,這些就是描述這個數據的元數據了
1.3. 注解的屬性
注解的屬性也叫做成員變量。注解只有成員變量,沒有方法。注解的成員變量在注解的定義中以“無形參的方法”形式來聲明,其方法名定義了該成員變量的名字,其返回值定義了該成員變量的類型。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation { int id(); String msg(); }
上面代碼定義了 TestAnnotation 這個注解中擁有 id 和 msg 兩個屬性。在使用的時候,我們應該給它們進行賦值。
賦值的方式是在注解的括號內以 value=”” 形式,多個屬性之前用 ,隔開。
@TestAnnotation(id=3,msg="hello annotation") public class Test { }
需要注意的是,在注解中定義屬性時它的類型必須是 8 種基本數據類型外加 類、接口、注解及它們的數組。
注解中屬性可以有默認值,默認值需要用 default 關鍵值指定。比如:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation { public int id() default -1; public String msg() default "Hi"; }
TestAnnotation 中 id 屬性默認值為 -1,msg 屬性默認值為 Hi。
二、根據【注解參數】 分類
根據注解參數的個數,我們可以將注解分為:標記注解、單值注解、完整注解三類。
2.1. 標記注解
標記注解不包含成員/元素。它僅用于標記聲明。
其語法為:@AnnotationName()
由于這些注解不包含元素,因此不需要括號。例如:@Override
2.2. 單元素注解
單個元素注解僅包含一個元素。
其語法為:@AnnotationName(elementName = "elementValue")
如果只有一個元素,則習慣上將該元素命名為value:@AnnotationName(value = "elementValue")
在這種情況下,也可以移除元素名稱。元素名稱默認為value:@AnnotationName("elementValue")
2.3. 多元素注解
這些注解包含多個用逗號分隔的元素。
其語法為:@AnnotationName(element1 = "value1", element2 = "value2")
三、根據【注解作用】分類
3.1. 預定義的注解
這幾個注解都是java.lang包下的,也就是Java提供的基礎注解,我們在使用的時候是不需要導包的!
3.1.1. @Deprecated
此注解可以用在方法,屬性,類上,表示不推薦程序員使用,但是還可以使用,示例如下:

/** * 測試Deprecated注解 * @author Administrator */ public class DeprecatedDemoTest { public static void main(String[]args) { // 使用DeprecatedClass里聲明被過時的方法 DeprecatedClass.DeprecatedMethod(); } } class DeprecatedClass { @Deprecated public static void DeprecatedMethod() { } }
3.1.2. @Override
它的作用是對覆蓋超類中方法的方法進行標記,如果被標記的方法并沒有實際覆蓋超類中的方法,則編譯器會發出錯誤警告。
public interface Test { public String getStr(); } class TestImpl implements Test{ // 假如返回參數和方法參數其中一個不一致,就會警告 @Override public String getStr() { return null; } }
3.1.3. @SuppressWarnings
我們在寫代碼的時候,不論是導入的包,還是聲明的對象,有時候會出現黃線,感覺就很難受!
@SuppressWarnings注解主要用在取消一些編譯器產生的警告,警告對于運行代碼實際上并沒有影響,但是出于部分程序員具有潔癖的嗜好,通常會采用@SuppressWarnings來消除警告。
示例一:警告如圖所示

這只是一個service接口:
public interface BannerService { }
這時候我們在方法上加上@SuppressWarnings注解就可以消除這些警告的產生,注解的使用方式有三種:
@SuppressWarnings(“unchecked”) [^ 抑制單類型的警告]
@SuppressWarnings(“unchecked”,“rawtypes”) [^ 抑制多類型的警告]
@SuppressWarnings(“all”) [^ 抑制所有類型的警告]
通過源碼分析可知@SuppressWarnings其注解目標為類、字段、函數、函數入參、構造函數和函數的局部變量。建議把注解放在警告發生的位置。
消除警告:

這個警告是spring framerwork 4.0以后開始出現的,spring 4.0開始就不推薦使用屬性注入,改為推薦構造器注入和setter注入。當然他只是推薦,如果我們想要消除警告也可以直接使用@Resource。盡管他推薦了,但是一般實際開發當中很少會使用構造器注入和setter注入。
@Autowired 是Spring提供的,@Resource 是J2EE提供的。
@Autowired與@Resource都可以用來裝配bean,都可以寫在字段或setter方法上
@Autowired默認按類型裝配,默認情況下必須要求依賴對象存在,如果要允許null值,可以設置它的required屬性為false。如果想使用名稱裝配可以結合@Qualifier注解進行使用。
@Resource,默認按照名稱進行裝配,名稱可以通過name屬性進行指定,如果沒有指定name屬性,當注解寫在字段上時,默認取字段名進行名稱查找。如果注解寫在setter方法上默認取屬性名進行裝配。當找不到與名稱匹配的bean時才按照類型進行裝配。但是需要注意的是,如果name屬性一旦指定,就只會按照名稱進行裝配。
示例二:警告如圖所示

通過添加@SuppressWarnings("unchecked")來消除unchecked的警告,這個警告實際上主要是集合沒有加泛型導致的!


3.1.4. @SafeVarargs
在聲明具有模糊類型(比如:泛型)的可變參數的構造函數或方法時,Java編譯器會報unchecked警告。鑒于這些情況,如果程序員斷定聲明的構造函數和方法的主體不會對其varargs參數執行潛在的不安全的操作,可使用@SafeVarargs進行標記,這樣的話,Java編譯器就不會報unchecked警告。
使用前提:
?、俜椒ū仨毷怯蓅tatic或者final修飾的方法!
?、谥荒苡糜跇擞洏嬙旌瘮岛头椒?br> ③只能用在可變長參數的方法上
@SafeVarargs public static <T> T useVarargs(T... args) { return args.length > 0 ? args[0] : null; }
3.1.5. @FunctionalInterface
被@FunctionalInterface注解標記的類型表明這是一個函數接口。從概念上講,函數接口只有一個抽象方法。也就是一旦不滿足函數接口,就會報錯,比如他有兩個抽象方法。
@FunctionalInterface注解,只能用于接口類。其實,它的應用范圍更小,只能應用于接口類型。
@FunctionalInterface public interface Test { public String getStr(); }
3.2. 元注解
以下的注解都來源于java.lang.annotation,描述數據的數據都是元數據,描述注解的注解都是元注解!也就是這些注解只能用在修飾注解上,不能使用在其他地方,比如方法、類等等!
@Native注解除外,他的使用范圍是FIELD(字段、枚舉的常量),但是@Native也是在java.lang.annotation包下!

3.2.1. @Retention
注解按生命周期來劃分可分為3類:
RetentionPolicy.SOURCE:注解只保留在源文件,當Java文件編譯成class文件的時候,注解被遺棄;也就是編譯時有效。
RetentionPolicy.CLASS:注解被保留到class文件,但jvm加載class文件時候被遺棄,這是默認的生命周期;加載時被拋棄。
RetentionPolicy.RUNTIME:注解不僅被保存到class文件中,jvm加載class文件之后,仍然存在;一直有效!
lombok可以通過@Data注解省去get set 方法,實際上@Data的生命周期就是RetentionPolicy.SOURCE,他是通過注解來標記這個方法要生成get set方法,然后在編譯的時候直接會生成get set。生成過后,就被拋棄了。

3.2.2. @Documented
@Documented和@Deprecated注解長得有點像,@Deprecated是用來標注某個類或者方法不建議再繼續使用,@Documented只能用在注解上,如果一個注解@B,被@Documented標注,那么被@B修飾的類,生成文檔時,會顯示@B。如果@B沒有被@Documented標注,最終生成的文檔中就不會顯示@B。這里的生成文檔指的JavaDoc文檔!
@Deprecated注解基本上所有框架自定義的注解都會添加,所謂javadoc其實就是JDK給我們提供的一個生成文檔的工具!
由于篇幅問題,@Documented詳解請看這篇文章:https://blog.csdn.net/weixin_43888891/article/details/126981711
或轉發的地址:http://www.rzrgm.cn/sxdcgaq8080/p/18668690
3.2.3. @Target
@Target:@Target只能用在注解上,指定修飾的注解的使用范圍
@Target(ElementType.TYPE) —— 接口、類、枚舉、注解
@Target(ElementType.FIELD) —— 字段、枚舉的常量
@Target(ElementType.METHOD) —— 方法
@Target(ElementType.PARAMETER) —— 方法參數
@Target(ElementType.CONSTRUCTOR) —— 構造函數
@Target(ElementType.LOCAL_VARIABLE) —— 局部變量
@Target(ElementType.ANNOTATION_TYPE) —— 注解
@Target(ElementType.PACKAGE) —— 包
比如@Target({ElementType.TYPE, ElementType.METHOD}),就代表著@RequestMapping可以用在 接口、類、枚舉、注解上、還可以用在方法上!

3.2.4. @Inherited
如果一個類用上了@Inherited修飾的注解,那么其子類也會繼承這個注解。
當用了@Inherited修飾的注解的@Retention是RetentionPolicy.RUNTIME,則增強了繼承性,在反射中可以獲取得到
代碼示例:
1.首先自定義一個注解使用@Inherited修飾,然后這個注解用來修飾到父類上
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface ATable { public String name() default ""; }
2.這個注解帶不帶@Inherited都可以,我們主要用來修飾子類
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface BTable { public String name() default ""; }
3.定義一個父類跟一個子類,然后在父類上用上我們自定義的@ATable注解
@ATable public class Super { public static void main(String[] args) { Class<Sub> clazz = Sub.class; System.out.println("============================AnnotatedElement==========================="); // 獲取自身和父級帶有@Inherited修飾的注解。如果@ATable未加@Inherited修飾,則獲取的只是自身的注解而無法獲取父級修飾的@ATable注解 System.out.println(Arrays.toString(clazz.getAnnotations())); System.out.println("------------------"); } } @BTable class Sub extends Super { }
4.這個是@ATable帶有@Inherited修飾的 運行結果。

5.這個是沒有用@Inherited修飾的 運行結果。

3.2.5. @Repeatable
@Repeatable 注解是用于聲明其它類型注解的元注解,來表示這個聲明的注解是可重復的。@Repeatable的值是另一個注解,其可以通過這個另一個注解的值來包含這個可重復的注解。
代碼示例:
1.自定義Value注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Repeatable(Values.class) public @interface Value { String value() default "value"; }
2.自定義Values 注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Values { Value[] value(); }
其中,@Value注解上的元注解@Repeatable中的值,使用了@Values注解,@Values注解中包含的值類型是一個@Value注解的數組!這就解釋了官方文檔中@Repeatable中值的使用。
3.測試
import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Arrays; public class AnnotationClass { @Value("hello") @Value("world") public void test(String var1, String var2) { System.out.println(var1 + " " + var2); } public static void main(String[] args) { Method[] methods = AnnotationClass.class.getMethods(); for (Method method : methods){ if (method.getName().equals("test")) { Annotation[] annotations = method.getDeclaredAnnotations(); System.out.println(annotations.length); System.out.println(method.getName() + " = " + Arrays.toString(annotations)); } } } }
4.運行結果

5.假如Value注解沒有使用@Repeatable修飾,編譯器會報錯,是不允許出現注解重復的

注意事項:
@Repeatable 所聲明的注解,其元注解@Target的使用范圍要比@Repeatable的值聲明的注解中的@Target的范圍要大或相同,否則編譯器錯誤,顯示@Repeatable值所聲明的注解的元注解@Target不是@Repeatable聲明的注解的@Target的子集
@Repeatable注解聲明的注解的元注解@Retention的周期要比@Repeatable的值指向的注解的@Retention得周期要小或相同。
周期長度為 SOURCE(源碼) < CLASS (字節碼) < RUNTIME(運行)
3.2.6. @Native
@Native注解修飾成員變量,則表示這個變量可以被本地代碼引用,常常被代碼生成工具使用。對于 @Native 注解不常使用,了解即可!
3.3. 自定義注解
修飾符: 訪問修飾符必須為public,不寫默認為pubic;
關鍵字: 關鍵字為@interface;
注解名稱: 注解名稱為自定義注解的名稱,使用時還會用到;
注解內容: 注解中內容,對注解的描述。
我們自定義一個注解,然后這個注解可以在entity當中的set方法上初始化值。只是一個簡單案例,供大家參考學習!
第一步:自定義@Init注解
@Documented @Inherited @Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) //可以在字段、枚舉的常量、方法 @Retention(RetentionPolicy.RUNTIME) public @interface Init { String value() default ""; }
第二步:創建User類
public class User { private String name; private String age; public String getName() { return name; } @Init("louis") public User setName(String name) { this.name = name; return this; } public String getAge() { return age; } @Init("22") public User setAge(String age) { this.age = age; return this; } }
第三步:創建User的工廠類,所謂工廠類就是專門為了生產User對象
import java.lang.reflect.Method; public class UserFactory { public static User create() { User user = new User(); // 獲取User類中所有的方法(getDeclaredMethods也行) Method[] methods = User.class.getMethods(); try { for (Method method : methods) { // 判斷方法上是否存在Init注解,存在就返回true,否則返回false if (method.isAnnotationPresent(Init.class)) { // 此方法返回該元素的注解在此元素的指定注釋類型(如果存在),否則返回null Init init = method.getAnnotation(Init.class); // 如果Method代表了一個方法 那么調用它的invoke就相當于執行了它代表的這個方法,在這里就是給set方法賦值 method.invoke(user, init.value()); } } } catch (Exception e) { e.printStackTrace(); return null; } return user; } }
第四步:測試
public static void main(String[] args) { User user = UserFactory.create(); user.setAge("30"); System.out.println(user.getName()); System.out.println(user.getAge()); }
第五步:運行結果,我們并沒有設置User的name屬性,只是在User類的 set方法 添加了一個注解。輸出結果如下:

四、反射
JAVA機制反射是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。Java注解一旦離開了反射就什么都不是?。?!
4.1. 反射常用到的API
1.只能拿到public方法(包括繼承的類或接口的方法)
public Method[] getMethods()
2.getDeclaredMethods返回 Method 對象的一個數組,這些對象反映此 Class 對象表示的類或接口聲明的所有方法,包括公共、保護、默認(包)訪問和私有方法,但不包括繼承的方法。
public Method[] getDeclaredMethods()
3.在Class對象和Method對象是都存在isAnnotationPresent這個方法的,作用是判斷它是否應用了某個注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
4.通過 getAnnotation() 方法來獲取 Annotation 對象。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
5.或者是 getAnnotations() 方法,獲取所有的注解
如果獲取到的 Annotation 如果不為 null,則就可以調用它們的屬性方法了。
public Annotation[] getAnnotations() {}
6.獲取到Annotation之后,就可以通過annotationType獲取注解的class結構信息,有了class信息就可以獲取注解的變量名,等等
Class<? extends Annotation> annotationType();
7.getParameterAnnotations :返回表示按照聲明順序對此 Method 對象所表示方法的形參進行注釋的那個數組的數組
public Annotation[][] getParameterAnnotations();
4.2. 讀取類上的注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation { public int id() default -1; public String msg() default "Hi"; }
import java.lang.reflect.Method; @TestAnnotation(id = 222,msg = "awdawd") public class Test { public static void main(String[] args) { Method[] declaredMethods1 = TestAnnotation.class.getDeclaredMethods(); for (Method meth : declaredMethods1) { System.out.println("注解的變量名為:" + meth.getName()); } // 首先判斷Test類上是否使用了TestAnnotation注解 boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class); if (hasAnnotation) { // 獲取注解,這個相當于是真正的拿到注解了,只有獲取到這個才能獲取到注解當中設置的值 TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class); System.out.println("id:" + testAnnotation.id()); System.out.println("msg:" + testAnnotation.msg()); } } }
輸出結果:

4.3. 讀取方法上的注解
public class User { private String name; private String age; public String getName() { return name; } @Init("louis") public User setName(String name) { this.name = name; return this; } public String getAge() { return age; } @Init("22") public User setAge(String age) { this.age = age; return this; } }
//讀取注解信息 public class ReadAnnotationInfoTest { public static void main(String[] args) throws Exception { // 測試AnnotationTest類,得到此類的類對象 Class c = Class.forName("com.gzl.cn.springbootnacos.my.User"); // 獲取該類所有聲明的方法 Method[] methods = c.getDeclaredMethods(); // 聲明注解集合 Annotation[] annotations; // 遍歷所有的方法得到各方法上面的注解信息 for (Method method : methods) { // 獲取每個方法上面所聲明的所有注解信息 annotations = method.getDeclaredAnnotations(); System.out.println("方法名為:" + method.getName()); if (method.isAnnotationPresent(Init.class)){ Init annotation = method.getAnnotation(Init.class); System.out.println("注解設置的value值為:" + annotation.value()); } for (Annotation an : annotations) { System.out.println("其上面的注解為:" + an.annotationType().getSimpleName()); // 獲取注解class對象 Class<? extends Annotation> aClass = an.annotationType(); // 通過class對象獲取他的所有屬性方法 Method[] meths = aClass.getDeclaredMethods(); // 遍歷每個注解的所有變量 for (Method meth : meths) { System.out.println("注解的變量名為:" + meth.getName()); } } } } }
輸出結果:

4.4. 讀取字段上的注解
public class User { @Init("張三") private String name; @Init("33") private String age; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } }
public static void main(String[] args) throws IllegalAccessException { User user = new User(); user.setAge("111"); user.setName("www"); Class<User> userClass = User.class; Field[] fields = userClass.getDeclaredFields(); for (Field field : fields) { System.out.println("屬性名稱是:" + field.getName()); Init annotation = field.getAnnotation(Init.class); System.out.println("注解的value值是:" + annotation.value()); // 字段是私有的想要獲取就需要將Accessible設置為true,否則報錯 field.setAccessible(true); Object o = field.get(user); System.out.println("user對象的屬性值是:" + o); System.out.println(); } }
輸出的結果:

4.5. 讀取方法參數注解
@RequestMapping(value = "/get", method = RequestMethod.GET) @ResponseBody public boolean get(@RequestParam("username") String usname) { return useLocalCache; }
public static void main(String[] args) throws NoSuchMethodException { Class<ConfigController> configControllerClass = ConfigController.class; // 獲取get方法,第一個參數是方法名,第二個是參數類型 Method get = configControllerClass.getDeclaredMethod("get", String.class); // 方法上可能有多個參數,每個參數上可能有多個注解,所以是二維數組 // annotations[0][0]表示第1個參數的第1個注解 // annotations[0][1]表示第1個參數的第2個注解 Annotation[][] annotations = get.getParameterAnnotations(); for (int i = 0; i < annotations.length; i++) { for (int j = 0; j < annotations[i].length; j++) { if (annotations[i][j] instanceof RequestParam) { RequestParam myParam1 = (RequestParam) annotations[i][j]; System.out.println(myParam1.value()); } } } }
輸出結果:

4.6. 注解配合枚舉使用
@RequestMapping當中method屬性就是使用的枚舉。
@RequestMapping(value = "/get", method = RequestMethod.GET) @ResponseBody public boolean get(@RequestParam("username") String usname) { return useLocalCache; }
public static void main(String[] args) throws NoSuchMethodException { Class<ConfigController> configControllerClass = ConfigController.class; // 獲取get方法,第一個參數是方法名,第二個是參數類型 Method get = configControllerClass.getDeclaredMethod("get", String.class); RequestMapping annotation = get.getAnnotation(RequestMapping.class); RequestMethod[] method = annotation.method(); RequestMethod requestMethod = method[0]; switch (requestMethod) { case GET: System.out.println("get請求"); break; case POST: System.out.println("post請求"); break; } }
輸出結果:


浙公網安備 33010602011771號