【实战】Spring生成beanName冲突的解决之道:附源码分析

一、问题描述

最近公司项目打算模块化,其实一个原因也是为了能够整合公司多个业务的代码,比如一个资源xxx,两个业务中都有对这个资源的管理,虽然是一个资源,但是是完全不同的定义、完全不同的表、不同的处理逻辑。所以打算把类名弄成一样的,但是包名不一样。

但这样会产生问题,按照Spring的默认beanName生成规则,会直接将类名首字母小写作为bean的名字,如两个模块里的这个资源都叫xxxJob,这样在Spring启动的时候就会报错。错误如下conflicts with existing, non-compatible bean definition of same name and class [xxxxJob],意思就是说两个bean同名了,这样启动就报错了。

二、源码分析

解决方法我们可以手动修改bean名称的生成策略,这里直接就是用实现类的全限定名称(com.abc.job.xxxJob)作为bean的名称。

翻翻源码,我们先来看默认生成规则:

1.判断bean上的注解中是否指定了名字,如果指定直接返回,否则去构造bean的名称。

public class AnnotationBeanNameGenerator implements BeanNameGenerator {

    @Override
	public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
		if (definition instanceof AnnotatedBeanDefinition) {
                //看bean上的注解中是否指定了bean的名字
			String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
			if (StringUtils.hasText(beanName)) {
				// 如果指定了名字,直接返回
				return beanName;
			}
		}
		// 没有指定名字,去构造一个bean的名称
		return buildDefaultBeanName(definition, registry);
	}

    。。。
}

2.bean构造逻辑

    protected String buildDefaultBeanName(BeanDefinition definition) {
		String beanClassName = definition.getBeanClassName();
		Assert.state(beanClassName != null, "No bean class name set");
		String shortClassName = ClassUtils.getShortName(beanClassName);
		return Introspector.decapitalize(shortClassName);
	}

这里definition.getBeanClassName()是获取全限定名称的,ClassUtils.getShortName是获取类名的,下面的Introspector.decapitalize实际上就是把首字母变小写的。 

    public static String getShortName(String className) {
		Assert.hasLength(className, "Class name must not be empty");
		int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
		int nameEndIndex = className.indexOf(CGLIB_CLASS_SEPARATOR);
		if (nameEndIndex == -1) {
			nameEndIndex = className.length();
		}
		String shortName = className.substring(lastDotIndex + 1, nameEndIndex);
		shortName = shortName.replace(INNER_CLASS_SEPARATOR, PACKAGE_SEPARATOR);
		return shortName;
	}

核心是getShortName方法,其实就是个字符串截取,将包和后缀都去掉,生成一个短bean的名称。

三、解决方案

到这里我们应该看得很清楚了,不同包名但相同类名的类,Spring的默认生成beanName规则生成出来的名称是一样的,难怪Spring在启动会报错了,那我们要做的就是修改beanName的生成规则,做法如下:

我们这里要设置为全限定名称,我们可以新写一个类,假设叫MyBeanNameGenerator

,然后继承AnnotationBeanNameGenerator之后重写buildDefaultBeanName方法,返回definition.getBeanClassName(),这样我们这个生成策略就写好了。代码如下:

public class BeanNameGenerator extends AnnotationBeanNameGenerator{
	@Override
	public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        //这里也也可以加自己的逻辑,比如只有特定包下面的bean需要使用这种生成规则,其他包下面的bean还采用默认生成规则,各位看官自己去发挥即可
		return  definition.getBeanClassName();

}

接下来对@ComponentScan注解添加一个nameGenerator属性就好了,指定为我们自定义的bean生成策略。

@ComponentScan(basePackages = "com.xxx.job.**",nameGenerator = MyBeanNameGenerator.class)

这样就完美了,这时候所有bean的默认名称就是我们设置的全限定名了,不过如果我们在类上显式的写了bean的id的话,还是会用我们自定义的bean的name的。

最后:感谢我司可少大力支持!

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
SpringBean生命周期是由Spring容器负责管理的,主要包括以下阶段: 1. 实例化:Spring容器根据配置信息或注解,创建Bean的实例。这一过程一般通过Java反射机制实现。 2. 属性注入:Spring容器将依赖注入到Bean的属性中,可以通过构造函数、Setter方法或者字段注入来实现。 3. 初始化:在Bean实例创建完成后,Spring容器会调用一系列的初始化回调方法。常见的初始化回调方法有`@PostConstruct`注解、`InitializingBean`接口的`afterPropertiesSet()`方法以及自定义的初始化方法。 4. 使用:Bean实例被使用,执行业务逻辑。 5. 销毁:当Bean不再被使用时,Spring容器会调用一系列的销毁回调方法。常见的销毁回调方法有`@PreDestroy`注解、`DisposableBean`接口的`destroy()`方法以及自定义的销毁方法。 下面是一个简化的示例代码,展示了Bean生命周期的常用方法: ```java public class MyBean implements InitializingBean, DisposableBean { private String name; public MyBean() { System.out.println("Bean实例化"); } public void setName(String name) { this.name = name; } @Override public void afterPropertiesSet() throws Exception { System.out.println("初始化回调方法"); } public void doSomething() { System.out.println("执行业务逻辑:" + name); } @Override public void destroy() throws Exception { System.out.println("销毁回调方法"); } } ``` 此外,Spring还提供了更细粒度的扩展点,如BeanPostProcessor接口和BeanFactoryPostProcessor接口,可以在Bean的实例化和初始化过程中进行自定义处理。 以上是一个简单的概述,实际的源码分析涉及到Spring框架的很多细节,包括BeanDefinition、BeanFactory、ApplicationContext等。你可以参考Spring源码来深入了解Bean生命周期的实现细节。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十步杀一人_千里不留行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值