java中注解的實現(xiàn)原理詳解
1.什么是注解
注解也叫元數(shù)據(jù),例如我們常見的@Override和@Deprecated,注解是JDK1.5版本開始引入的一個特性,用于對代碼進行說明,可以對包、類、接口、字段、方法參數(shù)、局部變量等進行注解
一般常用的注解可以分為三類:
- 一類是Java自帶的標(biāo)準(zhǔn)注解,包括@Override(標(biāo)明重寫某個方法)、@Deprecated(標(biāo)明某個類或方法過時)和@SuppressWarnings(標(biāo)明要忽略的警告),使用這些注解后編譯器就會進行檢查。
- 一類為元注解,元注解是用于定義注解的注解,包括@Retention(標(biāo)明注解被保留的階段)、@Target(標(biāo)明注解使用的范圍)、@Inherited(標(biāo)明注解可繼承)、@Documented(標(biāo)明是否生成javadoc文檔)
- 一類為自定義注解,可以根據(jù)自己的需求定義注解
2.注解的用途
在看注解的用途之前,有必要簡單的介紹下XML和注解區(qū)別,
注解:是一種分散式的元數(shù)據(jù),與源代碼緊綁定。
xml:是一種集中式的元數(shù)據(jù),與源代碼無綁定
當(dāng)然網(wǎng)上存在各種XML與注解的辯論哪個更好,這里不作評論和介紹,主要介紹一下注解的主要用途:
- 生成文檔,通過代碼里標(biāo)識的元數(shù)據(jù)生成javadoc文檔。
- 編譯檢查,通過代碼里標(biāo)識的元數(shù)據(jù)讓編譯器在編譯期間進行檢查驗證。
- 編譯時動態(tài)處理,編譯時通過代碼里標(biāo)識的元數(shù)據(jù)動態(tài)處理,例如動態(tài)生成代碼。
- 運行時動態(tài)處理,運行時通過代碼里標(biāo)識的元數(shù)據(jù)動態(tài)處理,例如使用反射注入實例
3.注解使用演示
這邊總共定義了4個注解來演示注解的使用
- 定義一個可以注解在Class,interface,enum上的注解,
- 定義一個可以注解在METHOD上的注解
- 定義一個可以注解在FIELD上的注解
- 定義一個可以注解在PARAMETER上的注解
具體代碼如下:
/**
* 定義一個可以注解在Class,interface,enum上的注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetType {
/**
* 定義注解的一個元素 并給定默認值
* @return
*/
String value() default "我是定義在類接口枚舉類上的注解元素value的默認值";
}/**
* 定義一個可以注解在METHOD上的注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetMethod {
/**
* 定義注解的一個元素 并給定默認值
*/
String value() default "我是定義在方法上的注解元素value的默認值";
}/**
* 定義一個可以注解在FIELD上的注解
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetField {
/**
* 定義注解的一個元素 并給定默認值
*/
String value() default "我是定義在字段上的注解元素value的默認值";
}/**
* 定義一個可以注解在PARAMETER上的注解
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetParameter {
/**
* 定義注解的一個元素 并給定默認值
*/
String value() default "我是定義在參數(shù)上的注解元素value的默認值";
}編寫一個測試處理類處理以上注解,這邊不過多解釋,代碼中都寫了對應(yīng)的注釋如下:
/**
* 測試java注解類
*/
@MyAnTargetType
public class AnnotationTest {
@MyAnTargetField
private String field = "我是字段";
@MyAnTargetMethod("測試方法")
public void test(@MyAnTargetParameter String args) {
System.out.println("參數(shù)值 === "+args);
}
public static void main(String[] args) {
// 獲取類上的注解MyAnTargetType
MyAnTargetType t = AnnotationTest.class.getAnnotation(MyAnTargetType.class);
System.out.println("類上的注解值 === "+t.value());
MyAnTargetMethod tm = null;
try {
// 根據(jù)反射獲取AnnotationTest類上的test方法
Method method = AnnotationTest.class.getDeclaredMethod("test",String.class);
// 獲取方法上的注解MyAnTargetMethod
tm = method.getAnnotation(MyAnTargetMethod.class);
System.out.println("方法上的注解值 === "+tm.value());
// 獲取方法上的所有參數(shù)注解 循環(huán)所有注解找到MyAnTargetParameter注解
Annotation[][] annotations = method.getParameterAnnotations();
for(Annotation[] tt : annotations){
for(Annotation t1:tt){
if(t1 instanceof MyAnTargetParameter){
System.out.println("參數(shù)上的注解值 === "+((MyAnTargetParameter) t1).value());
}
}
}
method.invoke(new AnnotationTest(), "改變默認參數(shù)");
// 獲取AnnotationTest類上字段field的注解MyAnTargetField
MyAnTargetField fieldAn = AnnotationTest.class.getDeclaredField("field").getAnnotation(MyAnTargetField.class);
System.out.println("字段上的注解值 === "+fieldAn.value());
} catch (Exception e) {
e.printStackTrace();
}
}
}運行結(jié)果如下:
類上的注解值 === 我是定義在類接口枚舉類上的注解元素value的默認值
參數(shù)上的注解值 === 我是定義在參數(shù)上的注解元素value的默認值
參數(shù)值 === 改變默認參數(shù)
方法上的注解值 === 測試方法
字段上的注解值 === 我是定義在字段上的注解元素value的默認值
4.注解的實現(xiàn)原理
以上只抽取了注解的其中幾種類型演示,下面讓我們一起來看看他們是怎么工作的
讓我們先看一下實現(xiàn)注解三要素:
1,注解聲明、
2,使用注解的元素、
3,操作注解使其起作用(注解處理器)
注解聲明
首先我們讓看一下java中的元注解(也就是上面提到的注解的注解),總共有4個如下:
- @Target,
- @Retention,
- @Documented,
- @Inherited
這4個元注解都是在jdk的java.lang.annotation包下面
@Target
Target說明的是Annotation所修飾的對象范圍,源碼如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}其中只有一個元素ElementType,再看看它的源碼如下:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}ElementType是一個枚舉類定義注解可以作用的類型上,上面例子中演示了TYPE,F(xiàn)IELD,METHOD,PARAMETER 4種可以作用的目標(biāo)
@Retention
定義了該Annotation被保留的時間長短:某些Annotation僅出現(xiàn)在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀?。ㄕ堊⒁獠⒉挥绊慶lass的執(zhí)行,因為Annotation與class在使用上是被分離的)。使用這個元注解可以對 Annotation的“生命周期”限制
@Documented
@Documented用于描述其它類型的annotation應(yīng)該被作為被標(biāo)注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個標(biāo)記注解,沒有成員
@Inherited
@Inherited 元注解是一個標(biāo)記注解,@Inherited闡述了某個被標(biāo)注的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用于一個class,則這個annotation將被用于該class的子類
注意:@Inherited annotation類型是被標(biāo)注過的class的子類所繼承。類并不從它所實現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation。
當(dāng)@Inherited annotation類型標(biāo)注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強了這種繼承性。如果我們使用java.lang.reflect去查詢一個@Inherited annotation類型的annotation時,反射代碼檢查將展開工作:檢查class和其父類,直到發(fā)現(xiàn)指定的annotation類型被發(fā)現(xiàn),或者到達類繼承結(jié)構(gòu)的頂層。
這邊針對這個小編專門寫了一個例子方便大家理解,在以上MyAnTargetType注解類中增加@Inherited注解,如下:
/**
* 定義一個可以注解在Class,interface,enum上的注解
* 增加了@Inherited注解代表允許繼承
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnTargetType {
/**
* 定義注解的一個元素 并給定默認值
* @return
*/
String value() default "我是定義在類接口枚舉類上的注解元素value的默認值";
}增加一個子類ChildAnnotationTest繼承AnnotationTest測試如下:
/**
* 增加一個子類繼承AnnotationTest 演示@Inherited注解允許繼承
*/
public class ChildAnnotationTest extends AnnotationTest {
public static void main(String[] args) {
// 獲取類上的注解MyAnTargetType
MyAnTargetType t = ChildAnnotationTest.class.getAnnotation(MyAnTargetType.class);
System.out.println("類上的注解值 === "+t.value());
}
}運行如下:
類上的注解值 === 我是定義在類接口枚舉類上的注解元素value的默認值
說明已經(jīng)獲取到了父類AnnotationTest的注解了
如果MyAnTargetType去掉@Inherited注解運行則報錯如下:
Exception in thread "main" java.lang.NullPointerException
at com.zhang.run.ChildAnnotationTest.main(ChildAnnotationTest.java:17)
使用注解的元素
使用注解沒什么好說的就是在你需要的地方加上對應(yīng)的你寫好的注解就行
注解處理器
這個是注解使用的核心了,前面我們說了那么多注解相關(guān)的,那到底java是如何去處理這些注解的呢
從getAnnotation進去可以看到j(luò)ava.lang.class實現(xiàn)了AnnotatedElement方法
MyAnTargetType t = AnnotationTest.class.getAnnotation(MyAnTargetType.class);
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElementjava.lang.reflect.AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通過反射獲取了某個類的AnnotatedElement對象之后,程序就可以調(diào)用該對象的如下四個個方法來訪問Annotation信息:
- 方法1: T getAnnotation(Class annotationClass): 返回改程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null。
- 方法2:Annotation[] getAnnotations():返回該程序元素上存在的所有注解。
- 方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false.
- 方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒有注釋直接存在于此元素上,則返回長度為零的一個數(shù)組。)該方法的調(diào)用者可以隨意修改返回的數(shù)組;這不會對其他調(diào)用者返回的數(shù)組產(chǎn)生任何影響
到此這篇關(guān)于java中注解的實現(xiàn)原理詳解的文章就介紹到這了,更多相關(guān)java注解的原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Native打包本地鏡像的操作方法(無需通過Graal的maven插件buildtools)
這篇文章主要介紹了Spring?Native打包本地鏡像,無需通過Graal的maven插件buildtools,本文探索一下,如果不通過這個插件來生成鏡像,結(jié)合實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2023-02-02
如何自定義springboot-starter日志組件供各個服務(wù)使用(系統(tǒng)日志優(yōu)化)
文章介紹了如何將各個微服務(wù)的接口調(diào)用日志邏輯優(yōu)化為一個可共享的Spring Boot Starter,通過自定義注解和自動裝配機制實現(xiàn),本文給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧2025-01-01
Java開發(fā)者必備10大數(shù)據(jù)工具和框架
這篇文章主要為大家詳細介紹了Java開發(fā)者必備10大數(shù)據(jù)工具和框架,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06
SpringBoot實現(xiàn)Read Through模式的操作過程
Read Through模式通常是指一種緩存策略,其中當(dāng)應(yīng)用程序嘗試讀取數(shù)據(jù)時,緩存系統(tǒng)首先被檢查以查看數(shù)據(jù)是否已經(jīng)存在于緩存中,這篇文章主要介紹了SpringBoot實現(xiàn)Read Through模式,需要的朋友可以參考下2024-07-07

