一个Mybatisplus组件扫描不当引起的bug:弄巧成拙,认真的锅,自我怀疑

在我们系统基建层的业务组件包 sby-biz-component 中,最初,我写了两个业务组件,一个是 通道错误码组件,一个是 审核流水组件。

这两个业务组件都要依赖Mybatisplus来操作数据。

复制代码
com.sby.bizcomponent
 ├── auditflow
 │ └── AuditMapper.java
 │ └── AuditService.java
 │ └── AuditAutoConfiguration.java
 │ └── MybatisPlusMapperScanConfig.java
 │ └── package-info.java
 └── payerrorcode
   └── PayErrorCodeMapper.java
   └── PayErrorCodeService.java
   └── PayErrorCodeAutoConfiguration.java
   └── MybatisPlusMapperScanConfig.java
   └── package-info.java

其中的 AuditAutoConfiguration.javaPayErrorCodeAutoConfiguration.java实现组件的自动装配。以 payerrorcode/PayErrorCodeAutoConfiguration.java 为例,代码如下。

java 复制代码
@Configuration
@ComponentScan(basePackageClasses = {PayErrorCodeAutoConfiguration.class})
@ConditionalOnProperty(name = "bizcomponent.payerrorcode.enable",havingValue = "true")
public class PayErrorCodeAutoConfiguration {
}

这里要说的是两个 MybatisPlusMapperScanConfig.java,两者的职责都是用来指定 Mybatisplus的 @MapperScan 注解,来定义Mapper接口的扫描路径。

java 复制代码
// payerrorcode/MybatisPlusMapperScanConfig.java
@Configuration
@MapperScan(value = {"com.serviceshare.bizcomponent.payerrorcode"})
public class MybatisPlusMapperScanConfig {
}

// auditflow/MybatisPlusMapperScanConfig.java
@Configuration
@MapperScan(value = {"com.serviceshare.bizcomponent.auditflow"})
public class MybatisPlusMapperScanConfig {
}

显然,这两个类重复度极高。对于此类代码,我的一贯风格是,盘它!我一向比较认真地致力于构建易于维护的应用系统。

我的方案是,我在 sby-biz-component 里新增了一个名为"config"的package。

复制代码
com.sby.bizcomponent
 ├── auditflow
 │ └── AuditMapper.java
 │ └── AuditService.java
 │ └── AuditAutoConfiguration.java
 │ └── package-info.java
 ├── config
 │ └── MybatisPlusMapperScanConfig.java
 └── payerrorcode
   └── PayErrorCodeMapper.java
   └── PayErrorCodeService.java
   └── PayErrorCodeAutoConfiguration.java
   └── package-info.java

改动后的 config/MybatisPlusMapperScanConfig.java,代码如下:

java 复制代码
@Configuration
@MapperScan(value = {"com.serviceshare.bizcomponent.*.**"})
public class MybatisPlusMapperScanConfig {
}

同时,修改两个 AutoConfiguration类,增加对MybatisPlusMapperScanConfig的扫描。例如 payerrorcode/PayErrorCodeAutoConfiguration.java:

java 复制代码
@Configuration
@ComponentScan(basePackageClasses = {PayErrorCodeAutoConfiguration.class,MybatisPlusMapperScanConfig.class})
@ConditionalOnProperty(name = "bizcomponent.payerrorcode.enable",havingValue = "true")
public class PayErrorCodeAutoConfiguration {
}

调整后的代码投产后,依赖这个 sby-biz-component 组件的应用服务,没有问题。

不过,好景持续了一年之久。

最近,我在 sby-biz-component 中,新开发了记账的业务组件。

然后,问题出现了------------我们的一个依赖了 sby-biz-component 的中台 zhongtai-base 服务,本地环境启动报错:org.springframework.boot.SpringApplication:837 - Application run failed:org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'accountMapper' for bean class [com.serviceshare.bizcomponent.accounting.service.AccountMapper] conflicts with existing, non-compatible bean definition of same name and class [org.mybatis.spring.mapper.MapperFactoryBean]

这个错误是说,sby-biz-component 里 AccountMapper 与当前 zhongtai-base 服务里的 AccountMapper 存在bean冲突。

错误中提到的 sby-biz-component 里的 AccountMapper,在我新开发的记账组件package中。

这令我和一个开发伙伴摸不着头脑。

经过进一步分析和思考后,发现症结所在。

这牵涉到我去年后来在sby-biz-component 中开发的 datamig 数据迁移组件。

与其他组件所不同的是,这个 datamig 组件是自动生效的,不需要@ConditionalOnProperty配置。见下方 datamig.TimeBasedDataMigAutoConfiguration 代码。

java 复制代码
// datamig/TimeBasedDataMigAutoConfiguration.java

