本文主要讲热部署和热加载的区别、原理,以及常用的热部署的方式实践心得,其中包括HotSwap、Spring-loaded、Spring-boot-devtools、HotCode2和JRebel,诸多方式任你选择,希望能为你的开发进一步提效
1 热部署和热加载
开篇先说下热部署和热加载的区别:
热部署:在服务器运行时重新部署应用,也就是说在不停止容器的情况下实现整个应用的重新加载部署,这种方式会释放内存,多用于生成环境
热加载:在应用运行时重新加载class,主要依赖于Java的类加载机制,如果监控的类文件有改变,则重新载入,多用于应用开发阶段,又叫开发者模式,在开发过程中会经常性的进行修改文件或debug,频繁启动应用会花费很多时间成本,热加载机制可以极大的提升开发效率,这也是写这篇文章的主要原因,提高大家的开发效率。
但我们通常所说的热部署其实包括热加载的,可以理解为热加载是热部署的一种VIP情况
2 热加载的原理
2.1 类加载过程
在了解热加载之前,首先说下类加载的过程,类加载的过程简单来说就是JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程;类加载的过程主要分为三大部分:加载、连接、初始化,其中连接又分为:验证、准备、解析;示例图如下:
- 编译:把Java文件编译成.class字节码的过程
- 加载:类加载过程的开始,把class字节码文件从各个源通过类加载器载入内存中
- 连接
- 验证:确保类加载的正确性,保证加载进来的字节流符合JVM规范,不会造成安全问题
- 准备:类变量(注意,不是实例变量,是static变量)分配内存,并且设置这些类变量的初始值
- 解析:将常量池内的符号引用替换为直接引用的过程
- 初始化:是类加载过程的最后一步,可以理解为是执行类构造器<clinit>()方法的过程,真正开始执行类中定义的Java程序代码或者说字节码。
2.2 剖析原理
首先通过Java编译器把Java文件编译成class字节码,Java类加载器(classLoad)读取字节码到内存中,生成实例对象,一个类加载器中的Java全限定名是全局唯一的,也就是说一个类加载器只能加载一个同名类,classLoader内部会缓存已经加载过的class,重新加载的话,是直接读 取缓存的,如果是使用自定义的classLoader加载,不使用双亲委派模型,绕过判断,但是在JVM解析、验证class时也是会抛出异常的,所以实现__热部署的关键是在可更改已加载的class文件,用新的class文件替换同名的old class文件(或者更改class字节码),或者是重新创建一个classLoader进行加载,然后把老的classLoader卸载掉__;第二种方案可以理解成热部署,所以这种方案为开发提效有限;而第一种方案是更新或替换old class文件所以这种热加载的方式对开发提效非常明显。
2.3 发展历程
在JDK1.4时,Sun在JVM中引入了HotSwap的实验性技术,这一技术被合成到了Debugger API
内部,其允许调试者使用同一个类标识来更新类的字节码,这就意味着在不重载容器的情况下,允许动态更新class文件,使应用程序在执行时可执行新的代码。
从JDK1.5开始,提出了“Instrumentation”特性,可通过Instrumentation API
直接提供给Java应用使用,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义,但在 JDK1.5 中,需要要求在运行前利用命令行参数或者系统参数来设置代理类。
JDK1.6之后,对Instrumentation进行了加强,启动后的 instrument、本地代码(native code)instrument,以及动态改变 classpath 等等,“java.lang.instrument”包的具体实现,依赖于 JVMTI(Java Virtual Machine Tool Interface),JVMTI 提供了一套”代理”程序机制,可以支持第三方工具程序以代理的方式连接和访问 JVM,并利用 JVMTI 提供的丰富的编程接口,完成很多跟 JVM 相关的功能,JVMTI 还在虚拟机内存管理,线程控制,方法和变量操作等等方面提供了大量有价值的函数,Instrumentation 的最大作用,就是类定义动态改变和操作。--参见IBM Developer
3 Hot Swap实现热部署
这种方式有两个弊端:
1 就是仅限于修改方法体,对比如新增方法、字段之类 就需要重新部署才可以了,这种方式有没有解决方案呐,答案是肯定的,收费的JRebel,或者免费的HotSwapAgent+DCEVM
2 HotSwap需要依托于IDE集成,比如主流的IDE:IntlliJ IDEA、Eclipse、MyEclipse、NetBeans等
下面HotSwap以IDEA为例实现方法体内的热部署功能
3.1 IDEA基于HotSwap的热部署功能
使用IDEA自动编译部署功能,实现应用的热部署(Hot Swap)
3.1.1 检查是否开启HotSwap,默认是开启的
3.1.2 开启IDEA自动编译功能
弹出registry浮层面板,快捷键control+shift+a,然后找到compiler.automake.allow.when.app.runing勾选
快捷键不对的可以在keymap面板中找下:
3.1.3 设置IDEA自动编译
3.1.4 手工编译(注意类加载顺序)
修改某个类中的方法中的内容后,在菜单栏中Build菜单中Recompile或右键点击Recompile,编译完成之后,再次访问,可验证加的内容是否生效
或:
点击Recompile之后,第一次会弹出框让选择:是否重新加载classes,选择是
3.1.5 验证结果
4 Spring-loaded实现热部署
spring-loaded是针对Springboot类的项目,springloaded热部署只可以修改已有方法与页面,如果是新加的方法或者类则无法实现热部署,并且需要配置启动参数-javaagent(-javaagent:.\lib\springloaded-1.2.8.RELEASE.jar -noverify)。这种方式需要指定springloaded jar包的位置,我不怎么用,就不多说,有兴趣的同学可以自己玩下。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
5 Spring-boot-devtools实现热部署
注意:PandoraBoot不支持devtools实现热部署
Devtools是为springboot应用提供的一个开发者服务模块,其原理就是上面第二章节所说的先重新创建类加载器然后销毁old类加载器;devtools在监控到代码有更改之后,会快速重启应用,这里重启并不是整个应用重启,而是重新加载部分classLoader,因为其有两个classLoader,一个classLoader加载不会改变的类(如各种第三方jar包、二方包等),另一个classLoader加载会更改的类可以理解成自己开发的类,被称为RestartClassLoader,这样有代码更新时,会重新创建一个RestartClassLoader,并销毁之前的RestartClassLoader,由于只加载部分类,所以相对整个应用重启 用时相对较少,但相较于JRebel和hotSwap速度还是大打折扣的,比hotSwap优势就是可以支持静态资源文件(页面、配置文件等,监控范围是classpath下的文件)、新增方法、字段之类的更新。
源码:
5.1 常用配置项
-
默认情况下,以下文件夹下的文件修改不会使应用重启,但是会重新加载(devtools内嵌了一个LiveReload server,当资源发生改变时,浏览器刷新)。
META-INF/maven/<strong>,META-INF/resources/</strong>,resources/<strong>,static/</strong>,public/<strong>,templates/</strong>,**/*Test.class,**/*Tests.class,git.properties,META-INF/build-info.properties
-
如果想改变默认的设置,可以自己设置不重启的目录:spring.devtools.restart.exclude=static/**,public/**,这样的话,就只有这两个目录下的文件修改不会导致restart操作了。
-
如果要在保留默认设置的基础上还要添加其他的排除目录:spring.devtools.restart.additional-exclude
源码:
- 如果想要使得当非classpath下的文件发生变化时应用得以重启,使用:spring.devtools.restart.additional-paths,这样devtools就会将该目录列入了监听范围
源码:
- 如果你想关闭自动restart功能,通过spring.devtools.restart.enabled属性配置即可
- 如果想开启或关闭远程debug功能,通过spring.devtools.remote.debug.enabled属性配置,默认是true
源码:
- 实现页面热部署,通过spring.thymeleaf.cache属性配置为false即可,默认为true
还有远程相关的其他一些配置、代理配置、重启轮询时间等待时间等的配置可以查看源码中的spring-configuration-metadata.json文件
5.2 使用示例
1 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
Eclipse: 下面是在eclipse中实现热部署的演示,项目启动后,新加类TestController,并新增getName()方法,保存后可以观察到后台已进行热部署了,在浏览器中访问如下RPC,可正常返回结果
6 JRebel 实现热部署
强烈推荐,有商业版和个人免费版(个人版申请通道好像已关闭,但有试用版或其他解决方案(仅供学习使用,请支持正版))
它可以在无需动态类加载器的情况下更新类,且只做极少的限制(改变static静态字段值不支持),它的工作原理就是监控磁盘上的已编译的class文件,并通过rebel.xml配置文件把归档的应用(zip、jar、war)和应用模块间建立一个对应关系,通过该文件JRebel可以直接找到归档应用对应的源文件,当某个类被更新时,该文件告诉JRebel在哪里可以找到源文件,使其被从工作区中而不是从归档文件中读取。也就是因为它的这一特性,使其可以对诸如Html、xml、.properties、.yml等资源类的文件也可以及时更新。
6.1 使用示例
6.1.1 安装JRebel
6.1.2 使用JRebel启动项目
安装完成之后,会在工作栏中显示出JRebel图标
或
6.1.3 验证JRebel是否启动成功
项目启动时,控制台出现如下信息时表示JRebel配置正确可以正常使用
6.1.4 设置热部署模块
在IDEA视图窗口中打开JRebel,选中jrcon复选框,JRebel会自动在对应模块下生成rebel.xml文件,该文件将被放置到项目的源代码树中,对于Maven项目,它将被放置到src/main/resources中,以便在构建期间自动获取。
注:每个模块下resources资源文件下会产生一个rebel.xml配置文件,文件的功能基本同HotCode2的workspace.xml,用于指定target/classes的dir路径,当文件有更新时JRebel会自动将其映射到工作区。
6.1.5 验证
点击保存之后,可以在控制台看到JRebel打印的Reloading日志,说明新加的方法已经生效
7 总结
有了热部署不仅能大幅度节省重启项目的时间,而且能够在编程时避免中断思路,将更多的时间用于Coding和思考问题。
本文主要是基于本地开发的维度进行演示说明,具体使用哪一种各位可根据自己开发中实际情况进行自由选择,当然个人还是比较优先推荐JRebel,稳定好用省心,其次是HotCode2,如果原生的SpringBoot类(PandoraBoot不支持)的项目可以使用Spring-boot-devtools或者Spring-loaded尝尝鲜,最后支持工程热部署方式最少的但也是最方便的就是HotSwap,各个IDE工具基本都已集成开启即可使用。