Spring 注解和 XML 配置文件重复定义 Bean,会怎样?

作者:明明如月学长, CSDN 博客专家,蚂蚁集团高级 Java 工程师,《性能优化方法论》作者、《解锁大厂思维:剖析《阿里巴巴Java开发手册》》、《再学经典:《EffectiveJava》独家解析》专栏作者。

热门文章推荐

一、背景

今天一个偶然的机会,发现某个同事在使用 Spring 的时候,有一个 Bean 在类上既加上了 @Service 注解,又在 Spring 的 XML 配置文件中也加了 的定义。
那么,如果两处都进行了配置,如果两处配置不一致会怎样?
我们今天简单分析下。

二、场景复现

2.1 直接复现

2.1.1 复现

添加 Spring 依赖:

   <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.23</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.23</version>
        </dependency>

模拟 Bean 的定义:

package org.example.third.spring;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Data
@Component("student")
public class Student {
    
    

    @Value("李四")
    private String name;

    @Value("18")
    private Integer age;

}

Spring 的 XML 配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example.third.spring"/>

    <bean id="student" class="org.example.third.spring.Student">
        <property name="name" value="张三"/>
        <property name="age" value="22"/>
    </bean>
</beans>

加载执行:

package org.example.third.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class InjectValueXmlApplication {
    
    

    public static void main(String[] args) throws Exception {
    
    
        ApplicationContext ctx = new ClassPathXmlApplicationContext("inject-value.xml");

        Student student = ctx.getBean(Student.class);
        System.out.println("student : " + student);
    }
}

打印的结果:

student : Student(name=张三, age=22)

通过观察可以看到,同时采用 @Component 注解和 xml 定义 Bean 时, xml 优先级更高。

2.1.2 分析

通过调试我们发现,解析 Bean 定义时,优先执行执行 component-scan 扫描注解:
image.png

然后再执行 176 行处理 xml 中 bean 定义
image.png

