Spring中@Configuration和@Component注解的區(qū)別及原理
1.背景
隨著Spring Boot的盛行,注解配置式開(kāi)發(fā)受到了大家的青睞,從此告別了基于Spring開(kāi)發(fā)的繁瑣XML配置。
這里先來(lái)提綱挈領(lǐng)的了解一下Spring內(nèi)部對(duì)于配置注解的定義,如@Component、@Configuration、@Bean、@Import等注解
從功能上來(lái)講,這些注解所負(fù)責(zé)的功能的確不相同,但是從本質(zhì)上來(lái)講,Spring內(nèi)部都將其作為配置注解進(jìn)行處理。
對(duì)于一個(gè)成熟的框架來(lái)講,簡(jiǎn)單及多樣化的配置是至關(guān)重要的,那么Spring也是如此,從Spring的配置發(fā)展過(guò)程來(lái)看,整體的配置方式從最初比較“原始”的階段到現(xiàn)在非常“智能”的階段,這期間Spring做出的努力是非常巨大的,從XML到自動(dòng)裝配,從Spring到Spring Boot,從@Component到@Configuration以及@Conditional,Spring發(fā)展到今日,在越來(lái)越好用的同時(shí),也為我們隱藏了諸多的細(xì)節(jié),那么今天讓我們一起探秘@Component與@Configuration。
我們平時(shí)在Spring的開(kāi)發(fā)工作中,基本都會(huì)使用配置注解,尤其以@Component及@Configuration為主,當(dāng)然在Spring中還可以使用其他的注解來(lái)標(biāo)注一個(gè)類(lèi)為配置類(lèi),這是廣義上的配置類(lèi)概念,但是這里我們只討論@Component和@Configuration,因?yàn)榕c我們的開(kāi)發(fā)工作關(guān)聯(lián)比較緊密,那么接下來(lái)我們先討論下一個(gè)問(wèn)題,就是 @Component與@Configuration有什么區(qū)別?
2.@Component與@Configuration使用
2.1注解定義
在討論兩者區(qū)別之前,先來(lái)看看兩個(gè)注解的定義:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Indexed public @interface Component { ? /** * The value may indicate a suggestion for a logical component name, * to be turned into a Spring bean in case of an autodetected component. * @return the suggested component name, if any (or empty String otherwise) */ String value() default ""; ? }
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { ? @AliasFor(annotation = Component.class) String value() default ""; ? boolean proxyBeanMethods() default true; ? }
從定義來(lái)看, @Configuration 注解本質(zhì)上還是 @Component,因此 @ComponentScan 能掃描到@Configuration 注解的類(lèi)。
2.2注解使用
接下來(lái)看看兩者在我們?nèi)粘i_(kāi)發(fā)中的使用:以這兩種注解來(lái)標(biāo)注一個(gè)類(lèi)為配置類(lèi)
@Configuration public class AppConfig { } ? @Component public class AppConfig { }
上面的程序,Spring會(huì)將其認(rèn)為配置類(lèi)來(lái)做處理,但是這里有一個(gè)概念需要明確一下,就是在Spring中,對(duì)于配置類(lèi)來(lái)講,其實(shí)是有分類(lèi)的,大體可以分為兩類(lèi),一類(lèi)稱為L(zhǎng)ITE模式,另一類(lèi)稱為FULL模式,那么對(duì)應(yīng)上面的注解,@Component就是LITE類(lèi)型,@Configuration就是FULL類(lèi)型,如何理解這兩種配置類(lèi)型呢?我們先來(lái)看這個(gè)程序。
當(dāng)我們使用@Component實(shí)現(xiàn)配置類(lèi)時(shí):
@Component public class AppConfig { @Bean public Foo foo() { System.out.println("foo() invoked..."); Foo foo = new Foo(); System.out.println("foo() 方法的 foo hashcode: " + foo.hashCode()); return foo; } ? @Bean public Eoo eoo() { System.out.println("eoo() invoked..."); Foo foo = foo(); System.out.println("eoo() 方法的 foo hashcode: "+ foo.hashCode()); return new Eoo(); } ? public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); } }
執(zhí)行結(jié)果如下:
foo() invoked...
foo() 方法的 foo hashcode: 815992954
eoo() invoked...
foo() invoked...
foo() 方法的 foo hashcode: 868737467
eoo() 方法的 foo hashcode: 868737467
從結(jié)果可知,foo()方法執(zhí)行了兩次,一次是bean方法執(zhí)行的,一次是eoo()調(diào)用執(zhí)行的,所以兩次生成的foo對(duì)象是不一樣的。很符合大家的預(yù)期,但是當(dāng)我們使用@Configuration標(biāo)注配置類(lèi)時(shí),執(zhí)行結(jié)果如下:
foo() invoked...
foo() 方法的 foo hashcode: 849373393
eoo() invoked...
eoo() 方法的 foo hashcode: 849373393
這里可以看到foo()方法只執(zhí)行了一次,同時(shí)eoo()方法調(diào)用foo()生成的foo對(duì)象是同一個(gè)。這也就是@Component和@Configuration的區(qū)別現(xiàn)象展示,那么為什么會(huì)有這樣的一個(gè)現(xiàn)象?我們來(lái)考慮一個(gè)問(wèn)題,就是eoo()方法中調(diào)用了foo()方法,很明顯這個(gè)foo()這個(gè)方法就是會(huì)形成一個(gè)新對(duì)象,假設(shè)我們調(diào)用的foo()方法不是原來(lái)的foo()方法,是不是就可能不會(huì)形成新對(duì)象?如果我們?cè)谡{(diào)用foo()方法的時(shí)候去容器中獲取一下foo這個(gè)Bean,是不是就可以達(dá)到這樣的效果?那如何才能達(dá)到這樣的效果呢?有一個(gè)方法,代理!換句話說(shuō),我們調(diào)用的eoo()和foo()方法,包括AppConfig都被Spring代理了,那么這里我們明白了@Component與@Configuration最根本的區(qū)別,那就是@Configuration標(biāo)注的類(lèi)會(huì)被Spring代理,其實(shí)這樣描述不是非常嚴(yán)謹(jǐn),更加準(zhǔn)確的來(lái)說(shuō)應(yīng)該是如果一個(gè)類(lèi)的BeanDefinition的Attribute中有Full配置屬性,那么這個(gè)類(lèi)就會(huì)被Spring代理
3.Spring如何實(shí)現(xiàn)FULL配置的代理
如果要明白這一點(diǎn),那么還需要明確一個(gè)前提,就是Spring在什么時(shí)間將這些配置類(lèi)轉(zhuǎn)變成FULL模式或者LITE模式的,接下來(lái)我們就要介紹個(gè)人認(rèn)為在Spring中非常重要的一個(gè)類(lèi),ConfigurationClassPostProcessor。
3.1ConfigurationClassPostProcessor是什么
首先來(lái)簡(jiǎn)單的看一下這個(gè)類(lèi)的定義:
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {}
由這個(gè)類(lèi)定義可知這個(gè)類(lèi)的類(lèi)型為BeanDefinitionRegistryPostProcessor,以及實(shí)現(xiàn)了眾多Spring內(nèi)置的Aware接口,如果了解Beanfactory的后置處理器,那應(yīng)該清楚ConfigurationClassPostProcessor的執(zhí)行時(shí)機(jī),當(dāng)然不了解也沒(méi)有問(wèn)題,我們會(huì)在后面將整個(gè)流程闡述清楚,現(xiàn)在需要知道的是ConfigurationClassPostProcessor這個(gè)類(lèi)是在什么時(shí)間被實(shí)例化的?
3.2ConfigurationClassPostProcessor在什么時(shí)間被實(shí)例化
要回答這個(gè)問(wèn)題,需要先明確一個(gè)前提,那就是ConfigurationClassPostProcessor這個(gè)類(lèi)對(duì)應(yīng)的BeanDefinition在什么時(shí)間注冊(cè)到Spring的容器中的,因?yàn)镾pring的實(shí)例化比較特殊,主要是基于BeanDefinition來(lái)處理的,那么現(xiàn)在這個(gè)問(wèn)題就可以轉(zhuǎn)變?yōu)镃onfigurationClassPostProcessor這個(gè)類(lèi)是在什么時(shí)間被注冊(cè)為一個(gè)Beandefinition的?這個(gè)可以在源代碼中找到答案,具體其實(shí)就是在初始化這個(gè)Spring容器的時(shí)候。
new AnnotationConfigApplicationContext(ConfigClass.class) -> new AnnotatedBeanDefinitionReader(this); -> AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); -> new RootBeanDefinition(ConfigurationClassPostProcessor.class); -> registerPostProcessor(BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName)
從這里可以看出,ConfigurationClassPostProcessor已經(jīng)被注冊(cè)為了一個(gè)BeanDefinition,上面我們講了Spring是通過(guò)對(duì)BeanDefinition進(jìn)行解析,處理,實(shí)例化,填充,初始化以及眾多回調(diào)等等步驟才會(huì)形成一個(gè)Bean,那么現(xiàn)在ConfigurationClassPostProcessor既然已經(jīng)形成了一個(gè)BeanDefinition。
3.3 @Component與@Configuration的實(shí)現(xiàn)區(qū)別
上面ConfigurationClassPostProcessor已經(jīng)注冊(cè)到BeanDefinition注冊(cè)中心了,說(shuō)明Spring會(huì)在某個(gè)時(shí)間點(diǎn)將其處理成一個(gè)Bean,那么具體的時(shí)間點(diǎn)就是在BeanFactory所有的后置處理器的處理過(guò)程。
AbstractApplicationContext -> refresh() -> invokeBeanFactoryPostProcessors(beanFactory); -> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
這個(gè)處理BeanFactory的后置處理器的方法比較復(fù)雜,簡(jiǎn)單說(shuō)來(lái)就是主要處理所有實(shí)現(xiàn)了BeanFactoryPostProcessor及BeanDefinitionRegistryPostProcessor的類(lèi),當(dāng)然ConfigurationClassPostProcessor就是其中的一個(gè),那么接下來(lái)我們看看實(shí)現(xiàn)的方法:
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { int registryId = System.identityHashCode(registry); if (this.registriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry); } if (this.factoriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanFactory already called on this post-processor against " + registry); } this.registriesPostProcessed.add(registryId); ? processConfigBeanDefinitions(registry); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { int factoryId = System.identityHashCode(beanFactory); if (this.factoriesPostProcessed.contains(factoryId)) { throw new IllegalStateException( "postProcessBeanFactory already called on this post-processor against " + beanFactory); } this.factoriesPostProcessed.add(factoryId); if (!this.registriesPostProcessed.contains(factoryId)) { // BeanDefinitionRegistryPostProcessor hook apparently not supported... // Simply call processConfigurationClasses lazily at this point then. processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory); } ? enhanceConfigurationClasses(beanFactory); beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory)); }
這兩個(gè)方法應(yīng)該就是ConfigurationClassPostProcessor最為關(guān)鍵的了,我們?cè)谶@里先簡(jiǎn)單的總結(jié)一下,第一個(gè)方法主要完成了內(nèi)部類(lèi),@Component,@ComponentScan,@Bean,@Configuration,@Import等等注解的處理,然后生成對(duì)應(yīng)的BeanDefinition,另一個(gè)方法就是對(duì)@Configuration使用CGLIB進(jìn)行增強(qiáng),那我們先來(lái)看Spring是在哪里區(qū)分配置的LITE模式和FULL模式?在第一個(gè)方法中有一個(gè)checkConfigurationClassCandidate方法:
public static boolean checkConfigurationClassCandidate( BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { // ... Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName()); if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); } else if (config != null || isConfigurationCandidate(metadata)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); } else { return false; } // ... }
根據(jù)程序的判斷可知,如果一個(gè)類(lèi)被@Configuration標(biāo)注且代理模式為true,那么這個(gè)類(lèi)對(duì)應(yīng)的BeanDefinition將會(huì)被Spring添加一個(gè)FULL配置模式的屬性,有些同學(xué)可能對(duì)這個(gè)”屬性“不太理解,這里可以簡(jiǎn)單說(shuō)一下,其實(shí)這個(gè)”屬性“在Spring中有一個(gè)特定的接口就是AttributeAccessor,BeanDefinition就是繼承了這個(gè)接口,如何理解這個(gè)AttributeAccessor呢?其實(shí)也很簡(jiǎn)單,想想看,BeanDefinition主要是做什么的?這個(gè)主要是用來(lái)描述Class對(duì)象的,例如這個(gè)Class是不是抽象的,作用域是什么,是不是懶加載等等信息,那如果一個(gè)Class對(duì)象有一個(gè)“屬性”是BeanDefinition描述不了的,那這個(gè)要如何處理呢?那這個(gè)接口AttributeAccessor又派上用場(chǎng)了,你可以向其中存放任何你定義的數(shù)據(jù),可以理解為一個(gè)map,現(xiàn)在了解BeanDefinition的屬性的含義了么?
在這里也能看到@Configuration(proxyBeanMethods = false)和@Component一樣效果,都是LITE模式
在這里第一步先判斷出這個(gè)類(lèi)是FULL模式還是LITE模式,那么下一步就需要開(kāi)始執(zhí)行對(duì)配置類(lèi)的注解的解析了,在ConfigurationClassParser這個(gè)類(lèi)有一個(gè)processConfigurationClass方法,里面有一個(gè)doProcessConfigurationClass方法,這里就是解析前文所列舉的@Component等等注解的過(guò)程,解析完成之后,在ConfigurationClassPostProcessor類(lèi)的方法processConfigBeanDefinitions,有一個(gè)loadBeanDefinitions方法,這個(gè)方法就是將前文解析成功的注解數(shù)據(jù)全都注冊(cè)成BeanDefinition,這就是ConfigurationClassPostProcessor這個(gè)類(lèi)的第一個(gè)方法所完成的任務(wù),另外這個(gè)方法在這里是非常簡(jiǎn)單的描述了一下,實(shí)際上這個(gè)方法非常的復(fù)雜,需要慢慢的研究。
接下來(lái)再說(shuō)ConfigurationClassPostProcessor類(lèi)的enhanceConfigurationClasses方法,這個(gè)方法主要完成了對(duì)@Configuration注解標(biāo)注的類(lèi)的增強(qiáng),進(jìn)行CGLIB代理,代碼如下:
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) { Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<String, AbstractBeanDefinition>(); for (String beanName : beanFactory.getBeanDefinitionNames()) { BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {//判斷是否被@Configuration標(biāo)注 if (!(beanDef instanceof AbstractBeanDefinition)) { throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" + beanName + "' since it is not stored in an AbstractBeanDefinition subclass"); } else if (logger.isWarnEnabled() && beanFactory.containsSingleton(beanName)) { logger.warn("Cannot enhance @Configuration bean definition '" + beanName + "' since its singleton instance has been created too early. The typical cause " + "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " + "return type: Consider declaring such methods as 'static'."); } configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef); } } if (configBeanDefs.isEmpty()) { // nothing to enhance -> return immediately return; } ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer(); for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) { AbstractBeanDefinition beanDef = entry.getValue(); // If a @Configuration class gets proxied, always proxy the target class beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); try { // Set enhanced subclass of the user-specified bean class Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader); Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);//生成代理的class if (configClass != enhancedClass) { if (logger.isDebugEnabled()) { logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " + "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName())); } //替換class,將原來(lái)的替換為CGLIB代理的class beanDef.setBeanClass(enhancedClass); } } catch (Throwable ex) { throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex); } } }
這里需要CGLIB的一些知識(shí),我就簡(jiǎn)單的在這里總結(jié)一下,這個(gè)方法從所有的BeanDefinition中找到屬性為FULL模式的BeanDefinition,然后對(duì)其進(jìn)行代理增強(qiáng),設(shè)置BeanDefinition的beanClass。然后在增強(qiáng)時(shí)有一些細(xì)節(jié)稍微需要明確一下,就是我們這個(gè)普通類(lèi)中的方法,比如eoo(),foo()等方法,將會(huì)被MethodInterceptor所攔截,這個(gè)方法的調(diào)用將會(huì)被BeanMethodInterceptor所代理,到這里我們大家應(yīng)該稍微明確了ConfigurationClassPostProcessor是在什么時(shí)間被實(shí)例化,什么時(shí)間解析注解配置,什么時(shí)間進(jìn)行配置增強(qiáng)。如果看到這里不太明白,那歡迎與我來(lái)討論。
4.總結(jié)
@Component在Spring中是代表LITE模式的配置注解,這種模式下的注解不會(huì)被Spring所代理,就是一個(gè)標(biāo)準(zhǔn)類(lèi),如果在這個(gè)類(lèi)中有@Bean標(biāo)注的方法,那么方法間的相互調(diào)用,其實(shí)就是普通Java類(lèi)的方法的調(diào)用。
@Configuration在Spring中是代表FULL模式的配置注解,這種模式下的類(lèi)會(huì)被Spring所代理,那么在這個(gè)類(lèi)中的@Bean方法的相互調(diào)用,就相當(dāng)于調(diào)用了代理方法,那么在代理方法中會(huì)判斷,是否調(diào)用getBean方法還是invokeSuper方法,這里就是這兩個(gè)注解的最根本的區(qū)別。
一句話概括就是 @Configuration 中所有帶 @Bean 注解的方法都會(huì)被動(dòng)態(tài)代理,因此調(diào)用該方法返回的都是同一個(gè)實(shí)例。
到此這篇關(guān)于Spring中@Configuration和@Component注解的區(qū)別及原理的文章就介紹到這了,更多相關(guān)@Configuration和@Component注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot中@Configuration和@Bean和@Component相同點(diǎn)詳解
- Spring解讀@Component和@Configuration的區(qū)別以及源碼分析
- Spring中@Configuration注解和@Component注解的區(qū)別詳解
- 詳解Spring中@Component和@Configuration的區(qū)別
- Spring注解@Configuration和@Component區(qū)別詳解
- Spring注解中@Configuration和@Component到底有啥區(qū)別
- Spring @Configuration和@Component的區(qū)別
- 揭秘Spring核心注解@Configuration與@Component的本質(zhì)區(qū)別
相關(guān)文章
java使用FFmpeg提取音頻的實(shí)現(xiàn)示例
在Java開(kāi)發(fā)中,我們經(jīng)常會(huì)遇到需要使用FFmpeg來(lái)處理音視頻文件的情況,本文主要介紹了java使用FFmpeg提取音頻的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01利用java+mysql遞歸實(shí)現(xiàn)拼接樹(shù)形JSON列表的方法示例
這篇文章主要給大家介紹了關(guān)于利用java+mysql遞歸實(shí)現(xiàn)拼接樹(shù)形JSON列表的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來(lái)一起看看吧。2017-08-08Java Dubbo協(xié)議下的服務(wù)端線程使用詳解
Dubbo是阿里開(kāi)源項(xiàng)目,國(guó)內(nèi)很多互聯(lián)網(wǎng)公司都在用,已經(jīng)經(jīng)過(guò)很多線上考驗(yàn)。Dubbo內(nèi)部使用了Netty、Zookeeper,保證了高性能高可用性,使用Dubbo可以將核心業(yè)務(wù)抽取出來(lái),作為獨(dú)立的服務(wù),逐漸形成穩(wěn)定的服務(wù)中心2023-03-03使用SpringBoot編寫(xiě)一個(gè)優(yōu)雅的單元測(cè)試
這篇文章主要為大家詳細(xì)介紹了如何使用SpringBoot編寫(xiě)一個(gè)優(yōu)雅的單元測(cè)試,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-07-07基于Maven骨架創(chuàng)建JavaWeb項(xiàng)目過(guò)程解析
這篇文章主要介紹了基于Maven骨架創(chuàng)建JavaWeb項(xiàng)目過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08