公共模块beanName冲突

1前言

 现今开发过程中持久层一般选用mybatis,按微服务风格构建项目时通常会把公共业务模块抽成common-Biz模块。方便管理和开发,防止代码冗余&错误开发。在此场景下同一个查询库表对象Mapper大概率同时出现在Biz模块和业务模块中。而默认的beanName规则是直接以类名(不是全类名)进行创建!这样子导致启动项目时发生ConflictingBeanDefinitionException。

问题显而易见就是beanName冲突,只需要解决beanName冲突问题既可解决Mybatis扫描Mapper报错问题。

2解决方案

  2.1保证只有一个MapperBean

   直接废弃业务模块中的Mapper,仅保留Biz模块中的Mapper。

   优点:避开BeanName冲突问题,保证功能可用

   缺点:其他业务模块持久层逻辑集成到公共Biz模块中,Biz将存在不符合公共业务的逻辑&持久层,导致Biz模块臃肿,并无关公共业务逻辑代码污染其他引用Biz模块的模块。

  2.2通过@Repository标记

   此方式可用,并不会引入无关公共业务的逻辑&持久层

   优点:简单易用,只需一个Repository(value="BizXxxMapper")既可

   缺点:Biz每一个Mapper需要开发手工添加Repository,可能遗漏,增加开发检查点和开发规范要求!当然这个可自定义Mybatis逆向工程模板解决。

  2.3在@MapperScan拓展-BeanNameGenerator

    此方式,无代码侵入,无代码冗余问题。只需要实现BeanNameGenerator接口。

    优点:开发一次,且只针对Mybatis,后续开发无需关心注解&beanName冲突。

    缺点: 暂时没有发现

   2.4拓展在@ComponentScan上定义全局的BeanNameGenerator

       此方式和2.3方案逻辑&原理基本一致,先判断是否冲突,然后根据包名判断是否为Biz模块下的Bean,然后添加特殊的PREFIX保证Biz的beanName不冲突。和2.3不同的是,2.3的BeanNameGenerator只处理mybatis的命名冲突,当前方案定义为全局的BeanNameGenerator理论上Biz所有BeanName和业务模块不冲突。

3实现@MapperScan-BeanNameGenerator

        通过查看myBatis的@MapperScan发现支持自定义BeanName拓展接口:

项目关系:

开始实现BeanName拓展接口:

重新启动项目:

 

 

4实现@ComponentScan上定义全局的BeanNameGenerator

        此方案是为预防后续Biz模块和业务模板出现同名BeanName(例如Service或Component)

创建同名Service:

启动报错:

 开始自定义全局BeanNameGenerator:

package com.example.commbiz.comm.interfaces;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.beans.Introspector;

@Slf4j
public class OverallBeanNameGenerator implements BeanNameGenerator {
    //获取2个默认的BeanNameGenerator 一般不会使用全类名进行name创建 忽略FullyQualifiedAnnotationBeanNameGenerator
    private static final  BeanNameGenerator defBeanNameGenerator = DefaultBeanNameGenerator.INSTANCE;
    private static final  BeanNameGenerator  annBeanNameGenerator = AnnotationBeanNameGenerator.INSTANCE;
    /** 冲突时使用的 prefix*/
    private final static String BEAN_NAME_PREFIX = "biz";
    @Override
    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        String beanName = null ;
        if(definition instanceof AnnotatedBeanDefinition){
            AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition)definition;
            beanName = annBeanNameGenerator.generateBeanName(annotatedBeanDefinition,registry);
        }else{
            beanName = defBeanNameGenerator.generateBeanName(definition,registry);
        }
        // 方式1
//        //默认 AaBbCc 的beanname==>aaBbCc
//        String defBeanName =  buildDefaultBeanName(definition);
//        if(StringUtils.equals(beanName,defBeanName) &&
//                !beanName.startsWith(BEAN_NAME_PREFIX) &&
//                definition.getBeanClassName().startsWith("com.example.commbiz")){
//            log.error("biz的{}名字冲突",definition.getBeanClassName());
//            log.error("对方的名字:{}",registry.getBeanDefinition(beanName).getSource());
//                        //原始是 xxxMapper--> XxxMapper
//            beanName =  beanName.substring(0,1).toLowerCase().concat(beanName.substring(1));
//            //--> bizXxxMapper 这样子就不会冲突了
//            beanName = BEAN_NAME_PREFIX.concat(beanName);
//        }
        //方式2   -- 方式1和方式2都可以 
        BeanDefinition existsInf ;
        try {
            existsInf =  registry.getBeanDefinition(beanName);
        }catch (NoSuchBeanDefinitionException e){
            //说明没有被注册
            return beanName;
        }catch (Exception e){
            log.error("获取Bean信息异常-err{}",e);
            throw new RuntimeException("获取Bean信息异常");
        }
        if(!StringUtils.equals(existsInf.getBeanClassName(),definition.getBeanClassName())&&
                !beanName.startsWith(BEAN_NAME_PREFIX) &&
                definition.getBeanClassName().startsWith("com.example.commbiz")){
              log.error("biz的{}名字冲突",definition.getBeanClassName());
            log.error("对方的名字:{}",existsInf.getBeanClassName());
                        //原始是 xxxMapper--> XxxMapper
            beanName =  beanName.substring(0,1).toLowerCase().concat(beanName.substring(1));
            //--> bizXxxMapper 这样子就不会冲突了
            beanName = BEAN_NAME_PREFIX.concat(beanName);
        }

        return beanName;
    }
    /**
     * 默认的创建BeanName  see AnnotationBeanNameGenerator
     * @param definition spring的bena信息
     * @return 返回名字
     */
    private 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);
    }
}

 注册名称处理器:

        

启动项目:

5总结和说明

 现使用我同事推荐的2.3方式,没有使用@Repository方式,项目正常使用。主要原理就是BeanNameGenerator实现。如有其他解决方案请不吝赐教谢谢。

       

  

  

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值