java注解的實現原理(1)
注解的本質就是一個繼承了Annotation接口的接口
寫在前面,在前面總結了java反射和動態代理的一些知識,同時之前沒有仔細研究注解這塊,只知道注解的實現原理是基於動態代理的,主要作用有一下:
- 1.編譯檢查:例如使用@SupperssWarnings,@Override都具有編譯檢查的作用。
- 2.可以幫助生成文檔,例如@Return @Param等注解。
- 3.在框架中替換之前的xml文件使用注解開發web,例如spring中的各種注解。
之前的理解一直差不多這種,最近剛好不忙想相似的了解一下java注解的實現原理,在開始看自定義注解中,看到很多博客里面寫的都是如下這樣:
自定義注解Info.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Info{
String value();
}
使用自定義的注解:People.java
public class People{
@Info("張三")
private String name;
public String getName(){
return this.name;
}
public void setName(String name){
this.name = name;
}
}
然后一般到這里就完了,但是當我照着這樣寫了一遍再用測試類來運行生成People類對象並調用getName()方法時,我們並得不到我們設置的張三,同時我也看不到這個動態代理在哪里。為此有翻閱了好久的資料發現注解不是這樣用的。我們自定義的注解其實還需要一個中間配置類來配置的我的注解解析。
如下我們自定義了2個注解@LoadProperty 用來配置加載我們的配置文件,@ConfigField用來配置下面的字段賦值。中間用來配合注解解析的類AnnoResolve.java,使用這兩個注解的User.java和測試主函數存在的類TestUser.java。具體代碼實現如下所示:
@LoadProperty 用來配置加載我們的配置文件
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoadProperty {
String value(); // 配置文件的路徑
}
@ConfigField用來配置下面的字段賦值
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigField{
String value();
}
使用以上兩個注解的User.java
package com.ths.annotaion;
@LoadProperty("D:\\JAVA_HOME\\bevisStudy\\src\\com\\ths\\annotaion\\config.properties")
public class User {
//在類中使用注解
@ConfigField("user.id")
private String id;
@ConfigField("name")
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
中間配置類AnnoResolve.java
public class AnnoResolve {
public static <T> void loadProperty(T t){
// 通過傳入的user對象來獲取User的Class對象cls
Class<? extends Object> cls =t.getClass();
// 通過isAnnotationPresent()方法判斷LoadProperty注解是否存在於此元素上面
boolean hasLoadPropertyAnno = cls.isAnnotationPresent(LoadProperty.class);
if(hasLoadPropertyAnno){
//為屬性賦值
try {
configField(cls,t);
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
private static <T> void configField(Class<? extends Object> cls,T t) throws IOException, IllegalAccessException {
// 獲取到cls資源上的注解代理類,使用其中的value()方法得到其中的配置文件路徑
String filePath = cls.getAnnotation(LoadProperty.class).value();
Properties properties = new Properties();
// InputStream is = AnnoResolve.class.getClassLoader().getResourceAsStream(filePath);
InputStream is = new FileInputStream(filePath);
System.out.println(is);
properties.load(is);
// 通過反射獲取到user上面的字段
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
// 遍歷找到字段上含有注解的字段
boolean hasConfigField = field.isAnnotationPresent(ConfigField.class);
String fieldValue = null;
// 如屬性上有注解,使用注解的值作為key去配置文件中查找
if(hasConfigField){
// 獲取注解的值
Object annoValue = field.getAnnotation(ConfigField.class).value();
fieldValue = properties.getProperty(annoValue.toString());
// 如屬性上沒有值
}else{
fieldValue = properties.getProperty(field.getName());
}
// 如果是私有成員變量需要設置為true
field.setAccessible(true);
field.set(t,fieldValue);
}
is.close();
}
}
測試注解在User中使用的TestUser.java
public class TestUser {
public static void main(String[] args) {
User user = new User();
AnnoResolve.loadProperty(user);
System.out.println(user.getId());
System.out.println(user.getName());
}
}
其中還要寫一個config.properties配置文件
其所有在的位置就是在User中需要在注解上傳入的值。通過運行TestUser就可以得到如下結果:
到此說明通過注解的方法,我們確實給我們創建的User對象賦了我們在配置文件中設置的字符,自定義注解使用成功,下面我們就詳細分析一下定義注解的一個過程:
-
1.注解的定義方式使用@interface關鍵字,注解里面的都是定義的方法,后面會在通過動態代理的方法實現該注解的代理類,然后調用代理類中的value()方法來獲取對應的值,一個注解中定義有多個函數時,在使用注解是需要顯示的賦值如下所示。
public Test{ // 加入定義的InfoTest注解里面有兩個函數value1和value2,使用時需要顯式的進行賦值 @InfoTest(value1="name",value2="test") private String name; }
-
2.在自定義注解時,注解上面的是java的一些元注解,作用如下:
@Target() //表示該注解使用的位置,一般有類,方法,字段,構造函數 ElementType.FIELD 使用在屬性字段上 ElementType.Type 使用在類上 ElementType.METHOD 使用在方法上 ElementType.PARAMETER 使用咋方法的參數上 ElementType.CONSTRUCTOR 使用在構造函數上 ElementType.ANNOTATION_TYPE 使用在注解上 ElementType.PACKAGE 使用在包上 ElementType.LOCAL_VARIABLE 使用在本地局部變量上 @Retention() // 定義該注解的生命周期 SOURCE:注解只保留在源文件,當Java文件編譯成class文件的時候,注解被遺棄; CLASS:注解被保留到class文件,但jvm加載class文件時候被遺棄,這是默認的生命周期; RUNTIME:注解不僅被保存到class文件中,jvm加載class文件之后,仍然存在; @Doucumented // 表示該注解會寫入到文檔中 @Inherited // 表示定義的這個注解當標注在一個類上時,當其有其他子類來繼承時,子類也會自動繼承標注在父類上的注解
-
3.注解的配置類這一部分其實是真正注解給注解的類賦值的實現過程。
- a.首先通過傳入的user獲取到User的Class 對象cls,判斷類上面是否有LoadProperty注解,同時可以通過cls.getAnnotation(傳入注解的Class對象)可以得到java動態代理生成該注解的代理類,然后使用對應的value()方法獲取到前面,我們使用注解傳入的配置文件路徑。(這一部分的實現源碼,在后面的博客中繼續分析)
- b.有的話,通過cls對象反射獲取到user中所有的成員字段,遍歷所有的字段,使用同樣的isAnnotationPresent()方法判斷在該元素上時候有我們需要的注解,用需要的方法來取我們需要用到的值。
- c.通過cls對象使用發射,如果是字段使用set方法來需要的字段賦值剛才通過注解獲取到的值。(配置到方法上的以后再看)
以上只是自定義注解的使用整體理解,對於其中的更近一層的細究在后面繼續進行。如在getAnnotation()在java源碼中是如何生成動態代理中。在程序使用debug你會發現通過getAnnotation()返回的對象屬性是$Prxoy1,不想繼續往下看,這樣記住返回出來的值就是一個該注解的一個動態代理的代理類。