meta-annotation
Java从JDK5.0开始便提供了四个meta-annotation用于自定义注解的时候使用,这四个注解为:@Target,@Retention,@Documented 和@Inherited。
@Target:用于描述注解的使用范围(即:被描述的注解可以用在什么地方),其源码如下:
| 1 | 
 | 
可见@Target 注解只有唯一成员value,类型为ElementType数组。查看ElementType的源码可以发现,ElementType可取的值有:
- CONSTRUCTOR:用于描述构造器; 
- FIELD:用于描述成员变量; 
- LOCAL_VARIABLE:用于描述局部变量; 
- METHOD:用于描述方法; 
- PACKAGE:用于描述包; 
- PARAMETER:用于描述参数; 
- TYPE:用于描述类、接口(包括注解类型) 或enum声明。 
@Retention:指定被描述的注解在什么范围内有效。源码如下:
| 1 | 
 | 
其中RetentionPolicy可取的值有:
- SOURCE:在源文件中有效(即源文件保留); 
- CLASS:在class文件中有效(即class保留); 
- RUNTIME:在运行时有效(即运行时保留)。 
@Documented:是一个标记注解,木有成员,用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。
@Inherited:元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
自定义annotation
用@interface 自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface 用来声明一个注解,其中的每一个方法实际上是声明了一个成员。方法的名称就是成员的名称,返回值类型就是成员的类型。可以通过default来声明参数的默认值。
自定义注解的基本格式为:
| 1 | public AnnotationName { | 
Annotation的成员定义必须满足以下三点:
- 成员只能用public或默认(default)这两个访问权修饰; 
- 成员的类型只能是基本类型,String,Enum,Class,Annotation以及它们的数组类型; 
- 如果只有一个成员,最好将其名称设为value。 
AnnotatedElement代表被注解的元素,其包含许多方法,如下图所示:

…
其中主要的几个方法有:
| 1 | <T extends Annotation> T getAnnotation(Class<T> annotationType) // 根据annotationType获取注解对象 | 
实战
假如现在有一个数据库表对应的POJO被一些自定义注解所标记,现在要根据这个POJO自动生成创建库表的SQL语句。其中POJO代码如下:
| 1 | (name = "Student") | 
需要生成类似如下的SQL语句:
| 1 | create table Student(age NUMBER(3),userName VARCHAR2(10),birthday DATE default sysdate) | 
Bean类被@Table 注解所标记,所以需要定义一个ElementType.TYPE级别的注解:
| 1 | (RetentionPolicy.RUNTIME) | 
而@Column 注解标注于Bean的成员变量,并且包含三个成员:
| 1 | (RetentionPolicy.RUNTIME) | 
接下来定义两个方法getTableName(Class<?> bean)和getColumns(Class<?> bean),分别用于获取@Table 注解中的表名和被@Column 注解标记的成员变量信息:
| 1 | private static String getTableName(Class<?> bean) { | 
最后编写生成SQL的方法:
| 1 | public static String createTable(Class<?> bean) { | 
测试
| 1 | public class Test { | 
生成的结果和预期一致
总结
上述的过程可归纳为以下几个步骤:
- 判断AnnotatedElement是否被某注解所标记: - AnnotatedElement.isAnnotationPresent(SomeAnnotation.class);
- 是的话,获取该注解对象: - Annotation annotation = bean.getAnnotation(SomeAnnotation.class);;
- 根据该注解对象获取某个成员参数(比如name): - Method method = SomeAnnotation.class.getMethod("name");;
- 利用反射机制,获取该注解中的某成员的值: - String name = (String) method.invoke(annotation);。

