Spring Bean名稱重復ConflictingBeanDefinitionException解決


沖突分析

如果我們希望將相同名稱的類放入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,
建立與其包名相同,類名相同的文件在項目中:

 

 

 

 

 

image.png
public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String COMPONENT_ANNOTATION_CLASSNAME = "org.springframework.stereotype.Component"; 

此時無須配置任何配置,默認會走此BeanNameGenerator。

版本控制



作者:BeRicher
鏈接:https://www.jianshu.com/p/74c801fd70f4
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM