Spring中@Configuration注解和@Component注解的區(qū)別詳解
1. 情景展現(xiàn)
@Configuration 和 @Component 到底有何區(qū)別呢?我先通過(guò)如下一個(gè)案例,在不分析源碼的情況下,小伙伴們先來(lái)直觀感受一下這兩個(gè)之間的區(qū)別。
@Configuration public class JavaConfig01 { } @Component public class JavaConfig02 { }
首先,分別向 Spring 容器中注入兩個(gè) Bean,JavaConfig01 和 JavaConfig02,其中,JavaConfig01 上添加的是 @Configuration 注解而 JavaConfig02 上添加的則是 @Component 注解。
現(xiàn)在,在 XML 文件中配置包掃描:
<context:component-scan base-package="org.javaboy.demo.p6"/>
最后,加載 XML 配置文件,初始化容器:
public class Demo { public static void main(String[] args) { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans_demo.xml"); JavaConfig01 config01 = ctx.getBean(JavaConfig01.class); JavaConfig02 config02 = ctx.getBean(JavaConfig02.class); System.out.println("config01.getClass() = " + config01.getClass()); System.out.println("config02.getClass() = " + config02.getClass()); } }
最終打印出來(lái)結(jié)果如下:
從上面這段代碼中,我們可以得出來(lái)兩個(gè)結(jié)論:
- @Configuration 注解也是 Spring 組件注解的一種,通過(guò)普通的 Bean 掃描也可以掃描到 @Configuration。
- @Configuration 注解注冊(cè)到 Spring 中的 Bean 是一個(gè) CGLIB 代理的 Bean,而不是原始 Bean,這一點(diǎn)和 @Component 不一樣,@Component 注冊(cè)到 Spring 容器中的還是原始 Bean。
一個(gè)問(wèn)題來(lái)了,@Configuration 標(biāo)記的類為什么注冊(cè)到 Spring 容器之后就變成了代理對(duì)象了呢?閉著眼睛大家也能猜到,肯定是為了通過(guò)代理來(lái)增強(qiáng)其功能,那么究竟增強(qiáng)什么功能呢?接下來(lái)我們通過(guò)源碼分析來(lái)和小伙伴們梳理一下這里的條條框框。
2. 源碼分析
要理解這個(gè)問(wèn)題,首先得結(jié)合我們前面的文章@Configuration 注解的 Full 模式和 Lite 模式!,在該文中,松哥提到了 @Configuration 模式分為了 Full 模式和 Lite 模式,所以,對(duì)于 @Configuration 注解的處理,在加載的時(shí)候,就需要首先區(qū)分出來(lái)是 Full 模式還是 Lite 模式。
負(fù)責(zé) @Configuration 注解的是 ConfigurationClassPostProcessor,這個(gè)處理器是一個(gè) BeanFactoryPostProcessor,BeanFactoryPostProcessor 的作用就是在 Bean 定義的時(shí)候,通過(guò)修改 BeanDefinition 來(lái)重新定義 Bean 的行為
同時(shí),ConfigurationClassPostProcessor 也是 BeanDefinitionRegistryPostProcessor 的實(shí)例,BeanDefinitionRegistryPostProcessor 是干嘛的呢?
BeanDefinitionRegistryPostProcessor 是 Spring 框架中的一個(gè)接口,它的作用是在應(yīng)用程序上下文啟動(dòng)時(shí),對(duì) BeanDefinitionRegistry 進(jìn)行后置處理。具體來(lái)說(shuō),BeanDefinitionRegistryPostProcessor 可以用于修改或擴(kuò)展應(yīng)用程序上下文中的 BeanDefinition,即在 Bean 實(shí)例化之前對(duì) BeanDefinition 進(jìn)行修改。它可以添加、刪除或修改 BeanDefinition 的屬性,甚至可以動(dòng)態(tài)地注冊(cè)新的 BeanDefinition。通過(guò)實(shí)現(xiàn) BeanDefinitionRegistryPostProcessor 接口,我們可以在 Spring 容器啟動(dòng)過(guò)程中干預(yù) Bean 的定義,以滿足特定的需求。這使得我們可以在應(yīng)用程序上下文加載之前對(duì) Bean 進(jìn)行一些自定義的操作,例如動(dòng)態(tài)注冊(cè) Bean 或者修改 Bean 的屬性。需要注意的是,BeanDefinitionRegistryPostProcessor 在 BeanFactoryPostProcessor 之前被調(diào)用,因此它可以影響到 BeanFactoryPostProcessor 的行為。
BeanFactoryPostProcessor 中的方法是 postProcessBeanFactory,而 BeanDefinitionRegistryPostProcessor 中的方法是 postProcessBeanDefinitionRegistry,根據(jù)前面的介紹,postProcessBeanDefinitionRegistry 方法將在 postProcessBeanFactory 方法之前執(zhí)行。
所以,我們就從 postProcessBeanDefinitionRegistry 方法開(kāi)始看起吧~
2.1 postProcessBeanDefinitionRegistry
@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); }
這個(gè)方面前面的代碼主要是為了確保該方法執(zhí)行一次,我們就不多說(shuō)了。關(guān)鍵在于最后的 processConfigBeanDefinitions 方法,這個(gè)方法就是用來(lái)決策配置類是 Full 模式還是 Lite 模式的。
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } //省略。。。 }
我省略了其他代碼,大家看,這個(gè)方法中,會(huì)首先根據(jù) beanName 取出來(lái) BeanDefinition,然后判斷 BeanDefinition 中是否包含 ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE 屬性,這個(gè)屬性上記錄了當(dāng)前配置類是 Full 模式還是 Lite 模式,不同模式將來(lái)的處理方案肯定也是不同的。如果是第一次處理,顯然 BeanDefinition 中并不包含該屬性,因此就會(huì)進(jìn)入到 ConfigurationClassUtils.checkConfigurationClassCandidate 方法中,正是在該方法中,判斷當(dāng)前配置類是 Full 模式還是 Lite 模式,并進(jìn)行標(biāo)記,checkConfigurationClassCandidate 方法的邏輯也挺長(zhǎng)的,我這里挑出來(lái)跟我們感興趣的部分:
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; } //省略 }
Full 模式情況很簡(jiǎn)單,就是如果配置類上存在 @Configuration 注解,并且該注解的 proxyBeanMethods 屬性值不為 false,那么就是 Full 模式
Lite 模式就情況多一些,首先 config!=null 就是說(shuō)現(xiàn)在也存在 @Configuration 注解,但是 proxyBeanMethods 屬性值此時(shí)為 false,那么就是 Lite 模式(proxyBeanMethods 屬性值為 true 的話就進(jìn)入到 if 分支中了)。
另外就是在 isConfigurationCandidate 方法中有一些判斷邏輯去鎖定是否為 Lite 模式:
static boolean isConfigurationCandidate(AnnotationMetadata metadata) { // Do not consider an interface or an annotation... if (metadata.isInterface()) { return false; } // Any of the typical annotations found? for (String indicator : candidateIndicators) { if (metadata.isAnnotated(indicator)) { return true; } } // Finally, let's look for @Bean methods... return hasBeanMethods(metadata); }
這個(gè)方法的判斷邏輯是這樣:
- 首先注解要是標(biāo)記的是接口,那就不能算是 Lite 模式。
- 遍歷 candidateIndicators,判斷當(dāng)前類上是否包含這個(gè) Set 集合中的注解,這個(gè) Set 集合中的注解有四個(gè),分別是 @Component、@ComponentScan、@Import、@ImportResource 四個(gè),也就是,如果類上標(biāo)記的是這四個(gè)注解的話,那么也按照 Lite 模式處理。
- 判斷當(dāng)前類中是否有 @Bean 標(biāo)記的方法,如果有則按照 Lite 模式處理,否則就不是 Lite 模式。
好了,經(jīng)過(guò)上面的處理,現(xiàn)在就已經(jīng)標(biāo) BeanDefinition 中標(biāo)記了這個(gè)配置類到底是 Full 模式還是 Lite 模式了。
2.2 postProcessBeanFactory
接下來(lái)我們就來(lái)看 postProcessBeanFactory 方法。
/** * Prepare the Configuration classes for servicing bean requests at runtime * by replacing them with CGLIB-enhanced subclasses. */ @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è)方法的注釋,注釋說(shuō)的很明確了,將 Configuration 類通過(guò) CGLIB 進(jìn)行增強(qiáng),以便在運(yùn)行時(shí)較好的處理 Bean 請(qǐng)求。
這個(gè)方法中還會(huì)再次確認(rèn)一下 postProcessBeanDefinitionRegistry 方法已經(jīng)處理過(guò)了,如果沒(méi)有處理的話,則會(huì)在該方法中調(diào)用 processConfigBeanDefinitions 去確認(rèn) Bean 使用的是哪種模式。
該方法的關(guān)鍵在于 enhanceConfigurationClasses,這個(gè)就是用來(lái)通過(guò)動(dòng)態(tài)代理增強(qiáng)配置類的,當(dāng)然這個(gè)方法也是比較長(zhǎng)的,我這里列出來(lái)一些關(guān)鍵的邏輯:
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) { StartupStep enhanceConfigClasses = this.applicationStartup.start("spring.context.config-classes.enhance"); Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>(); for (String beanName : beanFactory.getBeanDefinitionNames()) { BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE); if ((configClassAttr != null || methodMetadata != null) && (beanDef instanceof AbstractBeanDefinition abd) && !abd.hasBeanClass()) { // Configuration class (full or lite) or a configuration-derived @Bean method // -> eagerly resolve bean class at this point, unless it's a 'lite' configuration // or component class without @Bean methods. boolean liteConfigurationCandidateWithoutBeanMethods = (ConfigurationClassUtils.CONFIGURATION_CLASS_LITE.equals(configClassAttr) && annotationMetadata != null && !ConfigurationClassUtils.hasBeanMethods(annotationMetadata)); if (!liteConfigurationCandidateWithoutBeanMethods) { try { abd.resolveBeanClass(this.beanClassLoader); } } } if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) { configBeanDefs.put(beanName, abd); } } 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); // Set enhanced subclass of the user-specified bean class Class<?> configClass = beanDef.getBeanClass(); Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader); if (configClass != enhancedClass) { beanDef.setBeanClass(enhancedClass); } } enhanceConfigClasses.tag("classCount", () -> String.valueOf(configBeanDefs.keySet().size())).end(); }
這個(gè)方法的邏輯,我整體上將之分為兩部分:
第一部分就是先找到 Full 模式的配置類的名稱,存入到 configBeanDefs 集合中。
具體尋找的邏輯就是根據(jù)配置類的模式去尋找,如果配置類是 Full 模式,就將之存入到 configBeanDefs 中。如果配置類是 Lite 模式,且里邊沒(méi)有 @Bean 標(biāo)記的方法,那就說(shuō)明這可能并不是一個(gè)配置類,就是一個(gè)普通 Bean,那么就在這里加載類就行了。
第二步則是遍歷 configBeanDefs 集合,增強(qiáng)配置類。
這個(gè)如果大家了解 CGLIB 動(dòng)態(tài)代理的話,這個(gè)就很好懂了,關(guān)于 CGLIB 動(dòng)態(tài)代理松哥這里不啰嗦,最近更新的 Spring 源碼視頻中都有詳細(xì)講到。那么這里主要是通過(guò) enhancer.enhance 方法來(lái)生成代理類的,如下:
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) { if (EnhancedConfiguration.class.isAssignableFrom(configClass)) { return configClass; } Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader)); return enhancedClass; } private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(configSuperClass); enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class}); enhancer.setUseFactory(false); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); enhancer.setAttemptLoad(true); enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader)); enhancer.setCallbackFilter(CALLBACK_FILTER); enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes()); return enhancer; }
小伙伴們看到,增強(qiáng)類中的 setCallbackFilter 是 CALLBACK_FILTER,這個(gè)里邊包含了幾個(gè)方法攔截器,跟我們相關(guān)的是 BeanMethodInterceptor,我們來(lái)看下:
private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback { @Override @Nullable public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs, MethodProxy cglibMethodProxy) throws Throwable { ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance); String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod); // Determine whether this bean is a scoped-proxy if (BeanAnnotationHelper.isScopedProxy(beanMethod)) { String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName); if (beanFactory.isCurrentlyInCreation(scopedBeanName)) { beanName = scopedBeanName; } } // To handle the case of an inter-bean method reference, we must explicitly check the // container for already cached instances. // First, check to see if the requested bean is a FactoryBean. If so, create a subclass // proxy that intercepts calls to getObject() and returns any cached bean instance. // This ensures that the semantics of calling a FactoryBean from within @Bean methods // is the same as that of referring to a FactoryBean within XML. See SPR-6602. if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) && factoryContainsBean(beanFactory, beanName)) { Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName); if (factoryBean instanceof ScopedProxyFactoryBean) { // Scoped proxy factory beans are a special case and should not be further proxied } else { // It is a candidate FactoryBean - go ahead with enhancement return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName); } } if (isCurrentlyInvokedFactoryMethod(beanMethod)) { return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs); } return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName); } }
自己寫(xiě)過(guò) CGLIB 動(dòng)態(tài)代理的小伙伴都知道這里 intercept 方法的含義,這就是真正的攔截方法了,也就是說(shuō),如果我們的配置類是 Full 模式的話,那么將來(lái)調(diào)用 @Bean 注解標(biāo)記的方法的時(shí)候,調(diào)用的其實(shí)是這里的 intercept 方法。
上面方法,首先會(huì)判斷當(dāng)前代理是否為作用域代理,我們這里當(dāng)然不是。
接下來(lái)判斷請(qǐng)求的 Bean 是否是一個(gè) FactoryBean,如果是,則需要去代理其 getObject 方法,當(dāng)執(zhí)行到 getObject 方法的時(shí)候,就去 Spring 容器中查找需要的 Bean,當(dāng)然,我們這里也不屬于這種情況。
接下來(lái)判斷當(dāng)前正在執(zhí)行的方法,是否為容器中正在調(diào)用的工廠方法。
例如我有如下代碼:
@Configuration public class JavaConfig { @Bean User user() { User user = new User(); user.setDog(dog()); return user; } @Bean Dog dog() { return new Dog(); } }
那么如果是直接調(diào)用 dog() 方法,則 isCurrentlyInvokedFactoryMethod 返回 true,如果是在 user() 方法中調(diào)用的 dog() 方法,則 isCurrentlyInvokedFactoryMethod 返回 false。
當(dāng) isCurrentlyInvokedFactoryMethod 返回 true 的時(shí)候,就執(zhí)行 invokeSuper 方法,也就是真正的觸發(fā) dog() 方法的執(zhí)行。
當(dāng) isCurrentlyInvokedFactoryMethod 返回 false 的時(shí)候,則執(zhí)行下面的 resolveBeanReference 方法,這個(gè)方法會(huì)先去 Spring 容器中查找相應(yīng)的 Bean,如果 Spring 容器中不存在該 Bean,則會(huì)觸發(fā) Bean 的創(chuàng)建流程。
現(xiàn)在,小伙伴們應(yīng)該明白了為什么 Full 模式下,調(diào)用 @Bean 注解標(biāo)記的方法并不會(huì)導(dǎo)致 Bean 的重復(fù)創(chuàng)建了吧~
好啦,本文結(jié)合上文 Spring中@Configuration注解的Full模式和Lite模式詳解! 一起食用效果更佳哦~
到此這篇關(guān)于Spring中@Configuration注解和@Component注解的區(qū)別詳解的文章就介紹到這了,更多相關(guān)@Configuration和@Component內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java SpringBoot的相關(guān)知識(shí)點(diǎn)詳解
這篇文章主要介紹了SpringBoot的相關(guān)知識(shí)點(diǎn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-10-10springBoot熱部署、請(qǐng)求轉(zhuǎn)發(fā)與重定向步驟詳解
這篇文章主要介紹了springBoot熱部署、請(qǐng)求轉(zhuǎn)發(fā)與重定向,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06Spring項(xiàng)目中使用Junit單元測(cè)試并配置數(shù)據(jù)源的操作
這篇文章主要介紹了Spring項(xiàng)目中使用Junit單元測(cè)試并配置數(shù)據(jù)源的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09MyBatis-Plus實(shí)現(xiàn)邏輯刪除的示例代碼
本文主要介紹了MyBatis-Plus實(shí)現(xiàn)邏輯刪除的示例代碼,就是通過(guò)邏輯判斷的手段表示該條數(shù)據(jù)已刪除,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05Gradle相對(duì)于Maven有哪些優(yōu)點(diǎn)
這篇文章主要介紹了Gradle相對(duì)于Maven有哪些優(yōu)點(diǎn),幫助大家選擇合適的自動(dòng)構(gòu)建工具,更好的構(gòu)建項(xiàng)目,感興趣的朋友可以了解下2020-10-10MyBatis中RowBounds實(shí)現(xiàn)內(nèi)存分頁(yè)
RowBounds是MyBatis提供的一種內(nèi)存分頁(yè)方式,適用于小數(shù)據(jù)量的分頁(yè)場(chǎng)景,本文就來(lái)詳細(xì)的介紹一下,具有一定的參考價(jià)值,感興趣的可以了解一下2024-12-12