用javax.tools.JavaCompiler在内存中完全编译代码
我正在使用javax.tools包(JDK 1.7)中的JavaCompiler来编译一些东西,如下所示:
compiler.run(null, null, "-cp", paths, "path/to/my/file.java");
它的工作原理,但我想这样做在内存中(例如传递一个string的代码,而不是源文件,并获取字节码不是.class文件)。 我发现扩展InputStream
和OutputStream
参数是没用的,因为它可能和控制台中的一样。 你知道一种方法来使运行方法像这样工作吗? 或者你知道用getTask()
方法做到这一点的确认方式? (扩展FileManager看起来很容易,但并不那么容易:)
我已经在Mac OS Java 7中运行了上述代码。它们都不起作用。 所以我写了一个https://github.com/trung/InMemoryJavaCompiler
StringBuffer sourceCode = new StringBuffer(); sourceCode.append("package org.mdkt;\n"); sourceCode.append("public class HelloClass {\n"); sourceCode.append(" public String hello() { return \"hello\"; }"); sourceCode.append("}"); Class<?> helloClass = InMemoryJavaCompiler.compile("org.mdkt.HelloClass", sourceCode.toString());
我想这里可能有帮助,它基本上显示了如何从内存中编译Java源代码(该string位于类中)。
它使用PrinterWriter
和StringWriter
将源代码写入内存中的String
中,然后使用JavaCompiler
类(自JDK 6开始)编译并运行该程序:
import javax.tools.Diagnostic; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaFileObject; import javax.tools.SimpleJavaFileObject; import javax.tools.ToolProvider; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; public class CompileSourceInMemory { public static void main(String args[]) throws IOException { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>(); StringWriter writer = new StringWriter(); PrintWriter out = new PrintWriter(writer); out.println("public class HelloWorld {"); out.println(" public static void main(String args[]) {"); out.println(" System.out.println(\"This is in another java file\");"); out.println(" }"); out.println("}"); out.close(); JavaFileObject file = new JavaSourceFromString("HelloWorld", writer.toString()); Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file); CompilationTask task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits); boolean success = task.call(); for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { System.out.println(diagnostic.getCode()); System.out.println(diagnostic.getKind()); System.out.println(diagnostic.getPosition()); System.out.println(diagnostic.getStartPosition()); System.out.println(diagnostic.getEndPosition()); System.out.println(diagnostic.getSource()); System.out.println(diagnostic.getMessage(null)); } System.out.println("Success: " + success); if (success) { try { URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { new File("").toURI().toURL() }); Class.forName("HelloWorld", true, classLoader).getDeclaredMethod("main", new Class[] { String[].class }).invoke(null, new Object[] { null }); } catch (ClassNotFoundException e) { System.err.println("Class not found: " + e); } catch (NoSuchMethodException e) { System.err.println("No such method: " + e); } catch (IllegalAccessException e) { System.err.println("Illegal access: " + e); } catch (InvocationTargetException e) { System.err.println("Invocation target: " + e); } } } } class JavaSourceFromString extends SimpleJavaFileObject { final String code; JavaSourceFromString(String name, String code) { super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension),Kind.SOURCE); this.code = code; } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return code; } }
如果你看一下参考链接,你还会发现其他一些例子
参考:
- 从内存编译
这是一个完全在内存中编译的类。
我几乎从Rekha Kumari(2011年6月)的http://javapracs.blogspot.de/2011/06/dynamic-in-memory-compilation-using.html中获得了这些内容。; 虽然这个版本比100行更短,并且有更多的function(但是没有文档:P)。
编辑:因为我刚刚得到2 upvotes,我想回来,编辑我的新版本:它一次编译多个类,唯一的方法来编译相互依赖的类。 如果您想了解“CompilerFeedback”这个类:我正在编写一个小型的Java IDE,用于需要的编码游戏。 我在这里包括它,因为我认为你想用这个做一些事情,而简化可能会帮助你。 编辑:而且我意识到CompilerFeedback类中的一些代码是完全废话。 我最近刚刚开始这个项目,但是这个class从一个多年的尝试中回收。
编辑:你好,另一个upvote! 添加一个从类的源代码派生完整类名的方法(包括包名,如果提供的话)。 非常有用的调用编译器,这需要这些信息。
编辑:添加一个MISSING方法,确定完整的类名的方法。 (抱歉)
演示课程:
import java.util.ArrayList; import java.util.List; public class Demo { public static void main(final String[] args) { final InMemoryCompiler.IMCSourceCode cls1source; final InMemoryCompiler.IMCSourceCode cls2source; final StringBuilder sb = new StringBuilder(); sb.append("package toast;\n"); sb.append("public class DynaClass {\n"); sb.append(" public static void main(final String[] args) {"); sb.append(" System.out.println(\"Based massively on the work of Rekha Kumari, http://javapracs.blogspot.de/2011/06/dynamic-in-memory-compilation-using.html\");\n"); sb.append(" System.out.println(\"This is the main method speaking.\");\n"); sb.append(" System.out.println(\"Args: \" + java.util.Arrays.toString(args));\n"); sb.append(" final Test test = new Test();\n"); sb.append(" }\n"); sb.append(" public String toString() {\n"); sb.append(" return \"Hello, I am \" + "); sb.append("this.getClass().getSimpleName();\n"); sb.append(" }\n"); sb.append("}\n"); cls1source = new InMemoryCompiler.IMCSourceCode("toast.DynaClass", sb.toString()); sb.setLength(0); sb.append("package toast;\n"); sb.append("public class Test {\n"); sb.append(" public Test() {\n"); sb.append(" System.out.println(\"class Test constructor reporting in.\");\n"); sb.append(" System.out.println(new DynaClass());\n"); sb.append(" }\n"); sb.append("}\n"); cls2source = new InMemoryCompiler.IMCSourceCode("toast.Test", sb.toString()); final List<InMemoryCompiler.IMCSourceCode> classSources = new ArrayList<>(); classSources.add(cls1source); classSources.add(cls2source); final InMemoryCompiler uCompiler = new InMemoryCompiler(classSources); final CompilerFeedback compilerFeedback = uCompiler.compile(); System.out.println("\n\nCOMPILER FEEDBACK: " + compilerFeedback); if (compilerFeedback != null && compilerFeedback.success) { try { System.out.println("\nTOSTRING DEMO:"); uCompiler.runToString(cls1source.fullClassName); } catch (Exception e) { e.printStackTrace(); } try { System.out.println("\nMAIN DEMO:"); uCompiler.runMain(cls1source.fullClassName, new String[] { "test1", "test2" }); } catch (Exception e) { e.printStackTrace(); } } System.exit(0); } }
编译器类别:
import javax.tools.*; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import java.security.SecureClassLoader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * MASSIVELY based on http://javapracs.blogspot.de/2011/06/dynamic-in-memory-compilation-using.html by Rekha Kumari * (June 2011) */ final public class InMemoryCompiler { final public static class IMCSourceCode { final public String fullClassName; final public String sourceCode; /** * @param fullClassName Full name of the class that will be compiled. If the class should be in some package, * fullName should contain it too, for example: "testpackage.DynaClass" * @param sourceCode the source code */ public IMCSourceCode(final String fullClassName, final String sourceCode) { this.fullClassName = fullClassName; this.sourceCode = sourceCode; } } final public boolean valid; final private List<IMCSourceCode> classSourceCodes; final private JavaFileManager fileManager; public InMemoryCompiler(final List<IMCSourceCode> classSourceCodes) { this.classSourceCodes = classSourceCodes; final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); if (compiler == null) { fileManager = null; valid = false; System.err.println("ToolProvider.getSystemJavaCompiler() returned null! This program needs to be run on a system with an installed JDK."); return; } valid = true; fileManager = new ForwardingJavaFileManager<JavaFileManager>(compiler.getStandardFileManager(null, null, null)) { final private Map<String, ByteArrayOutputStream> byteStreams = new HashMap<>(); @Override public ClassLoader getClassLoader(final Location location) { return new SecureClassLoader() { @Override protected Class<?> findClass(final String className) throws ClassNotFoundException { final ByteArrayOutputStream bos = byteStreams.get(className); if (bos == null) { return null; } final byte[] b = bos.toByteArray(); return super.defineClass(className, b, 0, b.length); } }; } @Override public JavaFileObject getJavaFileForOutput(final Location location, final String className, final JavaFileObject.Kind kind, final FileObject sibling) throws IOException { return new SimpleJavaFileObject(URI.create("string:///" + className.replace('.', '/') + kind.extension), kind) { @Override public OutputStream openOutputStream() throws IOException { ByteArrayOutputStream bos = byteStreams.get(className); if (bos == null) { bos = new ByteArrayOutputStream(); byteStreams.put(className, bos); } return bos; } }; } }; } public CompilerFeedback compile() { if (!valid) { return null; } final List<JavaFileObject> files = new ArrayList<>(); for (IMCSourceCode classSourceCode : classSourceCodes) { URI uri = null; try { uri = URI.create("string:///" + classSourceCode.fullClassName.replace('.', '/') + JavaFileObject.Kind.SOURCE.extension); } catch (Exception e) { // e.printStackTrace(); } if (uri != null) { final SimpleJavaFileObject sjfo = new SimpleJavaFileObject(uri, JavaFileObject.Kind.SOURCE) { @Override public CharSequence getCharContent(final boolean ignoreEncodingErrors) { return classSourceCode.sourceCode; } }; files.add(sjfo); } } final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>(); final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); if (files.size() > 0) { final JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null, null, files); return new CompilerFeedback(task.call(), diagnostics); } else { return null; } } public void runToString(final String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException { if (!valid) { return; } final Class<?> theClass = getCompiledClass(className); final Object instance = theClass.newInstance(); System.out.println(instance); } public void runMain(final String className, final String[] args) throws IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException { if (!valid) { return; } final Class<?> theClass = getCompiledClass(className); final Method mainMethod = theClass.getDeclaredMethod("main", String[].class); mainMethod.invoke(null, new Object[] { args }); } public Class<?> getCompiledClass(final String className) throws ClassNotFoundException { if (!valid) { throw new IllegalStateException("InMemoryCompiler instance not usable because ToolProvider.getSystemJavaCompiler() returned null: No JDK installed."); } final ClassLoader classLoader = fileManager.getClassLoader(null); final Class<?> ret = classLoader.loadClass(className); if (ret == null) { throw new ClassNotFoundException("Class returned by ClassLoader was null!"); } return ret; } }
COMPILERFEEDBACK类别:
import javax.tools.Diagnostic; import javax.tools.DiagnosticCollector; import javax.tools.JavaFileObject; import javax.tools.SimpleJavaFileObject; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; final public class CompilerFeedback { final public boolean success; final public List<CompilerMessage> messages = new ArrayList<>(); public CompilerFeedback(final Boolean success, final DiagnosticCollector<JavaFileObject> diagnostics) { this.success = success != null && success; for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) { messages.add(new CompilerMessage(diagnostic)); } } public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("SUCCESS: ").append(success).append('\n'); final int iTop = messages.size(); for (int i = 0; i < iTop; i++) { sb.append("\n[MESSAGE ").append(i + 1).append(" OF ").append(iTop).append("]\n\n"); // sb.append(messages.get(i).toString()).append("\n"); // sb.append(messages.get(i).toStringForList()).append("\n"); sb.append(messages.get(i).toStringForDebugging()).append("\n"); } return sb.toString(); } final public static class CompilerMessage { final public Diagnostic<? extends JavaFileObject> compilerInfo; final public String typeOfProblem; final public String typeOfProblem_forDebugging; final public String multiLineMessage; final public int lineNumber; final public int columnNumber; final public int textHighlightPos_lineStart; final public int textHighlightPos_problemStart; final public int textHighlightPos_problemEnd; final public String sourceCode; final public String codeOfConcern; final public String codeOfConcernLong; CompilerMessage(final Diagnostic<? extends JavaFileObject> diagnostic) { final JavaFileObject sourceFileObject = diagnostic.getSource(); String sourceCodePreliminary = null; if (sourceFileObject instanceof SimpleJavaFileObject) { final SimpleJavaFileObject simpleSourceFileObject = (SimpleJavaFileObject) sourceFileObject; try { final CharSequence charSequence = simpleSourceFileObject.getCharContent(false); sourceCodePreliminary = charSequence.toString(); } catch (IOException e) { e.printStackTrace(); } } if (sourceCodePreliminary == null) { sourceCode = "[SOURCE CODE UNAVAILABLE]"; } else { sourceCode = sourceCodePreliminary; } compilerInfo = diagnostic; typeOfProblem = diagnostic.getKind().name(); typeOfProblem_forDebugging = "toString() = " + diagnostic.getKind().toString() + "; name() = " + typeOfProblem; lineNumber = (int) compilerInfo.getLineNumber(); columnNumber = (int) compilerInfo.getColumnNumber(); final int sourceLen = sourceCode.length(); textHighlightPos_lineStart = (int) Math.min(Math.max(0, diagnostic.getStartPosition()), sourceLen); textHighlightPos_problemStart = (int) Math.min(Math.max(0, diagnostic.getPosition()), sourceLen); textHighlightPos_problemEnd = (int) Math.min(Math.max(0, diagnostic.getEndPosition()), sourceLen); final StringBuilder reformattedMessage = new StringBuilder(); final String message = diagnostic.getMessage(Locale.US); final int messageCutOffPosition = message.indexOf("location:"); final String[] messageParts; if (messageCutOffPosition >= 0) { messageParts = message.substring(0, messageCutOffPosition).split("\n"); } else { messageParts = message.split("\n"); } for (String s : messageParts) { String s2 = s.trim(); if (s2.length() > 0) { boolean lengthChanged; do { final int lBeforeReplace = s2.length(); s2 = s2.replace(" ", " "); lengthChanged = (s2.length() != lBeforeReplace); } while (lengthChanged); reformattedMessage.append(s2).append("\n"); } } codeOfConcern = sourceCode.substring(textHighlightPos_problemStart, textHighlightPos_problemEnd); codeOfConcernLong = sourceCode.substring(textHighlightPos_lineStart, textHighlightPos_problemEnd); if (!codeOfConcern.isEmpty()) { reformattedMessage.append("Code of concern: \"").append(codeOfConcern).append('\"'); } multiLineMessage = reformattedMessage.toString(); } public String toStringForList() { if (compilerInfo == null) { return "No compiler!"; } else { return compilerInfo.getCode(); } } public String toStringForDebugging() { final StringBuilder ret = new StringBuilder(); ret.append("Type of problem: ").append(typeOfProblem_forDebugging).append("\n\n"); ret.append("Message:\n").append(multiLineMessage).append("\n\n"); ret.append(compilerInfo.getCode()).append("\n\n"); ret.append("line number: ").append(lineNumber).append("\n"); ret.append("column number: ").append(columnNumber).append("\n"); ret.append("textHighlightPos_lineStart: ").append(textHighlightPos_lineStart).append("\n"); ret.append("textHighlightPos_problemStart: ").append(textHighlightPos_problemStart).append("\n"); ret.append("textHighlightPos_problemEnd: ").append(textHighlightPos_problemEnd).append("\n"); return ret.toString(); } @Override public String toString() { // return compilerInfo.getMessage(Locale.US); return typeOfProblem + ": " + multiLineMessage + "\n"; } } }
实用方法(三个类不需要):
final public static String PREFIX_CLASSNAME = "class "; final public static String PREFIX_PACKAGENAME = "package "; final public static String CHARSET_JAVAKEYWORDENDERS = " \n[](){}<>;,\"\\/*+-=%!&?@:"; /** * @return eg "com.dreamspacepresident.TestClass" if the source's first root level "class" (I'm talking about {} * hierarchy.) is named "TestClass", and if the "package" name is "com.dreamspacepresident". Null is returned if * sourceCode is null or does not provide a class name. (Mind that the parsing is done in a quite crappy way.) */ public static String deriveFullClassNameFromSource(final String sourceCode) { if (sourceCode == null) { return null; } final int firstBr = sourceCode.indexOf('{'); if (firstBr >= 0) { // DETERMINE CLASS NAME final int firstClass = sourceCode.indexOf(PREFIX_CLASSNAME); if (firstClass < firstBr) { String className = sourceCode.substring(firstClass + PREFIX_CLASSNAME.length(), firstBr).trim(); final int classNameEnd = indexOfAnyOfThese(className, CHARSET_JAVAKEYWORDENDERS); if (classNameEnd >= 0) { className = className.substring(0, classNameEnd); } if (!className.isEmpty()) { // DETERMINE PACKAGE NAME String packageName = null; final int firstPackage = sourceCode.indexOf(PREFIX_PACKAGENAME); if (firstPackage >= 0 && firstPackage < firstBr && firstPackage < firstClass) { packageName = sourceCode.substring(firstPackage + PREFIX_PACKAGENAME.length(), firstBr).trim(); final int packageNameEnd = indexOfAnyOfThese(packageName, CHARSET_JAVAKEYWORDENDERS); if (packageNameEnd >= 0) { packageName = packageName.substring(0, packageNameEnd); } } return (packageName != null && !packageName.isEmpty() ? packageName + "." : "") + className; } } } return null; } /** * Looks for the first occurrence of ANY of the given characters, which is easier than using a bunch of * String.indexOf() calls. * * @return -1 if not found, otherwise the String index of the first hit */ public static int indexOfAnyOfThese(final String text, final String characters) { if (text != null && !text.isEmpty() && characters != null && !characters.isEmpty()) { final int lenT = text.length(); final int lenC = characters.length(); for (int i = 0; i < lenT; i++) { final char c = text.charAt(i); for (int ii = 0; ii < lenC; ii++) { if (c == characters.charAt(ii)) { return i; } } } } return -1; }
几年前我写了一个图书馆做这个。 它需要一个可以包含嵌套类的string,编译它们并可select地将它们加载到当前的类加载器中(所以你不需要额外的类加载器)如果JVM以debugging模式运行,它会将生成的代码写入文件所以你可以通过你生成的代码。
http://vanillajava.blogspot.co.uk/2010_11_01_archive.html
用erolagnab
来解释erolagnab
你可以做的例子
StringBuilder sourceCode = new StringBuilder(); sourceCode.append("package org.mdkt;\n") .append("public class HelloClass {\n") .append(" public String hello() { return \"hello\"; }") .append("}"); Class<?> helloClass = CACHED_COMPILER.compile("org.mdkt.HelloClass", sourceCode.toString());
更新,来源可在这里https://github.com/OpenHFT/Java-Runtime-Compiler
你可以通过maven http://search.maven.org/#browse%7C842970587获得最新版本;
一个更长的例子。
// this writes the file to disk only when debugging is enabled. CachedCompiler cc = CompilerUtils.DEBUGGING ? new CachedCompiler(new File(parent, "src/test/java"), new File(parent, "target/compiled")) : CompilerUtils.CACHED_COMPILER; String text = "generated test " + new Date(); Class fooBarTeeClass = cc.loadFromJava("eg.FooBarTee", "package eg;\n" + '\n' + "import eg.components.BarImpl;\n" + "import eg.components.TeeImpl;\n" + "import eg.components.Foo;\n" + '\n' + "public class FooBarTee{\n" + " public final String name;\n" + " public final TeeImpl tee;\n" + " public final BarImpl bar;\n" + " public final BarImpl copy;\n" + " public final Foo foo;\n" + '\n' + " public FooBarTee(String name) {\n" + " // when viewing this file, ensure it is synchronised with the copy on disk.\n" + " System.out.println(\"" + text + "\");\n" + " this.name = name;\n" + '\n' + " tee = new TeeImpl(\"test\");\n" + '\n' + " bar = new BarImpl(tee, 55);\n" + '\n' + " copy = new BarImpl(tee, 555);\n" + '\n' + " // you should see the current date here after synchronisation.\n" + " foo = new Foo(bar, copy, \"" + text + "\", 5);\n" + " }\n" + '\n' + " public void start() {\n" + " }\n" + '\n' + " public void stop() {\n" + " }\n" + '\n' + " public void close() {\n" + " stop();\n" + '\n' + " }\n" + "}\n"); // add a debug break point here and step into this method. FooBarTee fooBarTee = new FooBarTee("test foo bar tee"); Foo foo = fooBarTee.foo; assertNotNull(foo); assertEquals(text, foo.s);
我想了:
- 内存中编译的Java文件 (对于Java作为脚本语言非常有用)
- 没有额外的依赖(易于设置)
- 以尽可能less的文件实现(易于纳入项目)
您可以先尝试一下: http : //ideone.com/cu1GhE#view_edit_box
以下代码基于Rekha Kumari代码:
Main.java
package com.mycompany.java; //import org.slf4j.Logger; //import org.slf4j.LoggerFactory; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Main { //private static final Logger logger = LoggerFactory.getLogger(Main.class); public static void main(String[] args) { try { StringWriter writer = new StringWriter(); PrintWriter out = new PrintWriter(writer); out.println("package com.mycompany.script;"); out.println(""); out.println("public class HelloWorld {"); out.println(" public static void main(String args[]) {"); out.println(" System.out.println(\"This is in another java file\");"); out.println(" }"); out.println("}"); out.close(); String fullName = "com.mycompany.script.HelloWorld"; String src = writer.toString(); DynamicCompiler uCompiler = new DynamicCompiler(fullName, src); uCompiler.compile(); uCompiler.run(); } catch (Exception e) { //logger.error("Exception:", e); System.out.print("Exception"); } } }
DynamicCompiler.java
package com.mycompany.java; //import org.slf4j.Logger; //import org.slf4j.LoggerFactory; import javax.tools.*; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.security.SecureClassLoader; import java.util.ArrayList; import java.util.List; // Based on: http://javapracs.blogspot.cz/2011/06/dynamic-in-memory-compilation-using.html public class DynamicCompiler { //private static final Logger logger = LoggerFactory.getLogger(DynamicCompiler.class); private JavaFileManager fileManager; private String fullName; private String sourceCode; public DynamicCompiler(String fullName, String srcCode) { this.fullName = fullName; this.sourceCode = srcCode; this.fileManager = initFileManager(); } public JavaFileManager initFileManager() { if (fileManager != null) return fileManager; else { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); fileManager = new ClassFileManager(compiler .getStandardFileManager(null, null, null)); return fileManager; } } public void compile() { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); List<JavaFileObject> files = new ArrayList<>(); files.add(new CharSequenceJavaFileObject(fullName, sourceCode)); compiler.getTask( null, fileManager, null, null, null, files ).call(); } public void run() throws InstantiationException, IllegalAccessException, ClassNotFoundException { try { fileManager .getClassLoader(null) .loadClass(fullName) .getDeclaredMethod("main", new Class[]{String[].class}) .invoke(null, new Object[]{null}); } catch (InvocationTargetException e) { System.out.print("InvocationTargetException"); //logger.error("InvocationTargetException:", e); } catch (NoSuchMethodException e) { System.out.print("NoSuchMethodException "); //logger.error("NoSuchMethodException:", e); } } public class CharSequenceJavaFileObject extends SimpleJavaFileObject { /** * CharSequence representing the source code to be compiled */ private CharSequence content; public CharSequenceJavaFileObject(String className, CharSequence content) { super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); this.content = content; } public CharSequence getCharContent(boolean ignoreEncodingErrors) { return content; } } public class ClassFileManager extends ForwardingJavaFileManager { private JavaClassObject javaClassObject; public ClassFileManager(StandardJavaFileManager standardManager) { super(standardManager); } @Override public ClassLoader getClassLoader(Location location) { return new SecureClassLoader() { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] b = javaClassObject.getBytes(); return super.defineClass(name, javaClassObject.getBytes(), 0, b.length); } }; } public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { this.javaClassObject = new JavaClassObject(className, kind); return this.javaClassObject; } } public class JavaClassObject extends SimpleJavaFileObject { protected final ByteArrayOutputStream bos = new ByteArrayOutputStream(); public JavaClassObject(String name, Kind kind) { super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind); } public byte[] getBytes() { return bos.toByteArray(); } @Override public OutputStream openOutputStream() throws IOException { return bos; } } }
我想介绍一下在生产中运行良好的解决scheme。
这里是三个源代码文件。
MemoryJavaCompiler.java
package me.soulmachine.compiler; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.tools.*; /** * Simple interface to Java compiler using JSR 199 Compiler API. */ public class MemoryJavaCompiler { private javax.tools.JavaCompiler tool; private StandardJavaFileManager stdManager; public MemoryJavaCompiler() { tool = ToolProvider.getSystemJavaCompiler(); if (tool == null) { throw new RuntimeException("Could not get Java compiler. Please, ensure that JDK is used instead of JRE."); } stdManager = tool.getStandardFileManager(null, null, null); } /** * Compile a single static method. */ public Method compileStaticMethod(final String methodName, final String className, final String source) throws ClassNotFoundException { final Map<String, byte[]> classBytes = compile(className + ".java", source); final MemoryClassLoader classLoader = new MemoryClassLoader(classBytes); final Class clazz = classLoader.loadClass(className); final Method[] methods = clazz.getDeclaredMethods(); for (final Method method : methods) { if (method.getName().equals(methodName)) { if (!method.isAccessible()) method.setAccessible(true); return method; } } throw new NoSuchMethodError(methodName); } public Map<String, byte[]> compile(String fileName, String source) { return compile(fileName, source, new PrintWriter(System.err), null, null); } /** * compile given String source and return bytecodes as a Map. * * @param fileName source fileName to be used for error messages etc. * @param source Java source as String * @param err error writer where diagnostic messages are written * @param sourcePath location of additional .java source files * @param classPath location of additional .class files */ private Map<String, byte[]> compile(String fileName, String source, Writer err, String sourcePath, String classPath) { // to collect errors, warnings etc. DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>(); // create a new memory JavaFileManager MemoryJavaFileManager fileManager = new MemoryJavaFileManager(stdManager); // prepare the compilation unit List<JavaFileObject> compUnits = new ArrayList<JavaFileObject>(1); compUnits.add(fileManager.makeStringSource(fileName, source)); return compile(compUnits, fileManager, err, sourcePath, classPath); } private Map<String, byte[]> compile(final List<JavaFileObject> compUnits, final MemoryJavaFileManager fileManager, Writer err, String sourcePath, String classPath) { // to collect errors, warnings etc. DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>(); // javac options List<String> options = new ArrayList<String>(); options.add("-Xlint:all"); // options.add("-g:none"); options.add("-deprecation"); if (sourcePath != null) { options.add("-sourcepath"); options.add(sourcePath); } if (classPath != null) { options.add("-classpath"); options.add(classPath); } // create a compilation task javax.tools.JavaCompiler.CompilationTask task = tool.getTask(err, fileManager, diagnostics, options, null, compUnits); if (task.call() == false) { PrintWriter perr = new PrintWriter(err); for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { perr.println(diagnostic); } perr.flush(); return null; } Map<String, byte[]> classBytes = fileManager.getClassBytes(); try { fileManager.close(); } catch (IOException exp) { } return classBytes; } }
MemoryJavaFileManager.java
package me.soulmachine.compiler; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URI; import java.nio.CharBuffer; import java.util.HashMap; import java.util.Map; import javax.tools.FileObject; import javax.tools.ForwardingJavaFileManager; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.JavaFileObject.Kind; import javax.tools.SimpleJavaFileObject; /** * JavaFileManager that keeps compiled .class bytes in memory. */ @SuppressWarnings("unchecked") final class MemoryJavaFileManager extends ForwardingJavaFileManager { /** Java source file extension. */ private final static String EXT = ".java"; private Map<String, byte[]> classBytes; public MemoryJavaFileManager(JavaFileManager fileManager) { super(fileManager); classBytes = new HashMap<>(); } public Map<String, byte[]> getClassBytes() { return classBytes; } public void close() throws IOException { classBytes = null; } public void flush() throws IOException { } /** * A file object used to represent Java source coming from a string. */ private static class StringInputBuffer extends SimpleJavaFileObject { final String code; StringInputBuffer(String fileName, String code) { super(toURI(fileName), Kind.SOURCE); this.code = code; } public CharBuffer getCharContent(boolean ignoreEncodingErrors) { return CharBuffer.wrap(code); } } /** * A file object that stores Java bytecode into the classBytes map. */ private class ClassOutputBuffer extends SimpleJavaFileObject { private String name; ClassOutputBuffer(String name) { super(toURI(name), Kind.CLASS); this.name = name; } public OutputStream openOutputStream() { return new FilterOutputStream(new ByteArrayOutputStream()) { public void close() throws IOException { out.close(); ByteArrayOutputStream bos = (ByteArrayOutputStream)out; classBytes.put(name, bos.toByteArray()); } }; } } public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, Kind kind, FileObject sibling) throws IOException { if (kind == Kind.CLASS) { return new ClassOutputBuffer(className); } else { return super.getJavaFileForOutput(location, className, kind, sibling); } } static JavaFileObject makeStringSource(String fileName, String code) { return new StringInputBuffer(fileName, code); } static URI toURI(String name) { File file = new File(name); if (file.exists()) { return file.toURI(); } else { try { final StringBuilder newUri = new StringBuilder(); newUri.append("mfm:///"); newUri.append(name.replace('.', '/')); if(name.endsWith(EXT)) newUri.replace(newUri.length() - EXT.length(), newUri.length(), EXT); return URI.create(newUri.toString()); } catch (Exception exp) { return URI.create("mfm:///com/sun/script/java/java_source"); } } } }
MemoryClassLoader.java
package me.soulmachine.compiler; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.StringTokenizer; /** * ClassLoader that loads .class bytes from memory. */ final class MemoryClassLoader extends URLClassLoader { private Map<String, byte[]> classBytes; public MemoryClassLoader(Map<String, byte[]> classBytes, String classPath, ClassLoader parent) { super(toURLs(classPath), parent); this.classBytes = classBytes; } public MemoryClassLoader(Map<String, byte[]> classBytes, String classPath) { this(classBytes, classPath, ClassLoader.getSystemClassLoader()); } public MemoryClassLoader(Map<String, byte[]> classBytes) { this(classBytes, null, ClassLoader.getSystemClassLoader()); } public Class load(String className) throws ClassNotFoundException { return loadClass(className); } public Iterable<Class> loadAll() throws ClassNotFoundException { List<Class> classes = new ArrayList<Class>(classBytes.size()); for (String name : classBytes.keySet()) { classes.add(loadClass(name)); } return classes; } protected Class findClass(String className) throws ClassNotFoundException { byte[] buf = classBytes.get(className); if (buf != null) { // clear the bytes in map -- we don't need it anymore classBytes.put(className, null); return defineClass(className, buf, 0, buf.length); } else { return super.findClass(className); } } private static URL[] toURLs(String classPath) { if (classPath == null) { return new URL[0]; } List<URL> list = new ArrayList<URL>(); StringTokenizer st = new StringTokenizer(classPath, File.pathSeparator); while (st.hasMoreTokens()) { String token = st.nextToken(); File file = new File(token); if (file.exists()) { try { list.add(file.toURI().toURL()); } catch (MalformedURLException mue) {} } else { try { list.add(new URL(token)); } catch (MalformedURLException mue) {} } } URL[] res = new URL[list.size()]; list.toArray(res); return res; } }
Explanations :
- In order to represent a Java source file in memory instead of disk, I defined a
StringInputBuffer
class in theMemoryJavaFileManager.java
. - To save the compiled
.class
files in memory, I implemented a classMemoryJavaFileManager
. The main idea is to override the functiongetJavaFileForOutput()
to store bytecodes into a map. - To load the bytecodes in memory, I have to implement a customized classloader
MemoryClassLoader
, which reads bytecodes in the map and turn them into classes.
Here is a unite test.
package me.soulmachine.compiler; import org.junit.Test; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import static org.junit.Assert.assertEquals; public class MemoryJavaCompilerTest { private final static MemoryJavaCompiler compiler = new MemoryJavaCompiler(); @Test public void compileStaticMethodTest() throws ClassNotFoundException, InvocationTargetException, IllegalAccessException { final String source = "public final class Solution {\n" + "public static String greeting(String name) {\n" + "\treturn \"Hello \" + name;\n" + "}\n}\n"; final Method greeting = compiler.compileStaticMethod("greeting", "Solution", source); final Object result = greeting.invoke(null, "soulmachine"); assertEquals("Hello soulmachine", result.toString()); } }
参考
- JavaCompiler.java from Cloudera Morphlines
- 如何从Java中的string创build一个对象(如何评估一个string)?
- InMemoryJavaCompiler
- Java-Runtime-Compiler
- [dynamic的Java – 无废话JavaCompilerAPI中文指南]
- Compile and Run Java Source Code in Memory .
String fileToCompile = ; JavaCompile compiler = ToolProvider.getSystemJavaCompiler(); if( compiler.run(null, null, null, "PACKAGE_NAME" + java.io.File.separator +"CLASS_NAME.java") == 0 ) System.out.println("Compilation is successful"); else System.out.println("Compilation Failed");