Java实现热加载的三种方式

1,502 阅读2分钟

基于java agent方式

新建一个maven工程

public static void premain(String agentArgs, Instrumentation inst) {
    Trace.info("HotAgent-premain-start");
    Trace.info("isReadefineClassesSupported:" +inst.isRedefineClassesSupported());
    new Reloader(inst).reload();
}

public static void agentmain(String agentArgs, Instrumentation inst) {
    premain(agentArgs, inst);
}

再pom中增加一下

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.0.2</version>
            <configuration>
                <archive>
                    <manifestEntries>
                        <Can-Redefine-Classes>
                            true
                        </Can-Redefine-Classes>
                        <Premain-Class>
                            com.reload.HotAgent
                        </Premain-Class>
                        <Agent-Class>
                            com.reload.HotAgent
                        </Agent-Class>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>

</build>

核心代码如下
主要是利用JDK中Instrument的redefine方法去重定义class,去重新加载了Class.

for (ClassDefinitionWrap rWrap : redefineClassWrap) {
    Trace.info("redefine class" + rWrap.getClassname());
    inst.redefineClasses(new ClassDefinition(rWrap.getCls(), rWrap.getBs()));
    result.appendMsg("热加载类:ࣺ"+rWrap.getClassname()+"成功");
}

基于Classloader方式

热加载就是利用新建自定义Classloader去加载Class,让后利用Thread的contexClassloader去替换老的classloader加载的class,这样就能实现 热加载。以下是demo的代码:

TestClassLoader testClassLoader = new TestClassLoader();
System.out.println("parent classloader:" + testClassLoader.getParent());
Thread.currentThread().setContextClassLoader(testClassLoader);
Class clazz = testClassLoader.loadClass("com.plugin.AutoReload");
System.out.println(clazz.hashCode());
System.out.println(clazz.getClassLoader());
System.out.println(testClassLoader);
TestClassLoader newClassLoader = new TestClassLoader(Thread.currentThread().getContextClassLoader());
Thread.currentThread().setContextClassLoader(newClassLoader);
System.out.println(newClassLoader);
Class clazz1 = newClassLoader.loadClass("com.plugin.AutoReload");
System.out.println(clazz1.hashCode());
System.out.println(clazz1.getClassLoader());

自定义Classloader的示例代码:

public class TestClassLoader extends ClassLoader {

    public TestClassLoader(ClassLoader parent) {
        super(parent);
    }

    public TestClassLoader(){
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class c = null;
        synchronized (getClassLoadingLock(name)) {
            if (c == null) {
                try {
                    ClassLoader parent = getParent();
                    if (parent != null) {
                        c = parent.loadClass(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
            }
            if (c != null) {
                return c;
            }
            byte[] bytes = loadclassData(name);
            System.out.println("=====" + name);
            if(bytes != null) {
                return defineClass(name, bytes, 0, bytes.length);
            }
        }
        throw new ClassNotFoundException(name);
    }

    private byte[] loadclassData(String className) {
        return getContent(getResourceStream(className));
    }

    private InputStream getResourceStream(String toLoadClassName) {
        System.out.println("load class name " + toLoadClassName);
        try {
            JarFile jar = new JarFile(Constants.WATCH_PACKAGE + File.separator + "plugin-1.0-SNAPSHOT.jar");
            // 包名
            Enumeration<JarEntry> entry = jar.entries();
            JarEntry jarEntry;
            String name;
            String className;
            Class clazz = null;
            while (entry.hasMoreElements()) {
                jarEntry = entry.nextElement();
                name = jarEntry.getName();
                if (name.startsWith("/")) {
                    name = name.substring(1);
                }
                if (jarEntry.isDirectory() || !name.endsWith(".class")) {
                    continue;
                }
                // 去掉 .class
                className = name.substring(0, name.length() - 6).replace("/", ".");
                System.out.println(className);
                if (className.equals(toLoadClassName)) {
                    return jar.getInputStream(jarEntry);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public byte[] getContent(InputStream inputStream) {
        // 读取Class文件呢
        ByteArrayOutputStream byteSt = new ByteArrayOutputStream();
        // 写入byteStream
        int len = 0;
        try (InputStream in = inputStream) {
            while ((len = in.read()) != -1) {
                byteSt.write(len);
            }
        } catch (
                IOException e) {
            e.printStackTrace();
        }
        // 转换为数组
        return byteSt.toByteArray();
    }
}

基于groovy方式

基于Groovy方式,就是利用GroovyClassLoader的重新加载脚本,重新生成一个Class,GroovyClassLoader每次调用parseClass都是新城一个新的Class,这样就能实现Class的热加载.

      String scriptContent = "package com.reload\n" +
                "\n" +
                "import org.springframework.beans.factory.annotation.Autowired\n" +
                "\n" +
                "class Hello {\n" +
                "\n" +
                "    @Autowired\n" +
                "    HelloService service;\n" +
                "\n" +
                "    HelloService getService() {\n" +
                "        return service\n" +
                "    }\n" +
                "\n" +
                "    def run() {\n" +
                "        print(service.hello())\n" +
                "    }\n" +
                "}";
        GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
        Class clazz = null;

        for (int i = 0; i < 3; i++) {
            clazz =  groovyClassLoader.parseClass(scriptContent);
            System.out.println(clazz.hashCode());
        }
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext("com.reload");

        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        BeanDefinition beanDefinition =  beanDefinitionBuilder.getRawBeanDefinition();
        ((DefaultListableBeanFactory)annotationConfigApplicationContext.getBeanFactory()).registerBeanDefinition("hello", beanDefinition);
//       annotationConfigApplicationContext.getAutowireCapableBeanFactory().applyBeanPostProcessorsAfterInitialization(beanDefinition, "hello");
        GroovyObject object = (GroovyObject)annotationConfigApplicationContext.getBean("hello");
        object.invokeMethod("run",null);

总结
本文主要介绍了三种实现JAVA中Class的热加载的三种方式.\

  • 基于java agent方式,只能实现class的增加、修改、删除方法和属性等,不能实现Class的新增,
  • 基于Classloader方式, 可以实现class的增加、修改、删除方法和属性等,可以实现Class的新增,
  • 基于Groovy方式, 可以实现class的增加、修改、删除方法和属性等,可以实现Class的新增,