为什么要使用@Configuration

844 阅读6分钟

@Configuration这个注解相信大家都有使用过, 但是当你出去面试时, 面试官问你@Configuration注解的作用, 或者说它与@Component的区别时, 两眼一抹黑. 根本不知道为什么. Ok, 没关系, 今天我们就来扒一扒@Configuration注解到底是什么.

功能差异

我们先来看段代码让大家感受下在使用过程中, @Configuration和@Component的区别:

 @Component
 //@Configuration
 public class TestConfig {
   @Bean
   public Demo demo() {
     return new Demo();
   }
   
   @Bean
   public DemoFactory demoFactory() {
     DemoFactory demoFactory= new DemoFactory();
     demoFactory.setDemo(demo());
     return demoFactory;
   }
 }
 ​
 public class Test {
   @Test
   public void test() {
     AnnotationConfigApplicationContext applicationContext= new AnnotationConfigApplicationContext(App.class);
     Demo demo= applicationContext.getBean(Demo.class);
     DemoFactory demoFactory= applicationContext.getBean(DemoFactory.class);
     
     System.out.println(demo.hashCode());
     System.out.println(demoFactory.getDemo().hashCode());
   }
 }

上面这段代码, 大家可以自己尝试着执行一下 拿到的hashcode值, 在使用@Configuration注解时是相同的, 而在使用@Component注解时结果时不同的

两个问题

  1. 为什么Configuration返回的结果是它本身, 而Component却是一个新对象
  2. Configuration 是如何实现的

根据现象以及问题, 我们一个一个来开.

设计思想

为什么Configuration需要实现单例

我们都知道Spring中有一个很重要的核心思想那就是单例, 在正常情况下, 我们的项目都是使用单例来完成的, 但是从示例中我们看出来@Component显然是违背了这一准则, 我们从DemoFactory中获取出来的对象和Spring所维护的Bean并非是同一对象. 这么做合理吗? 允许吗?

显然不被允许, 那么怎么来理解这个过程. 我们来假设这样一个场景

数据库事务

需求: 我们需要同时操作两张表, 往表中各插入一条记录

配置类我们这么来写

 @Bean
 public JdbcTemplate jdbcTemplate() {
   return new JdbcTemplate(datasource());
 }
 
 @Bean
 public PlatformTransactionManager platformTransactionManager() {
   return new DataSourceTransactionManager(datasource());
 }

然后我们来看实现类

 @Transaction
 public int insertA() {
   jdbcTemplate.execute("insert into TestA(`a`) values('a')");
   
   applicationContext.getBean(TestService.class).insertB();
 }
 ​
 @Transaction
 public int insertB() {
   jdbcTemplate.execute("insert into TestB(`b`) values('b')");
   throw new RuntimeException("Error");
 }

细心的同学其实已经看出来了, 如果说我们用Component进行配置Bean的话, 那么Spring的jdbc模版类 和 事物类中 所获取的datasource并不是同一个, 这种情况下会导致事务失效. 而Spring为了解决这一类的问题, 就必须要实现单例

Configuration的实现原理

其实说到实现, 即便不看源码大家也能猜出一二, 因为很显然我们要从缓存中拿数据, 这样才能保证我们拿到的对象是同一个, 那么既然要实现这样的一个功能, 那不就是要通过代理模式吗.

源码解析

实现过程

首先我们找到对@Configuration做处理的位置. ConfigurationClassPostProcessor找到后置处理器的处理方法postProcessBeanFactory

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

这里有个非常重要的方法enhanceConfigurationClasses, 通过名字我们可以看出这是一个cglib的动态代理方法, 那么它必定包含enhance方法

 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);
     // Set enhanced subclass of the user-specified bean class
     Class<?> configClass = beanDef.getBeanClass();
     // 增强
     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);
     }
   }

我们已经找到了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;
 }
 ​
 private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
   Enhancer enhancer = new Enhancer();
   // 需要增强的类, 就是带有@Configuration注解的类
   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;
 }

