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的主要内容,如果未能解决你的问题,请参考以下文章

java注解使用

SSM/springboot注解总结

@Retention作用详析

java注解的基本知识

java注解设置字段不能为零

JAVA注解_概述内置注解RetentionTargetDocumentedInherited