Java 5和Java 6的内存中即时编译Java代码

18

如何在Java 5和Java 6中从任意字符串(内存中)编译Java代码,加载它并运行其中的一个特定方法(预定义)?

在抨击我之前,请注意这一点:

  • 大多数依赖于Java 6编译器API。
  • 那些不依赖的则依赖于一些技巧。
  • 是的,我看了commons-jci。 要么我太愚钝而无法理解它的工作原理,要么它根本无法胜任。
  • 我找不到如何将我的当前类路径(非常庞大)提供给编译器。
  • 在一个可行的实现中(在Java 6中),我找不到如何正确加载内部类或内部匿名类。
  • 如果整个过程都在内存中进行,我会很满意,因为该程序在多个环境上运行。

我相信这个问题以前已经被解决过了,但是我在谷歌上找不到任何看起来像是生产质量的东西(除了JCI之外,就像我之前说的,我没办法使用它)。

编辑:

  • 我看了JavaAssist-我需要内部类、Java 5.0语言级别支持和使用整个类路径进行编译。此外,我想即时创建新类。也许我错了,但我找不到如何使用JavaAssit做到这一点。
  • 我愿意使用基于文件系统的解决方案(调用javac),但是我不知道如何获得类路径,也不知道如何稍后加载文件(这些文件不在我的类路径中)并使用可以回收的特殊类加载器多次调用它们。虽然我知道如何研究它,但我更喜欢一个现成的解决方案。

编辑2:

目前,我对BeanShell的“evaluate”功能感到满意。 显然,它可以满足我所有的需求(获取一个字符串,在“当前”类路径的情况下评估它)。 它确实缺少一些Java 5功能,但它可以使用枚举类型(不能定义)和已编译的“通用”(擦除)类,因此应该足以满足我的需求。

我暂时不想把答案标记为已接受,因为我希望有更好的解决方案出现。

编辑3:接受了beanshell建议 - 它确实非常有效。

6个回答

10
JCI看起来不错。以下代码片段应该是您的基础:
JavaCompiler compiler = new JavaCompilerFactory().createCompiler("eclipse");

MemoryResourceReader mrr = new MemoryResourceReader();
mrr.add("resource name string", yourJavaSourceString.getBytes());

MemoryResourceStore mrs = new MemoryResourceStore();

CompilationResult result = compiler.compile(sources, mrr, mrs);

// don't need the result, unless you care for errors/warnings
// the class should have been compiled to your destination dir

有没有任何原因会导致这个功能无法实现?
编辑:添加了MemoryResourceStore以将编译后的类输出发送到内存,如所请求的那样。
此外,在您的情况下设置javac设置(例如classpath)可以通过JavacJavaCompilerSettings类中的setCustomArguments(String[] pCustomArguments)完成。

没有什么特别的原因:
  1. 我找不到如何揭示类路径。如我之前所说,它非常庞大且相当复杂。我希望编译器使用“内存中”的类进行编译。
  2. 我将文件输出作为最后的手段,但我想首先探索内存解决方案。
- Ran Biron

8

您可能还想查看Janino。

从他们的网站上可以了解到:

Janino是一个编译器,它读取JavaTM表达式、块、类体、源文件或一组源文件,并生成JavaTM字节码,直接加载和执行。Janino不是开发工具,而是用于运行时编译的嵌入式编译器,例如表达式评估器或“服务器页面”引擎,如JSP。

http://www.janino.net/

我目前正在一个相当大的关键项目中使用它,效果很好。


忘了提到。我确实查看了Janino。正如我所说,我需要5.0功能以便与现有代码进行互操作。 - Ran Biron
Janino支持一些5.0功能,我只是不确定它是否涵盖了你所需的功能。祝你好运;我很想听听你最终选择了什么。 - Bill Rawlinson
Janino似乎比BeanShell更出色。很遗憾缺少1.5的功能。 - NateS

3
如果你不完全依赖于编译,像Beanshell、Groovy和其他脚本语言这样的解决方案很容易被嵌入(实际上,Java内置了插入脚本语言的支持,因此你的代码甚至不知道脚本是用哪种语言编写的)。
据我所知,Beanshell应该可以运行任何100%的Java代码,而我相信Groovy可以运行大多数Java代码,可能是全部。

这是一个很好的解决方案,但我仍然希望能有一个真正编译过程更好的解决方案。如果没有其他问题,我会将其标记为已接受。谢谢。 - Ran Biron
你知道吗,这个API一直都可用,我想是在这里:http://openjdk.java.net/groups/compiler/doc/package-overview/index.html 但它不在Java中,因此可能会发生变化(尽管我认为它没有太大的变化)。 - Bill K
1
我并不是说这是每个人的正确解决方案,但它完美地满足了我的需求。BSH 2.0确实可以创造奇迹。 - Ran Biron

1

我查看了JavaAssist,但它并不完全符合我的需求。我想要运行以字符串形式给出的代码,这些代码依赖于我的类 - 我无法找到使用JavaAssist实现这一点的方法。此外,JavaAssist的限制(没有5.0功能)对我来说相当受限制。 - Ran Biron

0

ECJ Eclipse Java Compiler

Eclipse提供并使用其自己的编译器,而不是javac

  • Eclipse编译器在IDE(Eclipse)内部使用
  • Eclipse编译器也可以作为纯批处理编译器在Eclipse之外使用

编译源文件

$ java -jar ecj-3.5.2.jar HelloWorld.java


0

在像Tomcat这样的Web容器中运行,首先生成一个JSP页面,然后调用它。

这也允许您通过简单地覆盖JSP页面来摆脱旧的类定义,而不是让您的类加载器缓慢地运行满。

"内存"要求是由于速度还是由于不更改代码库?


实际上,到目前为止我一直使用那种方法。它很笨重,但是它能够工作。但是,我讨厌它,而且客户也不信任它。我宁愿使用更简洁(或更好隐藏)的方法。 - Ran Biron
我敢打赌你会更讨厌类加载器问题的;-)要非常小心你所做的事情 :) - Thorbjørn Ravn Andersen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接