深入理解Java中的注解Annotation
Java中的注解(Annotation)
單詞Annotation可翻譯為:注釋、注解。
單詞Comment可翻譯為:評(píng)論、議論、解釋。
在Java當(dāng)中,Comment充當(dāng)注釋的含義,Annotation充當(dāng)注解的含義。
注解和注釋有啥區(qū)別呢?
Java中有單行注釋、多行注釋、文檔注釋。
注釋Comment
- 單行注釋 //
- 多行注釋 /* */
- 文檔注釋 /** */
這些注釋都是在編譯以后不會(huì)出現(xiàn)在字節(jié)碼中的,僅僅是存在于java源文件中給程序員看的,它們對(duì)于程序的執(zhí)行沒(méi)有任何影響。
而注解就不同了,它是代碼級(jí)別的,是會(huì)編譯到class字節(jié)碼當(dāng)中,對(duì)程序的運(yùn)行產(chǎn)生影響或者對(duì)編譯產(chǎn)生影響。
注解嚴(yán)格意義上來(lái)說(shuō)就是Java中的類(lèi)成員,它和屬性、方法、構(gòu)造方法是一樣的級(jí)別。
初學(xué)注解
注解在Java中確實(shí)也很常見(jiàn),但是人們常常不會(huì)自己定義一個(gè)注解拿來(lái)用。我們雖然很少去自定義注解,但是學(xué)會(huì)注解的寫(xiě)法,注解的定義,學(xué)會(huì)利用反射解析注解中的信息,在開(kāi)發(fā)中能夠使用到,這是很關(guān)鍵的。
代碼運(yùn)行離不開(kāi)一些配置信息的支撐,有些配置信息我們選擇保存在文件中(.xml .properties),再利用IO流的技術(shù)讀取這些配置信息去使用,它們都是配置和代碼分離的形式,這種方式的好處是低耦合,代碼已經(jīng)打包壓縮好了不用動(dòng),而配置信息修改起來(lái)很方便,通過(guò)修改配置,可以讓代碼完成不同的任務(wù),這就很方便。不好的地方在于:開(kāi)發(fā)人員寫(xiě)的代碼和配置不在一個(gè)文件中,開(kāi)發(fā)過(guò)程中翻看就不是很方便。相當(dāng)于開(kāi)發(fā)麻煩了,維護(hù)卻簡(jiǎn)單了。
隨著開(kāi)發(fā)的越來(lái)越多,人們發(fā)現(xiàn)文件中的一些配置,是寫(xiě)到那里很少去更改的,沒(méi)有人會(huì)輕易修改那些重要的配置信息,這些信息就像被寫(xiě)死了一樣,所以這些信息就可以用注解寫(xiě),寫(xiě)到代碼里去,因?yàn)樽⒔夂痛a是內(nèi)聚在一起的,開(kāi)發(fā)過(guò)程就很方便??偨Y(jié)來(lái)說(shuō)是各有優(yōu)劣吧。
1.注解的寫(xiě)法
@XXX [(一些信息)]
這是使用注解的寫(xiě)法,使用注解前必須先定義注解,我們可以使用像@Override這樣的注解,那是因?yàn)檫@些注解在Java中已經(jīng)寫(xiě)好了,我們直接拿來(lái)用就好(下面會(huì)講到如何自定義注解)。
注解中的[一些信息]可能存在,也可能不存在,這需要看注解的定義者是如何定義該注解的。
2.注解放置在哪里
注解可以放置在:類(lèi)的上面、屬性上面、方法上面、構(gòu)造方法上面、局部變量上面、參數(shù)前面。
注解能夠放置在哪里,這也需要看注解的定義者是如何定義該注解的。
3.注解的作用
- 用來(lái)充當(dāng)注釋的作用(僅僅是一個(gè)文字的說(shuō)明) 例如:@Deprecated
- 用來(lái)做代碼的檢測(cè)(驗(yàn)證) 例如:@Override
- 可以攜帶一些信息(內(nèi)容) 就類(lèi)似于:文件.properties .xml 的作用
4.Java中有一些寫(xiě)好的注解供我們使用
- @Deprecated 用來(lái)說(shuō)明方法是廢棄的
- @Override 用來(lái)做代碼檢測(cè),檢測(cè)此方法是否是一個(gè)重寫(xiě)方法,如果不是,該注解會(huì)報(bào)錯(cuò)
- @SuppressWarnings({"","",""}) 它里面的參數(shù)是一個(gè)String類(lèi)型的數(shù)組,如果數(shù)組內(nèi)的元素只有一個(gè)長(zhǎng)度 ,則大括號(hào){}可以省略,該注解中可以定義的有意義參數(shù)值包括:
- unused:變量定義后未被使用
- serial:類(lèi)實(shí)現(xiàn)了序列化接口 不添加序列化ID號(hào)
- rawtypes:集合沒(méi)有定義泛型
- deprecation:方法已經(jīng)廢棄
- *unchecked:出現(xiàn)了泛型的問(wèn)題 可以不檢測(cè)
- all:包含了以上所有(不推薦)
5.注解中可以攜帶信息,也可以不攜帶
注意:注解信息不能隨意寫(xiě),注解信息的類(lèi)型只能是如下的類(lèi)型:
- 基本數(shù)據(jù)類(lèi)型
- String類(lèi)型
- 枚舉類(lèi)型enum
- 注解類(lèi)型@
- 數(shù)組類(lèi)型[],數(shù)組的內(nèi)部只能存儲(chǔ)如上的四種類(lèi)型
基本數(shù)據(jù)類(lèi)型String類(lèi)型枚舉類(lèi)型enum注解類(lèi)型@數(shù)組類(lèi)型[],數(shù)組的內(nèi)部只能存儲(chǔ)如上的四種類(lèi)型
自定義一個(gè)注解類(lèi)型
通過(guò)@interface 定義一個(gè)新的注解類(lèi)型
public @interface MyAnnatation {<!--{C}%3C!%2D%2D%20%2D%2D%3E-->}
- 可以發(fā)現(xiàn)注解的寫(xiě)法與接口非常相似(可以利用接口的特點(diǎn)來(lái)記憶注解)
- 可以描述public static final的屬性 比較少見(jiàn)可以描述public abstract的方法 方法要求必須有返回值 返回值類(lèi)型必須是如上那些(基本數(shù)據(jù)類(lèi)型、String類(lèi)型、枚舉類(lèi)型、注解類(lèi)型、數(shù)組類(lèi)型)
public @interface MyAnnatation { int NUM = 9;//注解中寫(xiě)屬性,很少見(jiàn) String test();//方法要求必須有返回值 }
注解元素的默認(rèn)值
注解元素必須有確定的值,要么在定義注解的默認(rèn)值中指定,要么在使用注解時(shí)指定,非基本類(lèi)型的注解元素的值不可為null。因此, 使用空字符串或0作為默認(rèn)值是一種常用的做法。這個(gè)約束使得處理器很難表現(xiàn)一個(gè)元素的存在或缺失的狀態(tài),因?yàn)槊總€(gè)注解的聲明中,所有元素都存在,并且都具有相應(yīng)的值,為了繞開(kāi)這個(gè)約束,我們只能定義一些特殊的值,例如空字符串或者負(fù)數(shù),一次表示某個(gè)元素不存在,在定義注解時(shí),這已經(jīng)成為一個(gè)習(xí)慣用法。
元注解
我們自己定義的注解如果想要拿來(lái)使用,光定義還不夠 ,還需要做很多細(xì)致的說(shuō)明(需要利用Java提供好的注解來(lái)說(shuō)明)
這就需要使用到元注解(也是注解 不是拿來(lái)使用的 是用來(lái)說(shuō)明注解的):
@Target
@Target說(shuō)明了Annotation所修飾的對(duì)象范圍:Annotation可被用于 packages、types(類(lèi)、接口、枚舉、Annotation類(lèi)型)、類(lèi)型成員(方法、構(gòu)造方法、成員變量、枚舉值)、方法參數(shù)和本地變量(如循環(huán)變量、catch參數(shù))。在Annotation類(lèi)型的聲明中使用了target可更加明晰其修飾的目標(biāo)。
作用:
用于描述注解的使用范圍(即:被描述的注解可以用在什么地方)
取值(ElementType)有:
- CONSTRUCTOR:用于描述構(gòu)造器
- FIELD:用于描述域
- LOCAL_VARIABLE:用于描述局部變量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述參數(shù)
- TYPE:用于描述類(lèi)、接口(包括注解類(lèi)型) 或enum聲明
更詳細(xì)的@Target定義可參照下表:
Target | 類(lèi)型描述 |
ElementType.TYPE | 應(yīng)用于類(lèi)、接口(包括注解類(lèi)型)、枚舉 |
ElementType.FIELD | 應(yīng)用于屬性(包括枚舉中的常量) |
ElementType.METHOD | 應(yīng)用于方法 |
ElementType.PARAMETER | 應(yīng)用于方法的形參 |
ElementType.CONSTRUCTOR | 應(yīng)用于構(gòu)造函數(shù) |
ElementType.LOCAL_VARIABLE | 應(yīng)用于局部變量 |
ElementType.ANNOTATION_TYPE | 應(yīng)用于注解類(lèi)型 |
ElementType.PACKAGE | 應(yīng)用于包 |
ElementType.TYPE_PARAMETER | 1.8版本新增,應(yīng)用于類(lèi)型變量) |
ElementType.TYPE_USE | 1.8版本新增,應(yīng)用于任何使用類(lèi)型的語(yǔ)句中(例如聲明語(yǔ)句、泛型和強(qiáng)制轉(zhuǎn)換語(yǔ)句中的類(lèi)型) |
@Retention
- 描述當(dāng)前的這個(gè)注解存在什么作用域中
- 注解存在的三種作用域:SOURCE、CLASS、RUNTIME
- 對(duì)應(yīng)于:源代碼文件—>編譯—>字節(jié)碼文件—>加載—>內(nèi)存執(zhí)行
@Retention定義了該Annotation被保留的時(shí)間長(zhǎng)短:某些Annotation僅出現(xiàn)在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會(huì)被虛擬機(jī)忽略,而另一些在class被裝載時(shí)將被讀取(請(qǐng)注意并不影響class的執(zhí)行,因?yàn)锳nnotation與class在使用上是被分離的)。使用這個(gè)meta-Annotation可以對(duì) Annotation的“生命周期”限制。
作用:表示需要在什么級(jí)別保存該注釋信息,用于描述注解的生命周期(即:被描述的注解在什么范圍內(nèi)有效)
取值(RetentionPoicy)有:
- SOURCE:在源文件中有效(即源文件保留)
- CLASS:在class文件中有效(即class保留)
- RUNTIME:在運(yùn)行時(shí)有效(即運(yùn)行時(shí)保留)
生命周期類(lèi)型 | 描述 |
RetentionPolicy.SOURCE | 編譯時(shí)被丟棄,不包含在類(lèi)文件中 |
RetentionPolicy.CLASS | JVM加載時(shí)被丟棄,包含在類(lèi)文件中,默認(rèn)值 |
RetentionPolicy.RUNTIME | 由JVM 加載,包含在類(lèi)文件中,在運(yùn)行時(shí)可以被獲取到 |
Retention meta-annotation類(lèi)型有唯一的value作為成員,它的取值來(lái)自java.lang.annotation.RetentionPolicy的枚舉類(lèi)型值。
@Inherited
描述當(dāng)前這個(gè)注解是否能被子類(lèi)對(duì)象繼承(不太常用)
@Inherited 元注解是一個(gè)標(biāo)記注解,@Inherited闡述了某個(gè)被標(biāo)注的類(lèi)型是被繼承的。如果一個(gè)使用了@Inherited修飾的annotation類(lèi)型被用于一個(gè)class,則這個(gè)annotation將被用于該class的子類(lèi)。
注意:@Inherited annotation類(lèi)型是被標(biāo)注過(guò)的class的子類(lèi)所繼承。類(lèi)并不從它所實(shí)現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation。
當(dāng)@Inherited annotation類(lèi)型標(biāo)注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強(qiáng)了這種繼承性。如果我們使用java.lang.reflect去查詢(xún)一個(gè)@Inherited annotation類(lèi)型的annotation時(shí),反射代碼檢查將展開(kāi)工作:檢查class和其父類(lèi),直到發(fā)現(xiàn)指定的annotation類(lèi)型被發(fā)現(xiàn),或者到達(dá)類(lèi)繼承結(jié)構(gòu)的頂層。
@Document
描述這個(gè)注解是否能被Javadoc 或類(lèi)似的工具文檔化(不常用)
自己使用自己描述的注解
自定義一個(gè)注解:
import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; @Target({METHOD,CONSTRUCTOR,FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String[] value();//方法不是做事情 為了攜帶信息 搬運(yùn)給該注解的解析者使用 //按道理講 注解定義者肯定和注解解析者是同一個(gè)人,而注解的使用者,它們無(wú)需定義和解析注解 /*方法名剛好是value,并且只有一個(gè)方法,使用該注解的時(shí)候就可以不指定方法名*/ }
使用自己的注解:
由注解的定義可知,該注解可以放置在方法、構(gòu)造方法、屬性上。
作用范圍是運(yùn)行時(shí)RUNTIME
public class Person { @MyAnnotation("TOM") private String name; }
問(wèn)題1. 在注解里面描述了一個(gè)方法,方法沒(méi)有參數(shù),方法有返回值String[]
使用注解的時(shí)候,讓我們傳遞參數(shù),如何理解該過(guò)程?
可以理解為:注解的方法做事,把我們傳遞給它的參數(shù)搬運(yùn)走了,給了別人,別人解析這些注解中的參數(shù),做相應(yīng)的處理
問(wèn)題2. 使用別人寫(xiě)好的注解不用寫(xiě)方法名,我們自己定義的方法必須寫(xiě)名字*
- 如果我們自己定義的注解 只有一個(gè)方法 方法名字叫value
- 在使用的時(shí)候就可以省略方法名
- 如果傳遞的信息是一個(gè)數(shù)組 數(shù)組內(nèi)只有一個(gè)元素 可以省略數(shù)組大括號(hào){}
- 如果方法是兩個(gè)以上 每一個(gè)方法都要使用 并且每一個(gè)方法必須寫(xiě)名字
舉例:
import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; @Target({METHOD,FIELD}) @Retention(RetentionPolicy.SOURCE) public @interface YouAnnotation { String [] value(); int count(); double price(); }
public class Student { //注解中有3個(gè)方法,則3個(gè)方法都要使用到,并且要指明方法名 //如果數(shù)組中只有一個(gè)數(shù)據(jù),則{}可以省略 @YouAnnotation(value = "qa",count = 8,price = 9.9) private String name; }
如何解析注解內(nèi)攜帶的信息(反射機(jī)制)
解析注解其實(shí)也很簡(jiǎn)單,它的思路是:先看該注解聲明在了哪里,比如,一個(gè)屬性name上面聲明了一個(gè)注解,那么就利用反射,先找到這個(gè)類(lèi),然后找到這個(gè)屬性name,根據(jù)這個(gè)屬性,調(diào)用getAnnotation()方法,得到這個(gè)注解,然后調(diào)用這個(gè)注解對(duì)象的getClass方法獲取注解的類(lèi)型,用注解的類(lèi)型,調(diào)用getMethod(methodName)方法,根據(jù)方法名獲取注解中的方法,然后,這個(gè)方法對(duì)象調(diào)用 invoke方法去執(zhí)行方法,方法的參數(shù)是那個(gè)annotation對(duì)象,方法的返回值就是這個(gè)注解中攜帶的信息了。
public class Demo { public static void main(String[] args) { Class<?> clazz = Person.class; try { Field field = clazz.getDeclaredField("name"); MyAnnotation annotation = field.getAnnotation(MyAnnotation.class); Class<? extends MyAnnotation> aClass = annotation.getClass(); Method method = aClass.getMethod("value"); String[] values = (String[])method.invoke(annotation); System.out.println(Arrays.toString(values)); } catch (Exception e) { e.printStackTrace(); } } }
做個(gè)小案例,感受一下控制反轉(zhuǎn)和依賴(lài)注入的基本原理
Spring的核心特性是控制反轉(zhuǎn)(IOC)和面向切面編程(AOP)。控制反轉(zhuǎn)是指對(duì)象的控制權(quán)不在我們手里了,而是交給了Spring給我們創(chuàng)建。我們只需要把實(shí)體類(lèi)定義好,提供好無(wú)參構(gòu)造方法和get、set方法就好了,它給你的對(duì)象自動(dòng)賦值了,這就叫依賴(lài)注入(DI)。該案例它只能夠處理9種屬性類(lèi)型,包括8種基本類(lèi)型的包裝類(lèi)以及String,其他的類(lèi)型還不能支持。
package test_annotation; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * @author 喬澳 * @version 1.0 * @title: MySpringDemo * @projectName Demo1 * @description: * @date 2020/8/17 18:22 */ public class MySpringDemo { public Object getBean(String className){ Object obj = null; Class<?> clazz = null; try { clazz = Class.forName(className); //獲取無(wú)參構(gòu)造方法 Constructor con = clazz.getConstructor(); //調(diào)用無(wú)參構(gòu)造方法創(chuàng)建對(duì)象 obj = con.newInstance(); //解析注解中的信息 //注解放在屬性上面,首先獲取所有的屬性 Field[] fields = clazz.getDeclaredFields(); for(int i = 0;i<fields.length;i++){ //根據(jù)屬性獲取屬性上面聲明的注解 Annotation annotation = fields[i].getAnnotation(MyAnnotation.class); Class<?> aClass = annotation.getClass(); Method aMethod = aClass.getMethod("value"); //獲取注解中的值 String[] values = (String[]) aMethod.invoke(annotation); //獲取屬性名 String fieldName = fields[i].getName(); //要給屬性賦值,屬性是私有的,雖然反射可以操作私有屬性,但是很不合理 //我們利用字符串的拼接,得到set方法的名字,再拿到set方法給屬性賦值 String firstLetter = fieldName.substring(0,1).toUpperCase();//首字母大寫(xiě) String otherLetters = fieldName.substring(1); StringBuilder setMethodName = new StringBuilder("set"); setMethodName.append(firstLetter); setMethodName.append(otherLetters); //拿到屬性的類(lèi)型,下面要用到 Class<?> fieldType = fields[i].getType(); //根據(jù)set方法名字拿到set方法 Method setMethod = clazz.getMethod(setMethodName.toString(),fieldType); //調(diào)用set方法,給對(duì)象賦值,如果屬性不是String類(lèi)型,是基本類(lèi)型的包裝類(lèi) //如果屬性是Character類(lèi)型,做單獨(dú)處理 if (fieldType==Character.class){ //把字符串轉(zhuǎn)化為字符 Character c = values[0].toCharArray()[0]; setMethod.invoke(obj,c); }else{ //不是Character類(lèi)型,而是String 或其他7種包裝類(lèi),調(diào)用包裝類(lèi)的帶String參數(shù)的構(gòu)造方法,構(gòu)造值 比如;new Integer(String v) setMethod.invoke(obj,fieldType.getConstructor(String.class).newInstance(values[0])); } //對(duì)于屬性為數(shù)組、集合、對(duì)象的情況,這里沒(méi)有做處理 } } catch (Exception e) { e.printStackTrace(); } return obj; } }
測(cè)試:
寫(xiě)一個(gè)實(shí)體類(lèi)
package test_annotation; public class Person { @MyAnnotation("Tom") private String name; @MyAnnotation("18") private Integer age; @MyAnnotation("男") private String sex; @MyAnnotation("A") private Character bloodType;//血型 public Person(){} public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Character getBloodType() { return bloodType; } public void setBloodType(Character bloodType) { this.bloodType = bloodType; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", sex='" + sex + '\'' + ", bloodType=" + bloodType + '}'; } }
main方法:
package test_annotation; public class TestMain { public static void main(String[] args) { MySpringDemo msd = new MySpringDemo(); Person p = (Person) msd.getBean("test_annotation.Person"); System.out.println(p); } }
到此這篇關(guān)于深入理解Java中的注解Annotation的文章就介紹到這了,更多相關(guān)深入理解Java注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解spring cloud hystrix 請(qǐng)求合并collapsing
這篇文章主要介紹了詳解spring cloud hystrix 請(qǐng)求合并collapsing,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05MyBatisPlus自定義SQL的實(shí)現(xiàn)
MyBatisPlus提供了自定義SQL功能,允許開(kāi)發(fā)者在Mapper接口中定義方法,并通過(guò)XML文件或注解編寫(xiě)SQL語(yǔ)句,本文詳解了如何在MP中使用自定義SQL,感興趣的可以了解一下2024-09-09Springmvc請(qǐng)求參數(shù)類(lèi)型轉(zhuǎn)換器及原生api代碼實(shí)例
這篇文章主要介紹了Springmvc請(qǐng)求參數(shù)類(lèi)型轉(zhuǎn)換器及原生api代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10怎樣將一個(gè)JAR包添加到Java應(yīng)用程序的Boot?Classpath中
本文文章給大家介紹如何將一個(gè)JAR包添加到Java應(yīng)用程序的Boot?Classpath中,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的的朋友參考下吧2023-11-11Java中bcrypt算法實(shí)現(xiàn)密碼加密的方法步驟
我們可以在Spring Boot和SSM中實(shí)現(xiàn)密碼加密,使用bcrypt算法可以保障密碼的安全性,并且減少了手動(dòng)編寫(xiě)哈希函數(shù)的工作量,本文就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下2023-08-08SpringBoot集成MyBatis對(duì)管理員的查詢(xún)操作
本文主要介紹了SpringBoot集成MyBatis對(duì)管理員的查詢(xún)操作,實(shí)現(xiàn)增刪改查中的查詢(xún)操作,對(duì)所有的普通管理員進(jìn)行查詢(xún)操作,感興趣的可以了解一下2023-11-11idea使用Maven Helper插件去掉無(wú)用的poom 依賴(lài)信息(詳細(xì)步驟)
這篇文章主要介紹了idea使用Maven Helper插件去掉無(wú)用的poom 依賴(lài)信息,本文分步驟給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04淺談Java數(shù)值類(lèi)型的轉(zhuǎn)換與強(qiáng)制轉(zhuǎn)換
這篇文章主要介紹了Java數(shù)值類(lèi)型的轉(zhuǎn)換與強(qiáng)制轉(zhuǎn)換,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04