1 // this is a component: 2 @Resource("hello") 3 public class Hello { 4 @Inject 5 int n; 6 7 @PostConstruct 8 public void hello(@Param String name) { 9 System.out.println(name); 10 } 11 12 @Override 13 public String toString() { 14 return "Hello"; 15 } 16 }
註釋會被編譯器直接忽略,註解則能夠被編譯器打包進入class文件,所以,註解是一種用做標註的「元數據」。數組
從JVM的角度看,註解自己對代碼邏輯沒有任何影響,如何使用註解徹底由工具決定。框架
Java的註解能夠分爲三類:ide
第一類是由編譯器使用的註解,這類註解不會被編譯進入.class
文件,它們在編譯後就被編譯器扔掉了。工具
@Override
:讓編譯器檢查該方法是否正確地實現了覆寫;@SuppressWarnings
:告訴編譯器忽略此處代碼產生的警告。.class
文件使用的註解,好比有些工具會在加載class的時候,對class作動態修改,實現一些特殊的功能。這類註解會被編譯進入.class
文件,但加載結束後並不會存在於內存中。這類註解只被一些底層庫使用,通常咱們沒必要本身處理。@PostConstruct
的方法會在調用構造方法後自動被調用(這是Java代碼讀取該註解實現的功能,JVM並不會識別該註解)。定義一個註解時,還能夠定義配置參數。配置參數能夠包括:測試
value
的配置參數,對此參數賦值,能夠只寫常量,至關於省略了value參數。若是隻寫註解,至關於所有使用默認值。
public class Hello { @Check(min=0, max=100, value=55) public int n; @Check(value=99) public int p; @Check(99) // @Check(value=99) public int x; @Check public int y; }
@interface
語法來定義註解(Annotation
),它的格式爲:
public @interface Report { int type() default 0; String level() default "info"; String value() default ""; }
註解的參數相似無參數方法,能夠用default
設定一個默認值(強烈推薦)。最經常使用的參數應當命名爲value
。this
有一些註解能夠修飾其餘註解,這些註解就稱爲元註解(meta annotation)。Java標準庫已經定義了一些元註解,咱們只須要使用元註解,一般不須要本身去編寫元註解。spa
最經常使用的元註解是@Target
。使用@Target
能夠定義Annotation
可以被應用於源碼的哪些位置。debug
ElementType.TYPE
;ElementType.FIELD
;ElementType.METHOD
;ElementType.CONSTRUCTOR
;ElementType.PARAMETER
。@Report
可用在方法上,咱們必須添加一個@Target(ElementType.METHOD)
。定義註解@Report
可用在方法或字段上,能夠把@Target
註解參數變爲數組{ ElementType.METHOD, ElementType.FIELD }
@Target(ElementType.METHOD) public @interface Report { int type() default 0; String level() default "info"; String value() default ""; }
@Target({ ElementType.METHOD, ElementType.FIELD }) public @interface Report { ... }
實際上@Target
定義的value
是ElementType[]
數組,只有一個元素時,能夠省略數組的寫法。code
@Retention
定義了Annotation
的生命週期。
RetentionPolicy.SOURCE
;RetentionPolicy.CLASS
;RetentionPolicy.RUNTIME
。 若是@Retention
不存在,則該Annotation
默認爲CLASS
。由於一般咱們自定義的Annotation
都是RUNTIME
,因此,務必要加上@Retention(RetentionPolicy.RUNTIME)
這個元註解。component
@Repeatable
這個元註解能夠定義Annotation
是否可重複。
@Repeatable(Reports.class) @Target(ElementType.TYPE) public @interface Report { int type() default 0; String level() default "info"; String value() default ""; } @Target(ElementType.TYPE) public @interface Reports { Report[] value(); }
通過@Repeatable修飾後,在某個類型聲明處,就能夠添加多個@Report註解: @Report(type=1, level="debug") @Report(type=2, level="warning") public class Hello { }
@Inherited
定義子類是否可繼承父類定義的Annotation
。@Inherited
僅針對@Target(ElementType.TYPE)
類型的annotation
有效,而且僅針對class
的繼承,對interface
的繼承無效。
@Inherited @Target(ElementType.TYPE) public @interface Report { int type() default 0; String level() default "info"; String value() default ""; } 在使用的時候,若是一個類用到了@Report: @Report(type=1) public class Person { } 則它的子類默認也定義了該註解: public class Student extends Person { }
1 //第一步,用@interface定義註解: 2 3 public @interface Report { 4 } 5 6 //第二步,添加參數、默認值: 7 8 public @interface Report { 9 int type() default 0; 10 String level() default "info"; 11 String value() default ""; 12 } 13 //把最經常使用的參數定義爲value(),推薦全部參數都儘可能設置默認值。 14 15 //第三步,用元註解配置註解: 16 17 @Target(ElementType.TYPE) 18 @Retention(RetentionPolicy.RUNTIME) 19 public @interface Report { 20 int type() default 0; 21 String level() default "info"; 22 String value() default ""; 23 }
其中,必須設置@Target
和@Retention
,@Retention
通常設置爲RUNTIME
,由於咱們自定義的註解一般要求在運行期讀取。通常狀況下,沒必要寫@Inherited
和@Repeatable
。
SOURCE
類型的註解主要由編譯器使用,所以咱們通常只使用,不編寫。CLASS
類型的註解主要由底層工具庫使用,涉及到class的加載,通常咱們不多用到。只有RUNTIME
類型的註解不但要使用,還常常須要編寫。Java提供的使用反射API讀取Annotation
的方法包括:
判斷某個註解是否存在於Class
、Field
、Method
或Constructor
:
Class.isAnnotationPresent(Class)
Field.isAnnotationPresent(Class)
Method.isAnnotationPresent(Class)
Constructor.isAnnotationPresent(Class)
// 判斷@Report是否存在於Person類: Person.class.isAnnotationPresent(Report.class);
使用反射API讀取Annotation:
Class.getAnnotation(Class)
Field.getAnnotation(Class)
Method.getAnnotation(Class)
Constructor.getAnnotation(Class)
// 獲取Person定義的@Report註解: Report report = Person.class.getAnnotation(Report.class); int type = report.type(); String level = report.level();
Annotation
有兩種方法。方法一是先判斷Annotation
是否存在,若是存在,就直接讀取。
Class cls = Person.class; if (cls.isAnnotationPresent(Report.class)) { Report report = cls.getAnnotation(Report.class); ... }
第二種方法是直接讀取Annotation
,若是Annotation
不存在,將返回null。
Class cls = Person.class; Report report = cls.getAnnotation(Report.class); if (report != null) { ... }
讀取方法、字段和構造方法的Annotation
和Class相似。但要讀取方法參數的Annotation
就比較麻煩一點,由於方法參數自己能夠當作一個數組,而每一個參數又能夠定義多個註解,因此,一次獲取方法參數的全部註解就必須用一個二維數組來表示。例如,對於如下方法定義的註解。
public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) { }
要讀取方法參數的註解,咱們先用反射獲取Method
實例,而後讀取方法參數的全部註解。
// 獲取Method實例: Method m = ... // 獲取全部參數的Annotation: Annotation[][] annos = m.getParameterAnnotations(); // 第一個參數(索引爲0)的全部Annotation: Annotation[] annosOfName = annos[0]; for (Annotation anno : annosOfName) { if (anno instanceof Range) { // @Range註解 Range r = (Range) anno; } if (anno instanceof NotNull) { // @NotNull註解 NotNull n = (NotNull) anno; } }
註解如何使用,徹底由程序本身決定。例如,JUnit是一個測試框架,它會自動運行全部標記爲@Test
的方法。
來看一個@Range
註解,咱們但願用它來定義一個String
字段的規則:字段長度知足@Range
的參數定義。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Range { int min() default 0; int max() default 255; }
在某個JavaBean中,咱們可使用該註解:
public class Person { @Range(min=1, max=20) public String name; @Range(max=10) public String city; }
可是,定義了註解,自己對程序邏輯沒有任何影響。咱們必須本身編寫代碼來使用註解。這裏,咱們編寫一個Person
實例的檢查方法,它能夠檢查Person
實例的String
字段長度是否知足@Range
的定義:
void check(Person person) throws IllegalArgumentException, ReflectiveOperationException { // 遍歷全部Field: for (Field field : person.getClass().getFields()) { // 獲取Field定義的@Range: Range range = field.getAnnotation(Range.class); // 若是@Range存在: if (range != null) { // 獲取Field的值: Object value = field.get(person); // 若是值是String: if (value instanceof String) { String s = (String) value; // 判斷值是否知足@Range的min/max: if (s.length() < range.min() || s.length() > range.max()) { throw new IllegalArgumentException("Invalid field: " + field.getName()); } } } } }
這樣一來,咱們經過@Range
註解,配合check()
方法,就能夠完成Person
實例的檢查。注意檢查邏輯徹底是咱們本身編寫的,JVM不會自動給註解添加任何額外的邏輯。