如何以编程方式编译以字符串形式提供的Java代码的问题被问相当频繁和以各种形式,有时引用存储在数据库或由用户..当我搜索有关这方面的信息时,我偶然发现了许多这样的问题,并且失望地看到,一般的建议是使用外部工具(BeanShell,Groovy.)。这个亚当·佩恩特对这个问题的回答是最有帮助的,至少找出了相关的关键词。但即使通过咨询更多的外部资源(比如Java 2的示例),我很难实现一个或多个Java类的纯内存编译(实际上是这样的)。工作过)只使用JavaCompilerAPI
下面是一个例子,展示了在运行时在内存中编译一个或多个类的整个过程,当它们的源代码以字符串的形式给出时。它是围绕一个小的实用工具类建造的,RuntimeCompiler,它只接收序列类名和相应的源代码,然后允许编译这些类并获得Class物品。
这是一个MCVE可以直接编译和执行的JDK, 不带着JRE,因为后者不包含像JavaCompiler.import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.lang.reflect.Modifier;
import java.net.URI;import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.Comparator;
import java.util.LinkedHashMap;import java.util.List;import java.util.Map;import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;import javax.tools.FileObject;import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;import javax.tools.JavaCompiler.CompilationTask;import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;import javax.tools.SimpleJavaFileObject;import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;public class RuntimeCompilerExample{
public static void main(String[] args) throws Exception
{
simpleExample();
twoClassExample();
useLoadedClassExample();
}
private static void simpleExample()
{
String classNameA = "ExampleClass";
String codeA =
"public class ExampleClass {" + "\n" +
" public static void exampleMethod(String name) {" + "\n" +
" System.out.println(\"Hello, \"+name);" + "\n" +
" }" + "\n" +
"}" + "\n";
RuntimeCompiler r = new RuntimeCompiler();
r.addClass(classNameA, codeA);
r.compile();
MethodInvocationUtils.invokeStaticMethod(
r.getCompiledClass(classNameA),
"exampleMethod", "exampleParameter");
}
private static void twoClassExample()
{
String classNameA = "ExampleClassA";
String codeA =
"public class ExampleClassA {" + "\n" +
" public static void exampleMethodA(String name) {" + "\n" +
" System.out.println(\"Hello, \"+name);" + "\n" +
" }" + "\n" +
"}" + "\n";
String classNameB = "ExampleClassB";
String codeB =
"public class ExampleClassB {" + "\n" +
" public static void exampleMethodB(String name) {" + "\n" +
" System.out.println(\"Passing to other class\");" + "\n" +
" ExampleClassA.exampleMethodA(name);" + "\n" +
" }" + "\n" +
"}" + "\n";
RuntimeCompiler r = new RuntimeCompiler();
r.addClass(classNameA, codeA);
r.addClass(classNameB, codeB);
r.compile();
MethodInvocationUtils.invokeStaticMethod(
r.getCompiledClass(classNameB),
"exampleMethodB", "exampleParameter");
}
private static void useLoadedClassExample() throws Exception
{
String classNameA = "ExampleComparator";
String codeA =
"import java.util.Comparator;" + "\n" +
"public class ExampleComparator " + "\n" +
" implements Comparator {" + "\n" +
" @Override" + "\n" +
" public int compare(Integer i0, Integer i1) {" + "\n" +
" System.out.println(i0+\" and \"+i1);" + "\n" +
" return Integer.compare(i0, i1);" + "\n" +
" }" + "\n" +
"}" + "\n";
RuntimeCompiler r = new RuntimeCompiler();
r.addClass(classNameA, codeA);
r.compile();
Class> c = r.getCompiledClass("ExampleComparator");
Comparator comparator = (Comparator) c.newInstance();
System.out.println("Sorting...");
List list = new ArrayList(Arrays.asList(3,1,2));
Collections.sort(list, comparator);
System.out.println("Result: "+list);
}}class RuntimeCompiler{
private final JavaCompiler javaCompiler;
private final Map classData;
private final MapClassLoader mapClassLoader;
private final ClassDataFileManager classDataFileManager;
private final List compilationUnits;
public RuntimeCompiler()
{
this.javaCompiler = ToolProvider.getSystemJavaCompiler();
if (javaCompiler == null)
{
throw new NullPointerException(
"No JavaCompiler found. Make sure to run this with "
+ "a JDK, and not only with a JRE");
}
this.classData = new LinkedHashMap();
this.mapClassLoader = new MapClassLoader();
this.classDataFileManager =
new ClassDataFileManager(
javaCompiler.getStandardFileManager(null, null, null));
this.compilationUnits = new ArrayList();
}
public void addClass(String className, String code)
{
String javaFileName = className + ".java";
JavaFileObject javaFileObject =
new MemoryJavaSourceFileObject(javaFileName, code);
compilationUnits.add(javaFileObject);
}
boolean compile()
{
DiagnosticCollector diagnosticsCollector =
new DiagnosticCollector();
CompilationTask task =
javaCompiler.getTask(null, classDataFileManager,
diagnosticsCollector, null, null,
compilationUnits);
boolean success = task.call();
compilationUnits.clear();
for (Diagnostic> diagnostic : diagnosticsCollector.getDiagnostics())
{
System.out.println(
diagnostic.getKind() + " : " +
diagnostic.getMessage(null));
System.out.println(
"Line " + diagnostic.getLineNumber() +
" of " + diagnostic.getSource());
System.out.println();
}
return success;
}
public Class> getCompiledClass(String className)
{
return mapClassLoader.findClass(className);
}
private static final class MemoryJavaSourceFileObject extends
SimpleJavaFileObject
{
private final String code;
private MemoryJavaSourceFileObject(String fileName, String code)
{
super(URI.create("string:///" + fileName), Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors)
throws IOException
{
return code;
}
}
private class MapClassLoader extends ClassLoader
{
@Override
public Class> findClass(String name)
{
byte[] b = classData.get(name);
return defineClass(name, b, 0, b.length);
}
}
private class MemoryJavaClassFileObject extends SimpleJavaFileObject
{
private final String className;
private MemoryJavaClassFileObject(String className)
{
super(URI.create("string:///" + className + ".class"),
Kind.CLASS);
this.className = className;
}
@Override
public OutputStream openOutputStream() throws IOException
{
return new ClassDataOutputStream(className);
}
}
private class ClassDataFileManager extends
ForwardingJavaFileManager
{
private ClassDataFileManager(
StandardJavaFileManager standardJavaFileManager)
{
super(standardJavaFileManager);
}
@Override
public JavaFileObject getJavaFileForOutput(final Location location,
final String className, Kind kind, FileObject sibling)
throws IOException
{
return new MemoryJavaClassFileObject(className);
}
}
private class ClassDataOutputStream extends OutputStream
{
private final String className;
private final ByteArrayOutputStream baos;
private ClassDataOutputStream(String className)
{
this.className = className;
this.baos = new ByteArrayOutputStream();
}
@Override
public void write(int b) throws IOException
{
baos.write(b);
}
@Override
public void close() throws IOException
{
classData.put(className, baos.toByteArray());
super.close();
}
}}class MethodInvocationUtils{
public static Object invokeStaticMethod(
Class> c, String methodName, Object... args)
{
Method m = findFirstMatchingStaticMethod(c, methodName, args);
if (m == null)
{
throw new RuntimeException("No matching method found");
}
try
{
return m.invoke(null, args);
}
catch (IllegalAccessException e)
{
throw new RuntimeException(e);
}
catch (IllegalArgumentException e)
{
throw new RuntimeException(e);
}
catch (InvocationTargetException e)
{
throw new RuntimeException(e);
}
catch (SecurityException e)
{
throw new RuntimeException(e);
}
}
private static Method findFirstMatchingStaticMethod(
Class> c, String methodName, Object ... args)
{
Method methods[] = c.getDeclaredMethods();
for (Method m : methods)
{
if (m.getName().equals(methodName) &&
Modifier.isStatic(m.getModifiers()))
{
Class>[] parameterTypes = m.getParameterTypes();
if (areAssignable(parameterTypes, args))
{
return m;
}
}
}
return null;
}
private static boolean areAssignable(Class> types[], Object ...args)
{
if (types.length != args.length)
{
return false;
}
for (int i=0; i
{
Object arg = args[i];
Class> type = types[i];
if (arg != null && !type.isAssignableFrom(arg.getClass()))
{
return false;
}
}
return true;
}}根据评论编辑:
为了编译包含在外部JAR文件中的类,只需将JAR添加到classpath调用应用程序。这个JavaCompiler然后将选择这个类路径来查找编译所需的类。
似乎涉及到了一些魔法。至少,我还没有弄清楚这背后的确切机制,只是用一个例子对其进行了测试。
还有一个旁白:当然,我们可以从字面上考虑任意这个类的扩展。我的目标是创建一个简单、独立、易于复制和传递的示例,它显示了整个过程,甚至可能对某些应用程序模式“有用”。
对于更复杂的功能,可以考虑相应地扩展这个类,或者查看一下,例如,Java-Runtime-编译器来自OpenHFT项目(我在写了这个答案几周后偶然发现了这个问题)。它基本上在内部使用相同的技术,但以一种更复杂的方式使用,并且还提供了用于处理外部依赖项的类加载器的专用机制。