源码解析@Configuration 和 @Component 的区别

1、前言

我们都知道在 Spring 中,使用 @Configuration 注解和 @Component 注解都能完成一些相同的功能。但是他们之间又是存在区别的,而不仅仅是别名那么简单。本文主要就是通过分析源码来剖析这两者之间的区别究竟是什么?我们通过一个Hello的例子来感受一下。

// 这段代码是配置类,它使用了 @Configuration 注解。
@Configuration
public class Config {

	@Bean
	public Hello hello(){
		return new Hello();
	}
}
// 这是主函数,这里有两行很重要的代码,分别都进行了注释
public class Test {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class); // 创建和初始化容器
		System.out.println(context.getBean("hello", Hello.class)); // ① 我们直接从容器中拿出 hello 对象
		Config config = context.getBean("config", Config.class);
		System.out.println(config.hello()); // ② 通过调用 config 对象中的 hello() 获取 hello 对象
	}
}
// 一个普通的 Java 类
public class Hello {}

我们可以先来猜测一下,这两行代码拿出来的对象是相同的吗?

正常来说,这两个 hello 对象应该是不相同的,因为第一个 hello 对象是我们从容器取出来的,它是由容器帮我们创建的一个单例对象;而第二个 hello 对象是我们通过手动调用 hello() 创建出来的。

那我们就来验证一下我们的这个猜想是否正确?我们直接执行主函数,最后会得到如下答案:

image-20210924100813841

结论:我们会发现他们得到的是同一个 hello 对象。

现在,我们将 @Configuration 注解替换成 @Component 注解,看看结果是否还是一样。执行主函数的结果如下:

image-20210924102112046

结论:我们会发现他们得到的不是同一个 hello 对象。我们可以多执行几次 config.hello(),会发现我们每次得到的 hello 对象都是不一样的,结果如下图所示:

image-20210924102200444

所以,我们可以得出一个结论就是:@Configuration 注解和 @Component 注解在对待 @Bean 注解的方法上有所不同,但 Spring 具体是怎么做的,请往下阅读。

1、前置知识

BeanFactory :bean 工厂,我们常说的 bean 容器就是它,它是贯彻 Spring 整体的核心对象。

BeanFactoryPostProcess:bean 工厂后置处理器。这是一个接口,是 Spring 提供的一个扩展点,该接口的主要作用是在实例化 bean 之前,对 bean 的元信息进行修改。

BeanDefinition:bean 的元信息,我们所有需要交给 Spring 管理的 bean,首先都会被转换为一个 BeanDefinition 存放于 BeanFactory 中,而 Spring 所需的所有 bean 的信息就来自于 BeanDefinition,而不是我们一开始提供的那个 bean。信息包括:bean名称、bean类型、lazy、scope、…

3、分析

Spring 对于用户提供的 @Configuration 注解的类是通过一个 bean 工厂后置处理器来处理的,而这个后置处理器是 ConfigurationClassPostProcessor,通过名字我们也可以很轻松的直到它是处理配置类的。

