动态编译Java代码

Java使得在运行时编译Java代码成为可能…任何Java代码!

编译的入口点是ToolProvider类。它的Javadoc如下:

Provides methods for locating tool providers, for example, providers of compilers. This class complements the functionality of ServiceLoader.

翻译一下就是,提供用于定位工具提供者(例如,编译器的提供者)的方法。此类补充了ServiceLoader的功能。

从10年前发布的1.6版开始,此类就可以在Java中使用,但似乎已被很大程度上忽略了。

代码

示例代码片段:

public class EvilExecutor {
    private String readCode(String sourcePath) throws FileNotFoundException {
        InputStream stream = new FileInputStream(sourcePath);
        String separator = System.getProperty("line.separator");
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
        return reader.lines().collect(Collectors.joining(separator));
    }
    private Path saveSource(String source) throws IOException {
        String tmpProperty = System.getProperty("java.io.tmpdir");
        Path sourcePath = Paths.get(tmpProperty, "Harmless.java");
        Files.write(sourcePath, source.getBytes(UTF_8));
        return sourcePath;
    }
    private Path compileSource(Path javaFile) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        compiler.run(null, null, null, javaFile.toFile().getAbsolutePath());
        return javaFile.getParent().resolve("Harmless.class");
    }
    private void runClass(Path javaClass)
            throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        URL classUrl = javaClass.getParent().toFile().toURI().toURL();
        URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{classUrl});
        Class<?> clazz = Class.forName("Harmless", true, classLoader);
        clazz.newInstance();
    }
    public void doEvil(String sourcePath) throws Exception {
        String source = readCode(sourcePath);
        Path javaFile = saveSource(source);
        Path classFile = compileSource(javaFile);
        runClass(classFile);
    }
    public static void main(String... args) throws Exception {
        new EvilExecutor().doEvil(args[0]);
    }
}

解释:

  • readCode():从文件系统上的任意文件读取源代码,并将其作为字符串返回。另一种实现方式是从整个网络获取源。
  • saveSource():根据源代码在已启用读取的目录中创建一个新文件。该文件名是硬编码的,更完善的版本将解析code参数以创建一个根据其包含的类名命名的文件。
  • compileSource():从Java文件中编译类文件。
  • runClass:加载已编译的类并实例化一个新对象。为了独立于任何强制类型转换,应在外部源代码类的构造函数中设置要执行的代码。

问题

从功能的角度来看,与不提供此功能的其他语言相比,即时编译代码可提高Java语言的价值。从安全角度来看,这是一场噩梦! 能够在生产中执行任意代码的想法应该使任何IT组织(包括开发人员)(如果不是大多数)中的任何人都感到不寒而栗。

经验丰富的开发人员/操作人员或普通读者可能还记得Java安全管理器以及如何激活它:

java -Djava.security.manager -cp target/classes ch.frankel.blog.runtimecompile.EvilExecutor harmless.txt

执行上述命令行将产生以下结果:

java.lang.SecurityManager@4e25154f
Exception in thread "main" java.security.AccessControlException:
    access denied ("java.io.FilePermission" "harmless.txt" "read")
  at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
  at java.security.AccessController.checkPermission(AccessController.java:884)
  at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
  at java.lang.SecurityManager.checkRead(SecurityManager.java:888)
  at java.io.FileInputStream.<init>(FileInputStream.java:127)
  at java.io.FileInputStream.<init>(FileInputStream.java:93)
  at ch.frankel.blog.runtimecompile.EvilExecutor.readCode(EvilExecutor.java:19)
  at ch.frankel.blog.runtimecompile.EvilExecutor.doEvil(EvilExecutor.java:47)
  at ch.frankel.blog.runtimecompile.EvilExecutor.main(EvilExecutor.java:56)

结论

JVM提供了很多功能。与任何工具一起使用时,无论好坏,它们都可以使用。每个人都有责任确保自己的JVM的安全,在敏感领域——银行业、军事等领域,情况更是如此。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
动态编译 Java 代码可以使用 Java Compiler API,它提供了一系列的类和接口,可以在 Java 应用程序中动态编译 Java 代码。下面是一个简单的示例: ```java import javax.tools.JavaCompiler; import javax.tools.ToolProvider; public class CompilerExample { public static void main(String[] args) { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); int result = compiler.run(null, null, null, "Path/To/Your/Java/File.java"); if (result == 0) { System.out.println("Compilation successful"); } else { System.out.println("Compilation failed"); } } } ``` 上述代码中,我们首先通过 `ToolProvider.getSystemJavaCompiler()` 获取 JavaCompiler 对象,然后调用 `compiler.run()` 方法来编译 Java 代码。最后判断编译结果,如果返回值为 0,则表示编译成功。 接下来是生成 Jar 文件的示例代码: ```java import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; public class JarExample { public static void main(String[] args) { String[] files = {"Path/To/Your/Java/File.class"}; try { JarOutputStream jos = new JarOutputStream(new FileOutputStream(new File("Path/To/Your/Jar/File.jar"))); for (String file : files) { JarEntry entry = new JarEntry(new File(file).getName()); jos.putNextEntry(entry); jos.write(readFile(file)); jos.closeEntry(); } jos.close(); System.out.println("Jar file created successfully"); } catch (IOException e) { e.printStackTrace(); } } private static byte[] readFile(String file) throws IOException { FileInputStream fis = new FileInputStream(file); byte[] data = new byte[fis.available()]; fis.read(data); fis.close(); return data; } } ``` 上述代码中,我们首先指定需要打包的文件路径,然后使用 JarOutputStream 创建 Jar 文件并写入文件。最后,关闭 JarOutputStream,Jar 文件就生成了。 需要注意的是,这里只是一个简单的示例,实际应用中可能需要更加复杂的操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值