这里主要的回调方法总共有三个

  • BeanMethodInterceptor -- 对带有@Bean注解的方法进行增强
  • BeanFactoryAwareMethodInterceptor -- 对实现了BeanFactoryAware接口中的setBeanFactory方法增强
  • NoOp.INSTANCE -- 忽略

我们就看第一个BeanMethodInterceptor, 其他的都是一样的.

 //只有当@Bean的方法是被spring调到的时候,才会走下来
 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()));
   }
   //调用被代理方法
   return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
 }
 
 //用代理对象手动调用@bean的方法的时候才会走下来
 return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);

这里的核心点是resolveBeanReference方法, 这里面我们需要掌握的就一行代码:

 // 从BeanFactory中获取实例对象
 Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
             beanFactory.getBean(beanName));

到这里就结束了? 是的, 这就结束了, 对象就是这么获取的. 但是难道就没别的疑问了吗? 必须有, 只有细节才能决定成败.

判断、收集过程

Spring 是如何知道哪些对象需要生成代理, 哪些对象不需要的?

我们逆向来推导这个过程, 先来看判断的代码

 if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) { // 判断标记是否为FULL
   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);
 }

这里会对configClassAttr进行判断, 如果是FULL, 则将其装配进configBeanDefs中, 随后进行遍历增强.

接下来我们来看收集的位置, 也就是设置FULL的地方, 代码位置在ConfigurationClassPostProcessor#processConfigBeanDefinitions中

 List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
 //获取所有的beanNames
 String[] candidateNames = registry.getBeanDefinitionNames();
 ​
 for (String beanName : candidateNames) {
   BeanDefinition beanDef = registry.getBeanDefinition(beanName);
   //如果有该表示就不再处理
   if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
     if (logger.isDebugEnabled()) {
       logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
     }
   }
   //判断是否是候选的需要处理的BeanDefinition, 如果是则放入configCandidates容器中
   else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
     configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
   }
 }

核心逻辑在ConfigurationClassUtils.checkConfigurationClassCandidate里面

 //从metadata中获取Configuration注解
 Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
 //如果有Configuration注解, 就是full匹配标识
 if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
   beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
 }
 //如果是有Component、ComponentScan、Import、ImportResource或者方法上面有@Bean, 就是lite匹配
 else if (config != null || isConfigurationCandidate(metadata)) {
   beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
 }
 else {
   return false;
 }

到这里我们就完成了所有从注解的解析到收集到判断到代理以及具体的实现全流程了.

扩展延伸

最后再给大家扩展两个问题

  1. 为什么createClass返回的是类的反射对象而不是类的实例? 我们回过头来看下ConfigurationClassEnhancer中的这段代码
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));

这个类我们最终是需要交给Spring来进行管理,所以实例化的工作最终是交给Spring容器来做,而不是cglib来完成

  1. isCurrentlyInvokedFactoryMethod(beanMethod)条件是怎么成立的? 要搞清楚这个问题, 我们首先要知道@Bean的对象是如何被Spring实例化的
/**
 * 如果有FactoryMethodName属性 @Bean
 * 这个if成立条件只有2种情况:
 *  1. <Bean> 标签中配置了factory-method属性
 *  2. 方法上面加上了@Bean
 */
if (mbd.getFactoryMethodName() != null) {
    // 反射的方式调用FactoryMethod
    return instantiateUsingFactoryMethod(beanName, mbd, args);
}

当Spring去实例化Bean的时候会去判断BeanDefinition是否包含FactoryMethod如果是,就会在容器currentlyInvokedFactoryMethod中添加, 而isCurrentlyInvokedFactoryMethod就是判断容器中的方法是否与目前正在调用的方法一致

总结

Configuration能够帮助我们解决以单例的方式获取其他的Bean实例, 就像开头的示例一样, 当找不到问题时, 或许可以回过头看一看配置类是否合理