上述两个入口都会走到这里: DefaultListableBeanFactory#registerBeanDefinition

	@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {
    
    

		Assert.hasText(beanName, "Bean name must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");

		if (beanDefinition instanceof AbstractBeanDefinition) {
    
    
			try {
    
    
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
    
    
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Validation of bean definition failed", ex);
			}
		}

		BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
		if (existingDefinition != null) {
    
    
			if (!isAllowBeanDefinitionOverriding()) {
    
    
				throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
			}
			else if (existingDefinition.getRole() < beanDefinition.getRole()) {
    
    
				// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
				if (logger.isInfoEnabled()) {
    
    
					logger.info("Overriding user-defined bean definition for bean '" + beanName +
							"' with a framework-generated bean definition: replacing [" +
							existingDefinition + "] with [" + beanDefinition + "]");
				}
			}
			else if (!beanDefinition.equals(existingDefinition)) {
    
    
				if (logger.isDebugEnabled()) {
    
    
					logger.debug("Overriding bean definition for bean '" + beanName +
							"' with a different definition: replacing [" + existingDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			else {
    
    
				if (logger.isTraceEnabled()) {
    
    
					logger.trace("Overriding bean definition for bean '" + beanName +
							"' with an equivalent definition: replacing [" + existingDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		else {
    
    
			if (hasBeanCreationStarted()) {
    
    
				// Cannot modify startup-time collection elements anymore (for stable iteration)
				synchronized (this.beanDefinitionMap) {
    
    
					this.beanDefinitionMap.put(beanName, beanDefinition);
					List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
					updatedDefinitions.add(beanName);
					this.beanDefinitionNames = updatedDefinitions;
					removeManualSingletonName(beanName);
				}
			}
			else {
    
    
				// Still in startup registration phase
				this.beanDefinitionMap.put(beanName, beanDefinition);
				this.beanDefinitionNames.add(beanName);
				removeManualSingletonName(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}

		if (existingDefinition != null || containsSingleton(beanName)) {
    
    
			resetBeanDefinition(beanName);
		}
		else if (isConfigurationFrozen()) {
    
    
			clearByTypeCache();
		}
	}

通过注解解析时,通过类文件中读取 Bean 的定义:
image.png

BeanDefinition 被放在 beanDefinitionMap , bean 名称被放在 beanDefinitionNames中。
image.png

然后从 xml 中加载重名的 bean 时,从xml 中读取 Bean 的定义。由于注解中已经定义,这里会走到1005 行:

image.png

org.springframework.beans.factory.support.DefaultListableBeanFactory默认允许 BeanDefinition 重写。

	/**
	 * Return whether it should be allowed to override bean definitions by registering
	 * a different definition with the same name, automatically replacing the former.
	 * @since 4.1.2
	 */
	public boolean isAllowBeanDefinitionOverriding() {
    
    
		return this.allowBeanDefinitionOverriding;
	}


	/** Whether to allow re-registration of a different definition with the same name. */
	private boolean allowBeanDefinitionOverriding = true;

因此, XML 中的 Bean 定义覆盖了注解中的配置。

2.2 在 xml 中重复定义

2.2.1 模拟

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example.third.spring"/>

    <bean id="student" class="org.example.third.spring.Student">
        <property name="name" value="张三"/>
        <property name="age" value="22"/>
    </bean>


    <bean id="student" class="org.example.third.spring.Student">
        <property name="name" value="李四"/>
        <property name="age" value="18"/>
    </bean>
</beans>
Exception in thread "main" org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Bean name 'student' is already used in this <beans> element
Offending resource: class path resource [inject-value.xml]

at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:72)
at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:119)
at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:111)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.error(BeanDefinitionParserDelegate.java:281)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.checkNameUniqueness(BeanDefinitionParserDelegate.java:488)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseBeanDefinitionElement(BeanDefinitionParserDelegate.java:434)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseBeanDefinitionElement(BeanDefinitionParserDelegate.java:405)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.processBeanDefinition(DefaultBeanDefinitionDocumentReader.java:306)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseDefaultElement(DefaultBeanDefinitionDocumentReader.java:197)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:176)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:149)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:96)

关键信息: BeanDefinitionParsingException: Configuration problem: Bean name 'student' is already used in this <beans> element
Offending resource: class path resource [inject-value.xml]

2.2.2 分析

Spring 的 Bean 加载离不开AbstractApplicationContext#refresh()

@Override
	public void refresh() throws BeansException, IllegalStateException {
    
    
		synchronized (this.startupShutdownMonitor) {
    
    
			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

			// 准备刷新此上下文。
			prepareRefresh();

			// 告诉子类刷新内部bean工厂。
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// 为在此上下文中使用的bean工厂做准备。
			prepareBeanFactory(beanFactory);

			try {
    
    
				// 允许在上下文子类中后处理bean工厂。
				postProcessBeanFactory(beanFactory);

				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
				// 调用在上下文中注册为bean的工厂处理器。
				invokeBeanFactoryPostProcessors(beanFactory);

				// 注册拦截bean创建的bean处理器。
				registerBeanPostProcessors(beanFactory);
				beanPostProcess.end();

				// 为此上下文初始化消息源。
				initMessageSource();

				// 为此上下文初始化事件多路广播器。
				initApplicationEventMulticaster();

				// 在特定上下文子类中初始化其他特殊bean。
				onRefresh();

				// 检查监听器bean并注册它们。
				registerListeners();

				// 实例化所有剩余的(非延迟初始化)单例。
				finishBeanFactoryInitialization(beanFactory);

				// 最后一步:发布相应的事件。
				finishRefresh();
			}

			catch (BeansException ex) {
    
    
				if (logger.isWarnEnabled()) {
    
    
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// 销毁已创建的单例以避免悬空资源。
				destroyBeans();

				// 重置“活动”标志。
				cancelRefresh(ex);

				// 将异常传播给调用者。
				throw ex;
			}

			finally {
    
    
				// 重置Spring核心中的常见内省缓存,因为我们可能不再需要单例bean的元数据...
				resetCommonCaches();
				contextRefresh.end();
			}
		}
	}

这个问题涉及到关键方法: AbstractRefreshableApplicationContext#refreshBeanFactory

@Override
	protected final void refreshBeanFactory() throws BeansException {
    
    
		if (hasBeanFactory()) {
    
    
			destroyBeans();
			closeBeanFactory();
		}
		try {
    
    
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
            
            // 加载 bean 定义
			loadBeanDefinitions(beanFactory);
			this.beanFactory = beanFactory;
		}
		catch (IOException ex) {
    
    
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

再往底层:AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String)

@Override
	public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
    
    
		return loadBeanDefinitions(location, null);
	}

再往底层: BeanDefinitionParserDelegate#checkNameUniqueness

	/**
	 * Validate that the specified bean name and aliases have not been used already
	 * within the current level of beans element nesting.
	 */
	protected void checkNameUniqueness(String beanName, List<String> aliases, Element beanElement) {
    
    
		String foundName = null;

		if (StringUtils.hasText(beanName) && this.usedNames.contains(beanName)) {
    
    
			foundName = beanName;
		}
		if (foundName == null) {
    
    
			foundName = CollectionUtils.findFirstMatch(this.usedNames, aliases);
		}
		if (foundName != null) {
    
    
			error("Bean name '" + foundName + "' is already used in this <beans> element", beanElement);
		}

		this.usedNames.add(beanName);
		this.usedNames.addAll(aliases);
	}

第一次会将 beanName 放在 usedNames 中。
image.png

读取第一个 bean 的名称时, usedNames 集合里面已经有了,就会报这个错误
image.png

通过阅读源码和调试,我们可以发现在 xml 中重复定义会有 bean 名称的重复检查。

三、启示

3.1 注解和 XML 哪种更好?

Spring 使用注解和使用 xml 的方式定义 bean 都有各自的优缺点,没有绝对的好坏,具体要根据实际情况和需求来选择。
适合使用注解的情况:

简化配置:使用注解可以减少XML配置文件的冗长,使代码更加简洁易读。

代码可读性:使用注解可以更加清晰地表达代码的意图,使代码更加易于理解。

依赖注入:注解可以很方便地进行依赖注入,省去了手动配置的麻烦。

自动装配:使用注解可以轻松实现自动装配,提高开发效率。

适合使用XML配置的情况:

统一管理:XML配置文件可以集中管理所有的配置信息,包括数据库连接、事务管理等。

灵活性:XML配置文件可以根据需要进行修改,而不需要修改代码。

依赖关系:XML配置文件可以清晰地表达Bean之间的依赖关系,使代码更加易于维护。

兼容性:XML配置文件具有很好的兼容性,可以在不同的环境中使用。

一般来说,注解方式更简洁、方便、灵活,但也可能造成代码和配置的耦合,而 xml 方式更清晰、规范、可扩展,但也可能造成配置文件的冗长和复杂。

3.2 如何选择

一般来说,如果需要使用一些第三方的库或者类,或者需要配置一些通用的或者复杂的 bean,可以使用 xml 配置,这样可以更好地管理和扩展。
image.png
如果需要使用自己开发的类或者简单的 bean,可以使用注解配置,这样可以更简洁和方便。
如果需要更好的类型安全和开发效率,也可以考虑使用注解;如果需要更好的灵活性和可读性,也可以考虑使用 xml。
最终还是要根据具体的项目需求和团队开发习惯来选择合适的方式。

3.3 注意事项

注解和 xml 的方式定义 bean 也可以同时使用,但要注意避免命名冲突的问题。如果出现两个相同名称的实例,Spring 会覆盖其中一个,xml 优先级高于注解;xml 中同时配置两个相同 id 的 bean,直接校验不通过报错。

四、总结

大家在日常开发中,尤其是工作一两年的同学,写代码一定不要止步于模仿,而要真正搞清楚为什么要这么做,避免一些“粗心” 带来不必要的问题。
大家不要止步于写出能跑的代码,而是要写出“对”的代码。


创作不易,如果本文对你有帮助,欢迎点赞、收藏加关注,你的支持和鼓励,是我创作的最大动力。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/w605283073/article/details/129824809