Java动态编译
2022-12-29 本文已影响0人
老鼠AI大米_Java全栈
写工具时遇到一个需求,程序跑起来之后,可以在程序上写代码并编译执行,这种情况就用到了Java动态编译运行
流程
获取JavaCompiler
获取JavaCompiler需要用到jdk的tools包,如果只有jre,就需要手动把tools包放到JAVA_HOME的lib目录下
private static JavaCompiler compiler;
static {
compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
String error = String.format("Java运行环境缺少文件:请将'系统jdk目录\\lib\\tools.jar'文件复制到'%s\\lib\\目录下'", System.getProperty("java.home"));
System.out.println("ClassUtil init: " + error);
throw new RuntimeException(error);
}
}
编译文件
调用JavaCompiler的run方法,即可编译Java文件,run方法接收一个输入流,两个输出流和若干个字符串参数,源码注释如下:
/**
* Run the tool with the given I/O channels and arguments. By
* convention a tool returns 0 for success and nonzero for errors.
* Any diagnostics generated will be written to either {@code out}
* or {@code err} in some unspecified format.
*
* @param in "standard" input; use System.in if null
* @param out "standard" output; use System.out if null
* @param err "standard" error; use System.err if null
* @param arguments arguments to pass to the tool
* @return 0 for success; nonzero otherwise
* @throws NullPointerException if the array of arguments contains
* any {@code null} elements.
*/
int run(InputStream in, OutputStream out, OutputStream err, String... arguments);
- 其中in输入流是运行起来后的输入,默认为null,运行时不需要输入;
- output输出流控制运行信息的输出,默认为null,信息会打印到控制台,也可以自定义一个输出流自定义输入,比如输出到指定的日志文件;
- error输入流和output类型,区别在于error只输出错误级别的信息;
- 最后的字符串参数则是控制编译的参数,即javac命令的参数,比如:
-d指定放置生成的类文件的位置
-s指定放置生成的源文件的位置
这些参数可以在命令指示符中输入javac命令查看
加载class文件
上面的编译步骤完成后,会输出class文件,想要将class运行起来,要先加载文件
使用ClassLoader来加载class文件
//编译的类文件路径
public static final String CLASS_PATH = System.getProperty("user.dir") + File.separator + "target" + File.separator + "classes";
static class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) {
String myPath = "file:///" + CLASS_PATH.replaceAll("\\\\", "/") + "/" + name.replace(".", "/") + ".class";
byte[] cLassBytes = null;
try {
Path path = Paths.get(new URI(myPath));
cLassBytes = Files.readAllBytes(path);
} catch (IOException | URISyntaxException e) {
System.out.println(e);
}
return defineClass(name, cLassBytes, 0, cLassBytes.length);
}
}
private static Class<?> load(String name) {
//加载类文件的方法,返回加载后的Class
Class<?> cls = null;
try {
//这里使用自定义的ClassLoader
ClassLoader classLoader = new MyClassLoader();
} catch (Exception e) {
System.out.println(e);
}
return cls;
}
使用加载后的类
将类文件加载后,就可以使用了,通过反射机制,获取类的方法并调用
/**
* 调用类方法
*
* @param cls 类
* @param methodName 方法名
* @param paramsCls 方法参数类型
* @param params 方法参数
* @return
*/
public static Object invoke(Class<?> cls, String methodName, Class<?>[] paramsCls, Object[] params)
throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
Method method = cls.getDeclaredMethod(methodName, paramsCls);
Object obj = cls.newInstance();
return method.invoke(obj, params);
}
public static void main(String[] args) {
Class<?> cls = load("com.xxx.xxx");
//调用加载后Class的方法
invoke(cls, methodName, paramsCls, params);
}
完整代码
Logger是自定义的日志类
package com.xxx.utils;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class ClassUtil {
public static final String CLASS_PATH = System.getProperty("user.dir") + File.separator + "target" + File.separator + "classes";
private static JavaCompiler compiler;
static {
compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
String error = String.format("Java运行环境缺少文件:请将'系统jdk目录\\lib\\tools.jar'文件复制到'%s\\lib\\目录下'", System.getProperty("java.home"));
Logger.e("ClassUtil init", error);
throw new RuntimeException(error);
}
}
static class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) {
String myPath = "file:///" + CLASS_PATH.replaceAll("\\\\", "/") +
"/" + name.replace(".", "/") + ".class";
byte[] cLassBytes = null;
try {
Path path = Paths.get(new URI(myPath));
cLassBytes = Files.readAllBytes(path);
} catch (IOException | URISyntaxException e) {
Logger.e(e);
}
return defineClass(name, cLassBytes, 0, cLassBytes.length);
}
}
public static Object execute(ExecuteOptions options)
throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
Logger.i("java file path: " + options.compilerOptions.srcPath);
compiler(options.compilerOptions);
Class<?> cls = load(String.format("%s.%s", options.pkgName, options.clzName));
return invoke(cls, options.methodName, options.paramsCls, options.params);
}
public static int compiler(CompilerOptions options) {
checkCompiler();
return compiler.run(options.in, options.out, options.error, "-d", options.targetPath, options.srcPath);
}
/**
* 加载类
*
* @param name 类名
* @return
*/
private static Class<?> load(String name) {
Class<?> cls = null;
try {
ClassLoader classLoader = new MyClassLoader();
//classLoader = ClassUtil.class.getClassLoader();
cls = classLoader.loadClass(name);
Logger.d("Load Class[" + name + "] by " + classLoader);
} catch (Exception e) {
Logger.e(e);
}
return cls;
}
/**
* 调用类方法
*
* @param cls 类
* @param methodName 方法名
* @param paramsCls 方法参数类型
* @param params 方法参数
* @return
*/
public static Object invoke(Class<?> cls, String methodName, Class<?>[] paramsCls, Object[] params)
throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
Method method = cls.getDeclaredMethod(methodName, paramsCls);
Object obj = cls.newInstance();
return method.invoke(obj, params);
}
private static void checkCompiler() {
if (compiler == null) {
compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
String error = String.format("Java运行环境缺少文件:请将'系统jdk目录\\lib\\tools.jar'文件复制到'%s\\lib\\目录下'", System.getProperty("java.home"));
Logger.e("ClassUtil init", error);
throw new RuntimeException(error);
}
}
}
/**
* 执行参数
*/
public static class ExecuteOptions{
public CompilerOptions compilerOptions;
public String pkgName;
public String clzName;
public String methodName;
public Class<?>[] paramsCls;
public Object[] params;
public ExecuteOptions() {
super();
}
public ExecuteOptions(CompilerOptions compilerOptions, String pkgName, String clzName, String methodName, Class<?>[] paramsCls, Object[] params) {
this.compilerOptions = compilerOptions;
this.pkgName = pkgName;
this.clzName = clzName;
this.methodName = methodName;
this.paramsCls = paramsCls;
this.params = params;
}
public ExecuteOptions setCompilerOptions(CompilerOptions compilerOptions) {
this.compilerOptions = compilerOptions;
return this;
}
public ExecuteOptions setPkgName(String pkgName) {
this.pkgName = pkgName;
return this;
}
public ExecuteOptions setClzName(String clzName) {
this.clzName = clzName;
return this;
}
public ExecuteOptions setMethodName(String methodName) {
this.methodName = methodName;
return this;
}
public ExecuteOptions setParamsCls(Class<?>[] paramsCls) {
this.paramsCls = paramsCls;
return this;
}
public ExecuteOptions setParams(Object[] params) {
this.params = params;
return this;
}
}
/**
* 编译参数
*/
public static class CompilerOptions {
public InputStream in;
public OutputStream out;
public OutputStream error;
public String targetPath = CLASS_PATH;
public String srcPath;
public CompilerOptions() {
super();
}
public CompilerOptions(String targetPath, String srcPath) {
this.targetPath = targetPath;
this.srcPath = srcPath;
}
public CompilerOptions(InputStream in, OutputStream out, OutputStream error, String targetPath, String srcPath) {
this.in = in;
this.out = out;
this.error = error;
this.targetPath = targetPath;
this.srcPath = srcPath;
}
public CompilerOptions setIn(InputStream in) {
this.in = in;
return this;
}
public CompilerOptions setOut(OutputStream out) {
this.out = out;
return this;
}
public CompilerOptions setError(OutputStream error) {
this.error = error;
return this;
}
public CompilerOptions setTargetPath(String targetPath) {
this.targetPath = targetPath;
return this;
}
public CompilerOptions setSrcPath(String srcPath) {
this.srcPath = srcPath;
return this;
}
}
}