Java动态编译

背景:

我正在开发一个平台,其中一个模块需要由用户来创建,也就是用户可以通过一些选项,系统生成可执行代码,并且可运行在当前平台中。

条件:

项目运行于tomcat中,并且平台所编译的class文件全部被打包成一个jar文件(这个是非常的不爽的,为什么,下面会讲)。所有的外部jar文件都在lib下

思路:

错误思路一:一开始我是准备生成java文件,想当然的觉得系统可以自动编译成class文件(不是手动编译),我明显就是too young too simple。因为你开发的时候,有的平台是可以的,因为它们是热部署的,但是tomcat中明显是不可以的。

错误思路二:生成java文件之后,然后调用java的api接口JavaCompiler进行手动编译(各种问题,一把辛酸泪),然后放到项目的正确位置(也就是包路径),但是我忽略了jar这个文件特殊性,jar本质是一个文件,并不是一个目录,java只有读取和修改的权利,并没有添加的权利。所以完全行不通,而且在项目的运行中,对jar进行操作也是非常的安全,非常的不推荐。

正确思路:既然生成了class文件,我为什么一定要放到项目文件中呢,完全可以放到别的文件夹,然后在系统需要运行该文件时,动态加载该文件。

代码:

1.动态编译java文件

 //编译依赖的包目录
 public final static String jarPath =  "xxx\xxxx\";
 //存放class文件目录
 public final static String directoryPath = "xxx\xxxx\";

 public static Boolean operate(String fileName, String source) throws Exception{
        //编译器    
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        //不想生成java文件,所以存储在内存中
        StringWriter writer = new StringWriter();
        PrintWriter out = new PrintWriter(writer);
        out.println(source);
        out.close();
        JavaFileObject file = new JavaSourceFromString(fileName, writer.toString());
        //javac的命令内容
        Iterable units = Arrays.asList(file);
        //jar 路径
        StringBuilder sb = new StringBuilder();
        //遍历目录下所有jar文件,生成classpath
        String[] filenames = new File(jarPath).list();
        for(int i = 0; i < filenames.length; i++) {
            if(i > 0) sb.append(File.pathSeparatorChar); 
            sb.append(jarPath+filenames[i]);
        }
        String classpath =  sb.toString();
        Iterable options = Arrays.asList("-classpath", classpath, "-d", directoryPath);
        //编译错误信息
        Writer sw = null;
        JavaCompiler.CompilationTask task = compiler.getTask(sw, null, null, options, null, units);
        //编译结果
        boolean success = task.call();
        if (!success) {
            String failedMsg = sw.toString();
            System.out.println("Build Error:" + failedMsg);
        } else {

        }
        return success;
    }

一定要载入所有该文件所用到的jar包以及外部class文件,不然编译不通过。

2.加载class文件,运行时需要

由于是生成外部class文件,所以我们不能用系统的类加载器,会报错,我们必须自己写一个类加载器,并且重写findClass和loadClass。

public class CheckoutClassLoader extends ClassLoader {

    //根路径(class文件路径)
    public final static String directoryPath = "xxx\xxxx\";


    //特定加载器(Checkout.class 这是我生成class文件中类继承的接口)
    public CheckoutClassLoader(){
        super(Checkout.class.getClassLoader());
    }

    //重写查找class文件
    @Override
    protected Class findClass(final String name) throws ClassNotFoundException {
        String fullName = name.replace('.', '/');
        fullName += ".class";
        String path = directoryPath + fullName ;
        try {
            FileInputStream fis = new FileInputStream(path);
            byte[] data = new byte[fis.available()];
            fis.read(data);
            Class res = defineClass(name, data, 0, data.length);
            fis.close();
            return res;
        } catch(Exception e) {
            return super.findClass(name);
        }
    }
}

3.运行

Class c = Class.forName("task.modeOne.type."+one.checkout.getFileName(),true,new CheckoutClassLoader());
checkout = (Checkout) c.newInstance();
//下面可以进行你需要做的事情了
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页