Java1.5推出元注解后,时下最活跃的开源社区Spring便开始大力推崇,原本大家熟悉的、一目了然的各种xml配置,突然消失了,各种注解纷至沓来,配置可读性受到严重威胁。另一方面,AOP的各种炫酷特性,让开发者情不自禁地使用各种自定义注解。在自定义元注解@Annotation的时候,有两个特性是必须要定义清楚的,一个是Target(注解目标),另一个就是Retention(注解生命周期,也叫声明周期),今天我们先认识下周期。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CallerServiceValidator {
String value() default "";
Class> validatorClass() default DEFAULT.class;
final class DEFAULT {}
}
元注解生命周期@Retention
元注解的生命周期有三种,枚举定义在RetentionPolicy中,分别是SOURCE、CLASS和RUNTIME。
自定义元注解时,绝大多数开发者(除非你是下面两种场景的使用者)都是使用RUNTIME,这个很好理解,我们期望在程序运行时,能够获取到这些注解,并干点有意思的事儿,而只有RetentionPolicy.RUNTIME,能确保自定义的注解在运行时依然可见。举个例子,在spring项目启动后,获取所有或者部分可用接口的定义并列出来:
try {
String basePath = "";
RequestMapping baseRequestMapping = AnnotationUtils.findAnnotation(bean.getClass(), RequestMapping.class);
if (baseRequestMapping != null) {
basePath = StringUtils.join(baseRequestMapping.path());
}
Method[] methods = ReflectionUtils.getAllDeclaredMethods(AopUtils.getTargetClass(bean));
if (methods != null) {
beanMetas = new CopyOnWriteArrayList<>();
for (Method method : methods) {
try {
RequestMapping methodRequestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
ApiIgnore apiIgnore = AnnotationUtils.findAnnotation(method, ApiIgnore.class);
if (methodRequestMapping != null && (apiIgnore == null || !apiIgnore.value())) {
PlatformApiMeta platformApiMeta = new PlatformApiMeta(AopUtils.getTargetClass(bean).getName(),
method.getName(), basePath + StringUtils.join(methodRequestMapping.path()));
RequestMethod[] httpMethods = methodRequestMapping.method();
if (httpMethods != null && httpMethods.length > 0) {
String[] httpMethodArr = new String[httpMethods.length];
for (int i = 0; i < httpMethods.length; i++) {
RequestMethod httpMethod = httpMethods[i];
httpMethodArr[i] = httpMethod.name();
}
platformApiMeta.setHttpMethods(httpMethodArr);
}
platformApiMeta.setModuleName(moduleName);
ApiOperation apiOperation = AnnotationUtils.findAnnotation(method, ApiOperation.class);
if (apiOperation != null) {
platformApiMeta.setDesc(apiOperation.value());
}
collectMethodParamsReturn(platformApiMeta, method, bean);
beanMetas.add(platformApiMeta);
}
} catch (Exception e) {
logger.error(ExceptionUtils.getStackTrace(e));
}
}
}
} catch (Exception e) {
logger.error(ExceptionUtils.getStackTrace(e));
}
RetentionPolicy.SOURCE一般的开发者很少用到,举几个Java自带的使用场景。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
看到了吗,这些注解只是在编译的时候用到,一旦编译完成后,运行时没有任何意义,所以他们被称作源码级别注解。有过代码自动生成经验的开发者,譬如lombok开发者,都知道它是通过注解在编译时自动生成一部分代码,让源码看起来更简洁,字节码却很强大。当然,这种方式有它自身的缺陷,譬如不一致性,问题排解时的困扰,以及依赖问题,在此不展开讨论。
同样的原因,RetentionPolicy.CLASS虽然作为注解的默认周期定义,也不是普通开发者自定义注解的首选,CLASS类型比起SOURCE和RUNTIME要更难理解些,因为通常开发者对编译前和运行时理解没有障碍,但是编译之后的字节码保留了元注解,又不能在运行时用到,这期间到底有什么用? 我们看个Spring-boot的例子。
Springboot的配置元数据注解处理器(ConfigurationMetadataAnnotationProcessor),是专门用来处理配置注解的,通过MetaCollector收集的,然后用MetaStore存在META-INF/spring-configuration-metadata.json。我们先不讨论Spring为何要这么做,有什么好处,感兴趣的可以自己去读源码,此刻我们关注的是RetentionPolic.CLASS,和这个实现有什么关系。ConfigurationMetadataAnnotationProcessor实现了Processor接口,而Processor接口,则是专门用来处理注解的,故名注解处理器。注解处理器,是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。注解处理器是运行它自己的虚拟机JVM中,javac启动一个完整Java虚拟机来运行注解处理器。这对你意味着什么?你可以使用任何你在其他java应用中使用的的东西。更多关于注解处理器的知识,感兴趣的可以参照这篇翻译文章(Java注解处理器),有干货。
最后小节下:
SOURCE是源码级别的注解,仅在编译时使用;
CLASS是字节码级别的注解,大多也是在编译时使用;
RUNTIME才是我们的亲朋好友,运行时可见的注解。