00-JAVA基础-动态编译

本文详细介绍了Java6引入的动态编译机制,探讨了使用Runtime.exec和JavaCompiler两种方式实现动态编译,并通过示例展示了如何在运行时编译Java源代码。
摘要由CSDN通过智能技术生成

动态编译

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");
    }


}

gitee源码

git clone https://gitee.com/dchh/JavaStudyWorkSpaces.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丨Anna丨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值