清单 5. 利用 ASM 修改方法体
  @Override
  public void visitTypeInsn(int opcode, String type) {
  if(opcode==Opcodes.<em>NEW</em> &amp;&amp; type.equals(className)){
  List&lt;LocalVariableNode&gt; variables = node.localVariables;
  String compileType = null;
  for(int i=0;i&lt;variables.size();i++){
  LocalVariableNode localVariable = variables.get(i);
  compileType = <em>formType</em>(localVariable.desc);
  if(matchType(compileType)&amp;&amp;!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&lt;?&gt; 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 应用对于更新更友好,避免不断重启应用浪费的时间。