Java元注解 - 生命周期 @Retention
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java元注解 - 生命周期 @Retention相关的知识,希望对你有一定的参考价值。
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,这个很好理解,我们期望在程序运行时,能够获取到这些注解,并干点有意思的事儿,而只有RetentionPolic.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));
}
RetentionPolic.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开发者,都知道它是通过注解在编译时自动生成一部分代码,让源码看起来更简洁,字节码却很强大。当然,这种方式有它自身的缺陷,譬如不一致性,问题排解时的困扰,以及依赖问题,在此不展开讨论。
同样的原因,RetentionPolic.CLASS虽然作为注解的默认周期定义,也不是普通开发者自定义注解的首选,CLASS类型比起SOURCE和RUNTIME要更难理解些,因为通常开发者对编译前和运行时理解没有障碍,但是编译之后的字节码保留了元注解,又不能在运行时用到,这期间到底有什么用? 我们看个Spring-boot的例子。
Springboot针对config元数据处理器(ConfigurationMetadataAnnotationProcessor),是通过MetaCollector收集的,然后用MetaStore存在META-INF/spring-configuration-metadata.json。我们先不讨论Spring为何要这么做,有什么好处,感兴趣的可以自己去读源码,此刻我们关注的是RetentionPolic.CLASS,和这个实现有什么关系。ConfigurationMetadataAnnotationProcessor实现了Processor接口,而Processor接口,则是专门用来处理元注解的,故名注解处理器。注解处理器,可以帮助我们从字节码层面,做些听起来很奇怪的事儿,譬如信息收集、字节码重构、字节码自动生成等。如果真用到这个层面,那么要恭喜你,因为你已经超过了大部分的Java开发者,至少你对asm有一定的了解,知道类的结构定义ClassNode,知道如何读取它们Cla***eader。
小节下:
SOURCE是源码级别的注解,仅在编译时使用;
CLASS是字节码级别的注解,大多也是在编译时使用;
RUNTIME才是我们的亲朋好友,运行时可见的注解。
以上是关于Java元注解 - 生命周期 @Retention的主要内容,如果未能解决你的问题,请参考以下文章