使用外部中间件时,如:金蝶、东方通 在不重启整个中间件的情况下,二次部署或多个服务同时部署在一个虚拟机下(JVM) IdTypeEnvironmentPostProcessor.setIdType 会将一个IdType对象put进SystemPropertiesPropertySource,而SystemPropertiesPropertySource在整个JVM中是共用的,导致两处问题:
报错信息:org.springframework.core.convert.ConverterNotFoundException:
No converter found capable of converting from type [com.baomidou.mybatisplus.annotation.IdType] to type [com.baomidou.mybatisplus.annotation.IdType]
问题一:IdTypeEnvironmentPostProcessor.getIdType 中 environment.getProperty(ID_TYPE_KEY, IdType.class) 获取到了上一次部署应用时的 IdType 对象,而上一次的 IdType 对象,和本次部署时 IdType.class 的类加载器不一致,导致报错;
问题二:org.springframework.boot.context.properties.bind.BindConverter.convert 中,delegate.canConvert 返回的都是false,最终:throw (failure != null) ? failure : new ConverterNotFoundException(sourceType, targetType);
原因分析:
首先 ConfigurableEnvironment
ConfigurableEnvironment.getProperty(...) 的查找顺序是分层次的:
1、命令行参数(CommandLinePropertySource,即 --key=value)
2、Java 系统属性(System.getProperties(),对应 SystemPropertiesPropertySource)
3、操作系统环境变量(System.getenv(),对应 SystemEnvironmentPropertySource)
4、application.yml / application.properties(OriginTrackedMapPropertySource)
5、默认属性(DefaultPropertiesPropertySource)
其次:Spring 的属性绑定用到了 ConfigurationPropertySource
ConfigurationPropertySource:
它是 Spring Boot 2.x 以后引入的抽象,表示配置属性的来源。
比如:
.properties / .yml 文件,
系统属性(System.getProperties()),
环境变量(System.getenv()),
甚至 Nacos、Apollo 这样的远程配置中心。
它统一成 ConfigurationPropertySource 接口,Spring Boot 就能用同一套逻辑去读取配置。
和 ConfigurableEnvironment 的关系
ConfigurableEnvironment 内部持有一系列 PropertySource。
Spring Boot 启动时会把这些 PropertySource 适配成 ConfigurationPropertySource,
这样属性绑定器(Binder)就可以从中读取配置值。
也就是说:
environment.getProperty("my.key") 读出来的值,
和 Binder 里 ConfigurationPropertySource 提供的值,
本质上是同一批配置源,只是走的 API 不一样。
导致的问题:org.springframework.boot.context.properties.bind.BindConverter.convert 的参数:Object source, TypeDescriptor sourceType, TypeDescriptor targetType
source 是上一次部署时的 IdType 对象
sourceType 的类加载器 (sourceType.getType().getClassLoader()) 与 targetType 的类加载器 (targetType.getType().getClassLoader())不一致,抛出:ConverterNotFoundException
org.springframework.boot.context.properties.bind.BindConverter.convert:
private Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
ConversionException failure = null;
for (ConversionService delegate : this.delegates) {
try {
if (delegate.canConvert(sourceType, targetType)) {
return delegate.convert(source, sourceType, targetType);
}
}
catch (ConversionException ex) {
if (failure == null && ex instanceof ConversionFailedException) {
failure = ex;
}
}
}
throw (failure != null) ? failure : new ConverterNotFoundException(sourceType, targetType);
}