您的位置:首页 > 编程语言 > Java开发

Java动态编译

2016-08-05 15:50 330 查看


背景:

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


条件:

项目运行于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();
//下面可以进行你需要做的事情了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java