前言
我们在开发的时候,经常会写一些类似xxxConfig这样的配置文件,配置文件中经常会出现如@Configuration、@ComponentScan、@Bean这样的注解。那么这些配置文件是不是就是配置类,我们一起来看一看在Spring中是如何定义配置类的
Spring中定义的配置类
Spring的解析工作都是由 ConfigurationClassPostProcessor 类的 postProcessBeanDefinitionRegistry 方法完成的,我来看一下相关源码
通过源码我们得出以下结论:
- Spring通过 ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE 属性判断BeanDefinition有没有被解析过
- Spring通过 checkConfigurationClassCandidate
- 如果不存在配置类,解析流程结束
由第三点可知,我们交由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;
}
通过这一段源码,我们得出以下结论:
- 类所属Class上存在@Configuration注解,那么这个类就是一个配置类
- 满足 isConfigurationCandidate 方法的类也是一个配置类
isConfigurationCandidate
由上述源码可知: 如果类上存在@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
相关流程如下 (#号前面表示类,后面表示方法):
调用流程如下:
- ConfigurationClassPostProcessor#postProcessBeanFactory
- ConfigurationClassPostProcessor#enhanceConfigurationClasses
- ConfigurationClassEnhancer#enhance
- ConfigurationClassEnhancer#newEnhancer
- ConfigurationClassEnhancer#createClass
进行完动态代理后,代理类会被注册几个CALLBACKS
我们重点关注一下这个 BeanMethodInterceptor
我们执行全配置类的@Bean方法就会进入 BeanMethodInterceptor 的 intercept 方法,那么这个intercept方法具体做了什么,我们继续追溯源码
我们可以看到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方法,查看运行结果
我们可以看到,第三步代码中m1 == m2成立,即ModelConfig类被cglib动态代理了,执行modelB()方法都会进入 BeanMethodInterceptor 的 intercept 方法,两次执行modelB()方法都是从单例池里面取对象,所以对象都是同一个
如果我们将@Configuration的属性 proxyBeanMethods 改成 false,运行结果为false
小结
我们有时会听到 @Configuration + @Bean