java中注解的實現(xiàn)原理詳解
1.什么是注解
注解也叫元數(shù)據(jù),例如我們常見的@Override和@Deprecated,注解是JDK1.5版本開始引入的一個特性,用于對代碼進行說明,可以對包、類、接口、字段、方法參數(shù)、局部變量等進行注解
一般常用的注解可以分為三類:
- 一類是Java自帶的標準注解,包括@Override(標明重寫某個方法)、@Deprecated(標明某個類或方法過時)和@SuppressWarnings(標明要忽略的警告),使用這些注解后編譯器就會進行檢查。
- 一類為元注解,元注解是用于定義注解的注解,包括@Retention(標明注解被保留的階段)、@Target(標明注解使用的范圍)、@Inherited(標明注解可繼承)、@Documented(標明是否生成javadoc文檔)
- 一類為自定義注解,可以根據(jù)自己的需求定義注解
2.注解的用途
在看注解的用途之前,有必要簡單的介紹下XML和注解區(qū)別,
注解:是一種分散式的元數(shù)據(jù),與源代碼緊綁定。
xml:是一種集中式的元數(shù)據(jù),與源代碼無綁定
當然網(wǎng)上存在各種XML與注解的辯論哪個更好,這里不作評論和介紹,主要介紹一下注解的主要用途:
- 生成文檔,通過代碼里標識的元數(shù)據(jù)生成javadoc文檔。
- 編譯檢查,通過代碼里標識的元數(shù)據(jù)讓編譯器在編譯期間進行檢查驗證。
- 編譯時動態(tài)處理,編譯時通過代碼里標識的元數(shù)據(jù)動態(tài)處理,例如動態(tài)生成代碼。
- 運行時動態(tài)處理,運行時通過代碼里標識的元數(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的默認值"; }
編寫一個測試處理類處理以上注解,這邊不過多解釋,代碼中都寫了對應的注釋如下:
/** * 測試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(); } } }
運行結果如下:
類上的注解值 === 我是定義在類接口枚舉類上的注解元素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種可以作用的目標
@Retention
定義了該Annotation被保留的時間長短:某些Annotation僅出現(xiàn)在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀?。ㄕ堊⒁獠⒉挥绊慶lass的執(zhí)行,因為Annotation與class在使用上是被分離的)。使用這個元注解可以對 Annotation的“生命周期”限制
@Documented
@Documented用于描述其它類型的annotation應該被作為被標注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個標記注解,沒有成員
@Inherited
@Inherited 元注解是一個標記注解,@Inherited闡述了某個被標注的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用于一個class,則這個annotation將被用于該class的子類
注意:@Inherited annotation類型是被標注過的class的子類所繼承。類并不從它所實現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation。
當@Inherited annotation類型標注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強了這種繼承性。如果我們使用java.lang.reflect去查詢一個@Inherited annotation類型的annotation時,反射代碼檢查將展開工作:檢查class和其父類,直到發(fā)現(xiàn)指定的annotation類型被發(fā)現(xiàn),或者到達類繼承結構的頂層。
這邊針對這個小編專門寫了一個例子方便大家理解,在以上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的默認值
說明已經獲取到了父類AnnotationTest的注解了
如果MyAnTargetType去掉@Inherited注解運行則報錯如下:
Exception in thread "main" java.lang.NullPointerException
at com.zhang.run.ChildAnnotationTest.main(ChildAnnotationTest.java:17)
使用注解的元素
使用注解沒什么好說的就是在你需要的地方加上對應的你寫好的注解就行
注解處理器
這個是注解使用的核心了,前面我們說了那么多注解相關的,那到底java是如何去處理這些注解的呢
從getAnnotation進去可以看到java.lang.class實現(xiàn)了AnnotatedElement方法
MyAnTargetType t = AnnotationTest.class.getAnnotation(MyAnTargetType.class);
public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement
java.lang.reflect.AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通過反射獲取了某個類的AnnotatedElement對象之后,程序就可以調用該對象的如下四個個方法來訪問Annotation信息:
- 方法1: T getAnnotation(Class annotationClass): 返回改程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null。
- 方法2:Annotation[] getAnnotations():返回該程序元素上存在的所有注解。
- 方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false.
- 方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒有注釋直接存在于此元素上,則返回長度為零的一個數(shù)組。)該方法的調用者可以隨意修改返回的數(shù)組;這不會對其他調用者返回的數(shù)組產生任何影響
到此這篇關于java中注解的實現(xiàn)原理詳解的文章就介紹到這了,更多相關java注解的原理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
POI XSSFSheet shiftRows bug問題解決
這篇文章主要介紹了POI XSSFSheet shiftRows bug問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07使用Sharding-JDBC對數(shù)據(jù)進行分片處理詳解
這篇文章主要介紹了使用Sharding-JDBC對數(shù)據(jù)進行分片處理詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10如何自定義hibernate validation注解示例代碼
Hibernate Validator 是 Bean Validation 的參考實現(xiàn) . Hibernate Validator 提供了 JSR 303 規(guī)范中所有內置 constraint 的實現(xiàn),下面這篇文章主要給大家介紹了關于如何自定義hibernate validation注解的相關資料,需要的朋友可以參考下2018-04-04