深度剖析!注解及反射的使用的底層原理,看完沒有不懂的!


前言

注解(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相關技術文章或行業資訊,歡迎大家關注和轉發文章!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM