沖突分析
如果我們希望將相同名稱的類放入spring中時,如果未指定bean名稱,則會拋出異常:
Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'xxxx' for bean class [xxx] conflicts with existing, non-compatible bean definition of same name and class [xxx]
翻看Spring源碼得知,當我們使用注解創建bean時,spring使用了AnnotationBeanNameGenerator來創建bean的名稱。
if (definition instanceof AnnotatedBeanDefinition) { String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition); if (StringUtils.hasText(beanName)) { // Explicit bean name found. return beanName; } } // Fallback: generate a unique default bean name. return buildDefaultBeanName(definition, registry);
如果我們項目中存在相同名稱的類,而在使用注解時(@Service、@Component)指定了不同的name,則不會拋出異常,否則Spring使用類的名稱作為bean的名稱,則會拋出異常。
但有些特殊場景,如多版本模塊,我們不希望版本號污染類名,而希望以包的形式控制。則此時我們需要替換spring的默認名稱生成器。
- com.v1
- UserController
- com.v2
- UserController
沖突解決
創建全類名的BeanNameGenerator
此處代碼摘自ConfigurationClassPostProcessor.importBeanNameGenerator。
public class AnnotationBeanNameGenerator implements BeanNameGenerator { @Override public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { if (definition instanceof AnnotatedBeanDefinition) { String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition); if (StringUtils.hasText(beanName)) { // Explicit bean name found. return beanName; } } String beanClassName = definition.getBeanClassName(); Assert.state(beanClassName != null, "No bean class name set"); return beanClassName; } }
Spring Boot
SpringBoot提供了在啟動時傳入BeanNameGenerator的方式,可以修改Spring掃描注解時使用的名稱。
@SpringBootApplication @ComponentScan(nameGenerator = AnnotationBeanNameGenerator.class)
Mybatis
而Mybatis並不是由Spring直接掃描的,而是由其本身自主掃描到Mapper而注入到Spring中。關鍵代碼如下:
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); ..... ..... }
mybatis創建了spring提供的ClassPathMapperScanner,其默認使用的扔是AnnotationBeanNameGenerator。而mybatis也提供了修改方式。
@MapperScan(nameGenerator = AnnotationBeanNameGenerator.class)
SpringBoot && Mybatis
完整代碼如下:
@SpringBootApplication @ComponentScan(nameGenerator = Application.SpringBeanNameGenerator.class) @MapperScan(value = "**.mapper", markerInterface = BaseMapper.class, nameGenerator = Application.SpringBeanNameGenerator.class) public class Application { public static class SpringBeanNameGenerator extends AnnotationBeanNameGenerator { @Override protected String buildDefaultBeanName(BeanDefinition definition) { if (definition instanceof AnnotatedBeanDefinition) { String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition); if (StringUtils.hasText(beanName)) { // Explicit bean name found. return beanName; } } return definition.getBeanClassName(); } } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
暴力覆蓋方案
上述方式到是一般情況下足夠了,但如果此時項目中又存在一個類似mybatis的組件,那么你仍需要配置BeanNameGenerator。我們可以暴力覆蓋原本的AnnotationBeanNameGenerator,
建立與其包名相同,類名相同的文件在項目中:

public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String COMPONENT_ANNOTATION_CLASSNAME = "org.springframework.stereotype.Component";
此時無須配置任何配置,默認會走此BeanNameGenerator。
版本控制
作者:BeRicher
鏈接:https://www.jianshu.com/p/74c801fd70f4
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。