开发者

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

开发者 https://www.devze.com 2023-01-04 10:25 出处:网络 作者: 程序员小潘
目录1. 前言2. ConfigurationClassPostProcessor3. ConfigurationClassEnhancer4. BeanFactoryAwareMethodInterceptor5. BeanMethodInterceptor1. 前言
目录
  • 1. 前言
  • 2. ConfigurationClassPostProcessor
  • 3. ConfigurationClassEnhancer
  • 4. BeanFactoryAwareMethodInterceptor
  • 5. BeanMethodInterceptor

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 != enhancedCl编程客栈ass) {
                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子类给拦截。

php
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 generwww.devze.comated 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,
                     开发者_JAVA入门   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.isCurrentlyInC编程客栈reation(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
     *python 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的单例语义。

到此这篇关于@Configuration保证@Bean单例语义方法介绍的文章就介绍到这了,更多相关@Configuration @Bean单例语义内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号