一、问题起因

       且听我吐槽一下(可忽略直接看下面正文),被这个问题折磨的太难受了。

       我的项目使用的是 SpringBoot + Mybatis-plus。项目用到了底层模块(Jar包引入)的工具类 SpringUtil。在项目的 DisciplineServiceImpl 中能正常注入的 DisciplineMapper,并且通过 SpringUtil.getBean(DisciplineMapper.class) 也能拿到对应的 Bean。但是底层模块的拦截器中获取 DisciplineMapper.class 的 Bean 死活拿不到(No Such Bean),这两个操作是相邻的调用完 DisciplineServiceImpl 方法,下一个就到拦截器。

       百思不得其解,上Debug 大法:

       1)怀疑是中间 Bean 被卸载了, Debug 发现并没有;
       2)怀疑中途 SpringUtil.beanFactory 不一样,Debug发现内存地址也一样。
       3)最后 Debug 到 DefaultListableBeanFactory.allBeanNamesByType,发现 allBeanNamesByType 中存在两个内存地址不一样的 DisciplineMapper.class(找到问题)。
       4)问题变成同一个类被加载了两次,查资料发现是 SprintBoot Devtools 搞鬼。

二、前言

       在开发过程中使用了 SpringBoot 的 DevTools 做热启动,减少启动时间。但在使用的过程中却发现使用 DevTools 会带来一些意想不到的问题。因此我最后果断把 SpringBoot DevTools 热启动关闭了。我最终的结论是,如果大家对启动时间并没有那么在意,或者当前项目的开发启动时间还可以接受的情况下,还是不要用 DevTools 了。

       接下来我将从DevTools的原理和使用方面一一给大家分析下DevTools到底会带来什么问题。以及这些问题我们可以如何解决,在使用的时候需要注意什么问题。

三、SprintBoot DevTools带来的问题

       项目中如何使用 SpringBoot DevTools 本文就不做介绍了,如果还有不知道的同学可以自行百度。这类教程网上特别多。

       我们在使用 SpringBoot DevTools 的时候,如果应用的第三方包提供了 SPI 功能(框架的扩展功能),且我们使用了其 SPI 功能,就会出现代码功能不能正常运行的问题。表象就是代码逻辑没有按照预想的方式执行,如果大家深入分析会发现 SPI 的实现类出现了两个同名的 Class类(包路径完全相同)。对你没有看错,是两个同名的 Class 类(大家可以思考下为什么同一个 JVM 中会出现两个同名的 Class 类)。

那么为什么会出现这样的情况呢?接下来我们将进行深入的分析。

四、SpringBoot DevTools 导致同名 Class 类出现的原因分析

1. SpringBoot DevTools 的原理

       在分析同名 Class 类出现的原因之前,我们需要先了解下 SpringBoot DevTools 的实现原理。

       其实 SpringBoot DevTools 的原理也比较简单,就是通过自定义类加载器实现了类的分离加载。SpringBoot DevTools 在启动加载类的时候,它会将我们所有应用的 Jar 包中类使用默认的类加载器加载(一般为 AppClassLoader ),然后所有我们自己写的类都是用用其自定义的类加载加载( RestartClassLoader,是 AppClassLoader 的儿子类加载)。其具体加载过程的核心代码如下。其中 findClass 负责加载我们编写的类和一些指定的 Jar,其他的都会通过异常的方式让父类加载器加载,即其他jar包中的类都会由 AppClassLoader 类加载器加载。

注意:这里的 RestartClassLoader 违背了双亲委派原则,它在加载类的时候并没有其看父类加载是否已经存在这个类,而是直接执行加载。

2. 为什么热启动能够更快

       通过上面我们知道 SpringBoot DevTools 通过两个不同的类加载器来分别加载我们自己实现的类和 Jar 包中的类。为了让启动更加快,热启动的时候,其不会重启容器(比如 Tomcat ),同时只重新加载自定义类,而不加载 Jar 包中的类。因此热启动的过程相对传统的重启过程少了两个过程:容器的重启、Jar 包中的类加载。所以它比普通的重启过程更快。

