likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 17

1.缘起

在看一段基于 spring security 的鉴权代码的时候,我发现一个有趣的 Bean 声明和方法调用。在一个 @Configuration 注解的配置类中用 @Bean 注解了一个方法 tokenStore,声明了 Spring bean: tokenStore。在 Spring 中,把 @Bean 注解的方法称为工厂方法,即用于创建 Spring bean 的方法。在同一个配置类另一个方法 configure 中调用了这个工厂方法 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);
        //直接调用工厂方法获取 TokenStore 实例
        endpoints.tokenStore(tokenStore())
        ...
    }
    ...
}

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

2.原理

通过调式代码,我们可以发现这个配置类已经被 CGLIB 代理了,这个配置类的实例类名变成了 AuthorizationConfig$$EnhancerBySpringCGLIB,如图: @Bean 注解的方法调用多次会创建多个bean 实例吗

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

由此可见spring 容器通过 CGLIB 代理了配置类,调用配置类 @Bean 注解的工厂方法时,这个方法会被拦截。拦截器会通过 Spring 容器的机制去获取这个工厂方法上声明的 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 的 intercept 实现逻辑:

private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {
   ...
   @Override
   @Nullable
   public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
            MethodProxy cglibMethodProxy) throws Throwable {

      ...
      if (isCurrentlyInvokedFactoryMethod(beanMethod)) {

         return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
      }

      return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
   }

   private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,
         ConfigurableBeanFactory beanFactory, String beanName) {

      // The user (i.e. not the factory) is requesting this bean through a call to
      // the bean method, direct or indirect. The bean may have already been marked
      // as 'in creation' in certain autowiring scenarios; if so, temporarily set
      // the in-creation status to false in order to avoid an exception.
      boolean alreadyInCreation = beanFactory.isCurrentlyInCreation(beanName);
      try {
         if (alreadyInCreation) {
            beanFactory.setCurrentlyInCreation(beanName, false);
         }
         boolean useArgs = !ObjectUtils.isEmpty(beanMethodArgs);
         if (useArgs && beanFactory.isSingleton(beanName)) {
            // Stubbed null arguments just for reference purposes,
            // expecting them to be autowired for regular singleton references?
            // A safe assumption since @Bean singleton arguments cannot be optional...
            for (Object arg : beanMethodArgs) {
               if (arg == null) {
                  useArgs = false;
                  break;
               }
            }
         }
         Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
               beanFactory.getBean(beanName));
         if (!ClassUtils.isAssignableValue(beanMethod.getReturnType(), beanInstance)) {
            // Detect package-protected NullBean instance through equals(null) check
            if (beanInstance.equals(null)) {
               if (logger.isDebugEnabled()) {
                  logger.debug(String.format("@Bean method %s.%s called as bean reference " +
                        "for type [%s] returned null bean; resolving to null value.",
                        beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName(),
                        beanMethod.getReturnType().getName()));
               }
               beanInstance = null;
            }
            else {
               String msg = String.format("@Bean method %s.%s called as bean reference " +
                     "for type [%s] but overridden by non-compatible bean instance of type [%s].",
                     beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName(),
                     beanMethod.getReturnType().getName(), beanInstance.getClass().getName());
               try {
                  BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName);
                  msg += " Overriding bean of same name declared in: " + beanDefinition.getResourceDescription();
               }
               catch (NoSuchBeanDefinitionException ex) {
                  // Ignore - simply no detailed message then.
               }
               throw new IllegalStateException(msg);
            }
         }
         Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
         if (currentlyInvoked != null) {
            String outerBeanName = BeanAnnotationHelper.determineBeanNameFor(currentlyInvoked);
            beanFactory.registerDependentBean(beanName, outerBeanName);
         }
         return beanInstance;
      }
      finally {
         if (alreadyInCreation) {
            beanFactory.setCurrentlyInCreation(beanName, true);
         }
      }
   }

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

总结

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