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


    @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;
            }
        }
        return definition.getBeanClassName();
    }

    /**
     * Derive a bean name from one of the annotations on the class.
     *
     * @param annotatedDef the annotation-aware bean definition
     * @return the bean name, or {@code null} if none is found
     */
    protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotatedDef) {
        AnnotationMetadata amd = annotatedDef.getMetadata();
        Set<String> types = amd.getAnnotationTypes();
        String beanName = null;
        for (String type : types) {
            AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(amd, type);
            if (isStereotypeWithNameValue(type, amd.getMetaAnnotationTypes(type), attributes)) {
                Object value = attributes.get("value");
                if (value instanceof String) {
                    String strVal = (String) value;
                    if (StringUtils.hasLength(strVal)) {
                        if (beanName != null && !strVal.equals(beanName)) {
                            throw new IllegalStateException("Stereotype annotations suggest inconsistent " +
                                    "component names: '" + beanName + "' versus '" + strVal + "'");
                        }
                        beanName = strVal;
                    }
                }
            }
        }
        return beanName;
    }

    /**
     * Check whether the given annotation is a stereotype that is allowed
     * to suggest a component name through its annotation {@code value()}.
     *
     * @param annotationType      the name of the annotation class to check
     * @param metaAnnotationTypes the names of meta-annotations on the given annotation
     * @param attributes          the map of attributes for the given annotation
     * @return whether the annotation qualifies as a stereotype with component name
     */
    protected boolean isStereotypeWithNameValue(String annotationType,
                                                Set<String> metaAnnotationTypes, Map<String, Object> attributes) {

        boolean isStereotype = annotationType.equals(COMPONENT_ANNOTATION_CLASSNAME) ||
                (metaAnnotationTypes != null && metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME)) ||
                annotationType.equals("javax.annotation.ManagedBean") ||
                annotationType.equals("javax.inject.Named");

        return (isStereotype && attributes != null && attributes.containsKey("value"));
    }

}

此时无须配置任何配置,默认会走此BeanNameGenerator。

版本控制

SpringBoot 2.x版本控制

快速开发框架
高质量图片压缩工具

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1、谈谈你对Struts的理解。 答: 1.struts是一个按MVC模式设计的Web层框架,其实它就是一个大大的...
    慕容小伟阅读 7,858评论 0 13
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 11,125评论 0 4
  • 1.StringBuffer与String的区别 StringBuffer是线程安全的,每次操作字符串,Strin...
    zdd5457阅读 4,613评论 0 5
  • 窗前树影斑驳 叶子婆娑风也吹不过 阳光只有无可奈何 洒不全叶的背面脉络 不必为我难过 背影我看的足够多 枕头上的眼...
    天赋还没用到阅读 2,939评论 0 2
  • 今天是我来到天音教育的第一天。当我怀着激动的心情走进崭新的教室,我按耐不住内心的激动开始东张西望起来。看着身旁面生...
    暧树上的果实阅读 1,898评论 0 1