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实现。如有其他解决方案请不吝赐教谢谢。