3. 何时需要热启动

       有了两个类加载器加载后,SpringBoot DevTools 是如何触发热启动的呢?SpringBoot DevTools 主要通过两种方式来触发热启动:不断扫描所有的类和不断扫描指定的Trigger文件。

       如果我们通过 trigger-file 制定了 Trigger 文件,那么 SpringBoot DevTools 后端就会不断扫描这文件,如果发现其变化了(通过修改时间或者文件大小),然后就会触发启动过程。

       如果没有指定 trigger-file 文件,SpringBoot DevTools 则会扫描所有的 Class 编译文件,当发现有文件变化时,就触发热启动过程。

       如下是不断扫描文件的代码:

        如下是如何判断文件是否发送变化的代码:

4. 热启动的过程

       当监控到文件变化之后,就会触发热启动。该过程通过 Restarter 类来完成。此过程只会重新加载 RestartClassLoader 类加载器负责的类,其大概过程如下:

       销毁之前的类加载器 RestartClassLoader 的实例(包括一些清理工作)
新建一个新的 RestartClassLoader 类实例
       使用新的 RestartClassLoader 类实例来加载我们自己的类和指定 Jar 中的类。
       整个热启动的大致过程如下:

五、导致同名 Class 类出现的场景与原因

       前提:我们引入了一个支持 SPI 扩展功能的框架 Jar 包,同时使用了其 SPI 功能,即实现了自己的扩展能力,假设该实现类为 SpiImpl。当 AppClassLoader 在加载 Jar 包中的类的时候,其会自动加载 SpiImpl 类(此时的类加载器为 AppClassLoader )。然后当使用 RestartClassLoader类加载器加载咱们自己编写的代码时,如果 SpiImpl 类被业务代码使用到了,又会再一次加载 SpiImpl 类(此时的类加载器为 RestartClassLoader )。那么这个时候我们的 JVM 中就存在了两个同名的 SpiImpl的Class 类。因此业务执行是就会带来一些问题。

       这里也就回答了上面的问题:什么场景下在 JVM 中同一个类会出现两个相同的 Class 类。即在同一个类被不同的类加载器加载的时候就会出现。

六、如何解决同名类问题

       SpringBoot 这么牛逼的项目当然是考虑到了这个问题,它提供了一种方法来解决这个问题。我们可以先试想一下,如果要我们需要自己来解决这个问题应该如何处理呢?很明显,最简单的办法就是让提供 SPI 能力的 Jar 包中的类和我们的代码在同一个类加载器中加载。这样 SpiImpl 就不会同时存在两个不同的类加载器中了。

       当然我们的 SpringBoot 也是使用的这个方法,他提供了 restart.include 来指定哪些jar包需要用 RestartClassLoader 类加载器加载,即和我们的代码使用同一个类加载器加载。其官方说明如下:

       所以我们可以通过 restart.include 来制定我们提供 SPI 功能的 Jar 包都使用RestartClassLoader 类来加载,这样即可解决同名 Class 类的问题。

七、什么场景下使用(为什么不建议使用)

       其实我最开始也是通过了 restart.include 来解决同名类的问题。但是随着开发的过程中(过了一段时间),程序由于引入了新的 SPI 功能又莫名的出现程序功能异常,此时由于时间过去了很久,俺早忘记了 SpringBoot DevTools 这回事。于是定位了很久,后来突然恍然大悟,原来是DevTools 这货带来的锅。于是俺一气之下直接把 DevTools 关闭了。

       其实当你和楼主遇到的情况一下,如果你很久突然忘记了,然后突然出问题往往需要定位很久才能找到原因。索性还不如直接关闭这个潜在隐患。特别是当你的项目出去构架构建阶段,很有可能不时需要引入很多 Jar 包,可能需要用到 SPI 的场景。

       当然,如果你的工程很成熟,且热启动确实能够带来比较明显的效率提升,当然还是建议使用 SpringBoot DevTools。但是你一定要记住这个潜在的隐患,免得有一天像楼主一样,需要耗费到了的时间在这个隐患上。

转自:https://blog.csdn.net/hilaryfrank/article/details/108052572

GitHub 加速计划 / de / devtools
58
5
下载
vuejs/devtools: Vue.js 开发者工具,这是一个浏览器插件,可以安装在 Chrome 和 Firefox 等现代浏览器中,用于调试 Vue 应用程序,提供了组件树查看、状态快照、时间旅行等高级调试功能。
最近提交(Master分支:2 个月前 )
79116147 - 1 年前
f0359002 - 1 年前
Logo

新一代开源开发者平台 GitCode,通过集成代码托管服务、代码仓库以及可信赖的开源组件库,让开发者可以在云端进行代码托管和开发。旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