本文永久地址:https://my.oschina.net/bysu/blog/1552933javascript
inkfish原創,請勿商業性質轉載,轉載請註明來源(http://blog.csdn.net/inkfish )。html
參考:使用 javax.tools 建立動態應用程序java
javax.tools 包是一種添加到 Java SE 6 的標準 API,能夠實現 Java 源代碼編譯,使您可以添加動態功能來擴展靜態應用程序。本文將探查javax.tools包中提供的主要類,以Java表達式表示計算一個數值函數y=x*x+x。更多詳情請參考《使用 javax.tools 建立動態應用程序》和javax.tools API docs 。git
complier.CharSequenceCompiler源碼:express
package complier; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardLocation; import javax.tools.ToolProvider; import javax.tools.JavaCompiler.CompilationTask; /** * 編譯{@link CharSequence}形式的源碼,並實例化,返回一個實例。<br> * 用法示例(以編譯MyInterface的一個實現類爲例): * * <pre> * MyInterface instance = null; * JavaStringCompiler<MyInterface> compiler = new JavaStringCompiler<MyInterface>(null, null); * try { * Class<MyInterface> newClass = compiler.compile("com.mypackage.NewClass", * stringContaininSourceForNewClass, new Class<?>[] { MyInterface.class }); * instance = newClass.newInstance(); * } catch (JavaStringCompilerException ex) { * ex.printStackTrace(); * } catch (IllegalAccessException ex) { * ex.printStackTrace(); * } * instance.someOperation(someArgs); * </pre> */ public class CharSequenceCompiler<T> { /** 真正使用的編譯器 */ private final JavaCompiler compiler; private final ClassLoaderImpl classLoader; /** 保存編譯器編譯中的診斷信息 */ private DiagnosticCollector<JavaFileObject> diagnostics; private final FileManagerImpl javaFileManager; /** 編譯參數 */ private final List<String> options; /** * 構造一個新的實例,該實例持有指定的classloader * * @param loader * 應用的{@link ClassLoader} * @param options * 編譯器的編譯參數,具體可參考javac編譯參數 * @throws IllegalStateException * 若是java編譯器不能正確載入則拋出異常 */ public CharSequenceCompiler(ClassLoader loader, Collection<String> options) { compiler = ToolProvider.getSystemJavaCompiler(); if (compiler == null) { throw new IllegalStateException("系統java編譯器沒法找到,請確認類路徑中已經包含tools.jar(注:JDK 6中默認自帶,JRE 6中默認不帶)。"); } if (loader == null) { classLoader = new ClassLoaderImpl(this.getClass().getClassLoader()); } else { classLoader = new ClassLoaderImpl(loader); } this.options = new ArrayList<String>(); if (options != null) { this.options.addAll(options); } diagnostics = new DiagnosticCollector<JavaFileObject>(); javaFileManager = new FileManagerImpl(compiler.getStandardFileManager(diagnostics, null, Charset .forName(Utils.ENCODING)), classLoader); } /** * 編譯多個Java類的源碼 * * @param classes * key爲類的徹底限定名,value爲對應的源碼。 * @return 編譯後的類 * @throws CharSequenceCompilerException */ public synchronized Map<String, Class<T>> compile(Map<String, CharSequence> classes) throws CharSequenceCompilerException { //準備待編譯文件 List<JavaFileObject> sources = new ArrayList<JavaFileObject>(); for (Entry<String, CharSequence> entry : classes.entrySet()) { String qualifiedClassName = entry.getKey(); CharSequence javaSource = entry.getValue(); if (javaSource != null) { int dotPos = qualifiedClassName.lastIndexOf('.'); String className = dotPos == -1 ? qualifiedClassName : qualifiedClassName .substring(dotPos + 1); String packageName = dotPos == -1 ? "" : qualifiedClassName.substring(0, dotPos); JavaFileObjectImpl source = new JavaFileObjectImpl(className, javaSource); sources.add(source); javaFileManager.putFileForInput(StandardLocation.SOURCE_PATH, packageName, className + ".java", source); } } //編譯代碼 CompilationTask task = compiler.getTask(null, javaFileManager, diagnostics, options, null, sources); Boolean result = task.call(); //返回編譯結果 if ((result == null) || !result.booleanValue()) { throw new CharSequenceCompilerException("Compilation failed.", classes.keySet(), diagnostics); } try { Map<String, Class<T>> compiled = new HashMap<String, Class<T>>(); for (String qualifiedClassName : classes.keySet()) { compiled.put(qualifiedClassName, loadClass(qualifiedClassName)); } return compiled; } catch (ClassNotFoundException ex) { throw new CharSequenceCompilerException(classes.keySet(), ex, diagnostics); } catch (IllegalArgumentException ex) { throw new CharSequenceCompilerException(classes.keySet(), ex, diagnostics); } catch (SecurityException ex) { throw new CharSequenceCompilerException(classes.keySet(), ex, diagnostics); } } /** * 編譯一個Java類。 * * @param qualifiedClassName * 類的徹底限定名。 * @param javaSource * 編譯的java類完整的源碼。 * @param types * 0或多個類,用以檢驗被編譯的類可否轉換成這些類中任何一個。 * @return 編譯後的類 * @throws CharSequenceCompilerException * 若是類沒法被編譯則拋出異常。 * @throws ClassCastException * 若是編譯後的類沒法轉換成types中的任何一種類型,則拋出異常。 */ public synchronized Class<T> compile(String qualifiedClassName, CharSequence javaSource, Class<?>... types) throws CharSequenceCompilerException, ClassCastException { diagnostics = new DiagnosticCollector<JavaFileObject>(); Map<String, CharSequence> classes = new HashMap<String, CharSequence>(1); classes.put(qualifiedClassName, javaSource); Map<String, Class<T>> compiled = compile(classes); Class<T> newClass = compiled.get(qualifiedClassName); for (Class<?> type : types) { if (!type.isAssignableFrom(newClass)) { throw new ClassCastException(type.getName()); } } return newClass; } /** 載入Java類。 */ @SuppressWarnings("unchecked") private Class<T> loadClass(final String qualifiedClassName) throws ClassNotFoundException { return (Class<T>) classLoader.loadClass(qualifiedClassName); } }
complier.CharSequenceCompilerException源碼:apache
package complier; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import javax.tools.DiagnosticCollector; import javax.tools.JavaFileObject; @SuppressWarnings("serial") public class CharSequenceCompilerException extends Exception { /** 全部被編譯的類的完整類名 */ private Set<String> classNames; transient private DiagnosticCollector<JavaFileObject> diagnostics; public CharSequenceCompilerException(Set<String> qualifiedClassNames, Throwable cause, DiagnosticCollector<JavaFileObject> diagnostics) { super(cause); setClassNames(qualifiedClassNames); setDiagnostics(diagnostics); } public CharSequenceCompilerException(String message, Set<String> qualifiedClassNames, DiagnosticCollector<JavaFileObject> diagnostics) { super(message); setClassNames(qualifiedClassNames); setDiagnostics(diagnostics); } public CharSequenceCompilerException(String message, Set<String> qualifiedClassNames, Throwable cause, DiagnosticCollector<JavaFileObject> diagnostics) { super(message, cause); setClassNames(qualifiedClassNames); setDiagnostics(diagnostics); } /** @return 返回編譯出問題的類的全名稱 */ public Collection<String> getClassNames() { return Collections.unmodifiableSet(classNames); } /** 獲得異常的診斷信息 */ public DiagnosticCollector<JavaFileObject> getDiagnostics() { return diagnostics; } private void setClassNames(Set<String> qualifiedClassNames) { classNames = new HashSet<String>(qualifiedClassNames); } private void setDiagnostics(DiagnosticCollector<JavaFileObject> diagnostics) { this.diagnostics = diagnostics; } }
complier.ClassLoaderImpl源碼:api
package complier; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import javax.tools.JavaFileObject; import org.apache.commons.io.IOUtils; /** {@link ClassLoader}的一個實現,它map類名和JavaFileObjectImpl的實例。本類在{@link CharSequenceCompiler}和{@link FileManagerImpl}中被使用。 */ final class ClassLoaderImpl extends ClassLoader { private final Map<String, JavaFileObjectImpl> classes = new HashMap<String, JavaFileObjectImpl>(); ClassLoaderImpl(final ClassLoader parentClassLoader) { super(parentClassLoader); } @Override public InputStream getResourceAsStream(final String name) { if (name.endsWith(".class")) { String qualifiedClassName = name.substring(0, name.length() - ".class".length()) .replace('/', '.'); JavaFileObjectImpl file = classes.get(qualifiedClassName); if (file != null) { try { return new ByteArrayInputStream(IOUtils.toByteArray(file.openInputStream())); } catch (IOException ex) { } } } return super.getResourceAsStream(name); } protected void add(final String qualifiedClassName, final JavaFileObjectImpl javaFile) { classes.put(qualifiedClassName, javaFile); } /** @return 返回不可變的Collection,含有全部持有的{@link JavaFileObject}對象 */ protected Collection<JavaFileObjectImpl> files() { return Collections.unmodifiableCollection(classes.values()); } @Override protected Class<?> findClass(final String qualifiedClassName) throws ClassNotFoundException { JavaFileObject file = classes.get(qualifiedClassName); if (file != null) { try { byte[] bytes = IOUtils.toByteArray(file.openInputStream()); return defineClass(qualifiedClassName, bytes, 0, bytes.length); } catch (IOException ex) { } } // Workaround in Java 6. see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6434149 try { Class<?> c = Class.forName(qualifiedClassName); return c; } catch (ClassNotFoundException nf) { } return super.findClass(qualifiedClassName); } @Override protected synchronized Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException { return super.loadClass(name, resolve); } }
complier.FileManagerImpl源碼:數組
package complier; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.tools.FileObject; import javax.tools.ForwardingJavaFileManager; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.StandardLocation; import javax.tools.JavaFileObject.Kind; /** * {@link JavaFileManager}的一個實例,用於管理Java源代碼和byte code。<br> * 全部的源碼以{@link CharSequence}的形式保存在內存中,byte code以byte數組形式存放在內存中。 */ final class FileManagerImpl extends ForwardingJavaFileManager<JavaFileManager> { private final ClassLoaderImpl classLoader; private final Map<URI, JavaFileObject> fileObjects = new HashMap<URI, JavaFileObject>(); FileManagerImpl(JavaFileManager fileManager, ClassLoaderImpl classLoader) { super(fileManager); this.classLoader = classLoader; } @Override public ClassLoader getClassLoader(JavaFileManager.Location location) { return classLoader; } @Override public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { FileObject o = fileObjects.get(uri(location, packageName, relativeName)); if (o != null) { return o; } return super.getFileForInput(location, packageName, relativeName); } @Override public JavaFileObject getJavaFileForOutput(Location location, String qualifiedName, Kind kind, FileObject outputFile) throws IOException { JavaFileObjectImpl file = new JavaFileObjectImpl(qualifiedName, kind); classLoader.add(qualifiedName, file); return file; } @Override public String inferBinaryName(Location loc, JavaFileObject file) { String result; if (file instanceof JavaFileObjectImpl) { result = file.getName(); } else { result = super.inferBinaryName(loc, file); } return result; } @Override public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse) throws IOException { Iterable<JavaFileObject> result = super.list(location, packageName, kinds, recurse); ArrayList<JavaFileObject> files = new ArrayList<JavaFileObject>(); if ((location == StandardLocation.CLASS_PATH) && kinds.contains(JavaFileObject.Kind.CLASS)) { for (JavaFileObject file : fileObjects.values()) { if ((file.getKind() == Kind.CLASS) && file.getName().startsWith(packageName)) { files.add(file); } } files.addAll(classLoader.files()); } else if ((location == StandardLocation.SOURCE_PATH) && kinds.contains(JavaFileObject.Kind.SOURCE)) { for (JavaFileObject file : fileObjects.values()) { if ((file.getKind() == Kind.SOURCE) && file.getName().startsWith(packageName)) { files.add(file); } } } for (JavaFileObject file : result) { files.add(file); } return files; } void putFileForInput(StandardLocation location, String packageName, String relativeName, JavaFileObject file) { fileObjects.put(uri(location, packageName, relativeName), file); } private URI uri(Location location, String packageName, String relativeName) { return Utils.toURI(new StringBuilder(location.getName()).append('/').append(packageName).append('/') .append(relativeName).toString()); } }
complier.JavaFileObjectImpl源碼:app
package complier; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import javax.tools.FileObject; import javax.tools.JavaFileObject; import javax.tools.SimpleJavaFileObject; /** * {@link FileObject}和{@link JavaFileObject}的一個實現,它能持有java源代碼或編譯後的class。這個類能夠用於: * <ol> * <li>存放須要傳遞給編譯器的源碼,這時使用的是{@link JavaFileObjectImpl#JavaFileObjectImpl(String, CharSequence)}構造器。</li> * <li>存放編譯器編譯完的byte code,這是使用的是{@link JavaFileObjectImpl#JavaFileObjectImpl(String, JavaFileObject.Kind)}</li> * </ol> */ final class JavaFileObjectImpl extends SimpleJavaFileObject { /** 若是kind == CLASS, 存儲byte code,能夠經過{@link #openInputStream()}獲得 */ private ByteArrayOutputStream byteCode; /** 若是kind == SOURCE, 存儲源碼 */ private final CharSequence source; /** * 建立持有源碼的實例 * * @param baseName * the base name * @param source * the source code */ JavaFileObjectImpl(final String baseName, final CharSequence source) { super(Utils.toURI(baseName + ".java"), Kind.SOURCE); this.source = source; } /** * 建立持有二進制byte code的實例 * * @param name * the file name * @param kind * the kind of file */ JavaFileObjectImpl(final String name, final Kind kind) { super(Utils.toURI(name), kind); source = null; } @Override public CharSequence getCharContent(final boolean ignoreEncodingErrors) throws UnsupportedOperationException { if (source == null) { throw new UnsupportedOperationException("getCharContent()"); } return source; } @Override public InputStream openInputStream() { return new ByteArrayInputStream(byteCode.toByteArray()); } @Override public OutputStream openOutputStream() { return (byteCode = new ByteArrayOutputStream()); } @Override public Writer openWriter() throws IOException { return new OutputStreamWriter(openOutputStream(), Utils.ENCODING); } }
complier.Utils源碼:dom
package complier; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; public abstract class Utils { public static final String ENCODING = Charset.defaultCharset().name(); /** 把String轉換成URI,若是轉換異常不拋出URISyntaxException,而直接拋出RuntimeException。 */ static URI toURI(String name) { try { return new URI(name); } catch (URISyntaxException e) { throw new RuntimeException(e); } } }
以上代碼爲complier包中全部類,它對外暴露的主要方法是:CharSequenceCompiler<T>.compile(String qualifiedClassName, CharSequence javaSource, Class<?>... types) throws CharSequenceCompilerException, ClassCastException,經過它來動態編譯字符串形式表示的java源代碼。除此以外,包中其餘方類和方法儘可能使用默認訪問權限,以免他人誤用以及隱藏實現細節。
下面是測試package中的內容:
complier.test.Function源碼:定義了一個接口,動態編譯的全部類實現這個接口。
package complier.test; public interface Function { double doFunction(double x); }
complier.test.Function實現類的模板,方便類的生成。
package $packageName; import static java.lang.Math.*; public class $className implements complier.test.Function { @Override public double doFunction(double x){ return $expression; } }
complier.test.ExpressionCal源碼:裏面含有一個靜態測試類ExpressionCal$Tester。
package complier.test; import java.io.IOException; import java.text.DecimalFormat; import java.util.Arrays; import java.util.Random; import javax.script.Bindings; import javax.script.Compilable; import javax.script.CompiledScript; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import javax.script.SimpleBindings; import javax.tools.Diagnostic; import javax.tools.DiagnosticCollector; import javax.tools.JavaFileObject; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.exception.ExceptionUtils; import complier.CharSequenceCompiler; import complier.CharSequenceCompilerException; import complier.Utils; public class ExpressionCal { /** 包名前綴 */ private static final String PACKAGE_NAME = "javaxtools.compiler.test.runtime"; public static class Tester { public static void main(String[] args) throws ScriptException { //第一遍測試 test(); System.out.println("------Test Twice------/n"); test(); } public static void test() throws ScriptException { DecimalFormat df = new DecimalFormat("0.00"); int loop = 10 * 10000 - 1; String exp = "x*x+x"; double d = new Random().nextDouble() * 100; long start; //直接計算 start = System.nanoTime(); for (int i = 0; i < loop; i++) { @SuppressWarnings("unused") double a = d * d + d; } System.out.printf(exp.replace("x", df.format(d)) + "=%2.4f/n", d * d + d); System.out.printf("Time of direct cal %d loops: %10.2f微秒./n/n", loop + 1, (System.nanoTime() - start) / 1000d); //編譯源碼並計算 start = System.nanoTime(); Function func = new ExpressionCal().newFunction(exp); System.out.printf("Java src complain time: %10.2f微秒, /t", (System.nanoTime() - start) / 1000d); start = System.nanoTime(); for (int i = 0; i < loop; i++) { func.doFunction(d); } System.out.printf(exp.replace("x", df.format(d)) + "=%2.4f/n", func.doFunction(d)); System.out.printf("Complained source %d loops: %10.2f微秒./n/n", loop + 1, (System.nanoTime() - start) / 1000d); //內置Javascript計算 start = System.nanoTime(); ScriptEngine se = new ScriptEngineManager().getEngineByName("ECMAScript"); CompiledScript script = ((Compilable) se).compile("var x;" + exp); System.out.printf("JS complain time: %10.2f微秒, /t", (System.nanoTime() - start) / 1000d); start = System.nanoTime(); Bindings binding = new SimpleBindings(); for (int i = 0; i < loop; i++) { binding.put("x", d); script.eval(binding); } binding.put("x", d); System.out.printf(exp.replace("x", df.format(d)) + "=%2.4f/n", script.eval(binding)); System.out.printf("Javascript %d loops: %10.2f微秒./n", loop + 1, (System.nanoTime() - start) / 1000d); } } /** 類名後綴 */ private int classNameSuffix = 0; /** 隨機數生成器,用於生成隨機的包名和類名 */ private static final Random random = new Random(); /** 字符串形式的Java源文件內容 */ private String template; private static final String TEMPLATE_NAME = "Function.java.template"; private static final String TARGET_VERSION = "1.6"; private final CharSequenceCompiler<Function> compiler = new CharSequenceCompiler<Function>(getClass() .getClassLoader(), Arrays.asList(new String[] { "-target", TARGET_VERSION, "-encoding", Utils.ENCODING })); public Function newFunction(String expr) { StringBuilder errStr = new StringBuilder(); Function result = null; try { //生成惟一的包名和類名 final String packageName = PACKAGE_NAME + digits(); final String className = "C_" + (classNameSuffix++) + digits(); final String qName = packageName + '.' + className; //生成類的源碼 final String source = fillTemplate(packageName, className, expr); //編譯源碼 Class<Function> compiledFunction = compiler.compile(qName, source, new Class<?>[] { Function.class }); result = compiledFunction.newInstance(); } catch (CharSequenceCompilerException ex) { errStr.append(log(ex.getDiagnostics())); ex.printStackTrace(); } catch (InstantiationException ex) { errStr.append(ExceptionUtils.getFullStackTrace(ex)).append("/n"); ex.printStackTrace(); } catch (IllegalAccessException ex) { errStr.append(ExceptionUtils.getFullStackTrace(ex)).append("/n"); ex.printStackTrace(); } catch (IOException ex) { errStr.append(ExceptionUtils.getFullStackTrace(ex)).append("/n"); ex.printStackTrace(); } if (errStr.toString().trim().length() > 0) { System.err.println(errStr.toString()); } return result; } /** @return 返回以'_'開頭的隨機16進制字符串 */ private String digits() { return '_' + Long.toHexString(random.nextLong()); } /** * 生成字符串形式的java源文件內容 * * @param packageName * 包名 * @param className * 類名 * @param expression * 表達式 * @return 字符串形式的java源文件內容 * @throws IOException */ private String fillTemplate(String packageName, String className, String expression) throws IOException { if (template == null) { template = IOUtils.toString(Function.class.getResourceAsStream(TEMPLATE_NAME), Utils.ENCODING); } String source = template.replace("$packageName", packageName)// .replace("$className", className)// .replace("$expression", expression); return source; } /** 記錄{@link DiagnosticCollector}中的錯誤內容 */ private CharSequence log(final DiagnosticCollector<JavaFileObject> diagnostics) { final StringBuilder msgs = new StringBuilder(); for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) { msgs.append(diagnostic.getMessage(null)).append("/n"); } return msgs; } }
ExpressionCal$Tester測試表達式x*x+x的運行,具體測試直接計算、java編譯並計算、javascript編譯並計算的效率,共測試三遍以觀察其效率的變化,每一遍中每種方法又用for循環運行10萬次計算,忽略打印輸出的耗時。測試時間使用微秒爲單位,精確度取決於System.nanoTime(),詳見其java docs中的說明。測試結果以下:
44.53*44.53+44.53=2027.7451 Time of direct cal 100000 loops: 6461.72微秒. Java src complain time: 604570.13微秒, 44.53*44.53+44.53=2027.7451 Complained source 100000 loops: 5412.14微秒. JS complain time: 23354.09微秒, 44.53*44.53+44.53=2027.7451 Javascript 100000 loops: 8671081.10微秒. ------Test twice------ 7.67*7.67+7.67=66.4529 Time of direct cal 100000 loops: 670.48微秒. Java src complain time: 44715.18微秒, 7.67*7.67+7.67=66.4529 Complained source 100000 loops: 1397.38微秒. JS complain time: 2375.44微秒, 7.67*7.67+7.67=66.4529 Javascript 100000 loops: 8493123.29微秒. ------Test third times------ 74.34*74.34+74.34=5600.4535 Time of direct cal 100000 loops: 572.14微秒. Java src complain time: 39487.42微秒, 74.34*74.34+74.34=5600.4535 Complained source 100000 loops: 1375.04微秒. JS complain time: 1867.56微秒, 74.34*74.34+74.34=5600.4535 Javascript 100000 loops: 8624124.85微秒.
整理獲得:
10萬次計算 (單位:毫秒) |
直接計算 | java編譯並計算 | JS編譯並計算 | ||
編譯 | 計算 | 編譯 | 計算 | ||
第一遍 | 6.46 | 604.57 | 5.41 | 23.35 | 8671.08 |
第二遍 | 0.67 | 44.71 | 1.4 | 2.38 | 8493.12 |
第三遍 | 0.57 | 39.49 | 1.38 | 1.87 | 8624.12 |
能夠看出,java直接計算速度超快,java編譯並計算速度仍是比直接計算慢1倍(不計編譯時間),而JS的計算速度比直接計算慢4個數量級,簡直慘不忍睹。第一次運行除JS計算外,均比較耗時,其中java第一次編譯須要從磁盤上讀取template文件,之後則均爲內存操做。
從測試結果看,若是須要運行一個固定的表達式,能夠寫死在Java程序中(廢話),若是須要計算一個動態變化的表達式,若是計算次數較少(500次如下),JS較爲划算,若是計算次數十分巨大,則須要考慮java編譯並計算。