@Configuration
@ComponentScan(basePackageClasses = {TimeBasedDataMigAutoConfiguration.class, MybatisPlusMapperScanConfig.class})
//@ConditionalOnProperty(name = "bizcomponent.datamig.enable",havingValue = "true")//**默认开启数据迁移组件
public class TimeBasedDataMigAutoConfiguration {
}

问题就在于 datamig 的这个自动配置class,它扫描了 MybatisPlusMapperScanConfig, 这就导致 sby-biz-component 中所有的 Mapper接口类都作为spring容器的bean生效了。那么,这自然就包括新开发的记账组件package中的 AccountMapper。而 zhongtai-base 项目中自身有 AccountMapper, 就会出现 bean冲突异常,导致 zhongtai-base 服务无法启动。

这引起了我的自我反思。本意是为了降低重复,提升代码可维护性。结果却"弄巧成拙"了。生活中,对于我来说,也存在类似的情况。例如,刷完碗,为了控水,我会把一个碗倒扣在另外两个碗上,然后,自己在忙活中,不小心把碗碰到了地上,结果就碎碎平安了~

回归正题。我很快想到了fix这个问题的方案------------

删掉 config.MybatisPlusMapperScanConfig.java,并由每个组件自己的自动配置类里,来指定Mybatisplus的 @MapperScan。

java 复制代码
// payerrorcode/PayErrorCodeAutoConfiguration.java
@Configuration
@ComponentScan(basePackageClasses = {PayErrorCodeAutoConfiguration.class})
@MapperScan(basePackageClasses = {PayErrorCodeAutoConfiguration.class})
@ConditionalOnProperty(name = "bizcomponent.payerrorcode.enable",havingValue = "true")
public class PayErrorCodeAutoConfiguration {
}

// auditflow/AuditAutoConfiguration.java
@Configuration
@ComponentScan(basePackageClasses ={ AuditAutoConfiguration.class})
@MapperScan(basePackageClasses = {AuditAutoConfiguration.class})
@ConditionalOnProperty(name = "bizcomponent.audit.enable",havingValue = "true")
public class AuditAutoConfiguration {
}

// datamig/TimeBasedDataMigAutoConfiguration.java
@Configuration
@ComponentScan(basePackageClasses = {TimeBasedDataMigAutoConfiguration.class})
@MapperScan(basePackageClasses = {TimeBasedDataMigAutoConfiguration.class})
//@ConditionalOnProperty(name = "bizcomponent.datamig.enable",havingValue = "true")//默认开启数据迁移组件
public class TimeBasedDataMigAutoConfiguration {
}

// accounting/AccountingAutoConfiguration.java
@Configuration
@ComponentScan(basePackageClasses = {AccountingAutoConfiguration.class, DDLCreator.class})
@MapperScan(basePackageClasses = {AccountingAutoConfiguration.class})
@ConditionalOnProperty(name = "bizcomponent.accounting.enable",havingValue = "true")
public class AccountingAutoConfiguration {
}

显然,这个方案既没有增加重复代码。如果我当初在解决重复代码时,使用这个方案,就不会遇到本文提到的这个问题了。

话说回来,在系统开发中,我常常为了追求一些自认为美好的东西,会认真地进行一些努力和实践。例如本文说的消除代码重复,再例如完善代码的javadoc,再例如优化异步调用方式,再例如更正数据类型、重命名变量/参数名,再例如使用enum提高可读性。

有时呢,后来证明,我的认真,反而让我犯了错。一些努力和实践带来了一些负面影响,然后我要为我的"认真"付出代价。当然,我始终认为这些是应该做的,应该致力于做。当然,有时候,也难免自我怀疑、自我否定。人大概就是在不断地自我摸索、自我怀疑、自我否定中成长吧!

相关推荐
BD_Marathon5 小时前
【Flink】部署模式
java·数据库·flink
鼠鼠我捏,要死了捏8 小时前
深入解析Java NIO多路复用原理与性能优化实践指南
java·性能优化·nio
CodeCraft Studio8 小时前
3D文档控件Aspose.3D实用教程:使用 C# 构建 OBJ 到 U3D 转换器
开发语言·3d·c#·3d渲染·aspose·3d文件格式转换·3d sdk
ningqw8 小时前
SpringBoot 常用跨域处理方案
java·后端·springboot
superlls8 小时前
(Redis)主从哨兵模式与集群模式
java·开发语言·redis
chenglin0169 小时前
C#_gRPC
开发语言·c#
骑驴看星星a10 小时前
数学建模--Topsis(Python)
开发语言·python·学习·数学建模
叫我阿柒啊10 小时前
Java全栈工程师面试实战:从基础到微服务的深度解析
java·redis·微服务·node.js·vue3·全栈开发·电商平台
hqxstudying11 小时前
mybatis过渡到mybatis-plus过程中需要注意的地方
java·tomcat·mybatis
tju新生代魔迷11 小时前
C语言宏的实现作业
c语言·开发语言