spring源码:@Configuration源码

本文详细分析了Spring中@Configuration注解的作用,包括全配置类的概念,配置类代理对象的生成,以及对象调用的过程。通过示例和源码解析,解释了加与不加@Configuration注解对bean初始化的影响,展示了@Configuration如何影响bean的生命周期。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

@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注解使用
可以看到:
如果加了@Configuration注解,那么,在我们打印出来的AppConfig对象就是一个CGLIB代理对象

com.study.extension.configuration.AppConfig$$EnhancerBySpringCGLIB$$b954b13@6e0dec4a

并且,在配置类中,只会打印一次 “初始化bean01”

如果不添加@Configuration注解,那么,我们打印出来的AppConfig对象就是一个普通的bean

com.study.extension.configuration.AppConfig@b7f23d9

并且,在配置类中,会打印两次 “初始化bean01”

原因说明

我先把这里的原因解释明白,后面再用源码来说明

  1. spring在源码中的某一步,会对配置类进行解析,如果配置类加了@Configuration注解,就会将对应beanDefinition对象的configurationClass属性设置为full;否则,设置为lite
  2. 在spring初始化的过程中,在某一步,spring会为加了@Configuration注解的类,生成代理对象,这是我们看到为什么打印出来的对应一个是代理对象,一个是普通类的原因;这里代理对象的生成,依赖于第一步的解析
  3. 如果配置类是代理对象,在调用配置类中的方法的时候,就会判断当前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()方法的时候,这里的解析思想是这样的:

  1. 从spring容器的beanDefinitionNames中获取所有的beanDefinition的name,这个集合是在将bean转换成beanDefinition对象,存入到BeanDefinitionMap中的时候,会将bdName存入到另外一个集合,就是beanDefinitionNames;
  2. 循环所有的bdName,并根据bdName从bdMap中获取bd对象
  3. 重要的是上面源码中序号2这里的逻辑,这里的思想是:spring在将bean转换为BeanDefinition对象的时候,并不会设置beanDefinition的configurationClass属性,所以:这里如果bd的configurationClass属性已经被赋值了,说明是已经被解析过一次了,就不会添加到configCandidates这个集合中,也即:序号4这里,如果configCandidates为空,就表示当前没有待解析的类,return即可
  4. 序号2 这里的isFullConfigurationClass和isLiteConfigurationClass两个方法的源码我就不贴出来了,里面的判断很简单,就是判断beanDefinition的configurationClass属性值是full?还是lite?如果是第一次解析配置类,这里的判断条件肯定是false,会进入到else的逻辑中
  5. 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源码的个人学习和一些注解,可以看下,我学习到了某个点,会及时更新上去的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值