Spring Boot Devtools Cannot cast x.y.Z to x.y.Z

版权声明:版权归博主所有,转载请带上本文链接!联系方式:abel533@gmail.com https://blog.csdn.net/isea533/article/details/80100352

Spring DevTools 介绍
https://blog.csdn.net/isea533/article/details/70495714

Spring Boot Devtools Cannot cast x.y.Z to x.y.Z

经过挺长时间的测试和分析,才找到原因,并且有了本项目。

项目地址:https://github.com/abel533/mapper-cast-exception

为什么会出现 Cannot cast x.y.Z to x.y.Z

首先 Devtools 是应用于开发时的,也就是在 IDE 中运行时,当使用 jar 方式运行时,就会自动禁用 Devtools 工具。在 IDE 中运行时,Devtools 会通过独立的类加载器来加载会发生变化的资源,通常是 IDE 各个模块的 classes 目录。对于通过依赖导入的 jar 包是不会自动更新的。

这两部分内容分别由 RestartClassLoader 和 AppClassLoader 加载的,并且前者的 parent 是后者,也就是说 AppClassLoader 加载的类可以在 RestartClassLoader 加载的类中使用,但是 RestartClassLoader 加载的类不能在 AppClassLoader 中使用。

这里有下面三个项目:

  • mapper-cast-exception(包含启动类,依赖 mapper-cast-exception-mapper 和 mapper-cast-exception-model)
  • mapper-cast-exception-model(包含实体类)
  • mapper-cast-exception-mapper(包含 Mapper 接口,依赖 mapper-cast-exception-model)

假如这3个项目都已经在本地或者私服打包。

在 IDE 中只引入 mapper-cast-exception 和 mapper-cast-exception-model 项目。

此时使用 Devtools 时,RestartClassLoader 会加载并检测这两个项目的变化,mapper 项目会使用 AppClassLoader 加载,AppClassLoader 启动时也会加载前两个项目中的类。

RestartClassLoader 实现中,是把 AppClassLoader 已经加载的类中,所有以 file: 开头和 / 结尾的类路径(非 .jar 文件,也就是所有类路径中的目录)加载到了 RestartClassLoader 中,所以 AppClassLoader 实际上是全的。后续通过 RestartClassLoader 加载时,会先判断 RestartClassLoader 中是否包含类,这里优先使用 RestartClassLoader 提供的类,如果不存在才会通过 parent(AppClassLoader)查找。

在下面的方法中,当通过 AppClassLoader 加载的 mapper 调用返回 model 时,该 model 类是由 AppClassLoader 加载的。而下面代码中的 Country 是 RestartClassLoader 加载的,由于是不同的 ClassLoader,因此就会抛出异常。

@RequestMapping("/{id}")
@ResponseBody
public Country byId(@PathVariable("id") Long id) {
  //这样不报错
  Object obj = countryMapper.selectByPrimaryKey(id);
  //这样会报错,直接 return 也报错
  Country country = countryMapper.selectByPrimaryKey(id);
  return country;
}

有些人可能会问,为什么 Country 是 RestartClassLoader 加载的?

因为 Country 是在当前线程加载的,而加载类的 ClassLoader 默认就是当前线程的 ContextClassLoader。

Devtools 在启动时(你运行的 main 方法),悄悄的创建了一个新的 restartedMain 线程,然后把你启动的 main 线程终止了。新的 restartedMain 线程使用的就是 RestartClassLoader,后续通过该线程创建的其他线程也都是这个类加载器(Spring 内部 ClassUtils 会 优先使用这个类加载器)。重启时的 Application 启动类就是由这个类加载器加载的,后续通过该类加载的其他类都使用的这一个类加载器。所以 Country 就是 RestartClassLoader 加载的。

这里原来说明有误,代码中的类是由代码所在类的加载器加载的,不是 Thread 的contextClassLoader。

到这里就应该明白了,实际上这个问题产生的原因和通用 Mapper 没有关系,在通用 Mapper 中增加的配置也没有用。

通用 Mapper 也不是完全没影响,使用通用 Mapper 时,即使你 IDE 导入了这 3 个项目,仍然会有这个问题,这是因为 Mapper 中的 MsUtil 中缓存了 EntityClass,这也会导致一些问题,后续的 4.0.3 版本会完全支持 Devtools 工具。

还有一个问题,为什么只往 IDE 加载两个项目,而不是全部项目呢?

如果你参与的项目有几十个模块,几十个模块之间可能存在类似的依赖关系,在工作时,往往只会把需要用到或者修改的项目导入到 IDE 中,因此就出现了这个问题。如果你真遇到这个问题,最直接的方法就是禁用 Devtools 工具。

后续

为了更了解 Devtools,仔细看了几遍 Devtools 的源码,其中有些很好的设计思路,后续会写几篇博客来深入了解 Devtools。

阅读更多

扫码向博主提问

isea533

博客专家

MyBatis相关答疑
  • 擅长领域:
  • MyBatis
  • Spring Boo
  • Spring
去开通我的Chat快问

没有更多推荐了,返回首页