天天看點

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,

建立與其包名相同,類名相同的檔案在項目中:

Spring Bean名稱重複 ConflictingBeanDefinitionException 解決

image.png

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;
    }
}      

此時無須配置任何配置,預設會走此BeanNameGenerator。

版本控制

​​SpringBoot 2.x版本控制​​

​​Faster-Framework​​還提供了更多有趣簡便的元件,感興趣的朋友可以點選檢視。