Java使用Instrumentation来进行热加载

java

项目结构

项目结构效果图

tree /f指令查看如下:

│  pom.xml // maven项目pom文件
│          
├─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─wait
│  │  │          └─test
│  │  │                  JavaAgent.java // 用于接收Instrumentation的类
│  │  │                  Sky.java // 测试数据
│  │  │                  SkyData.java // 测试数据
│  │  │                  TestA.java // main class,执行热更,查看结果
│  │  │                  
│  │  └─resources
│  └─test
│      └─java
└─target
    │  w-1.0-SNAPSHOT.jar
    │  
    ├─classes
    │  └─com
    │      └─wait
    │          └─netty
    │                  JavaAgent.class
    │                  Sky.class
    │                  SkyData.class
    │                  TestA.class

增加运行参数

首先用maven生成target/w-1.0-SNAPSHOT.jar,接着编辑Run-->Edit Configurations,增加运行参数,如下图: 编辑java的运行参数

修改SkyData并编译

修改SkyData里的toString方法如下(其实就是加了一句打印):

@Override
public String toString() {
    System.err.println("=====================");
    return "SkyData{" +
            "number=" + number +
            ", skies=" + skies.hashCode() + "(" + skies + ")" +
            '}';
}

Ctrl+Shif+F9重新编译SkyData,运行TestA,由于TestA是直接去读取target下的class文件,这样可以免去拷贝(我比较懒- -),运行结果如下: 运行结果

其中,TestA的代码如下:

package com.wait.test;

import java.io.IOException;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.nio.file.Files;
import java.nio.file.Paths;

/**
 * Created by wait on 2016/8/5.
 */
public class TestA {

    public static void main(String[] args) throws IOException, UnmodifiableClassException, ClassNotFoundException {
        SkyData skyData = new SkyData();
        skyData.add(); // 模拟进行了逻辑
        System.err.println(skyData);
        if (JavaAgent.getIns() != null) {
            byte[] data = Files.readAllBytes(Paths.get("E:/iwork/w/target/classes/com/wait/test/", "SkyData.class"));
            Instrumentation ins = JavaAgent.getIns();
            // 动态加载类, 只是为了测试写死, 用在真实项目的话, 可以把类名和byte数据通过socket等传进来
            ins.redefineClasses(new ClassDefinition(SkyData.class, data));
            System.err.println(skyData);
        }
    }
}

注意点和收获

  1. pom中自定义MAINFEST.MF。以前没折腾过,现在知道了。其中true这个参数一定要设置,要不然调用redefineClasses会抛出UnsupportedOperationException异常
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.0.2</version>
        <configuration>
            <archive>
                <manifest>
                    <mainClass>com.wait.test.TestA</mainClass>
                </manifest>
                <manifestEntries>
                    <Premain-Class>com.wait.test.JavaAgent</Premain-Class>
                    <Agent-Class>com.wait.test.JavaAgent</Agent-Class>
                    <Can-Redefine-Classes>true</Can-Redefine-Classes>
                    <Can-Retransform-Classes>true</Can-Retransform-Classes>
                    <Boot-Class-Path>w-1.0-SNAPSHOT.jar</Boot-Class-Path>
                </manifestEntries>
            </archive>
        </configuration>
    </plugin>    
  1. 需要在pom.xml中增加这个,要不然打包会报找不到类定义的错。
    <profiles>
        <profile>
            <id>windows_profile</id>
            <activation>
                <os>
                    <family>Windows</family>
                </os>
            </activation>
            <dependencies>
                <dependency>
                    <groupId>com.sun</groupId>
                    <artifactId>tools</artifactId>
                    <version>1.8</version>
                    <scope>system</scope>
                    <systemPath>${java.home}/../lib/tools.jar</systemPath>
                </dependency>
            </dependencies>
        </profile>
    </profiles>    
  1. 这个redefineClasses有一个比较好的地方。

    This method does not cause any initialization except that which would occur under the customary JVM semantics

    就是不会调用初始化,从SkyDatatoString方法看来,里面的数据和引用都没有发生改变,而且我用过Guice测试过,调用redefineClasses之后的类,Guice里面也能直接生效。

  2. 又看了一下,发现这个redefineClasses还可以新增private static/final的方法,犀利。 redefineClasses方法的功能 参见:JVM源码分析之javaagent原理完全解读

测试代码

=======代码下载=======