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 {
看到了吗, 这些注解只是在编译的时候用到, 一旦编译完成后, 运行时没有任何意义, 所以他们被称作源码级别注解. 有过代码自动生成经验的开发者, 譬如 https://projectlombok.org/ 开发者, 都知道它是通过注解在编译时自动生成一部分代码, 让源码看起来更简洁, 字节码却很强大. 当然, 这种方式有它自身的缺陷, 譬如不一致性, 问题排解时的困扰, 以及依赖问题, 在此不展开讨论.
同样的原因, 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 才是我们的亲朋好友, 运行时可见的注解.
来源: http://www.bubuko.com/infodetail-2742572.html