热部署是在不重启 Java 虚拟机的前提下,能自动侦测到 class 文件的变化,更新运行时 class 的行为。Java 类是通过 Java 虚拟机加载的,某个类的 class 文件在被 classloader 加载后,会生成对应的 Class 对象,之后就可以创建该类的实例。
1、热部署现状
热部署一直以来是一个难以解决的问题,目前java虚拟机只能实现方法体的修改热部署,对整个类的结构修改,仍需要重启。osgi的出现,让模块重启变得可行,但是对于模块之间存在调用关系的情况,会出现短暂的功能性休克。
本文讲述如何实现整个类的热部署
2、类加载细节
虚拟机默认只是在启动的时候加载class文件,如果有个类需要更新的话,单纯替换编译后的class文件,虚拟机是不会重新加载的,要实现热部署,最根本的方式就是修改虚拟机的源代码,让虚拟机监测class文件的变化,如果发生更新就重新加载类。但是这样会破坏jvm的性能。埋下隐患。
另外,就是创建自己的classLoader来加载需要监听的class。
下面来简单列举一下需要做的工作。
- 创建自定义的 classloader,加载需要监听改变的类,在 class 文件发生改变的时候,重新加载该类
- 改变创建对象的行为,使他们在创建时使用自定义 classloader 加载的 class。
既然在类加载器中,java类只能被加载一次,并且无法卸载。那是不是可以直接把类加载器给换了?答案是可以的,我们可以自定义类加载器,并重写ClassLoader的findClass方法。想要实现热部署可以分以下三个步骤:
1、销毁该自定义ClassLoader
2、更新class类文件
3、创建新的ClassLoader去加载更新后的class类文件。
package com.csair.soc.hotswap;
import java.io.IOException;
import java.io.InputStream;
/**
* 自定义类加载器,并override findClass方法
*/
public class MyClassLoader extends ClassLoader{
@Override
public Class<?> findClass(String name) throws ClassNotFoundException{
try{
String fileName = name.substring(name.lastIndexOf("." )+1) + ".class" ;
InputStream is = this.getClass().getResourceAsStream(fileName);
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b. length);
} catch(IOException e){
throw new ClassNotFoundException(name);
}
}
}
需要更新的类文件:
package com.csair.soc.hotswap;
public class HelloWorld {
public void say(){
System. out.println( "Hello World V1");
}
}
在工程的根目录下,生成V2版本的HelloWorld.class,内容如下。
package com.csair.soc.hotswap;
public class HelloWorld {
public void say(){
System. out.println( "Hello World V2");
}
}
测试主程序
package com.csair.soc.hotswap;
import java.io.File;
import java.lang.reflect.Method;
public class Hotswap {
public static void main(String[] args) throws Exception {
loadHelloWorld();
// 回收资源,释放HelloWorld.class文件,使之可以被替换
System. gc();
Thread. sleep(1000);// 等待资源被回收
File fileV2 = new File( "HelloWorld.class");
File fileV1 = new File(
"bin\\com\\csair\\soc\\hotswap\\HelloWorld.class" );
fileV1.delete(); //删除V1版本
fileV2.renameTo(fileV1); //更新V2版本
System. out.println( "Update success!");
loadHelloWorld();
}
public static void loadHelloWorld() throws Exception {
MyClassLoader myLoader = new MyClassLoader(); //自定义类加载器
Class<?> class1 = myLoader
.findClass( "com.csair.soc.hotswap.HelloWorld");//类实例
Object obj1 = class1.newInstance(); //生成新的对象
Method method = class1.getMethod( "say");
method.invoke(obj1); //执行方法say
System. out.println(obj1.getClass()); //对象
System. out.println(obj1.getClass().getClassLoader()); //对象的类加载器
}
}