@Configuration保證@Bean單例語(yǔ)義方法介紹
1. 前言
Spring允許通過(guò)@Bean注解方法來(lái)向容器中注冊(cè)Bean,如下所示:
@Bean
public Object bean() {
return new Object();
}
默認(rèn)情況下,bean應(yīng)該是單例的,但是如果我們手動(dòng)去調(diào)用@Bean方法,bean會(huì)被實(shí)例化多次,這破壞了bean的單例語(yǔ)義。
于是,Spring提供了@Configuration注解,當(dāng)一個(gè)配置類被加上@Configuration注解后,Spring會(huì)基于該配置類生成CGLIB代理類,子類會(huì)重寫@Bean方法,來(lái)保證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();
}
}
即使手動(dòng)觸發(fā)多次bean()方法,也只會(huì)生成一個(gè)Object對(duì)象,保證了bean的單例語(yǔ)義。Spring是如何做到的呢?
2. ConfigurationClassPostProcessor
ConfigurationClassPostProcessor是BeanFactoryPostProcessor的子類,屬于Spring的擴(kuò)展點(diǎn)之一,它會(huì)在BeanFactory準(zhǔn)備完畢后,處理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 才會(huì)生成代理類
* 避免@Bean方法被反復(fù)調(diào)用,生成多個(gè)實(shí)例,破壞了singleton語(yǔ)義
* @see ConfigurationClassEnhancer#enhance(Class, ClassLoader)
*/
enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
processConfigBeanDefinitions()方法會(huì)處理ConfigurationClass的@ComponentScan注解完成類的掃描和注冊(cè),解析@Bean方法等,不是本文分析的重點(diǎn),略過(guò)。
我們重點(diǎn)關(guān)注enhanceConfigurationClasses()方法,它會(huì)過(guò)濾出容器內(nèi)所有Full模式的ConfigurationClass,只有Full模式的ConfigurationClass才會(huì)生成CGLIB代理類。
何為Full模式的的ConfigurationClass?
ConfigurationClass分為兩種模式,加了@Configuration注解的類才是Full模式,否則是Lite模式。
/**
* 過(guò)濾出所有的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,則需要挨個(gè)處理,生成CGLIB代理類,然后將BeanDefinition的beanClass指向CGLIB代理類,這樣Spring在實(shí)例化ConfigurationClass對(duì)象時(shí),生成的就是CGLIB代理對(duì)象了。
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(),我們重點(diǎn)關(guān)注。
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
/**
* 生成的CGLIB代理類會(huì)實(shí)現(xiàn)EnhancedConfiguration接口,
* 如果已經(jīng)實(shí)現(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;
}
重點(diǎn)是生成Enhancer對(duì)象,然后調(diào)用Enhancer#createClass()來(lái)生成增強(qiáng)后的子類。
newEnhancer()方法我們重點(diǎn)關(guān)注,重點(diǎn)是Enhancer#setCallbackFilter()方法,當(dāng)我們調(diào)用ConfigurationClass的方法時(shí),會(huì)被這里設(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;
}
我們重點(diǎn)看CALLBACK_FILTER屬性:
private static final Callback[] CALLBACKS = new Callback[]{
new BeanMethodInterceptor(),
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE
};
private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);
它擁有三個(gè)Callback實(shí)現(xiàn)類,分別是:
- BeanMethodInterceptor:@Bean方法攔截器。
- BeanFactoryAwareMethodInterceptor:
BeanFactoryAware#setBeanFactory()方法攔截器。 - NoOp:空殼方法,什么也不做。
4. BeanFactoryAwareMethodInterceptor
生成的CGLIB代理類要保證@Bean方法的單例語(yǔ)義,首先可以確定的一點(diǎn)是:它必須依賴Spring IOC容器,也就是BeanFactory對(duì)象。 Spring是如何處理的呢?
生成的CGLIB代理類,默認(rèn)會(huì)實(shí)現(xiàn)EnhancedConfiguration接口,用來(lái)標(biāo)記它是通過(guò)Enhancer生成的ConfigurationClass增強(qiáng)類。 而EnhancedConfiguration接口又繼承了BeanFactoryAware接口,也就是說(shuō)CGLIB代理類必須重寫setBeanFactory()方法,來(lái)存放beanFactory對(duì)象。
setBeanFactory()方法會(huì)被BeanFactoryAwareMethodInterceptor類攔截,看看它的intercept()方法。原來(lái)生成的CGLIB代理類會(huì)有一個(gè)名為$$beanFactory的屬性,類型是BeanFactory,setBeanFactory()的邏輯僅僅是給$$beanFactory的屬性賦值而已。
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
/**
* 生成的CGLIB代理類會(huì)有一個(gè)名為$$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
重頭戲來(lái)了,看名字就知道,BeanMethodInterceptor類是用來(lái)攔截@Bean方法的,我們直接看intercept()方法:
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {
/**
* 獲取BeanFactory
* 生成的子類實(shí)現(xiàn)了BeanFactoryAware接口,會(huì)把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實(shí)現(xiàn)類,需要?jiǎng)?chuàng)建代理類來(lái)增強(qiáng)getObject()方法返回緩存的bean實(shí)例
*/
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時(shí),容器不存在,需要?jiǎng)?chuàng)建bean
* 1.實(shí)例化bean時(shí),會(huì)把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.代理對(duì)象判斷method已經(jīng)被調(diào)用,則直接調(diào)用父類方法生成bean
* 3.實(shí)例化完,會(huì)清空ThreadLocal
* 4.再次調(diào)用,將直接進(jìn)resolveBeanReference()從容器中獲取緩存bean
*
* Spring調(diào)用了createBean(),就意味著需要調(diào)用父類方法生成bean,Spring本身保證單例語(yǔ)義
* 用戶觸發(fā)的@Bean方法,需要從BeanFactory#getBean()獲取,當(dāng)容器內(nèi)不存在bean時(shí),Spring自然會(huì)調(diào)用createBean(),
* 會(huì)再次進(jìn)入到這里
*/
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,對(duì)于單例bean,只會(huì)觸發(fā)一次
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
// 從容器加載bean
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}
攔截方法主要做了以下幾件事:
- 獲取beanFactory
- 根據(jù)@Bean方法名生成beanName
- 如果是FactoryBean子類,則需要針對(duì)FactoryBean生成代理類,增強(qiáng)getObject()方法
- 判斷是否要調(diào)用父類方法,生成bean
- 如果不需要調(diào)用父類方法,則從beanFactory去獲取bean
重點(diǎn)在于第4步的判斷,cglibMethodProxy#invokeSuper()會(huì)去調(diào)用父類的@Bean方法生成bean對(duì)象,而方法isCurrentlyInvokedFactoryMethod()決定了Spring要不要調(diào)用父類方法。說(shuō)白了,要想保證單例,得保證cglibMethodProxy#invokeSuper()只調(diào)用一次。
Spring的解決方案是:用ThreadLocal記錄FactoryMethod?。?!
/**
* FactoryMethod當(dāng)前是否已調(diào)用?
*/
private boolean isCurrentlyInvokedFactoryMethod(Method method) {
/**
* Spring createBean()會(huì)將FactoryMethod寫入到ThreadLocal
* 再進(jìn)這個(gè)方法就是true了,也就是回去調(diào)用父類方法生成bean
*/
Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
return (currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName()) &&
Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes()));
}
當(dāng)我們調(diào)用getBean()方法時(shí),如果這個(gè)bean是單例的,且容器內(nèi)不存在bean對(duì)象時(shí),Spring才會(huì)調(diào)用createBean()方法創(chuàng)建bean,否則直接返回容器內(nèi)緩存的bean對(duì)象。也就是說(shuō),對(duì)于單例bean,Spring本身會(huì)保證**createBean()**方法只會(huì)觸發(fā)一次,只要調(diào)用了**createBean()**,代理類就應(yīng)該調(diào)用父類@Bean方法產(chǎn)生bean對(duì)象。
而createBean()方法會(huì)調(diào)用SimpleInstantiationStrategy#instantiate()實(shí)例化bean,在這個(gè)方法里面Spring玩了點(diǎn)小花樣,它在調(diào)用目標(biāo)方法前將factoryMethod寫入到ThreadLocal里了。
Method priorInvokedFactoryMethod = currentlyInvokedFactoryMethod.get();
try{
//先將factoryMethod寫入ThreadLocal
currentlyInvokedFactoryMethod.set(factoryMethod);
//再反射調(diào)用目標(biā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();
}
}如此一來(lái),在反射調(diào)用目標(biāo)代理方法時(shí),isCurrentlyInvokedFactoryMethod()方法就會(huì)返回true,代理方法就會(huì)去調(diào)用父類方法生成bean對(duì)象,代理方法執(zhí)行完畢后,Spring會(huì)將ThreadLocal清空。當(dāng)我們?cè)偈謩?dòng)去調(diào)用@Bean方法時(shí),isCurrentlyInvokedFactoryMethod()方法就會(huì)返回false,代理方法將不再調(diào)用父類方法,而是通過(guò)BeanFactory#getBean()方法向容器拿bean,因?yàn)槿萜饕呀?jīng)存在bean了,所以會(huì)直接返回,不會(huì)再調(diào)用factoryMethod方法了,這樣就保證了父類方法只會(huì)觸發(fā)一次,也就保證了bean的單例語(yǔ)義。
到此這篇關(guān)于@Configuration保證@Bean單例語(yǔ)義方法介紹的文章就介紹到這了,更多相關(guān)@Configuration @Bean單例語(yǔ)義內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)用工具庫(kù)commons-lang3的使用
Apache?Commons?Lang?3是一個(gè)流行的Java實(shí)用工具庫(kù),提供了對(duì)java.lang包的擴(kuò)展,包括字符串操作、正則表達(dá)式處理、數(shù)字操作、日期和時(shí)間操作、隨機(jī)字符串生成和對(duì)象操作等功能2025-03-03
Java多態(tài)中的向上轉(zhuǎn)型與向下轉(zhuǎn)型淺析
多態(tài)是指不同類的對(duì)象在調(diào)用同一個(gè)方法是所呈現(xiàn)出的多種不同行為,下面這篇文章主要給大家介紹了關(guān)于Java多態(tài)中向上轉(zhuǎn)型與向下轉(zhuǎn)型的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02
Java多線程編程實(shí)現(xiàn)socket通信示例代碼
這篇文章主要介紹了Java多線程編程實(shí)現(xiàn)socket通信示例代碼,詳細(xì)介紹了tcp、udp協(xié)議,以及基于socket的Java網(wǎng)絡(luò)編程的相關(guān)內(nèi)容及代碼示例,代碼測(cè)試可用,供大家參考。2017-10-10
Java中try-catch的使用及注意細(xì)節(jié)
現(xiàn)在有很多的語(yǔ)言都支持try-catch,比如常見(jiàn)的就是c++,java等,這篇文章主要給大家介紹了關(guān)于Java中try-catch的使用及注意細(xì)節(jié)的相關(guān)資料,文中通過(guò)圖文以及實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06
SpringBoot實(shí)現(xiàn)接口防刷的兩種方法
接口被刷指的是同一接口被頻繁調(diào)用,可能是由于以下原因?qū)е拢簮阂夤艉驼`操作或程序錯(cuò)誤,本文給大家介紹了SpringBoot實(shí)現(xiàn)接口防刷的兩種方法,并有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下2024-06-06

