动态编译
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";// 获取JavaFileObjectJavaFileObject 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) {@Overridepublic 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");}}
gitee源码
git clone https://gitee.com/dchh/JavaStudyWorkSpaces.git