返回顶部
首页 > 资讯 > 后端开发 > Python >@Configuration保证@Bean单例语义方法介绍
  • 264
分享到

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

@Configuration@Bean@Configuration@Bean单例语义 2023-01-03 12:01:06 264人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

目录1. 前言2. ConfigurationClassPostProcessor3. ConfigurationClassEnhancer4. BeanFactoryAwareMe

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);
    }
    
    enhanceConfigurationClasses(beanFactory);
    beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

processConfigBeanDefinitions()方法会处理ConfigurationClass的@ComponentScan注解完成类的扫描和注册,解析@Bean方法等,不是本文分析的重点,略过。

我们重点关注enhanceConfigurationClasses()方法,它会过滤出容器内所有Full模式的ConfigurationClass,只有Full模式的ConfigurationClass才会生成CGLIB代理类。

何为Full模式的的ConfigurationClass?

ConfigurationClass分为两种模式,加了@Configuration注解的类才是Full模式,否则是Lite模式。


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


private boolean isCurrentlyInvokedFactoryMethod(Method method) {
    
    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单例语义内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: @Configuration保证@Bean单例语义方法介绍

本文链接: https://lsjlt.com/news/176460.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作