前言

我们在开发的时候,经常会写一些类似xxxConfig这样的配置文件,配置文件中经常会出现如@Configuration、@ComponentScan、@Bean这样的注解。那么这些配置文件是不是就是配置类,我们一起来看一看在Spring中是如何定义配置类的

Spring中定义的配置类

Spring的解析工作都是由 ConfigurationClassPostProcessor 类的 postProcessBeanDefinitionRegistry 方法完成的,我来看一下相关源码

spring配置类重复定义如何解决 spring配置类的作用_spring配置类重复定义如何解决

spring配置类重复定义如何解决 spring配置类的作用_配置文件_02

通过源码我们得出以下结论:

  1. Spring通过 ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE 属性判断BeanDefinition有没有被解析过
  2. Spring通过 checkConfigurationClassCandidate
  3. 如果不存在配置类,解析流程结束

由第三点可知,我们交由Spring解析的第一个Class文件必须是配置类,不然Spring容器中只有一些内置的Bean (由内置BeanDefinition解析而成)。接下来我们通过checkConfigurationClassCandidate 方法,来看一下Spring是如何定义配置类的

checkConfigurationClassCandidate

public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {

    // 省略一些代码

    // 类所属class是否存在@Configuration注解
    Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
    // 如果存在@Configuration注解,且其proxyBeanMethods属性为true,就给相关BeanDefinition对象的CONFIGURATION_CLASS_ATTRIBUTE属性设置为CONFIGURATION_CLASS_FULL
    if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
        beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
    // 如果存在@Configuration注解,且其proxyBeanMethods属性为false,
    // 或者isConfigurationCandidate方法返回true
    // 就给相关BeanDefinition对象的CONFIGURATION_CLASS_ATTRIBUTE属性设置为CONFIGURATION_CLASS_LITE
    } else if (config != null || isConfigurationCandidate(metadata)) {
        beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
    } else {
        return false;
    }

    // 省略一些代码

    return true;
}

通过这一段源码,我们得出以下结论:

  1. 类所属Class上存在@Configuration注解,那么这个类就是一个配置类
  2. 满足 isConfigurationCandidate 方法的类也是一个配置类

isConfigurationCandidate

spring配置类重复定义如何解决 spring配置类的作用_后端_03

spring配置类重复定义如何解决 spring配置类的作用_java_04

由上述源码可知: 如果类上存在@Component、@ComponentScan、@Import、@ImportResource 注解,或者存在 @Bean

针对上述几种情况,我们做出以下约定:

  • 如果类被解析成的BeanDefinition对象含有属性 CONFIGURATION_CLASS_FULL,则称之为全配置类,
  • 如果类被解析成的BeanDefinition对象含有属性 CONFIGURATION_CLASS_LITE, 则称之为半配置类。

全配置类

  • 含有 @Configuration 注解,并且 proxyBeanMethods

半配置类

  • 含有 @Configuration 注解,并且 proxyBeanMethods
  • 含有 @Component、@ComponentScan、@Import、@ImportResource注解之一为半配置类
  • 含有 @Bean 注解标注的方法

不知道有没有小伙伴有这样的疑惑:如果含有 @Bean 注解标注的方法所属的类,如果不存在@Component注解,它是怎么被扫描成BeanDefinition的?对于这个问题,我想到了两种情况:

  • 通过BeanFactoryPostProcessor手动注册进去的
  • 如果项目中存在相关依赖,类上存在 @ManagedBean 和 @Named 注解也会被扫描成BeanDefinition。

全配置和半配置的区别

ConfigurationClassPostProcessor 类的 postProcessBeanDefinitionRegistry 方法在执行完成后,会在后续的流程中执行其 postProcessBeanFactory

相关流程如下 (#号前面表示类,后面表示方法):

spring配置类重复定义如何解决 spring配置类的作用_配置文件_05

spring配置类重复定义如何解决 spring配置类的作用_java_06

spring配置类重复定义如何解决 spring配置类的作用_java_07

spring配置类重复定义如何解决 spring配置类的作用_spring配置类重复定义如何解决_08

调用流程如下: 

  1. ConfigurationClassPostProcessor#postProcessBeanFactory
  2. ConfigurationClassPostProcessor#enhanceConfigurationClasses
  3. ConfigurationClassEnhancer#enhance
  4. ConfigurationClassEnhancer#newEnhancer
  5. ConfigurationClassEnhancer#createClass

进行完动态代理后,代理类会被注册几个CALLBACKS

spring配置类重复定义如何解决 spring配置类的作用_后端_09

我们重点关注一下这个 BeanMethodInterceptor

我们执行全配置类的@Bean方法就会进入 BeanMethodInterceptor 的 intercept 方法,那么这个intercept方法具体做了什么,我们继续追溯源码

spring配置类重复定义如何解决 spring配置类的作用_spring_10

spring配置类重复定义如何解决 spring配置类的作用_java_11

我们可以看到Spring尝试根据beanName和args,从BeanFactory中获取结果,如果我们没有配置Scope,则bean默认是单例的,所以如果我们多次执行全配置类的@Bean标注的方法,返回的对象是同一个。

举例验证

创建普通对象ModelA,ModelB
package com.test.spring.model;

public class ModelA {
}
package com.test.spring.model;

public class ModelB {
}
创建配置类AppConfig
package com.test.spring.config;

import org.springframework.context.annotation.ComponentScan;

/**
 * 这里可以不用加@Configuration
 * AnnotationConfigApplicationContext会将构造方法注入的类,解析成bd
 */
@ComponentScan("com.test.spring")
public class AppConfig {
}
创建ModelConfig
package com.test.spring.config;

import com.test.spring.model.ModelA;
import com.test.spring.model.ModelB;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ModelConfig {

    @Bean
    public ModelA modelA() {
        ModelB m1 = this.modelB();
        ModelB m2 = this.modelB();
        System.out.println(m1 == m2);

        return new ModelA();
    }

    @Bean
    public ModelB modelB() {
        return new ModelB();
    }
}
运行Main方法,查看运行结果

spring配置类重复定义如何解决 spring配置类的作用_java_12

我们可以看到,第三步代码中m1 == m2成立,即ModelConfig类被cglib动态代理了,执行modelB()方法都会进入 BeanMethodInterceptor 的 intercept 方法,两次执行modelB()方法都是从单例池里面取对象,所以对象都是同一个

如果我们将@Configuration的属性 proxyBeanMethods 改成 false,运行结果为false

spring配置类重复定义如何解决 spring配置类的作用_spring配置类重复定义如何解决_13

小结 

我们有时会听到 @Configuration + @Bean