动态编译
JAVA 6 引入了动态编译机制。Java 动态编译是指在运行时将Java源代码编译成可执行的字节码。这通常使用Java的内置编译器API javax.tools.JavaCompiler 来实现。
动态编译的应用场景
- 可以做一个浏览器编写java代码,上传服务器编译和运行的在线测评系统
- 服务器动态加载某些类文件进行编译
动态编译的两种实现方式
-
JAVA6之前,可以通过Runtime调用javac,启动新的进程去操作
Runtime run = Runtime.getRuntime(); Process process = run.exec("javac D:/myjava/demo/HelloWorld.java");
-
JAVA6之后,可以使用JavaCompiler实现
public static in compileFile(String sourceFile){ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); int result = compiler.run(null,null,null,sourceFile); System.out.println(result == 0 ? "编译通过" : "编译失败") return result; }
compiler.run(null,null,null,sourceFile)参数说明:
第一个参数:为java编译器提供参数
第二个参数:得到Java编译器的输出信息
第三个参数:接受编译器的错误信息
第四个参数:可变参数(是一个String[]数组)能传入一个或多个Java源文件
返回值:0 表示编译成 ,非0 表示编译失败
使用runtime.exec方法编译
package demo2;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
/**
* 使用Runtime编译,通过反射调用main方法
*
* @author Anna.
* @date 2024/4/4 11:13
*/
public class DyanmaicDemo {
public static void main(String[] args) throws Exception {
String path = DyanmaicDemo.class.getResource("").getPath();
String javaName = "Hello";
// 创建文件
File tempFile = createFile(path, javaName);
// 使用Runtime 编译
compilation(tempFile);
// 反射执行文件
extracted(path, javaName);
}
/**
* 创建文件
*
* @param path
* @param javaName
* @return java.io.File
* @author Anna.
* @date 2024/4/4 13:05
*/
private static File createFile(String path, String javaName) throws IOException {
// 创建编译内容
StringBuffer sb = new StringBuffer();
sb.append("public class ").append(javaName).append("{")
.append("public static void main(String[] args){")
.append("System.out.println(\"hello world !!!\");")
.append("}")
.append("}");
// 写入临时文件
File tempFile = new File(path + javaName + ".java");
FileWriter fileWriter = new FileWriter(tempFile);
fileWriter.write(sb.toString());
fileWriter.close();
return tempFile;
}
/**
* 编译
*
* @param tempFile
* @return void
* @author Anna.
* @date 2024/4/4 13:05
*/
private static void compilation(File tempFile) throws IOException, InterruptedException {
String replace = tempFile.getAbsolutePath().replace(tempFile.getName(), "");
replace = replace.substring(0, replace.length() - 1);
String str = "javac " + tempFile.getAbsolutePath();
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(str);
// 读取命令的标准输出
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// 读取命令的错误输出
BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
while ((line = errorReader.readLine()) != null) {
System.err.println(line);
}
// 等待进程结束并获取退出值
int exitValue = process.waitFor();
if (exitValue == 0) {
System.out.println("Compilation is successful");
} else {
System.out.println("Compilation Failed");
}
}
/**
* 反射执行main方法
*
* @param path
* @param javaName
* @return void
* @author Anna.
* @date 2024/4/4 13:05
*/
private static void extracted(String path, String javaName) throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:/" + path)});
Class<?> clazz = urlClassLoader.loadClass(javaName);
Method mainMethod = clazz.getMethod("main", String[].class);
// 注意:由于可变参数是JDK5之后才有的,下面代码如果new String[]{"1","2"}强制在转换才Object,则会被编译成 mainMethod.invoke(null,"1","2"),从而导致找不到方法。
// 因此,如果传参则徐亚加上(Object),避免这个问题
mainMethod.invoke(null, (Object) new String[]{"1", "2"});
}
}
执行结果
使用JavaCompiler实现
package demo1;
import javax.tools.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Collections;
/**
* 使用JavaCompiler编译,通过反射调用main方法
*
* @author Anna.
* @date 2024/4/4 10:55
*/
public class DyanmicDemo1 {
public static void main(String[] args) throws Exception {
// 创建编译内容
String javaName = "Hello";
// 获取JavaFileObject
JavaFileObject source = getJavaFileObject(javaName);
// 编译
compiler(source);
// 执行
extracted(javaName);
}
/**
* 获取JavaFileObject
*
* @param javaName
* @return javax.tools.JavaFileObject
* @author Anna.
* @date 2024/4/4 13:56
*/
private static JavaFileObject getJavaFileObject(String javaName) {
StringBuffer sb = new StringBuffer();
sb.append("public class ").append(javaName).append("{")
.append("public static void main(String[] args){")
.append("System.out.println(\"hello world !!!\");")
.append("}")
.append("}");
// 创建一个Java源代码文件 string:///是一个特殊的URI协议,用于表示源代码内容直接来自一个字符串,而不是来自文件系统中的一个文件。 好处是,你可以完全在内存中处理源代码,无需涉及文件系统的I/O操作。
JavaFileObject source = new SimpleJavaFileObject(URI.create("string:///" + javaName + ".java"), JavaFileObject.Kind.SOURCE) {
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return sb.toString();
}
};
return source;
}
/**
* 编译
*
* @param source
* @return void
* @author Anna.
* @date 2024/4/4 13:49
*/
private static void compiler(JavaFileObject source) {
// 获取系统Java编译器
JavaCompiler systemJavaCompiler = ToolProvider.getSystemJavaCompiler();
// 创建诊断收集器
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
// 创建编译任务
StandardJavaFileManager fileManager = systemJavaCompiler.getStandardFileManager(diagnostics, null, null);
// 设置输出目录
Iterable<? extends File> locations = fileManager.getLocation(StandardLocation.CLASS_OUTPUT);
try {
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(new File(DyanmicDemo1.class.getClassLoader().getResource("").getPath())));
} catch (IOException e) {
e.printStackTrace();
return;
}
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(source);
JavaCompiler.CompilationTask task = systemJavaCompiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits);
// 执行编译任务
boolean success = task.call();
// 关闭文件管理器
try {
fileManager.close();
} catch (IOException e) {
e.printStackTrace();
}
// 处理编译诊断信息
for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) {
System.out.println(diagnostic);
System.out.println(diagnostic.getKind() + ": " + diagnostic.getMessage(null));
}
System.out.println(success ? "编译通过" : "编译失败");
}
/**
* 反射执行
*
* @param javaName
* @return void
* @author Anna.
* @date 2024/4/4 13:50
*/
private static void extracted(String javaName) throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:/" + DyanmicDemo1.class.getClassLoader().getResource("").getPath())});
Class<?> clazz = urlClassLoader.loadClass(javaName);
Method mainMethod = clazz.getMethod("main", String[].class);
// 注意:由于可变参数是JDK5之后才有的,下面代码如果new String[]{"1","2"}强制在转换才Object,则会被编译成 mainMethod.invoke(null,"1","2"),从而导致找不到方法。
// 因此,如果传参则徐亚加上(Object),避免这个问题
mainMethod.invoke(null, (Object) new String[]{"1", "2"});
}
}
执行结果
注意:
Runtime不仅仅是可以用于执行javac,当然可以用来执行其他命令,这里就不进一步说明了
CommandUtil调用系统命令工具类
import org.apache.commons.exec.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* 调用系统命令工具类
*
* @author Anna.
* @date 2021/9/8 16:05
*/
public class CommandUtil {
private static Logger logger = LoggerFactory.getLogger(CommandUtil.class);
private static final String DEFAULT_CHARSET = "UTF-8";
private static final Long TIMEOUT = 10000L;
/**
* 执行指定命令
*
* @param command 命令
* @return 命令执行完成返回结果
* @throws RuntimeException 失败时抛出异常,由调用者捕获处理
*/
public synchronized static String exeCommand(String command) throws RuntimeException {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
int exitCode = exeCommand(command, out);
if (exitCode == 0) {
logger.info("命令运行成功:" + System.currentTimeMillis());
} else {
logger.info("命令运行失败:" + System.currentTimeMillis());
}
return out.toString(DEFAULT_CHARSET);
} catch (Exception e) {
logger.info(e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
/**
* 执行指定命令,输出结果到指定输出流中
*
* @param command 命令
* @param out 执行结果输出流
* @return 执行结果状态码:执行成功返回0
* @throws ExecuteException 失败时抛出异常,由调用者捕获处理
* @throws IOException 失败时抛出异常,由调用者捕获处理
*/
public synchronized static int exeCommand(String command, OutputStream out) throws ExecuteException, IOException {
CommandLine commandLine = CommandLine.parse(command);
PumpStreamHandler pumpStreamHandler = null;
if (null == out) {
pumpStreamHandler = new PumpStreamHandler();
} else {
pumpStreamHandler = new PumpStreamHandler(out);
}
// 设置超时时间为10秒
ExecuteWatchdog watchdog = new ExecuteWatchdog(TIMEOUT);
DefaultExecutor executor = new DefaultExecutor();
executor.setStreamHandler(pumpStreamHandler);
executor.setWatchdog(watchdog);
return executor.execute(commandLine);
}
public static void main(String[] args) {
String out = null;
try {
// out = CommandUtil.exeCommand("ipconfig");
out = CommandUtil.exeCommand("D:\\SoftWare\\ffmpeg\\bin\\ffmpeg.exe -y -i D:/test/1631090617181_191979483992900.mp3 -acodec pcm_s16le -f s16le -ac 1 -ar 16000 D:/test/PCM1631090617181_191979483992900.mp3.pcm");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(out);
//CommandUtil.executeCommand("kill -9 3104");
}
}