1. 前言
Spring允许通过@Bean
注解方法来向容器中注册Bean,如下所示:
@Bean
public Object bean() {
return new Object();
}
默认情况下,bean应该是单例的,但是如果我们手动去调用@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();
}
}
即使手动触发多次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方法被反复调用,生成多个实例,破坏了singleton语义
* @see ConfigurationClassEnhancer#enhance(Class, ClassLoader)
*/
enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
processConfigBeanDefinitions()
方法会处理ConfigurationClass的@ComponentScan
注解完成类的扫描和注册,解析@Bean
方法等,不是本文分析的重点,略过。
我们重点关注enhanceConfigurationClasses()
方法,它会过滤出容器内所有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;
}
如果容器内存在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()
,我们重点关注。
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
/**
* 生成的CGLIB代理类会实现EnhancedConfiguration接口,
* 如果已经实现了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对象,然后调用Enhancer#createClass()
来生成增强后的子类。
newEnhancer()
方法我们重点关注,重点是Enhancer#setCallbackFilter()
方法,当我们调用ConfigurationClass的方法时,会被这里设置的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实现类,分别是:
- BeanMethodInterceptor:@Bean方法拦截器。
- BeanFactoryAwareMethodInterceptor:
BeanFactoryAware#setBeanFactory()
方法拦截器。 - NoOp:空壳方法,什么也不做。
4. BeanFactoryAwareMethodInterceptor
生成的CGLIB代理类要保证@Bean方法的单例语义,首先可以确定的一点是:它必须依赖Spring IOC容器,也就是BeanFactory对象。 Spring是如何处理的呢?
生成的CGLIB代理类,默认会实现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
* 生成的子类实现了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实现类,需要创建代理类来增强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);
}
}
/**
* 判断是否要调用父类方法,生成bean
* 以singleton为例:首次getBean时,容器不存在,需要创建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已经被调用,则直接调用父类方法生成bean
* 3.实例化完,会清空ThreadLocal
* 4.再次调用,将直接进resolveBeanReference()从容器中获取缓存bean
*
* Spring调用了createBean(),就意味着需要调用父类方法生成bean,Spring本身保证单例语义
* 用户触发的@Bean方法,需要从BeanFactory#getBean()获取,当容器内不存在bean时,Spring自然会调用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()));
}
// 调用父类方法生成bean,对于单例bean,只会触发一次
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
// 从容器加载bean
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}
拦截方法主要做了以下几件事:
- 获取beanFactory
- 根据@Bean方法名生成beanName
- 如果是FactoryBean子类,则需要针对FactoryBean生成代理类,增强getObject()方法
- 判断是否要调用父类方法,生成bean
- 如果不需要调用父类方法,则从beanFactory去获取bean
重点在于第4步的判断,cglibMethodProxy#invokeSuper()
会去调用父类的@Bean方法生成bean对象,而方法isCurrentlyInvokedFactoryMethod()
决定了Spring要不要调用父类方法。说白了,要想保证单例,得保证cglibMethodProxy#invokeSuper()
只调用一次。
Spring的解决方案是:用ThreadLocal记录FactoryMethod!!!
/**
* FactoryMethod当前是否已调用?
*/
private boolean isCurrentlyInvokedFactoryMethod(Method method) {
/**
* Spring createBean()会将FactoryMethod写入到ThreadLocal
* 再进这个方法就是true了,也就是回去调用父类方法生成bean
*/
Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
return (currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName()) &&
Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes()));
}
当我们调用getBean()
方法时,如果这个bean是单例的,且容器内不存在bean对象时,Spring才会调用createBean()
方法创建bean,否则直接返回容器内缓存的bean对象。也就是说,对于单例bean,Spring本身会保证**createBean()**
方法只会触发一次,只要调用了**createBean()**
,代理类就应该调用父类@Bean方法产生bean对象。
而createBean()
方法会调用SimpleInstantiationStrategy#instantiate()
实例化bean,在这个方法里面Spring玩了点小花样,它在调用目标方法前将factoryMethod
写入到ThreadLocal里了。
如此一来,在反射调用目标代理方法时,isCurrentlyInvokedFactoryMethod()
方法就会返回true,代理方法就会去调用父类方法生成bean对象,代理方法执行完毕后,Spring会将ThreadLocal清空。当我们再手动去调用@Bean方法时,isCurrentlyInvokedFactoryMethod()
方法就会返回false,代理方法将不再调用父类方法,而是通过BeanFactory#getBean()
方法向容器拿bean,因为容器已经存在bean了,所以会直接返回,不会再调用factoryMethod
方法了,这样就保证了父类方法只会触发一次,也就保证了bean的单例语义。