重新認識Java註解,帶你找回初戀的感覺
在開發過程中,註解是我們最爲常見的代碼之一,JDK內置註解、元註解、自定義註解等,例如Spring中的@Component、@Service、@Controller等;MybaitsPlus中@TableName等;JDK自帶註解@Override、@Deprecated、@SuppressWarnings等,
常見的情況下,註解可以作用在類、方法、成員變量等上。他是如何實現配置的?
此篇文章帶你從以下角度重新認識註解
- 什麼是註解
- 什麼是元註解
- 元註解包含哪些
- 自定義註解
1、什麼是註解
官方說法:
Annotation(註解)是JDK1.5及以後版本引入的。它可以用於創建文檔,跟蹤代碼中的依賴性,甚至執行基本編譯時檢查。從某些方面看,annotation就像修飾符一樣被使用,並應用於包、類 型、構造方法、方法、成員變量、參數、本地變量的聲明中。這些信息被存儲在Annotation的“name=value”結構對中。
大白話就是註解其實是一個接口,是描述數據的數據,常與反射結伴而行。
2、爲什麼使用註解
那爲什麼要使用註解,因爲現在我們已經默認接受了註解,無法想象沒有註解的時代是什麼樣。
在JDK1.5之前,沒有推出註解的年代,怎麼實現描述數據?答案是使用XML。
舉個非常簡單的例子,Spring相信大家都是用過,Spring常用的編程風格有以下三種
- schemal-based-------xml
- annotation-based-----annotation
- java-based----java Configuration
如果你使用你經常使用annotation和-java Configuration,讓你在重新使用XML配置Spring你肯定無法接受。
3、註解的作用
- 提供信息給編譯器: 編譯器可以利用註解來探測錯誤和警告信息,如 @Override、@Deprecated。
- 編譯階段時的處理: 軟件工具可以用來利用註解信息來生成代碼、Html 文檔或者做其它相應處理,如 @Param、@Return、@See、@Author 用於生成 Javadoc 文檔。
- 運行時的處理: 某些註解可以在程序運行的時候接受代碼的提取,值得注意的是,註解不是代碼本身的一部分。如Spring 2.5 開始註解配置,減少了配置。
4、註解的分類
根據註解的參數個數分類:
- 標記註解,一個沒有成員的Annotation類型被稱爲標記註解,這種類型僅僅使用自身的存在與否來爲我們提供信息,比如常見的@Override
- 單值註解
- 完整註解
根據註解使用的方法和用途分類:
- JDK內置系統註解
- 元註解
- 自定義註解
5、元註解
元註解的作用就是負責註解其他註解,java 5.0定義了4個meta-annotation類型。
- @Target
- @Retention
- @Document
- @Inhrited
5.1 @Target
修飾的對象範圍:packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。
作用:用於描述註解的使用範圍。
取值範圍(查看源碼,一目瞭然):
/*
* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package java.lang.annotation;
/**
* The constants of this enumerated type provide a simple classification of the
* syntactic locations where annotations may appear in a Java program. These
* constants are used in {@link Target java.lang.annotation.Target}
* meta-annotations to specify where it is legal to write annotations of a
* given type.
*
* <p>The syntactic locations where annotations may appear are split into
* <em>declaration contexts</em> , where annotations apply to declarations, and
* <em>type contexts</em> , where annotations apply to types used in
* declarations and expressions.
*
* <p>The constants {@link #ANNOTATION_TYPE} , {@link #CONSTRUCTOR} , {@link
* #FIELD} , {@link #LOCAL_VARIABLE} , {@link #METHOD} , {@link #PACKAGE} ,
* {@link #PARAMETER} , {@link #TYPE} , and {@link #TYPE_PARAMETER} correspond
* to the declaration contexts in JLS 9.6.4.1.
*
* <p>For example, an annotation whose type is meta-annotated with
* {@code @Target(ElementType.FIELD)} may only be written as a modifier for a
* field declaration.
*
* <p>The constant {@link #TYPE_USE} corresponds to the 15 type contexts in JLS
* 4.11, as well as to two declaration contexts: type declarations (including
* annotation type declarations) and type parameter declarations.
*
* <p>For example, an annotation whose type is meta-annotated with
* {@code @Target(ElementType.TYPE_USE)} may be written on the type of a field
* (or within the type of the field, if it is a nested, parameterized, or array
* type), and may also appear as a modifier for, say, a class declaration.
*
* <p>The {@code TYPE_USE} constant includes type declarations and type
* parameter declarations as a convenience for designers of type checkers which
* give semantics to annotation types. For example, if the annotation type
* {@code NonNull} is meta-annotated with
* {@code @Target(ElementType.TYPE_USE)}, then {@code @NonNull}
* {@code class C {...}} could be treated by a type checker as indicating that
* all variables of class {@code C} are non-null, while still allowing
* variables of other classes to be non-null or not non-null based on whether
* {@code @NonNull} appears at the variable's declaration.
*
* @author Joshua Bloch
* @since 1.5
* @jls 9.6.4.1 @Target
* @jls 4.1 The Kinds of Types and Values
*/
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration 用於描述類、接口(包括註解類型) 或enum聲明*/
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
}
【注】JDK1.8後新增了TYPE_PARAMETER與TYPE_USE兩個枚舉值,這種註解被稱爲Type Annotation(類型註解),Type Annotation可用在任何用到類型的地方。
5.2 Retention
定義了該Annotation被保留的時間長短:某些Annotation僅出現在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因爲Annotation與class在使用上是被分離的)。使用這個meta-Annotation可以對 Annotation的“生命週期”限制。
作用:表示需要在什麼級別保存該註釋信息,用於描述註解的生命週期(即:被描述的註解在什麼範圍內有效)
取值範圍(查看源碼,一目瞭然):
/*
* Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package java.lang.annotation;
/**
* Annotation retention policy. The constants of this enumerated type
* describe the various policies for retaining annotations. They are used
* in conjunction with the {@link Retention} meta-annotation type to specify
* how long annotations are to be retained.
*
* @author Joshua Bloch
* @since 1.5
*/
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler. 在源文件中有效(即源文件保留)
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior. 在class文件中有效(即class保留)
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement 在運行時有效(即運行時保留)
*/
RUNTIME
}
【注】Name註解的RetentionPolicy的值爲RUNTIME,這樣註解處理器可以通過反射,獲取到該註解的屬性,從而做一些運行時的邏輯處理。
5.3 @Document
用於描述其它類型的annotation應該被作爲被標註的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個標記註解,沒有成員。
作用:說明該註解將被包含在javadoc中
5.4 @Inhrited
是一個標記註解,@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類型被發現,或者到達類繼承結構的頂層。
6、自定義註解
使用@interface自定義註解時,自動繼承了java.lang.annotation.Annotation接口,由編譯程序自動完成其他細節。在定義註解時,不能繼承其他的註解或接口。@interface用來聲明一個註解,其中的每一個方法實際上是聲明瞭一個配置參數。方法的名稱就是參數的名稱,返回值類型就是參數的類型(返回值類型只能是基本類型、Class、String、enum)。可以通過default來聲明參數的默認值。
自定義註解格式:
public @interface 註解名{註解體}
1.所有基本數據類型(int,float,boolean,byte,double,char,long,short)
2.String類型
3.Class類型
4.enum類型
5.Annotation類型
6.以上所有類型的數組
Annotation類型裏面的參數該怎麼設定:
- 只能用public或默認(default)這兩個訪問權修飾.例如,String value();這裏把方法設爲defaul默認類型;
- 參數成員只能用基本類型byte,short,char,int,long,float,double,boolean八種基本數據類型和 String,Enum,Class,annotations等數據類型,以及這一些類型的數組.例如,String value();這裏的參數成員就爲String;
- 如果只有一個參數成員,最好把參數名稱設爲"value",後加小括號.例:下面的例子Name註解就只有一個參數成員。
eg:
/**
* 自定義註解
*
* @author lixiang
* @version V1.0
* @date 2020/6/13 14:37
**/
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Entity {
String value() default "";
}
註解元素的默認值
註解元素必須有確定的值,要麼在定義註解的默認值中指定,要麼在使用註解時指定,非基本類型的註解元素的值不可爲null。因此, 使用空字符串或0作爲默認值是一種常用的做法。這個約束使得處理器很難表現一個元素的存在或缺失的狀態,因爲每個註解的聲明中,所有元素都存在,並且都具有相應的值,爲了繞開這個約束,我們只能定義一些特殊的值,例如空字符串或者負數,一次表示某個元素不存在,在定義註解時,這已經成爲一個習慣用法。
與反射結合使用
/**通過反射獲取對象註解上的數據**/
Class<?> clazz = obj.getClass();
if (clazz.isAnnotationPresent(Entity.class)) {
Entity declaredAnnotation = clazz.getDeclaredAnnotation(Entity.class);
String value = declaredAnnotation.value();
}