摘要:和不同,接口可以根據(jù)條件一般是注解的屬性指定需要導入的類抽象類實現(xiàn)了接口,并限定選擇條件只能是枚舉類,也就是說你自定義的注解必須包含屬性。
@Enable** 注解,一般用于開啟某一類功能。類似于一種開關(guān),只有加了這個注解,才能使用某些功能。
spring boot 中經(jīng)常遇到這樣的場景,老大讓你寫一個定時任務腳本、開啟一個spring緩存,或者讓你提供spring 異步支持。你的做法肯定是 @EnableScheduling+@Scheduled,@EnableCaching+@Cache,@EnableAsync+@Async 立馬開始寫邏輯了,但你是否真正了解其中的原理呢?之前有寫過一個項目,是日志系統(tǒng),其中要提供spring 注解支持,簡化配置,當時就是參考以上源碼的技巧實現(xiàn)的。
1 原理先來看@EnableScheduling源碼
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(SchedulingConfiguration.class) @Documented public @interface EnableScheduling { }
可以看到這個注解是一個混合注解,和其他注解的唯一區(qū)別就是多了一個@Import注解
通過查詢spring api文檔
Indicates one or more @Configuration classes to import. Provides functionality equivalent to theelement in Spring XML. Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations, as well as regular component classes (as of 4.2; analogous to AnnotationConfigApplicationContext.register(java.lang.Class>...)). 表示要導入的一個或多個@Configuration類。 提供與Spring XML中的 元素等效的功能。 允許導入@Configuration類,ImportSelector和ImportBeanDefinitionRegistrar實現(xiàn),以及常規(guī)組件類(從4.2開始;類似于AnnotationConfigApplicationContext.register(java.lang.Class <?> ...))。
可以看出,通過這個注解的作用是導入一些特定的配置類,這些特定類包括三種
@Configuration 注解的類
實現(xiàn)ImportSelector接口的類
實現(xiàn)ImportBeanDefinitionRegistrar接口的類
1.1 @Configuration注解類先來看看導入@Configuration注解的例子,打開SchedulingConfiguration類
發(fā)現(xiàn)他是屬于第一種,直接注冊了一個ScheduledAnnotationBeanPostProcessor 的 Bean
簡單介紹一下ScheduledAnnotationBeanPostProcessor這個類干了什么事,他實現(xiàn)了BeanPostProcessor類。這個類可以在bean初始化后,容器接管前實現(xiàn)自己的邏輯。在bean 初始化之后,通過AnnotatedElementUtils.getMergedRepeatableAnnotations()方法去拿到當前bean有@Scheduled和@Schedules注解的方法。如果有的話,將其注冊到內(nèi)部ScheduledTaskRegistrar變量中,開啟定時任務并執(zhí)行。順便說一下,BeanPostProcessor接口對所有bean適用,每個要注冊的bean都會走一遍postProcessAfterInitialization方法。
可以看出,這種方法適用于初始化時便獲取到全部想要的信息,如@Scheduled的元數(shù)據(jù)等。同時需要注意:被注解方法不能有參數(shù),不能有返回值。
1.2 實現(xiàn)ImportSelector接口的類再來看看第二種實現(xiàn)方式,打開EnableAsync類
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(AsyncConfigurationSelector.class) public @interface EnableAsync { Class extends Annotation> annotation() default Annotation.class; boolean proxyTargetClass() default false; AdviceMode mode() default AdviceMode.PROXY; int order() default Ordered.LOWEST_PRECEDENCE; }
可以看到他通過導入AsyncConfigurationSelector類來開啟異步支持,打開AsyncConfigurationSelector類
public class AsyncConfigurationSelector extends AdviceModeImportSelector{ private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration"; @Override public String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { case PROXY: return new String[] { ProxyAsyncConfiguration.class.getName() }; case ASPECTJ: return new String[] { ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME }; default: return null; } } }
AdviceModeImportSelector是一個抽象類,他實現(xiàn)了ImportSelector類的selectImports方法,先來看一下selectImports的api 文檔
Interface to be implemented by types that determine which @Configuration class(es) should be imported based on a given selection criteria, usually one or more annotation attributes. An ImportSelector may implement any of the following Aware interfaces, and their respective methods will be called prior to selectImports(org.springframework.core.type.AnnotationMetadata):
EnvironmentAware
BeanFactoryAware
BeanClassLoaderAware
ResourceLoaderAware
ImportSelectors are usually processed in the same way as regular @Import annotations, however, it is also possible to defer selection of imports until all @Configuration classes have been processed (see DeferredImportSelector for details). 通過一個給定選擇標準的類型來確定導入哪些@Configuration,他和@Import的處理方式類似,只不過這個導入Configuration可以延遲到所有Configuration都加載完
總結(jié)起來有一下幾點:
selectImports 接口和@Configuration類似,用于導入類。
和@Configuration不同,selectImports 接口可以根據(jù)條件(一般是注解的屬性)指定需要導入的類
AdviceModeImportSelector 抽象類實現(xiàn)了SelectImports接口,并限定選擇條件只能是AdviceMode枚舉類,也就是說你自定義的注解必須包含AdviceMode屬性。
1.3實現(xiàn)ImportBeanDefinitionRegistrar接口的類查看@EnableAspectJAutoProxy
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(AspectJAutoProxyRegistrar.class) public @interface EnableAspectJAutoProxy { boolean proxyTargetClass() default false; boolean exposeProxy() default false; }
這個注解導入的時AspectJAutoProxyRegistrar類,AspectJAutoProxyRegistrar實現(xiàn)了
ImportBeanDefinitionRegistrar接口,實現(xiàn)類
public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class); if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } if (enableAspectJAutoProxy.getBoolean("exposeProxy")) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } }
我們來看一下ImportBeanDefinitionRegistrar官方api文檔
Interface to be implemented by types that register additional bean definitions when processing @Configuration classes. Useful when operating at the bean definition level (as opposed to @Bean method/instance level) is desired or necessary. Along with @Configuration and ImportSelector, classes of this type may be provided to the @Import annotation (or may also be returned from an ImportSelector).
這個接口的兩個參數(shù),AnnotationMetadata 表示當前類的注解,BeanDefinitionRegistry 注冊bean。
可以看出和前兩種方式比,這種方式更加精細,需要你自己去實現(xiàn)bean的注冊邏輯。第二種方式只傳入了一個AnnotationMetadata,返回類全限定名,框架自動幫你注冊。而第三種方式,還傳入了一個BeanDefinitionRegistry讓你自己去注冊。
其實三種方式都能很好的實現(xiàn)導入邏輯。他們的優(yōu)缺點如下:
@Configuration 需要手動判斷如何導入。
SelectImports 封裝較好,可根據(jù)選擇導入,尤其當你選擇的條件是AdviceMode,還可以選擇AdviceModeSelector,幾行代碼搞定。
ImportBeanDefinitionRegistrar 最幸苦也最靈活,一些邏輯自己寫。
2 實踐最后,我們需要來寫一個自實現(xiàn)的@EnableDisconfig功能。disconfig是一種配置中心,我們一般的用法是寫兩個bean
@Bean(destroyMethod="destroy") public DisconfMgrBean disconfMgrBean(){ ..... } @Bean(initMethod="int", destroyMethod="destroy") public DisconfMgrBeanSecond disconfMgrBeanSecond(){ ...... }
每次搭框架這么寫確實挺費事的,即使你記在筆記上了,復制粘貼也還需要改scan路徑。下面我們用優(yōu)雅的代碼來實現(xiàn)一下。
2.1 @EnableDisconf首先定義一個注解類@EnableDisconf
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({DisconfConfig.class}) public @interface EnableDisconf{ String scanPackages() default ""; }
接著實現(xiàn)DisconfConfig類
@Configuration public class DisconfConfig implements ApplicationContextAware { private ApplicationContext applicationContext; public DisconfConfig() { } @Bean( destroyMethod = "destroy" ) @ConditionalOnMissingBean public DisconfMgrBean disconfMgrBean() { DisconfMgrBean bean = new DisconfMgrBean(); MapbootBeans = this.applicationContext.getBeansWithAnnotation(EnableDisconf.class); Set scanPackagesList = new HashSet(); if (!CollectionUtils.isEmpty(bootBeans)) { Iterator var4 = bootBeans.entrySet().iterator(); while(var4.hasNext()) { Entry configBean = (Entry)var4.next(); Class> bootClass = configBean.getValue().getClass(); if (bootClass.isAnnotationPresent(EnableDisconf.class)) { EnableDisconf enableDisconf = (EnableDisconf)bootClass.getAnnotation(EnableDisconf.class); String scanPackages = enableDisconf.scanPackages(); if (StringUtils.isEmpty(scanPackages)) { scanPackages = bootClass.getPackage().getName(); } scanPackagesList.add(scanPackages); } } } if (CollectionUtils.isEmpty(scanPackagesList)) { bean.setScanPackage(System.getProperty("scanPackages")); } else { bean.setScanPackage(StringUtils.join(scanPackagesList, ",")); } return bean; } @Bean( initMethod = "init", destroyMethod = "destroy" ) @ConditionalOnMissingBean public DisconfMgrBeanSecond disconfMgrBeanSecond() { return new DisconfMgrBeanSecond(); } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }
這里有兩點需要說明
如果你的jar包是需要給其他人使用,一定要加上@ConditionalOnMissingBean,確保Bean只會被創(chuàng)建一次。
ApplicationContextAware 這個類是我們程序感知spring容器上下文的類,簡單來說就是通過類似**Aware這樣的類去拿容器中的信息。感興趣的同學可以看一下spring中關(guān)于**Aware類的使用。
最后你只需要將項目打成jar包,上傳私服,然后就可以很輕松的使用@Enable帶來的便捷了。
@SpringBootApplication @EnableDisconf(scanPackages="com.demo") public class Application{ public static void main(String[] args){ SpringApplication.run(Application.class,args); } }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/74460.html
摘要:配置應用名稱,在注冊中心中顯示的服務注冊名稱。配置為為喜歡,即連接注冊中心使用地址形式,也可以使用,但生產(chǎn)環(huán)境不推薦。配置注冊中心清理無效節(jié)點的時間間隔,默認毫秒,即秒。 Spring Cloud的注冊中心可以由Eureka、Consul、Zookeeper、ETCD等來實現(xiàn),這里推薦使用Spring Cloud Eureka來實現(xiàn)注冊中心,它基于Netfilix的Eureka做了二次...
摘要:請求重試攔截器錯誤解碼器在發(fā)生請求錯誤包括發(fā)生異常或者響應數(shù)據(jù)不符合預期的時候,錯誤解碼器可將相關(guān)信息解碼到自定義異常中。 在SpringBoot項目直接使用okhttp、httpClient或者RestTemplate發(fā)起HTTP請求,既繁瑣又不方便統(tǒng)一管理。因此,在這里推薦一個適...
摘要:所以,所謂的自動裝配,實際上就是如何自動將裝載到容器中來。實際上在版本中,模塊驅(qū)動注解的出現(xiàn),已經(jīng)有了一定的自動裝配的雛形,而真正能夠?qū)崿F(xiàn)這一機制,還是在版本中,條件注解的出現(xiàn)。,我們來看一下的自動裝配是怎么一回事。在前面的分析中,Spring Framework一直在致力于解決一個問題,就是如何讓bean的管理變得更簡單,如何讓開發(fā)者盡可能的少關(guān)注一些基礎(chǔ)化的bean的配置,從而實現(xiàn)自動裝...
摘要:將領(lǐng)域中所發(fā)生的活動建模成一系列離散事件。領(lǐng)域事件是領(lǐng)域模型的組成部分,表示領(lǐng)域中所發(fā)生的事情。創(chuàng)建領(lǐng)域事件事件命名在建模領(lǐng)域事件時,我們應該根據(jù)限界上下文中的通用語言來命名事件。 使用領(lǐng)域事件來捕獲發(fā)生在領(lǐng)域中的一些事情。 領(lǐng)域驅(qū)動實踐者發(fā)現(xiàn)他們可以通過了解更多發(fā)生在問題域中的事件,來更好的理解問題域。這些事件,就是領(lǐng)域事件,主要是與領(lǐng)域?qū)<乙黄疬M行知識提煉環(huán)節(jié)中獲得。 領(lǐng)域事件,可...
閱讀 3627·2023-04-26 02:32
閱讀 3905·2021-11-23 10:05
閱讀 2291·2021-10-08 10:04
閱讀 2711·2021-09-22 16:06
閱讀 3612·2021-09-22 15:27
閱讀 764·2019-08-30 15:54
閱讀 1698·2019-08-30 13:50
閱讀 2704·2019-08-29 13:56