自定义类加载器和动态加载 Java 代码

有时候,我们需要 java 像脚本一样的运行,甚至是希望我们的代码是热部署,一旦代码文件发生变动就重新加载这个代码,能实现吗?今天就来试着解决下。

自定义类加载器

我们需要一个自定义的类加载器,完成任何路径包括网络的文件加载,这个是取得 java 字节码文件,也就是编译后的 class 文件,他可能在世界的某个角落。 实现自定义的类加载器首先是继承ClassLoader这个类,来看下构造方法代码

public class MyClassLoad extends ClassLoader {    private String rootPath;    public MyClassLoad(String rootPath) {        this.rootPath = rootPath;
    }
}

构造方法,仅仅是把路径传入,也就是 class 文件的文件夹,不包括包名称的路径; 接下来重写findClass方法;

/**
     * 根据name来寻找该类
     * 
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> c = findLoadedClass(name);        if (c == null) { // 内存堆中还没加载该类
            c = findMyClass(name); // 自己实现加载类
        }        return c;
    }

首先在内存堆里面查找,没有加载的话就到自己实现,看下findMyClass方法

/**
     * 加载该类
     * 
     * @param name
     * @return
     */
    private Class<?> findMyClass(String name) {        try {            byte[] bytes = getData(name);            return this.defineClass(null, bytes, 0, bytes.length); // 调用父类方法,生成具体类
        } catch (Exception e) {
            e.printStackTrace();
        }        return null;
    }

该方法根据字节数组返回Class类,根据 class 文件获取字节数组可以使用Apache 文件操作相关辅助类,这里使用原生 jdk 实现;

private byte[] getData(String className) {
        String path = rootPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
        InputStream is = null;        try {
            is = new FileInputStream(path);
            ByteArrayOutputStream stream = new ByteArrayOutputStream();            byte[] buffer = new byte[2048];            int num = 0;            while ((num = is.read(buffer)) != -1) {
                stream.write(buffer, 0, num);
            }            return stream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {            if (is != null) {                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }        return null;
    }

这个简单的自定义类加载器就差不多了,如果需要实现自己加密解密的可以在字节数组里面进行折腾,这里不再深入,我们的目标是热加载一段 java代码,可能的解决方法是,构建一个 java 模板,里面内置一些方法,外界可以增加一些新的方法,也可以调用内置方法。

好!开始一个简单的,把一段代码加载到内存并且执行吧。

import java.io.File;import java.io.FileWriter;import javax.tools.JavaCompiler;import javax.tools.JavaCompiler.CompilationTask;import classload.MyClassLoad;import javax.tools.JavaFileObject;import javax.tools.StandardJavaFileManager;import javax.tools.ToolProvider;public class LoadJava {    public static final String javaCode = "package classload;public class HelloWorld2 {public HelloWorld2() {System.out.println(\"Hello World\");}}";    public static void runJavaCode() throws Exception {        // 把 java String 存储到文件
        String fileName = "/Users/XXXXXXX/Documents/demo/java/classload/HelloWorld2.java";
        File file = new File(fileName);
        FileWriter fw = new FileWriter(file);
        fw.write(javaCode);
        fw.flush();
        fw.close();        //
        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager standardFileManager = javaCompiler.getStandardFileManager(null, null, null);
        Iterable<? extends JavaFileObject> iterable = standardFileManager.getJavaFileObjects(fileName);        // 执行编译任务
        CompilationTask task = javaCompiler.getTask(null, standardFileManager, null, null, null, iterable);
        task.call();
        standardFileManager.close();        // 把编译后的 class 文件加载到内存
        ClassLoader pcl = new MyClassLoad("/Users/XXXXXXX/Documents/demo/java/");
        Class c = pcl.loadClass("classload.HelloWorld2");
        System.out.println(c.newInstance());

    }    public static void main(String[] args) {        try {
            LoadJava.runJavaCode();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

代码注释的很清楚了,勿再多言。

原文发布于微信公众号 - 技术与生活(technology_life)

原文发表时间:2017-01-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java工会

JAVA 同步实现原理

Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。Synchronized的作用主要有三个:

7500
来自专栏前端布道

JavaScript设计模式与开发实践 - 高阶函数的应用

定义 高阶函数是指至少满足下列条件之一的函数: 函数可以作为参数被传递; 函数可以作为返回值输出。 JavaScript语言中的函数显然满足高阶函数的条件,在实...

33130
来自专栏积累沉淀

JDK动态代理的底层实现原理

JDK动态代理的底层实现原理      动态代理是许多框架底层实现的基础,比如Spirng的AOP等,其实弄清楚了动态代理的实现原理,它就没那么神奇了,下面就来...

62570
来自专栏吴伟祥

Shell脚本学习总结(二) 流程控制 转

Shell case语句为多选择语句。可以用case语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。case语句格式如下:

8220
来自专栏云霄雨霁

Java--类和对象之基础知识

16830
来自专栏進无尽的文章

编码篇-iOS程序中的内存分配 栈区堆区全局区等相关知识

在计算机的系统中,运行的应用程序中的数据都是保存在内存中,不同类型的数据,保存的内存区域不同。内存区域大致可以分为:栈区、堆区、全局区(静态区)、文字常量区、程...

26520
来自专栏微信公众号:Java团长

Java堆和栈的区别

在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作...

29130
来自专栏积累沉淀

JSON

JSON的全称是”JavaScript Object Notation”,意思是JavaScript对象表示法,它是一种基于文本,独立于语言的轻量级数据交换格式...

38080
来自专栏逆向技术

框架原理第二讲,RTTI,运行时类型识别.(以MFC框架讲解)

           框架原理第二讲,RTTI,运行时类型识别.(以MFC框架讲解) 一丶什么是RTTI,以及RTTI怎么设计 通过第一讲,我们知道了怎么样升成...

219100
来自专栏达摩兵的技术空间

js中的作用域

相信自从es6出来之后,你一定多少知道或者已经在项目中实践了部分的块级作用域,在函数或者类的内部命名变量已经在使用let了,但是你知道它真正的作用是什么吗?又是...

16520

扫码关注云+社区

领取腾讯云代金券