1. 命令行编译
javac –d . -encoding utf-8 CompareString.java //编译命令
//执行命令
Process process = Runtime.getRuntime().exec("java -cp D:/myjava HelloWorld") //执行字节码
InputStream inputStream = process.getInputStream();
BufferedReader fBufferedInputStream = new BufferedReader(new InputStreamReader(inputStream)); //获取执行结果
2. 借助tools.jar实现手动编译
安装tools.jar
本地安装 C:\Program Files\Java\jdk1.8.0_251\lib\tools.jar
mvn install:install-file -Dfile=D:\tools.jar -DgroupId=com.sun -DartifactId=tools -Dversion=1.8 -Dpackaging=jar
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
</dependency>
MyTest
public class MyTest {
public static void main(String[] args) {
System.out.println("Hello, World");
}
}
FileURLClassLoader 自定义实现类加载器
import org.springframework.util.Assert;
import java.io.File;
import java.net.*;
import java.util.Arrays;
/**
* 文件路径类加载器,不需要写findClass()方法及其class文件的字节流转换逻辑
*
* @author liuxubo
* @date 2023/3/19 18:50
*/
public class FileURLClassLoader extends URLClassLoader {
/**
* 。class文件目录
*/
private String classFileDir;
public FileURLClassLoader(String classFileDir, ClassLoader parent) {
super(getURL(classFileDir), parent);
}
public FileURLClassLoader(String classFileDir) {
super(getURL(classFileDir));
}
public FileURLClassLoader(String classFileDir, ClassLoader parent, URLStreamHandlerFactory factory) {
super(getURL(classFileDir), parent, factory);
}
public FileURLClassLoader(String[] classFileDir, ClassLoader parent) {
super(getURL(classFileDir), parent);
}
public FileURLClassLoader(String[] classFileDir) {
super(getURL(classFileDir));
}
public FileURLClassLoader(String[] classFileDir, ClassLoader parent, URLStreamHandlerFactory factory) {
super(getURL(classFileDir), parent, factory);
}
/**
* 将路径转为url
*
* @param classFileDir
* @return
*/
private static URL[] getURL(String classFileDir) {
File file = new File(classFileDir);
if (file.isFile()) {
file = file.getParentFile();
}
// File to URI
URI uri = file.toURI();
try {
URL[] urls = {uri.toURL()};
return urls;
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
/**
* 将多个目录路径转为url
*
* @param classFileDirs
* @return
*/
private static URL[] getURL(String[] classFileDirs) {
Assert.notNull(classFileDirs, "类目录不能为空");
URL[] urls = new URL[classFileDirs.length];
for (int i = 0; i < classFileDirs.length; i++) {
File file = new File(classFileDirs[i]);
if (file.isFile()) {
file = file.getParentFile();
}
// File to URI
URI uri = file.toURI();
try {
urls[i] = uri.toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
return urls;
}
}
测试
//依赖 tools.jar 调用JavaCompiler获取编译器
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
// 获取文件管理器StandardJavaFileManager,用来管理要编译的.java文件
StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(null, Locale.CHINA, StandardCharsets.UTF_8);
// 获取表示给定文件的文件对象
String fileName = "D:/upload/test/MyTest.java";
Iterable iterable = standardJavaFileManager.getJavaFileObjects(fileName);
// 获取编译任务的future接口CompilationTask 当调用它的call方法时,开始编译
JavaCompiler.CompilationTask compilationTask = javaCompiler.getTask(null, standardJavaFileManager, null, null, null, iterable);
compilationTask.call();
// 加载 指定路径的 .class文件
FileURLClassLoader fileURLClassLoader = new FileURLClassLoader("D:/upload/test");
Class<?> aClass = fileURLClassLoader.loadClass("MyTest");
//执行 main方法
Method method = aClass.getMethod("main", String[].class);
method.invoke(null, new Object[]{new String[0]});
3. 内存中编译执行
上面需要.java文件,编译.class文件,最后再类加载实现
这里实现内存中Java源码字符串编译执行
依赖
<dependency>
<groupId>org.mdkt.compiler</groupId>
<artifactId>InMemoryJavaCompiler</artifactId>
<version>1.3.0</version>
</dependency>
测试
public static void main(String[] args) throws Exception {
StringBuffer sourceCode = new StringBuffer();
sourceCode.append("package com.yl;\n");
sourceCode.append("public class Main {\n" +
"\tpublic static void main(String[] args) {\n" +
"\t\tSystem.out.println(\"Hello, World\");\n" +
"\t}\n" +
"}");
Class<?> helloClass = InMemoryJavaCompiler.newInstance().compile("com.yl.Main", sourceCode.toString());
//执行 main方法
Method method = helloClass.getMethod("main", String[].class);
method.invoke(null, new Object[]{new String[0]});
}
4. beanshell脚本实现动态编译执行
2中需要.java文件,编译.class文件,最后再类加载实现
3中内存中编译执行,但是执行慢,平均1-2s
思路:jmeter中有使用beanshell,jbpm也有用到,经测试执行代码几十毫秒,效率高
依赖
<dependency>
<groupId>org.apache-extras.beanshell</groupId>
<artifactId>bsh</artifactId>
<version>2.0b6</version>
</dependency>
测试
package com.yl.util;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 获取静态方法main执行结果
*
* @author liuxb
* @date 2023/3/20 23:21
*/
@Slf4j
public class StaticMainUtil {
/**
* 获取main方法执行结果
* @param clazz
* @return
*/
public static String getMain(Class<?> clazz) {
try {
//执行 main方法
Method method = clazz.getMethod("main", String[].class);
//输出流重定向
// 改变输出语句的位置(重定向)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
// 把系统打印流改成我们自己的打印流
System.setOut(ps);
method.invoke(null, new Object[]{new String[0]});
//必须恢复重定向
PrintStream printStream = printConsoleStream();
System.setOut(printStream);
log.info("main方法执行结果:{}", baos);
//输出打印结果
return baos.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 创建控制台输出流,参考 {@link System newPrintStream(java.io.FileOutputStream, java.lang.String)}
*/
private static PrintStream printConsoleStream() {
OutputStream fos = new FileOutputStream(FileDescriptor.out);
String enc = System.getProperty("sun.stdout.encoding");
if (enc != null) {
try {
return new PrintStream(new BufferedOutputStream(fos, 128), true, enc);
} catch (UnsupportedEncodingException uee) {}
}
return new PrintStream(new BufferedOutputStream(fos, 128), true);
}
}
//测试方法
public String bshCompiler(String code) {
Interpreter interpreter = new Interpreter();
try {
//beanshel解释器运行,返回Class
Class aClass = (Class) interpreter.eval(code);
//执行 main方法
return StaticMainUtil.getMain(aClass);
} catch (EvalError e) {
throw new RuntimeException(e);
}
}