此类中的 processConfigBeanDefinitions() 主要就是处理配置类。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    	// 存放所有待解析的配置类
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    	// 取出所有已经注册为 BeanDefinition 的 bean。目前只有 Config。
		String[] candidateNames = registry.getBeanDefinitionNames();
		
		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            // 检测 bean 元信息中是否包含有 CONFIGURATION_CLASS_ATTRIBUTE 属性,已经被解析过的 bean 就会设置该属性
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
            // 这个方法很重要,它会根据一定规则去设置 CONFIGURATION_CLASS_ATTRIBUTE 属性,该属性有两个值:full, lite。
			// 如果是 @Configuration 并且属性 proxyBeanMethods 为 true,则为 full,否则为 lite
			// 如果是 @Component @ComponentScan @Import @ImportSource 则为 lite
			// 如果上面两种都不是,但是具有 @Bean 修饰的方法,则为 lite
            // 如果上面三种都不符合,则什么都不做
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

		// Return immediately if no @Configuration classes were found
		if (configCandidates.isEmpty()) {
			return;
		}

		// 根据 @Order 排序
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});

		// Detect any custom bean name generation strategy supplied through the enclosing application context
		SingletonBeanRegistry sbr = null;
		if (registry instanceof SingletonBeanRegistry) {
			sbr = (SingletonBeanRegistry) registry;
			if (!this.localBeanNameGeneratorSet) {
				BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
						AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
				if (generator != null) {
					this.componentScanBeanNameGenerator = generator;
					this.importBeanNameGenerator = generator;
				}
			}
		}

		if (this.environment == null) {
			this.environment = new StandardEnvironment();
		}

		// Parse each @Configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
            // 解析扫描出来的配置类
			parser.parse(candidates);
			parser.validate();
			......
		}
		while (!candidates.isEmpty());

		......
	}

我们这里可以看到,Spring 会给每一个配置类都设置一个属性 CONFIGURATION_CLASS_ATTRIBUTE,该属性只有两个值,分别为:full、lite,具体设置该属性的规则在代码中的注释给出了。

然后这个代码还做了另外一个事情,就是调用解析配置类的方法,开始解析配置类。

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
	processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}

这个方法只是一个代理方法,真正执行解析的逻辑在 processConfigurationClass() 里面。

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
        return;
    }

    .......

    // Recursively process the configuration class and its superclass hierarchy.
    SourceClass sourceClass = asSourceClass(configClass, filter);
    do {
        sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
    }
    while (sourceClass != null);

    this.configurationClasses.put(configClass, configClass);
}

我们发现这个方法也没有做什么事情,我们最主要的就是看 doProcessConfigurationClass()

protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {

		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
			processMemberClasses(configClass, sourceClass, filter);
		}

		// 1.Process any @PropertySource annotations
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			}
			else {
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}

		// 2.Process any @ComponentScan annotations
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
				// 解析指定包下的类,将它们转换为 beanDefinition
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
                    // 跟之前的设置 CONFIGURATION_CLASS_ATTRIBUTE 属性的函数是一样的。对每一个扫描出来的类都进行设置,并且进行解析。所以,每一个扫描出来的类都是当作配置类来处理。
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

		// 3.Process any @Import annotations
		processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

		// 4.Process any @ImportResource annotations
		AnnotationAttributes importResource =
				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		if (importResource != null) {
			String[] resources = importResource.getStringArray("locations");
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
			for (String resource : resources) {
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
				configClass.addImportedResource(resolvedResource, readerClass);
			}
		}

		// 5.Process individual @Bean methods
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

		// Process default methods on interfaces
		processInterfaces(configClass, sourceClass);

		// Process superclass, if any
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!this.knownSuperclasses.containsKey(superclass)) {
				this.knownSuperclasses.put(superclass, configClass);
				// Superclass found, return its annotation metadata and recurse
				return sourceClass.getSuperClass();
			}
		}

		// No superclass -> processing is complete
		return null;
	}

我们从注释中可以得知,解析配置类的主要流程:

  1. 处理 @Component 注解的成员类
  2. 处理 @PropertySource 注解
  3. 处理 @ComponentScans、@ComponentScan 注解
  4. 处理 @Import 注解
  5. 处理 @ImportResource 注解
  6. 处理 @Bean 注解

这里有个很关键的的地方就是,在处理 @ComponentScan 扫描出来的所有类时,会将所有的类都进行一次与 Config 类相同的处理逻辑。换句话说,@ComponentScan 扫描出来的所有类都当作配置类进行处理。

