@Configuration注解
在使用spring的时候,如果我们加了一个配置类,通常会在配置类上加上一个@Configuration注解,这个注解的作用,是我们要探讨的内容
结论:
1.加了@Configuration注解的类,我们称之为全配置类,这里之所以这么叫,在后面我会解释(这里所说的全配置类,我只是为了和普通类区分开来)
2.对于加了@Configuration的配置类,spring会为该类生成代理对象(CGLIB代理)
3.配置类加与不加@Configuration,都会注入bean,只是,通过@Bean在配置类中注入的bean,有可能是非单实例的bean
应用
先看下我自己的写的demo
@Configuration
@ComponentScan("com.study.extension.configuration")
public class AppConfig {
@Bean
public TestBean01 testBean01(){
System.out.println("初始化bean01");
return new TestBean01();
}
@Bean
public TestBean02 testBean02(){
System.out.println("初始化bean02");
testBean01();
return new TestBean02();
}
}
public class TestBean01 {
}
public class TestBean02 {
}
public class TestConfiguration {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(ac.getBean(AppConfig.class));
}
}
这上面是我的源码,下面我录的一个gif,分别展示了appConfig类中加@Configuration注解和不加@Configuration注解的区别:
可以看到:
如果加了@Configuration注解,那么,在我们打印出来的AppConfig对象就是一个CGLIB代理对象
com.study.extension.configuration.AppConfig$$EnhancerBySpringCGLIB$$b954b13@6e0dec4a
并且,在配置类中,只会打印一次 “初始化bean01”
如果不添加@Configuration注解,那么,我们打印出来的AppConfig对象就是一个普通的bean
com.study.extension.configuration.AppConfig@b7f23d9
并且,在配置类中,会打印两次 “初始化bean01”
原因说明
我先把这里的原因解释明白,后面再用源码来说明
- spring在源码中的某一步,会对配置类进行解析,如果配置类加了@Configuration注解,就会将对应beanDefinition对象的configurationClass属性设置为full;否则,设置为lite
- 在spring初始化的过程中,在某一步,spring会为加了@Configuration注解的类,生成代理对象,这是我们看到为什么打印出来的对应一个是代理对象,一个是普通类的原因;这里代理对象的生成,依赖于第一步的解析
- 如果配置类是代理对象,在调用配置类中的方法的时候,就会判断当前bean是否已经在容器中,如果已经在容器中了,就直接从容器中获取,而不会初始化;但是如果配置类是普通对象,在调用配置类中的方法的时候,就会直接调用对应的方法
这里说的第一步是在:org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
这个方法中完成的
第二步,动态对象对象的生成,是在org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanFactory中完成的
源码解析
解析配置类
这个gif的所有代码,都是在org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors方法里面,这里直接跳转到的是我们要看的第一处源码:对@Configuration注解的解析
org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
/**
* 序号1:
* 获取所有BeanDefinitionNames,这里第一次进来的时候,理论上只会有spring自带的和我们注入的配置类
* 1.new AnnotatedBeanDefinitionReader(this);中注入的spring自己的beanPostProcessor;
* 2.以及添加到beanDefinitionMap中的配置类
*/
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
/**
* 根据beanName,从beanDefinitionMap中获取beanDefinition
*/
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
/**
* 序号2:
* 在配置类被添加到beanDefinitionMap的时候,是不会给configurationClass这个属性赋值的
* 如果bean是配置类,configurationClass属性值就是full,否则就是lite
* 这里,如果当前bean 的configurationClass属性已经被设置值了,说明当前bean已经被解析过来(也即:后面的代码已经执行过了),就无需再次解析
*
* 被解析是指:在判断bean是全配置类(full)或者是普通bean(lite)之后,会把configurationClass属性值添加到beanDefinition的attributes
* 这个map中
*/
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
/**
* 序号3:
* 校验bean是否包含@Configuration 也就是校验bean是哪种配置类?全配置类?还是普通的配置类
*/
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// Return immediately if no @Configuration classes were found
//序号4:表示当前未执行的bean为空,return即可;防止重复解析
if (configCandidates.isEmpty()) {
return;
}
这个解析的代码,是在执行ConfigurationClassPostProcessor的postProcessorBeanDefinitionRegistry()方法的时候,这里的解析思想是这样的:
- 从spring容器的beanDefinitionNames中获取所有的beanDefinition的name,这个集合是在将bean转换成beanDefinition对象,存入到BeanDefinitionMap中的时候,会将bdName存入到另外一个集合,就是beanDefinitionNames;
- 循环所有的bdName,并根据bdName从bdMap中获取bd对象
- 重要的是上面源码中序号2这里的逻辑,这里的思想是:spring在将bean转换为BeanDefinition对象的时候,并不会设置beanDefinition的configurationClass属性,所以:这里如果bd的configurationClass属性已经被赋值了,说明是已经被解析过一次了,就不会添加到configCandidates这个集合中,也即:序号4这里,如果configCandidates为空,就表示当前没有待解析的类,return即可
- 序号2 这里的isFullConfigurationClass和isLiteConfigurationClass两个方法的源码我就不贴出来了,里面的判断很简单,就是判断beanDefinition的configurationClass属性值是full?还是lite?如果是第一次解析配置类,这里的判断条件肯定是false,会进入到else的逻辑中
- ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory):这个方法里面的逻辑,我觉得可以称之为真正对@Configuration注解解析的地方
public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
String className = beanDef.getBeanClassName();
if (className == null || beanDef.getFactoryMethodName() != null) {
return false;
}
AnnotationMetadata metadata;
/**
* 序号1:
* 如果bean是注解版的,就调用AnnotatedBeanDefinition获取元数据
* 如果是非注解的,就用AbstractBeanDefinition获取元数据
*
* 如果一个类是配置类,那么在初始化的时候,spring是通过this.reader.registry()来将配置类注入到beanDefinitionMap中的
* 所以,配置类(AppConfig)对应的beanDefinition是AnnotatedBeanDefinition类型的
*
*/
if (beanDef instanceof AnnotatedBeanDefinition &&
className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
// Can reuse the pre-parsed metadata from the given BeanDefinition...
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
// Check already loaded Class if present...
// since we possibly can't even load the class file for this Class.
Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
metadata = new StandardAnnotationMetadata(beanClass, true);
}
else {
try {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
metadata = metadataReader.getAnnotationMetadata();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find class file for introspecting configuration annotations: " + className, ex);
}
return false;
}
}
/**
* 序号2:
* 这里的if else判断,简单而言,就是一句话:
* 如果当前bean加了@Configuration注解,就是全配置类(configurationClass属性为full)
* 如果只是加了@Component、@ComponentScan、@Import、@ImportResource注解,那就是一个普通的bean(configurationClass属性为lite)
*/
if (isFullConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
/**
* 序号3:
* else里面是校验当前bean是否包含
* candidateIndicators.add(Component.class.getName());
* candidateIndicators.add(ComponentScan.class.getName());
* candidateIndicators.add(Import.class.getName());
* candidateIndicators.add(ImportResource.class.getName());
*
* @Component @ComponentScan @Import @ImportResource
*/
else if (isLiteConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
return false;
}
// It's a full or lite configuration candidate... Let's determine the order value, if any.
Integer order = getOrder(metadata);
if (order != null) {
beanDef.setAttribute(ORDER_ATTRIBUTE, order);
}
return true;
}
我们可以看到,在判断当前bean是否是全配置类的时候,会先根据beanDefinition的类型,获取到对应的元数据也即:metedata对象;由于我们使用的是AnnotationConfigApplicationContext对象来初始化spring容器的,所以,配置类是AnnotatedBeanDefinition类型的;
序号2这里,我在注释中,解释的也比较明白,简单而言:
就是如果加了@Configuration注解,configurationClass属性就是full;
如果只是加了@Component、@ComponentScan、@Import、@ImportResource注解,那么configurationClass就是lite
动态代理对象的生成
我们前面有说过,动态代理对象的生成,是在
org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanFactory
这个方法完成的,在这个方法中,有一行代码,就是我们需要关注的核心方法,enhanceConfigurationClasses(beanFactory);这里,动态代理对象的生成,就是在这个方法中完成判断和吹的
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
/**
* 序号1:
* 判断当前beanDefinition是否是 配置类,通过ConfigurationClass的值来判断,在扫描bean的时候,会对bean进行判断
* 如果当前bean添加了@Configuration注解,就将该属性设置为full,否则就设置为lite
*/
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
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.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
logger.warn("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);
}
}
/**
* 序号2:
* 只有在configBeanDefs这个map不为空的时候 才会创建动态代理
* 也就是说 只有加了@Configuration注解 才会为配置类创建动态代理
*
* 简单而言:如果说AppConfig加了@Configuration注解,那么在spring容器中 appConfig就是一个代理对象
* 如果没有加注解,那APPConfig在spring容器中就是一个普通的bean
*/
if (configBeanDefs.isEmpty()) {
// nothing to enhance -> return immediately
return;
}
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);
try {
// Set enhanced subclass of the user-specified bean class
Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
if (configClass != null) {
//序号3: 完成对全注解类的cglib代理(全注解类就是加了@Configuration注解的配置类)
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " +
"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
}
beanDef.setBeanClass(enhancedClass);
}
}
}
catch (Throwable ex) {
throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
}
}
}
可以看到:
在进入到该方法之后,会先对beanDefinitionMap中的bd进行遍历,判断bd是否是全配置类,如果是全配置类,暂时放到一个集合中;
在序号2这里,会对集合进行判断,如果集合为空,就表示现在beanDefinitionMap中没有全配置类,无需进行动态代理对象的生成
在序号3这里,是对beanDefinitionMap中所有加了@Configuration注解的bean进行处理,生成代理对象,然后放到beanDefinition的beanClass属性中
这个方法,就是对全配置类,通过CGLIB生成了代理对象;
对象调用
这里要说明的是:如果配置类加了@Configuration注解,那么,在配置类中,实例化@Bean注入的对象时,会调用到org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor#intercept
这个代码里,这里面,会有一个比较巧妙的逻辑,我计划放到下一篇博客中单独说,我不确信这里的调用逻辑我能讲清楚,尽量吧;
https://github.com/mapy95/spring-sourceCode
这是我GitHub的地址,这是spring源码的个人学习和一些注解,可以看下,我学习到了某个点,会及时更新上去的