-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
devtools
最早在 Spring Boot 中使用 Devtools 时,使用通用 Mapper 会报错: can't cast x.y.Z to x.y.Z
,
包名和类名完全一样,为什么会出现这个错误呢?
最早有人通过 issue 提出了一个解决方案,就是增加下面这个配置(spring-devtools.properties):
restart.include.mapper=/mapper-[\\w-\\.]+jar
在一段时间内,这个配置解决了前面遇到的问题,但是 4.0 版本发布后,又发现有人遇到了类似问题。
后来发现 core
下面并没有 spring-devtools.properties 配置文件,我还以为缺了,还在 4.0.2 中提了这个,
实际上这个文件一直都没少,在 spring
子模块中一直都有这个文件,而且放在 spring
子模块也是最合适的位置。
为什么一直都有这个配置,还会出错呢?
实际上在后来发生的下面两个症状很可能都是 Devtools 导致的:
Error invoking SqlProvider method (tk.mybatis.mapper.provider.SpecialProvider.dynamicSQL)
java.lang.NoSuchMethodException: tk.mybatis.mapper.provider.SpecialProvider.<init>()
在 faq 中专门介绍了这两个错误的解决办法,但是一直没有完全处理所有的情况。
并且由于 Devtools 导致通用 Mapper 无法正常启动,因此也很难出现 can't cast x.y.Z to x.y.Z
。
首先 Devtools 是应用于开发时的,也就是在 IDE 中运行时,当使用 jar 方式运行时,就会自动禁用 Devtools 工具。
在 IDE 中运行时,Devtools 会通过独立的类加载器来加载会发生变化的资源,通常是 IDE 各个模块的 classes 目录。 对于通过依赖导入的 jar 包是不会自动更新的。
这两部分内容分别由 RestartClassLoader 和 AppClassLoader 加载的,并且前者的 parent 是后者, 也就是说 AppClassLoader 加载的类可以在 RestartClassLoader 加载的类中使用, 但是 RestartClassLoader 加载的类不能在 AppClassLoader 中使用。
当由不同的类加载器加载 x.y.Z 类时,他们算不同的类,无法互相转换。
由于我自始至终没有复现出这个问题,所以这里猜测下原因:
有可能是 Spring Boot 同时加载了 Jar 中的 x.y.Z,还加在了 IDE 中 classes 中的 x.y.Z。 当 Jvm 中处理类型时,classes 中 x.y.Z 产生的实例,在找类型时优先从父类加载器查找,因此产生了这个问题。
如果有人能够复现这个问题,希望可以给我发一份,或者谁能验证一下这个问题。
没有增加时,因为 IDE 运行时,通用 Mapper 的 mapper-x.x.x.jar 会被 AppClassLoader 加载,
因此会出现 can't cast x.y.Z to x.y.Z
。
当增加上面的配置后,mapper-x.x.x.jar 会被 RestartClassLoader 加载,此时他们就一致了, 表面上解决了这个问题。
就是因为这个配置导致的。
前面使用时,有可能你所有的 XXMapper 都在当前的 IDE 中,因此这些 XXMapper 都会使用 RestartClassLoader 加载。
但是如果你依赖的项目(包含 XXMapper)不在当前的 IDE 中,那么该 XXMapper 就会使用 AppClassLoader 加载。
因此就会出现 AppClassLoader 加载的 XXMapper 获取 RestartClassLoader 加载的 @RegisterMapper
注解时,
就会找不到,因此该 XXMapper 就无法初始化,调用 XXMapper 的方法时就会报下面的错:
Error invoking SqlProvider method (tk.mybatis.mapper.provider.SpecialProvider.dynamicSQL)
同样 XXMapper 对应 XX 使用 Example(XX.class)
时,就报错找不到 XX
对应的表名。
最合理的情况就是没有 spring-devtools.properties 配置文件,就算有,也应该是下面的配置:
restart.exclude.mapper=/mapper-[\\w-\\.]+jar
不能让 RestartClassLoader 加载 mapper-x.x.x.jar,这才是应该有的样子。
对于遇到 can't cast x.y.Z to x.y.Z
,这应该是依赖问题导致的,也有可能是低版本 Spring Boot 导致的。
由于对该问题的解决办法是基于前面的猜测,因此未必就是这个原因。
如果你有幸遇到了 can't cast x.y.Z to x.y.Z
,这里有一个办法可以印证上面的猜测。
首先 DEBUG 启动项目,出现该错误后,在代码中可运行到的某个地方断点,操作进入该断点。
在断点停止的时候,通过下面的操作来查找问题。
- 使用 IDEA 的 F8 或者 Watches,或者 Eclipse 的变量表达式进行下面的操作
- 输入
getClass().getClassLoader()
查看当前是哪个 ClassLoader - 一般断点在
target/classes
下面存在的类中时,应该是RestartClassLoader
,建议在这种类断点。 - 在
getClass().getClassLoader()
获取到RestartClassLoader
后,继续输入getClass().getClassLoader().findClass("x.y.Z")
获取
1. 集成通用 Mapper || 2. 对象关系映射 || 3. 配置介绍