深入探索Java热部署
作者:网络转载 发布时间:[ 2015/11/18 11:12:49 ] 推荐标签:测试开发技术 编程语言
清单 5. 利用 ASM 修改方法体
@Override
public void visitTypeInsn(int opcode, String type) {
if(opcode==Opcodes.<em>NEW</em> && type.equals(className)){
List<LocalVariableNode> variables = node.localVariables;
String compileType = null;
for(int i=0;i<variables.size();i++){
LocalVariableNode localVariable = variables.get(i);
compileType = <em>formType</em>(localVariable.desc);
if(matchType(compileType)&&!valiableIndexUsed[i]){
valiableIndexUsed[i] = true;
break;
}
}
mv.visitMethodInsn(Opcodes.<em>INVOKESTATIC</em>, <em>CLASSLOAD_TYPE</em>,
"getInstance", "()L"+<em>CLASSLOAD_TYPE</em>+";");
mv.visitLdcInsn(type.replace("/", "."));
mv.visitMethodInsn(Opcodes.<em>INVOKEVIRTUAL</em>, <em>CLASSLOAD_TYPE</em>,
"findClass", "(Ljava/lang/String;)Ljava/lang/Class;");
mv.visitMethodInsn(Opcodes.<em>INVOKEVIRTUAL</em>, "java/lang/Class",
"newInstance", "()Ljava/lang/Object;");
mv.visitTypeInsn(Opcodes.<em>CHECKCAST</em>, compileType);
flag = true;
} else {
mv.visitTypeInsn(opcode, type);
}
}
对于第二种创建方法,需要通过修改 Class.forName()和 ClassLoader.findClass()的行为,使他们通过自定义加载器加载类。
使用 JavaAgent 拦截默认加载器的行为
之 前实现的类加载器已经解决了热部署所需要的功能,可是 JVM 启动时,并不会用自定义的加载器加载 classpath 下的所有 class 文件,取而代之的是通过应用加载器去加载。如果在其之后用自定义加载器重新加载已经加载的 class,有可能会出现 LinkageError 的 exception。所以必须在应用启动之前,重新替换已经加载的 class。如果在 jdk1.4 之前,能使用的方法只有一种,改变 jdk 中 classloader 的加载行为,使它指向自定义加载器的加载行为。好在 jdk5.0 之后,我们有了另一种侵略性更小的办法,这是 JavaAgent 方法,JavaAgent 可以在 JVM 启动之后,应用启动之前的短暂间隙,提供空间给用户做一些特殊行为。比较常见的应用,是利用 JavaAgent 做面向方面的编程,在方法间加入监控日志等。
JavaAgent 的实现很容易,只要在一个类里面,定义一个 premain 的方法。
清单 6. 一个简单的 JavaAgent
public class ReloadAgent {
public static void premain(String agentArgs, Instrumentation inst){
GeneralTransformer trans = new GeneralTransformer();
inst.addTransformer(trans);
}
}
然后编写一个 manifest 文件,将 Premain-Class属性设置成定义一个拥有 premain方法的类名即可。
生成一个包含这个 manifest 文件的 jar 包。
manifest-Version: 1.0
Premain-Class: com.example.ReloadAgent
Can-Redefine-Classes: true
后需要在执行应用的参数中增加 -javaagent参数 , 加入这个 jar。同时可以为 Javaagent增加参数,下图中的参数是测试代码中 test project 的路径。这样在执行应用的之前,会优先执行 premain方法中的逻辑,并且预解析需要加载的 class。
图 1. 增加执行参数
这里利用 JavaAgent替换原始字节码,阻止原始字节码被 Java 虚拟机加载。只需要实现 一个 ClassFileTransformer的接口,利用这个实现类完成 class 替换的功能。
清单 7. 替换 class
@Override
public byte [] transform(ClassLoader paramClassLoader, String paramString,
Class<?> paramClass, ProtectionDomain paramProtectionDomain,
byte [] paramArrayOfByte) throws IllegalClassFormatException {
String className = paramString.replace("/", ".");
if(className.equals("com.example.Test")){
MyClassLoader cl = MyClassLoader.<em>getInstance</em>();
cl.defineReference(className, "com.example.Greeter");
return cl.getByteCode(className);
}else if(className.equals("com.example.Greeter")){
MyClassLoader cl = MyClassLoader.<em>getInstance</em>();
cl.redefineClass(className);
return cl.getByteCode(className);
}
return null;
}
至此,所有的工作大功告成,欣赏一下 hotswap 的结果吧。
图 2. Test 执行结果
结束语
解 决 hotswap 是个困难的课题,本文解决的仅仅是让新实例化的对象使用新的逻辑,并不能改变已经实例化对象的行为,如果 JVM 能够重新设计 class 的生命周期,支持运行时重新更新一个 class,hotswap 会成为 Java 的一个闪亮新特性。官方的 JVM 一直没有解决热部署这个问题,可能也是由于无法完全克服其中的诸多难点,希望未来的 Jdk 能解决这个问题,让 Java 应用对于更新更友好,避免不断重启应用浪费的时间。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11