如果用最簡單的話描述註解,那就是元數據,即一種描述數據的數據。所以,可以說註解就是源代碼的元數據。
在Java中叫Annotation,Annotation是一種應用於類、方法、參數、變量、構造器及包聲明中的特殊修飾符。
Java自帶的註解有@Override,@Supperwarning,@Deprecated
1.註解的作用
1.生成文檔,比如@see @param @return
2.在編譯時進行格式檢查。如@override 放在方法前,如果你這個方法並不是覆蓋了超類方法,則編譯時就能檢查出
3.追蹤代碼依賴性,實現替代配置文件,比如Spring的各種註解等
2.如何實現一個自定義註解
我們先寫一個最簡單的註解:
@Retention(RetentionPolicy.RUNTIME)
public @interface FunctionDesc {
}
上面這個註解沒有任何的屬性定義,我們看下定義一個註解所需的元素。首先使用關鍵字“@interface” 表明這類是註解類;然後這類上加上註解“@Retention”,這是定義註解所必須的。
註解元素
@Retention是註解的註解,稱爲元註解。我們看一下@Retention的內部:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
我們看到Retention本身也被@Retention註解,還有@Documented和@Target(ElementType.ANNOTATION_TYPE),ANNOTATION_TYPE表明這個註解應應用在註解上。
Retention有一個屬性value,類型爲枚舉的RetentionPolicy,它沒有默認值,所以不寫這個值就會報錯,我們繼續看一下RetentionPolicy:
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,
/**
* 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
}
這個枚舉定義了三個值,SOURCE,CLASS,RUNTIME。分別定義我們寫的註解保持到哪個生命階段。把上面的英文註釋翻譯過來:
- RetentionPolicy.SOURCE 註解的信息會被編譯器拋棄,不會留在class文件中
- RetentionPolicy.CLASS 註解將由編譯器記錄在類文件(.class)中 但不需要在VM在運行時保留。 這是默認值行爲
- RetentionPolicy.RUNTIME 註解的信息被保留在.class文件中當程序編譯時,會被虛擬機保留在運行時
如此,我們實現並解釋了最簡單的註解,那麼註解怎麼起作用呢,我們借測試來說明下:
public class AnnotationTest {
@FunctionDesc
public void work(){}
public static void main(String[] args) {
try {
Method workMethod = AnnotationTest.class.getMethod("work",null);
if(workMethod.isAnnotationPresent(FunctionDesc.class)){
System.out.println(workMethod.getAnnotation(FunctionDesc.class));
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
運行上面的代碼,會輸出下面這句:
@com.wy.core.annotation.FunctionDesc()
這表明我們的註解起作用了。細心的你會發現,註解的真正起作用是藉助於“反射”完成的,這個我們待會深講。
3.註解中的屬性
上面我們定義的@FunctionDesc是沒有任何功能的。實際上註解的功能實現是通過定義屬性完成的。我們給FunctionDesc加上屬性:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface FunctionDesc {
String desc();
int paramCount() default 0;
String returnDesc() default "";
}
public class AnnotationTest {
@FunctionDesc(desc = "幹活")
public void work(){}
public static void main(String[] args) {
try {
Method workMethod = AnnotationTest.class.getMethod("work",null);
if(workMethod.isAnnotationPresent(FunctionDesc.class)){
FunctionDesc fd = workMethod.getAnnotation(FunctionDesc.class);
System.out.println(fd);
System.out.println("desc="+fd.desc());
System.out.println("paramCount="+fd.paramCount());
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
運行代碼後輸出:
@com.wy.core.annotation.FunctionDesc(paramCount=0, returnDesc=, desc=幹活)
desc=幹活
paramCount=0
我們添加了@Target({ElementType.METHOD}),表明這個註解適用在方法上。內部定義了3個屬性,desc是用作方法描述 ,paramCount表明參數個數,returnDesc是返回值描述。
paramCount和returnDesc有默認值,註解時可以不填值,但是desc必須寫值。
上面這個測試的例子,我們從AnnotationTest.class 反射拿到了work方法的Method定義,從Method獲取了它上面的註解信息。
這就是註解功能處理實現的大致思路。
4.反射得到註解的相關方法
我們知道反射的相關類有Class,Method,Field,Constructor等,它們都實現了AnnotatedElement接口。AnnotatedElement是註解處理器類庫的核心元素。
我們看下AnnotatedElement的定義
public interface AnnotatedElement {
default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return getAnnotation(annotationClass) != null;
}
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
Annotation[] getAnnotations();
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
/*
* Definition of associated: directly or indirectly present OR
* neither directly nor indirectly present AND the element is
* a Class, the annotation type is inheritable, and the
* annotation type is associated with the superclass of the
* element.
*/
T[] result = getDeclaredAnnotationsByType(annotationClass);
if (result.length == 0 && // Neither directly nor indirectly present
this instanceof Class && // the element is a class
AnnotationType.getInstance(annotationClass).isInherited()) { // Inheritable
Class<?> superClass = ((Class<?>) this).getSuperclass();
if (superClass != null) {
// Determine if the annotation is associated with the
// superclass
result = superClass.getAnnotationsByType(annotationClass);
}
}
return result;
}
default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
Objects.requireNonNull(annotationClass);
// Loop over all directly-present annotations looking for a matching one
for (Annotation annotation : getDeclaredAnnotations()) {
if (annotationClass.equals(annotation.annotationType())) {
// More robust to do a dynamic cast at runtime instead
// of compile-time only.
return annotationClass.cast(annotation);
}
}
return null;
}
default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
Objects.requireNonNull(annotationClass);
return AnnotationSupport.
getDirectlyAndIndirectlyPresent(Arrays.stream(getDeclaredAnnotations()).
collect(Collectors.toMap(Annotation::annotationType,
Function.identity(),
((first,second) -> first),
LinkedHashMap::new)),
annotationClass);
}
Annotation[] getDeclaredAnnotations();
}
所以我們通過反射得到Class,Method,Field,Constructor之後,就可以通過以上方法獲取註解信息了,最常用的以下幾個方法
1.getAnnotation(Class annotationClass): 返回改程序元素上存在的、指定類型的註解,如果該類型註解不存在,則返回null。
2.getAnnotations():返回該程序元素上存在的所有註解。
3.isAnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的註解,存在則返回true,否則返回false.
4.Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的所有註解。與此接口中的其他方法不同,該方法將忽略繼承的註解。該方法的調用者可以隨意修改返回的數組;這不會對其他調用者返回的數組產生任何影響。