@Bean 注解的方法调用多次会创建多个bean 实例吗

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿

1.缘起

在看一段基于 spring security 的鉴权代码的时候,我发现一个有趣的 Bean 声明和方法调用。在一个 @Configuration 注解的配置类中用 @Bean 注解了一个方法 tokenStore,声明了 Spring bean: tokenStore。在同一个配置类另一个方法 configure 中调用了这个被 @Bean 注解的方法 tokenStore 来获取 TokenStore 实例。

那么这会导致系统中存在多个 TokenStore 实例吗?如果是两个实例,则一个应该是 Spring bean 实例,一个是 configure 方法中通过 tokenStroe() 方法创建的实例。代码如下:

@Configuration
public class AuthorizationConfig {

    @Bean
    public TokenStore tokenStore() {
        RedisTokenStore redis = new RedisTokenStore(connectionFactory);
        return redis;
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        /*使用oauth2的密码模式时需要配置authenticationManager*/
        endpoints.authenticationManager(authenticationManager);
        endpoints.tokenStore(tokenStore())
        ...
    }
    ...
}

答案是否定的,在系统中只有一个实例,这个实例就是 spring bean。这是怎么做到的呢?继续阅读之前,可以闭上眼睛思考一下。

2.原理

通过调式代码,我们可以发现这个配置类已经被 CGLIB 代理了,这个配置类的实例类名变成了 AuthorizationConfig$$EnhancerBySpringCGLIB,如图: image.png

断点堆栈如图: image.png 这个堆栈图从最底下向上看,配置类的 configure 方法调用本类方法 tokenStore,变成了调用AuthorizationConfig$$EnhancerBySpringCGLIB 的tokenStore了,而这个调用被 ConfigurationClassEnhancer$$BeanMethodInterceptor 拦截器拦截,接着堆栈出现了我们熟悉的 Spring getBean 的调用堆栈(当 bean 不存在的时候,就会触发创建 bean)。

由此可见spring 容器通过 CGLIB 代理了配置类,调用配置类 @Bean 注解的方法时,这个方法会被拦截。拦截器会通过 Spring 容器的机制去获取这个方法上 @Bean 注解声明的 bean,如果这个bean 实例还不存在,Spring 容器会创建 bean 实例,而这个 bean 是通过配置类的 tokenStore 方法创建的,所以最终找到通过代理类调用到了配置类的 tokenStore 方法创建了 bean 实例。

3.机制探究

Spring 为 @Configuration 专门设计了一个 BeanFactoryPostProcessor 实现类ConfigurationClassPostProcessor,我们知道 Spring 在初始化加载 bean 的过程中,预留了 BeanFactoryPostProcessor 扩展点这个扩展点的执行时机是在 BeanFactory 初始化之后,所有的Bean定义已经被加载,但Bean的实例还没被创建(不包括 BeanFactoryPostProcessor 实例)的时候。Spring 会在这个时候调用 BeanFactoryPostProcessor 的 postProcessBeanFactory 方法。这个扩展点通常用于修改 bean 的定义,bean 的属性值等

我们来看看 ConfigurationClassPostProcessor 的 postProcessBeanFactory 方法实现(省略了很多代码,只列出关键部分,可以参考 spring 5.2 版本的源代码:

/**
 * Prepare the Configuration classes for servicing bean requests at runtime
 * by replacing them with CGLIB-enhanced subclasses.
 * 通过 CGLIB 增强的子类来代替配置类来为 bean 请求提供支持
 */
@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)) {
      // BeanDefinitionRegistryPostProcessor hook apparently not supported...
      // Simply call processConfigurationClasses lazily at this point then.
      processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
   }
   //配置类主要的增强逻辑
   enhanceConfigurationClasses(beanFactory);
   beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
   Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
   for (String beanName : beanFactory.getBeanDefinitionNames()) {
      ...
      //如果配置类是 full 模式,则将配置类加入到需要增强的配置类列表中
      if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
         ...
         configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
      }
   }
   if (configBeanDefs.isEmpty()) {
      // nothing to enhance -> return immediately
      return;
   }
   
   //遍历需要增强的配置类列表,为每个配置类实现增强逻辑
   ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
   for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
      ...
      // 获取配置类的 Class 对象
      Class<?> configClass = beanDef.getBeanClass();
      // 增强实现
      Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
      if (configClass != enhancedClass) {
         ...
         //将增强后的配置类设置到 bean 定义对象中
         beanDef.setBeanClass(enhancedClass);
      }
   }
}

从上面关键代码,我们可以看出 Spring 会为符合条件的 full 模式的配置类实施增强。

注: Full 模式和 lite 模式

Spring 把 bean 分成两类:full 模式和 lite 模式。在 @Configuration 注解的配置类中声明的 bean 就是 full 模式的,其他的 spring bean,比如在 @Component 注解的类中声明的 bean 都是 lite 模式。也就是说通常只有 @Configuration 注解的配置类需要增强,这也是 @Configuration 注解和其他类型的组件注解的一个重要的区别。

下面我们接着探索 ConfigurationClassEnhancer 是如何实现增强的,关键代码如下:

public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
   if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
      ...
      //说明已经增强过了,直接返回
      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;
}

/**
 * Creates a new CGLIB {@link Enhancer} instance
 */
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
   Enhancer enhancer = new Enhancer();
   //将配置类设置为增强结果类的父类
   enhancer.setSuperclass(configSuperClass);
   enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
   enhancer.setUseFactory(false);
   //设置增加类的命名策略,即增加 BySpringCGLIB,可以看前面调试贴图中的类名
   enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
   enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
   //设置回调(拦截器)过滤器,就是说当配置类方法被调用的时候,会先执行符合过滤器条件的拦截器逻辑
   enhancer.setCallbackFilter(CALLBACK_FILTER);
   enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
   return enhancer;
}

/**
 * Uses enhancer to generate a subclass of superclass,
 * ensuring that callbacks are registered for the new subclass.
 */
private Class<?> createClass(Enhancer enhancer) {
   Class<?> subclass = enhancer.createClass();
   // 注册拦截器
   Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
   return subclass;
}

拦截器分析

增加逻辑主要是通过CGLIB enhancer 以配置类为父类创建一个代理子类,并设置了调用配置方法的时候,需要执行的拦截器。下面我们看看拦截器。

class ConfigurationClassEnhancer {

   // The callbacks to use. Note that these callbacks must be stateless.
   private static final Callback[] CALLBACKS = new Callback[] {
         new BeanMethodInterceptor(),
         new BeanFactoryAwareMethodInterceptor(),
         NoOp.INSTANCE
   };

这里面和我们主题相关的就是 BeanMethodInterceptor。这个拦截器的主要逻辑就是拦截对于 @Bean 注解方法的调用,并看声明的 Spring bean 是否已经存在,如果存在则直接返回容器中的 Spring bean。否则真正的配置类的方法创建 Spring bean 实例。

总结

Spring 利用 BeanFactoryPostProcessor 扩展点, 通过 CGLIB enhancer 增强了 @Configuration 注解的配置类。重载的方式是创建了一个新的以配置类为父类增强子类。对于配置类中 @Bean 注解的方法的调用将会被拦截器拦截。拦截器的逻辑是判断声明的 Spring bean 在容器中是否已经存在,如果存在则直接返回容器中的 Spring bean。否则真正的配置类的方法创建 Spring bean 实例。

猜你喜欢

转载自juejin.im/post/7116091617542406157