Java元註解 - 生命周期 @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