到目前为止,@Configuration 注解的类与 @Component 注解的类的唯一区别就只有他们的 BeanDefinition 中的 CONFIGURATION_CLASS_ATTRIBUTE 属性的值。@Configuration 注解的类中的 CONFIGURATION_CLASS_ATTRIBUTE 属性值为 full(注解中的 proxyBeanMethods 属性需要为 true,默认也为 true),@Compoennt 注解的类中的 CONFIGURATION_CLASS_ATTRIBUTE 属性为 lite。

当我们解析完所有的类时,就会返回到最初执行后置处理器的地方。并且执行后置处理器 ConfigurationClassPostProcessor 中的 postProcessBeanFactory() 方法。

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)) {
        // BeanDefinitionRegistryPostProcessor hook apparently not supported...
        // Simply call processConfigurationClasses lazily at this point then.
        processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
    }

    // 对使用 @Configuration 注解的类进行 cglib 代理
    enhanceConfigurationClasses(beanFactory);
    // 这个后置处理器很重要
    beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

第 15 行就是对所有扫描出来的类,通过 CONFIGURATION_CLASS_ATTRIBUTE 属性来判断是否需要对源类进行代理。但是这里并不会直接实例化代理类,而是替换到 BeanDefinition 中的 beanClass 属性。因为实例化单例时就是通过 beanClass 属性中的类型来进行实例化的,而不是通过源类型。

我们来看一下 enhanceConfigurationClasses()

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {    // 存储需要进行代理增强的 beanDefinition    Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();        // 循环每一个扫描出来的 beanDefinition,用于获取其中的 CONFIGURATION_CLASS_ATTRIBUTE 属性,以此判断其是否需要进行代理。    for (String beanName : beanFactory.getBeanDefinitionNames()) {        BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);        Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);        MethodMetadata methodMetadata = null;        if (beanDef instanceof AnnotatedBeanDefinition) {            methodMetadata = ((AnnotatedBeanDefinition) beanDef).getFactoryMethodMetadata();        }        if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) {            // Configuration class (full or lite) or a configuration-derived @Bean method            // -> resolve bean class at this point...            AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;            if (!abd.hasBeanClass()) {                try {                    abd.resolveBeanClass(this.beanClassLoader);                }                catch (Throwable ex) {                    throw new IllegalStateException(                        "Cannot load configuration class: " + beanDef.getBeanClassName(), ex);                }            }        }        // 判断每一个 beanDefinition 中的 CONFIGURATION_CLASS_ATTRIBUTE 属性是否为 full。        // 如果为 full,则将其加入到需要进行代理增强的集合中,如果为 lite,则什么都不需要做。        if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {            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);        }    }    // 判断是否有需要增强的类,如果没有,则直接返回。    if (configBeanDefs.isEmpty()) {        // nothing to enhance -> return immediately        return;    }	    // 用于对 beanDefinition 进行增强的类。内部通过 cglib 进行增强。    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,这里是得到一个 Class,而不是一个实例化的对象。        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()));            }            // 设置 beanClass 为增强后的 Class,而不是原来的 Class。用于在实例化此对象时,使用增强后的 Class 进行实例化,以达到代理的效果。            beanDef.setBeanClass(enhancedClass);        }    }}

这段代码的主要作用就是,判断之前的每一个 beanDefinition 中的 CONFIGURATION_CLASS_ATTRIBUTE 属性是否为 full。如果为 full,则会对它的 beanClass 设置为 cglib 生成的增强 Class,替换掉原来的 Class。

所以,之前的代码中对每一个扫描出来的类都会设置一个 CONFIGURATION_CLASS_ATTRIBUTE 属性的作用就是在这里用于判断是否需要为其生成一个代理类。为 full,则生成一个代理类;为 lite,则不需要生成。

整个流程的步骤如下:

  1. 解析 Config 类,将其的元信息转换为 BeanDefinition。
  2. 然后检测 Config 类上是否存在 @Configuration 注解,并且该注解中的 proxyBeanMethods 属性为 true。如果以上两点都满足,则会在 BeanDefinition 中设置 CONFIGURATION_CLASS_ATTRIBUTE 属性为 full。
  3. 最后会在增强的步骤中,检测 BeanDefinition 中的 CONFIGURATION_CLASS_ATTRIBUTE 属性是否为 full,如果为 full,则会为其生成一个代理类,并且将 BeanDefinition 中的 beanClass 属性设置为代理类。

代理类中主要就是拦截 Config 类中的方法,如下的代码中展示了代理类的拦截方法:

public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,					MethodProxy cglibMethodProxy) throws Throwable {    ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);    String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);    // Determine whether this bean is a scoped-proxy    if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {        String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);        if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {            beanName = scopedBeanName;        }    }    if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&        factoryContainsBean(beanFactory, beanName)) {        Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);        if (factoryBean instanceof ScopedProxyFactoryBean) {            // Scoped proxy factory beans are a special case and should not be further proxied        }        else {            // It is a candidate FactoryBean - go ahead with enhancement            return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);        }    }    if (isCurrentlyInvokedFactoryMethod(beanMethod)) {        return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);    }	// 获取指定 beanName 的 bean。是通过 getBean() 方法来获取的,也就是会从容器中获取到指定 beanName 的 bean。    return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);}

