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不会自动给注解添加任何额外的逻辑。