一、問題描述
最近公司項目打算模塊化,其實一個原因也是爲了能夠整合公司多個業務的代碼,比如一個資源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的。
最後:感謝我司可少大力支持!