1. 程式人生 > >Java元註解 - 生命周期 @Retention

Java元註解 - 生命周期 @Retention

nor 生命 *** pyo ota 部分 ret 就是 ppr

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