前言
注解(Annotation)又稱 Java 標注,是 JDK5.0 引入的一種注釋機制。Java 語言中的類、方法、變量、參數和包等都可以被標注,和 Javadoc(也就是所說的文檔注釋) 不同,Java 標注可以通過反射獲取標注內容。在編譯器生成類文件時,標注可以被嵌入到字節碼中。Java 虛擬機可以保留標注內容,在運行時可以獲取到標注內容 。 當然它也支持自定義 Java 標注。
上面這個太官方了點
我就用土鱉的言語說下,注釋 是為了方便我們閱讀代碼而標注的。而注解的出現我認為是方便我們去實現一些自定義的功能,或者說是方便一些功能的實現。使得我們能夠根據注解上的value去操作到對應的屬性,或找到對應的類。這使得代碼變得更為靈活。
怎么使用
這個注解它是用4個元注解去修飾這個注解。也就是說用元注解規定我們這個自定義注解的作用域等,有下面這四個元注解:
- 1.@Target,
- 2.@Retention,
- 3.@Documented,
- 4.@Inherited
@Target
@Target說明了Annotation所修飾的對象范圍:Annotation可被用於 packages、types(類、接口、枚舉、Annotation類型)、
類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。
在Annotation類型的聲明中使用了target可更加明晰其修飾的目標。
1.CONSTRUCTOR:用於描述構造器
2.FIELD:用於描述域
3.LOCAL_VARIABLE:用於描述局部變量
4.METHOD:用於描述方法
5.PACKAGE:用於描述包
6.PARAMETER:用於描述參數
7.TYPE:用於描述類、接口(包括注解類型) 或enum聲明
@Retention
@Retention定義了該Annotation被保留的時間長短:某些Annotation僅出現在源代碼中,而被編譯器丟棄;
而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀取
(請注意並不影響class的執行,因為Annotation與class在使用上是被分離的)。使用這個meta-Annotation可以對 Annotation的“生命周期”限制。
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在運行時有效(即運行時保留)
@Documented
@Documented指示某一類型的注釋將通過 javadoc 和類似的默認工具進行文檔化。應使用此類型來注釋這些類型的聲明:其注釋會影響由其客戶端注釋的元素的使用。
如果類型聲明是用 Documented 來注釋的,則其注釋將成為注釋元素的公共 API 的一部分。
@Inherited
@Inherited 元注解是一個標記注解,@Inherited闡述了某個被標注的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類。
注意:@Inherited annotation類型是被標注過的class的子類所繼承。類並不從它所實現的接口繼承annotation,方法並不從它所重載的方法繼承annotation。
當@Inherited annotation類型標注的annotation的Retention是RetentionPolicy.RUNTIME,
則反射API增強了這種繼承性。如果我們使用java.lang.reflect去查詢一個@Inherited annotation類型的annotation時,反射代碼檢查將展開工作:檢查class和其父類,
直到發現指定的annotation類型被發現,或者到達類繼承結構的頂層。
我們使用的時候主要還是使用前面兩個,@Target就是告訴編譯器,我這個是修飾在類上還是構造方法還是哪里。@Retention就是告訴編譯器,我的生命周期有多長,在哪里死去。
注解示例
其實要真正使用上注解,至少還需要搭配上泛型,反射的知識,那我下面先簡單寫兩段代碼,看一下這個自定義注解怎么寫出來,后面再介紹下反射,泛型的話我想,大家就自己學一下吧,哈哈。有的人可能早就知道了。
/**
*
* @author T_Antry
* @describe 用於與數據庫對應
* @target - 標記這個注解應該是那種java成員:類。字段、屬性、方法
* @Retention - 標識這個注解怎么保存,只在代碼中,還是編入class文件中,或者是在運行時可以反射
* @Documented - 標記這些注解是否包含在用戶文檔中
* @Inherited - 標記這個注解是繼承哪個注解類(默認注解沒有繼承於任何注解)
* return
* 2020年8月11日
*/
@Target(ElementType.FIELD)//聲明是修飾屬性的
@Retention(RetentionPolicy.RUNTIME)//通常都是選這個屬性,運行時存活,因為一般要配合反射使用
@Documented
@Inherited
public @interface MyField {
String value();//聲明了一個value的key她的值是string
}
一般默認都要有一個value,怎么說呢,用於我們通過value去查找到對應的類或者屬性。
現在看一下我寫的一個模板類,並將上面寫的注解放在屬性上方
/**
*
* @author T_Antry
* @describe 地區模板類
* T_Antry-0718
* 2020年7月19日
*/
public class Area {
@MyField("id")
private int id;
@MyField("pid")
private int pid;
@MyField("name")
private String name;
public Area() {
}
public Area(int id, int pid, String name) {
super();
this.id = id;
this.pid = pid;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
看到這個其實,還沒有任何意義,甚至不知道要干嘛,什么鬼注解有屁用。是的,還是毫無卵用。所以這里還是要介紹一下反射。
三、反射是什么?
反射是java高級特性
JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱為java語言的反射機制。
也就是拿到一個類或對象,就可以通過反射獲取它的所有屬性等,即使你不知道這個類有些什么。
反射機制的相關類
那反射可以拿到類,屬性,方法,構造,這些在java中我們也可以稱之為對象,既然可以稱之為對象,那么他們都有對應的類吧。是的
獲得類相關的方法
獲得類中屬性相關的方法
獲得類中注解相關的方法
獲得類中方法相關的方法
獲得類中構造器相關的方法=
類中其他重要的方法
Field類
Field代表類的成員變量(成員變量也稱為類的屬性)。
Method類
Constructor類
反射示例
首先我先寫一個模板類
/**
*
* @author T_Antry
* @describe 學生模板類
* T_Antry-Test
* 2020年9月7日
*/
public class Student {
private int id;
private String name;
public void doSome() {
}
public Student() {
// TODO Auto-generated constructor stub
}
public Student(int id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + "]";
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
非常簡單測試類,只測試屬性,其它的暫且不多講,認真理解一下屬性,其它的內容是一樣的。也希望通過這樣比較簡潔的介紹,能夠更清楚地了解如何配合注解的使用。
/**
*
* @author T_Antry
* @describe 反射測試
* T_Antry-Test
* 2020年9月9日
*/
public class Test {
/**
*
* @param args
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @describe
*/
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
Student st = new Student(520, "T_Antry");//創建一個實例
System.out.println("----------Begin");
System.out.println(st);
System.out.println("----------");
/**
* 首先我們要拿到類
*/
Class<?> c = st.getClass();
Field[] fArr = c.getDeclaredFields();//獲取本類所有屬性,包括私有屬性
// fArr = c.getFields();//只獲取公有屬性,且包括繼承來的屬性
for (Field field : fArr) {
field.setAccessible(true);//注意,我們的模板類寫的是幾個私有成員,這條是開啟權限,使得我們都能夠操作和讀取私有屬性的值
System.out.println(field.get(st));
}
}
}
我們看下運行的結果
可以看到我們通過反射,拿到類中的所有屬性,雖然我這邊只寫了私有屬性,大家可以自己寫點有公有屬性的,也可以寫一些父類。可以看到getDeclaredFields是可以獲得當前類的所有屬性,getFields是可以獲得當前類以及繼承的所有父類的公有屬性。
拿到屬性之后,我們可以遍歷屬性並查看傳入對象的屬性值。但是這邊要注意的是需要調用setAccessible(true)方法使得我們可以查看私有成員。
反射與注解的配合使用
首先在Student類中,我找一個屬性加上前面注解示例中寫的Myfield注解,例如我在name屬性上加一個注解,且給一個value為"fuck"
想了想還是讓大家看下我修改后的類吧
import anno.MyField;
/**
*
* @author T_Antry
* @describe 學生模板類
* T_Antry-Test
* 2020年9月7日
*/
public class Student {
private int id;
@MyField("fuck")
private String name;
public void doSome() {
}
public Student() {
// TODO Auto-generated constructor stub
}
public Student(int id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + "]";
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
現在模板類修改好了,我就可以開始寫特使類,接下來我要把類的屬性反射出來,同時還要找到帶有注解的屬性,把它打印出來。
更新之后的測試類,我在打印上為了更清楚,做了少許修改,可以copy去運行,包導入就行。
**
*
* @author T_Antry
* @describe 反射測試
* T_Antry-Test
* 2020年9月9日
*/
public class Test {
/**
*
* @param args
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @describe
*/
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
Student st = new Student(520, "T_Antry");//創建一個實例
System.out.println("----------Begin");
System.out.println(st);
System.out.println("----------反射示例");
/**
* 首先我們要拿到類
*/
Class<?> c = st.getClass();
Field[] fArr = c.getDeclaredFields();//獲取本類所有屬性,包括私有屬性
// fArr = c.getFields();//只獲取公有屬性,且包括繼承來的屬性
for (Field field : fArr) {
field.setAccessible(true);//注意,我們的模板類寫的是幾個私有成員,這條是開啟權限,使得我們都能夠操作和讀取私有屬性的值
System.out.println(field.getName()+":" +field.get(st));
}
System.out.println("----------反射+注解示例1:沒有注解的不打印");
for (Field field : fArr) {
field.setAccessible(true);//注意,我們的模板類寫的是幾個私有成員,這條是開啟權限,使得我們都能夠操作和讀取私有屬性的值
MyField anno = field.getDeclaredAnnotation(MyField.class);
if(null==anno)
continue;
System.out.println(field.getName()+":" +field.get(st));
}
}
}
運行結果
如圖可見,我們只打印了name屬性,因為我在name屬性上面加了注解,這里檢測到有注解,就打印了。
更多玩法
當然還有很多玩法,例如你還可以拿到注解上value的值去做一些事請,比如ORM原理就是才有這種做法。
實際上,我認為,注解可能更經常用於類的上方,隨后通過注解去找到對應的類。這種做法十分有利於代碼的簡潔。但是這個實例,因為還涉及網絡技術,服務器,以及整個項目的運轉,也可能是我水平不夠,就不好一下子介紹完,哈哈。后面有可能的話,希望還是可以將一下Servlet注解原理。
最后
感謝你看到這里,文章有什么不足還請指正,覺得文章對你有幫助的話記得給我點個贊,每天都會分享java相關技術文章或行業資訊,歡迎大家關注和轉發文章!