这段代码中最重要的就是最后一行,它会从容器中获取到指定的 bean。所以,可以得出的结论就是:通过代理类,即使多次执行 @Bean 修饰的方法,它也只会得到容器中的单例对象,而不是直接执行用户的函数。这是为了让 bean 符合它的生命周期而提供的做法。

4、结论

如果使用 @Configuration 注解修饰的类,并且该注解中的 proxyBeanMethods 属性的值为 true,则为这个 bean 创建一个代理类,该代理类会拦截所有被 @Bean 修饰的方法,在拦截的方法逻辑中,会从容器中返回所需要的单例对象。

如果使用 @Component 注解修饰的类,则不会为这个 bean 创建一个代理类。 那么我们就会直接执行用户的方法,所以每次都会返回一个新的对象。

那么,如果我们将 @Configuration 注解中的 proxyBeanMethods 属性的值设置为 false,那么它的行为是否就会跟 @Component 注解一样?

我们可以试验一下,毕竟实践出真知,我们只要将代码修改为如下所示即可:

@Configuration(proxyBeanMethods = false)public class Config {	@Bean	public Hello hello(){		return new Hello();	}}

然后执行主函数,执行的结果如下图所示:

image-20210924102725525

通过结果,我们可以得出结论是:如果将 @Configuration 注解中的 proxyBeanMethods 属性的值设置为 false,那么它的行为是否就会跟 @Component 注解一样。

5、主要知识点

ConfigurationClassPostProcessor 后置处理器

@Configuration 注解

@Component 注解

CONFIGURATION_CLASS_ATTRIBUTE 属性

@Configuration源码中的具体内容是什么?它的作用是什么? 回答: @Configuration注解是Spring框架中的一个注解,用于标记一个类作为配置类。通过@Configuration注解,可以将该类中声明的bean对象纳入到Spring容器的管理中。@Configuration注解可以与@Component注解一起使用,但是它们之间仍然有一些不同之处。 @Configuration注解的作用是告诉Spring容器,这个类是一个配置类,里面可以包含@Bean注解的方法用于创建bean对象。当Spring容器启动时,会解析@Configuration注解,读取其中的@Bean方法,并将这些方法返回的对象注册到容器中。 @Configuration注解还可以与其他注解一起使用,例如@EnableAutoConfiguration和@ComponentScan等,用于进一步配置Spring应用程序。 具体的@Configuration源码分析可以参考引用和引用中的文章,这些文章深入解析了@Configuration注解的原理和实现细节。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Spring中@Configuration源码深度解析(一)](https://blog.csdn.net/qq_35634181/article/details/104062321)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [spring源码:@Configuration源码](https://blog.csdn.net/CPLASF_/article/details/106840449)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值