【實戰】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的。

最後:感謝我司可少大力支持!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章