@Configuration保證@Bean單例語義方法介紹
1. 前言
Spring允許通過@Bean
注解方法來向容器中注冊Bean,如下所示:
@Bean public Object bean() { return new Object(); }
默認情況下,bean應該是單例的,但是如果我們手動去調(diào)用@Bean
方法,bean會被實例化多次,這破壞了bean的單例語義。
于是,Spring提供了@Configuration
注解,當一個配置類被加上@Configuration
注解后,Spring會基于該配置類生成CGLIB代理類,子類會重寫@Bean
方法,來保證bean是單例的。如下所示:
@Configuration public class BeanMethodConfig { @Bean public Object bean() { System.err.println("bean..."); return new Object(); } public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(BeanMethodConfig.class); BeanMethodConfig config = context.getBean(BeanMethodConfig.class); System.err.println("-----------"); config.bean(); config.bean(); config.bean(); } }
即使手動觸發(fā)多次bean()
方法,也只會生成一個Object對象,保證了bean的單例語義。Spring是如何做到的呢?
2. ConfigurationClassPostProcessor
ConfigurationClassPostProcessor是BeanFactoryPostProcessor的子類,屬于Spring的擴展點之一,它會在BeanFactory準備完畢后,處理BeanFactory里面所有ConfigurationClass類。
@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)) { processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory); } /** * FullConfigurationClass 才會生成代理類 * 避免@Bean方法被反復調(diào)用,生成多個實例,破壞了singleton語義 * @see ConfigurationClassEnhancer#enhance(Class, ClassLoader) */ enhanceConfigurationClasses(beanFactory); beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory)); }
processConfigBeanDefinitions()
方法會處理ConfigurationClass的@ComponentScan
注解完成類的掃描和注冊,解析@Bean
方法等,不是本文分析的重點,略過。
我們重點關(guān)注enhanceConfigurationClasses()
方法,它會過濾出容器內(nèi)所有Full模式的ConfigurationClass,只有Full模式的ConfigurationClass才會生成CGLIB代理類。
何為Full模式的的ConfigurationClass?
ConfigurationClass分為兩種模式,加了@Configuration
注解的類才是Full模式,否則是Lite模式。
/** * 過濾出所有的FullConfigurationClass 加了@Configuration注解的類 */ Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>(); for (String beanName : beanFactory.getBeanDefinitionNames()) { BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) { 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.isInfoEnabled() && beanFactory.containsSingleton(beanName)) { logger.info("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()) { return; }
如果容器內(nèi)存在Full模式的ConfigurationClass,則需要挨個處理,生成CGLIB代理類,然后將BeanDefinition的beanClass指向CGLIB代理類,這樣Spring在實例化ConfigurationClass對象時,生成的就是CGLIB代理對象了。
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); if (configClass != null) { Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader); if (configClass != enhancedClass) { if (logger.isTraceEnabled()) { logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " + "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName())); } beanDef.setBeanClass(enhancedClass); } } } catch (Throwable ex) { throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex); } }
3. ConfigurationClassEnhancer
代理類的生成邏輯在ConfigurationClassEnhancer#enhance()
,我們重點關(guān)注。
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) { /** * 生成的CGLIB代理類會實現(xiàn)EnhancedConfiguration接口, * 如果已經(jīng)實現(xiàn)了EnhancedConfiguration接口,則直接返回 */ if (EnhancedConfiguration.class.isAssignableFrom(configClass)) { if (logger.isDebugEnabled()) { logger.debug(String.format("Ignoring request to enhance %s as it has " + "already been enhanced. This usually indicates that more than one " + "ConfigurationClassPostProcessor has been registered (e.g. via " + "<context:annotation-config>). This is harmless, but you may " + "want check your configuration and remove one CCPP if possible", configClass.getName())); } return configClass; } // 生成代理類 Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader)); if (logger.isTraceEnabled()) { logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s", configClass.getName(), enhancedClass.getName())); } return enhancedClass; }
重點是生成Enhancer對象,然后調(diào)用Enhancer#createClass()
來生成增強后的子類。
newEnhancer()
方法我們重點關(guān)注,重點是Enhancer#setCallbackFilter()
方法,當我們調(diào)用ConfigurationClass的方法時,會被這里設(shè)置的Callback子類給攔截。
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.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader)); enhancer.setCallbackFilter(CALLBACK_FILTER); enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes()); return enhancer; }
我們重點看CALLBACK_FILTER
屬性:
private static final Callback[] CALLBACKS = new Callback[]{ new BeanMethodInterceptor(), new BeanFactoryAwareMethodInterceptor(), NoOp.INSTANCE }; private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);
它擁有三個Callback實現(xiàn)類,分別是:
- BeanMethodInterceptor:@Bean方法攔截器。
- BeanFactoryAwareMethodInterceptor:
BeanFactoryAware#setBeanFactory()
方法攔截器。 - NoOp:空殼方法,什么也不做。
4. BeanFactoryAwareMethodInterceptor
生成的CGLIB代理類要保證@Bean方法的單例語義,首先可以確定的一點是:它必須依賴Spring IOC容器,也就是BeanFactory對象。 Spring是如何處理的呢?
生成的CGLIB代理類,默認會實現(xiàn)EnhancedConfiguration接口,用來標記它是通過Enhancer生成的ConfigurationClass增強類。 而EnhancedConfiguration接口又繼承了BeanFactoryAware
接口,也就是說CGLIB代理類必須重寫setBeanFactory()
方法,來存放beanFactory對象。
setBeanFactory()
方法會被BeanFactoryAwareMethodInterceptor類攔截,看看它的intercept()
方法。原來生成的CGLIB代理類會有一個名為$$beanFactory
的屬性,類型是BeanFactory,setBeanFactory()
的邏輯僅僅是給$$beanFactory的屬性賦值而已。
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { /** * 生成的CGLIB代理類會有一個名為$$beanFactory的屬性,類型是BeanFactory * setBeanFactory()的邏輯就是給$$beanFactory的屬性賦值 */ Field field = ReflectionUtils.findField(obj.getClass(), BEAN_FACTORY_FIELD); Assert.state(field != null, "Unable to find generated BeanFactory field"); field.set(obj, args[0]); if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) { return proxy.invokeSuper(obj, args); } return null; }
5. BeanMethodInterceptor
重頭戲來了,看名字就知道,BeanMethodInterceptor類是用來攔截@Bean方法的,我們直接看intercept()
方法:
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs, MethodProxy cglibMethodProxy) throws Throwable { /** * 獲取BeanFactory * 生成的子類實現(xiàn)了BeanFactoryAware接口,會把BeanFactory賦值給屬性 $$beanFactory */ ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance); // @Bean方法名 決定BeanName String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod); if (BeanAnnotationHelper.isScopedProxy(beanMethod)) { String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName); if (beanFactory.isCurrentlyInCreation(scopedBeanName)) { beanName = scopedBeanName; } } /** * 如果ConfigurationClass是FactoryBean實現(xiàn)類,需要創(chuàng)建代理類來增強getObject()方法返回緩存的bean實例 */ if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) && factoryContainsBean(beanFactory, beanName)) { Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName); if (factoryBean instanceof ScopedProxyFactoryBean) { } else { return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName); } } /** * 判斷是否要調(diào)用父類方法,生成bean * 以singleton為例:首次getBean時,容器不存在,需要創(chuàng)建bean * 1.實例化bean時,會把FactoryMethod寫入ThreadLocal * @see SimpleInstantiationStrategy#instantiate(org.springframework.beans.factory.support.RootBeanDefinition, java.lang.String, org.springframework.beans.factory.BeanFactory, java.lang.Object, java.lang.reflect.Method, java.lang.Object...) * 2.代理對象判斷method已經(jīng)被調(diào)用,則直接調(diào)用父類方法生成bean * 3.實例化完,會清空ThreadLocal * 4.再次調(diào)用,將直接進resolveBeanReference()從容器中獲取緩存bean * * Spring調(diào)用了createBean(),就意味著需要調(diào)用父類方法生成bean,Spring本身保證單例語義 * 用戶觸發(fā)的@Bean方法,需要從BeanFactory#getBean()獲取,當容器內(nèi)不存在bean時,Spring自然會調(diào)用createBean(), * 會再次進入到這里 */ if (isCurrentlyInvokedFactoryMethod(beanMethod)) { if (logger.isInfoEnabled() && BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) { logger.info(String.format("@Bean method %s.%s is non-static and returns an object " + "assignable to Spring's BeanFactoryPostProcessor interface. This will " + "result in a failure to process annotations such as @Autowired, " + "@Resource and @PostConstruct within the method's declaring " + "@Configuration class. Add the 'static' modifier to this method to avoid " + "these container lifecycle issues; see @Bean javadoc for complete details.", beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName())); } // 調(diào)用父類方法生成bean,對于單例bean,只會觸發(fā)一次 return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs); } // 從容器加載bean return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName); }
攔截方法主要做了以下幾件事:
- 獲取beanFactory
- 根據(jù)@Bean方法名生成beanName
- 如果是FactoryBean子類,則需要針對FactoryBean生成代理類,增強getObject()方法
- 判斷是否要調(diào)用父類方法,生成bean
- 如果不需要調(diào)用父類方法,則從beanFactory去獲取bean
重點在于第4步的判斷,cglibMethodProxy#invokeSuper()
會去調(diào)用父類的@Bean方法生成bean對象,而方法isCurrentlyInvokedFactoryMethod()
決定了Spring要不要調(diào)用父類方法。說白了,要想保證單例,得保證cglibMethodProxy#invokeSuper()
只調(diào)用一次。
Spring的解決方案是:用ThreadLocal記錄FactoryMethod?。。?/p>
/** * FactoryMethod當前是否已調(diào)用? */ private boolean isCurrentlyInvokedFactoryMethod(Method method) { /** * Spring createBean()會將FactoryMethod寫入到ThreadLocal * 再進這個方法就是true了,也就是回去調(diào)用父類方法生成bean */ Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod(); return (currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName()) && Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes())); }
當我們調(diào)用getBean()
方法時,如果這個bean是單例的,且容器內(nèi)不存在bean對象時,Spring才會調(diào)用createBean()
方法創(chuàng)建bean,否則直接返回容器內(nèi)緩存的bean對象。也就是說,對于單例bean,Spring本身會保證**createBean()**
方法只會觸發(fā)一次,只要調(diào)用了**createBean()**
,代理類就應該調(diào)用父類@Bean方法產(chǎn)生bean對象。
而createBean()
方法會調(diào)用SimpleInstantiationStrategy#instantiate()
實例化bean,在這個方法里面Spring玩了點小花樣,它在調(diào)用目標方法前將factoryMethod
寫入到ThreadLocal里了。
Method priorInvokedFactoryMethod = currentlyInvokedFactoryMethod.get(); try{ //先將factoryMethod寫入ThreadLocal currentlyInvokedFactoryMethod.set(factoryMethod); //再反射調(diào)用目標方法-代理方法 Object result = factoryMethod.invoke(factoryBean,args); if (result == null){ result = new NullBean(); } return result; }finally{ if (priorInvokedFactoryMethod != null) { currentlyInvokedFactoryMethod.set(priorInvokedFactoryMethod); }else{ currentlyInvokedFactoryMethod.remove(); } }
如此一來,在反射調(diào)用目標代理方法時,isCurrentlyInvokedFactoryMethod()
方法就會返回true,代理方法就會去調(diào)用父類方法生成bean對象,代理方法執(zhí)行完畢后,Spring會將ThreadLocal清空。當我們再手動去調(diào)用@Bean方法時,isCurrentlyInvokedFactoryMethod()
方法就會返回false,代理方法將不再調(diào)用父類方法,而是通過BeanFactory#getBean()
方法向容器拿bean,因為容器已經(jīng)存在bean了,所以會直接返回,不會再調(diào)用factoryMethod
方法了,這樣就保證了父類方法只會觸發(fā)一次,也就保證了bean的單例語義。
到此這篇關(guān)于@Configuration保證@Bean單例語義方法介紹的文章就介紹到這了,更多相關(guān)@Configuration @Bean單例語義內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Cloud Gateway 使用JWT工具類做用戶登錄校驗功能
這篇文章主要介紹了Spring Cloud Gateway 使用JWT工具類做用戶登錄校驗的示例代碼,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01springboot druid數(shù)據(jù)庫配置密碼加密的實現(xiàn)
Druid是阿里開發(fā)的數(shù)據(jù)庫連接池,本文主要介紹了springboot druid數(shù)據(jù)庫配置密碼加密的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2024-06-06Mybatis-Plus3.x的創(chuàng)建步驟及使用教程
MyBatis-Plus是一個?MyBatis?的增強工具,在?MyBatis?的基礎(chǔ)上只做增強不做改變,為?簡化開發(fā)、提高效率而生,這篇文章主要介紹了Mybatis-Plus3.x的使用,需要的朋友可以參考下2023-10-10java基礎(chǔ)理論Stream的Filter與謂詞邏輯
這篇文章主要為大家介紹了java基礎(chǔ)理論Stream的Filter與謂詞邏輯,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-03-03