亲宝软件园·资讯

展开

@Configuration保证@Bean单例语义方法介绍

程序员小潘 人气:0

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实现类,分别是:

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);
}

拦截方法主要做了以下几件事:

重点在于第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的单例语义。

加载全部内容

相关教程
猜你喜欢
用户评论