@Configuration保证@Bean单例语义方法介绍
程序员小潘 人气:01. 前言
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里了。
Method priorInvokedFactoryMethod = currentlyInvokedFactoryMethod.get(); try{ //先将factoryMethod写入ThreadLocal currentlyInvokedFactoryMethod.set(factoryMethod); //再反射调用目标方法-代理方法 Object result = factoryMethod.invoke(factoryBean,args); if (result == null){ result = new NullBean(); } return result; }finally{ if (priorInvokedFactoryMethod != null) { currentlyInvokedFactoryMethod.set(priorInvokedFactoryMethod); }else{ currentlyInvokedFactoryMethod.remove(); } }
如此一来,在反射调用目标代理方法时,isCurrentlyInvokedFactoryMethod()
方法就会返回true,代理方法就会去调用父类方法生成bean对象,代理方法执行完毕后,Spring会将ThreadLocal清空。当我们再手动去调用@Bean方法时,isCurrentlyInvokedFactoryMethod()
方法就会返回false,代理方法将不再调用父类方法,而是通过BeanFactory#getBean()
方法向容器拿bean,因为容器已经存在bean了,所以会直接返回,不会再调用factoryMethod
方法了,这样就保证了父类方法只会触发一次,也就保证了bean的单例语义。
加载全部内容