本文的目的:
使用者在程序運行期間,可以動態的寫Java Class,不需要生成任何.Class文件就可以完全在內存中編譯,加載,實例化。
1、需要用到的組件介紹
1)JavaCompiler:用於編譯Java Code。
2)CharSequenceJavaFileObject:用於保存Java Code,提供方法給JavaCompiler獲取String形式的Java Code。
3)ClassFileManager:用於JavaCompiler將編譯好後的Class文件保存在指定對象中。
4)JavaClassObject:ClassFileManager告訴JavaCompiler需要將Class文件保存在JavaClassObject中,但是由JavaClassObject來決定最終以byte流來保存數據。
5)DynamicClassLoader:自定義類加載器,用於加載最後的二進制Class
2、源碼展現:
CharSequenceJavaFileObject.java
package com.ths.platform.framework.dynamic; import javax.tools.SimpleJavaFileObject; import java.net.URI; /** * 用於將java源碼保存在content屬性中 */ public class CharSequenceJavaFileObject extends SimpleJavaFileObject { /** * 保存java code */ private String content; /** * 調用父類構造器,並設置content * @param className * @param content */ public CharSequenceJavaFileObject(String className, String content){ super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); this.content = content; } /** * 實現getCharContent,使得JavaCompiler可以從content獲取java源碼 * @param ignoreEncodingErrors * @return */ @Override public String getCharContent(boolean ignoreEncodingErrors) { return content; } }
ClassFileManager.java
package com.ths.platform.framework.dynamic; import java.io.IOException; import javax.tools.*; /** * 類文件管理器 * 用於JavaCompiler將編譯好後的class,保存到jclassObject中 */ public class ClassFileManager extends ForwardingJavaFileManager { /** * 保存編譯後Class文件的對象 */ private JavaClassObject jclassObject; /** * 調用父類構造器 * @param standardManager */ public ClassFileManager(StandardJavaFileManager standardManager) { super(standardManager); } /** * 將JavaFileObject對象的引用交給JavaCompiler,讓它將編譯好後的Class文件裝載進來 * @param location * @param className * @param kind * @param sibling * @return * @throws IOException */ @Override public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { if (jclassObject == null) jclassObject = new JavaClassObject(className, kind); return jclassObject; } public JavaClassObject getJavaClassObject() { return jclassObject; } }
JavaClassObject.java
package com.ths.platform.framework.dynamic; import javax.tools.SimpleJavaFileObject; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URI; /** * 將輸出流交給JavaCompiler,最後JavaCompiler將編譯後的class文件寫入輸出流中 */ public class JavaClassObject extends SimpleJavaFileObject { /** * 定義一個輸出流,用於裝載JavaCompiler編譯後的Class文件 */ protected final ByteArrayOutputStream bos = new ByteArrayOutputStream(); /** * 調用父類構造器 * @param name * @param kind */ public JavaClassObject(String name, Kind kind) { super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind); } /** * 獲取輸出流為byte[]數組 * @return */ public byte[] getBytes() { return bos.toByteArray(); } /** * 重寫openOutputStream,將我們的輸出流交給JavaCompiler,讓它將編譯好的Class裝載進來 * @return * @throws IOException */ @Override public OutputStream openOutputStream() throws IOException { return bos; } /** * 重寫finalize方法,在對象被回收時關閉輸出流 * @throws Throwable */ @Override protected void finalize() throws Throwable { super.finalize(); bos.close(); } }
DynamicEngine.java(職責:使用JavaCompiler編譯Class,並且使用DynamicClassLoader加載Class)
package com.ths.platform.framework.dynamic; import java.io.File; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import javax.tools.Diagnostic; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.ToolProvider; /** * 在Java SE6中最好的方法是使用StandardJavaFileManager類。 * 這個類可以很好地控制輸入、輸出,並且可以通過DiagnosticListener得到診斷信息, * 而DiagnosticCollector類就是listener的實現。 * 使用StandardJavaFileManager需要兩步。 * 首先建立一個DiagnosticCollector實例以及通過JavaCompiler的getStandardFileManager()方法得到一個StandardFileManager對象。 * 最後通過CompilationTask中的call方法編譯源程序。 */ public class DynamicEngine { //單例 private static DynamicEngine ourInstance = new DynamicEngine(); public static DynamicEngine getInstance() { return ourInstance; } private URLClassLoader parentClassLoader; private String classpath; private DynamicEngine() { //獲取類加載器 this.parentClassLoader = (URLClassLoader) this.getClass().getClassLoader(); //創建classpath this.buildClassPath(); } /** * @MethodName : 創建classpath */ private void buildClassPath() { this.classpath = null; StringBuilder sb = new StringBuilder(); for (URL url : this.parentClassLoader.getURLs()) { String p = url.getFile(); sb.append(p).append(File.pathSeparator); } this.classpath = sb.toString(); } /** * @MethodName : 編譯java代碼到Object * @Description : TODO * @param fullClassName 類名 * @param javaCode 類代碼 * @return Object * @throws Exception */ public Object javaCodeToObject(String fullClassName, String javaCode) throws Exception { long start = System.currentTimeMillis(); //記錄開始編譯時間 Object instance = null; //獲取系統編譯器 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); // 建立DiagnosticCollector對象 DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>(); // 建立用於保存被編譯文件名的對象 // 每個文件被保存在一個從JavaFileObject繼承的類中 ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnostics, null, null)); List<JavaFileObject> jfiles = new ArrayList<JavaFileObject>(); jfiles.add(new CharSequenceJavaFileObject(fullClassName, javaCode)); //使用編譯選項可以改變默認編譯行為。編譯選項是一個元素為String類型的Iterable集合 List<String> options = new ArrayList<String>(); options.add("-encoding"); options.add("UTF-8"); options.add("-classpath"); options.add(this.classpath); JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, jfiles); // 編譯源程序 boolean success = task.call(); if (success) { //如果編譯成功,用類加載器加載該類 JavaClassObject jco = fileManager.getJavaClassObject(); DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this.parentClassLoader); Class clazz = dynamicClassLoader.loadClass(fullClassName,jco); instance = clazz.newInstance(); } else { //如果想得到具體的編譯錯誤,可以對Diagnostics進行掃描 String error = ""; for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { error += compilePrint(diagnostic); } } long end = System.currentTimeMillis(); System.out.println("javaCodeToObject use:"+(end-start)+"ms"); return instance; } /** * @MethodName : compilePrint * @Description : 輸出編譯錯誤信息 * @param diagnostic * @return */ private String compilePrint(Diagnostic diagnostic) { System.out.println("Code:" + diagnostic.getCode()); System.out.println("Kind:" + diagnostic.getKind()); System.out.println("Position:" + diagnostic.getPosition()); System.out.println("Start Position:" + diagnostic.getStartPosition()); System.out.println("End Position:" + diagnostic.getEndPosition()); System.out.println("Source:" + diagnostic.getSource()); System.out.println("Message:" + diagnostic.getMessage(null)); System.out.println("LineNumber:" + diagnostic.getLineNumber()); System.out.println("ColumnNumber:" + diagnostic.getColumnNumber()); StringBuffer res = new StringBuffer(); res.append("Code:[" + diagnostic.getCode() + "]\n"); res.append("Kind:[" + diagnostic.getKind() + "]\n"); res.append("Position:[" + diagnostic.getPosition() + "]\n"); res.append("Start Position:[" + diagnostic.getStartPosition() + "]\n"); res.append("End Position:[" + diagnostic.getEndPosition() + "]\n"); res.append("Source:[" + diagnostic.getSource() + "]\n"); res.append("Message:[" + diagnostic.getMessage(null) + "]\n"); res.append("LineNumber:[" + diagnostic.getLineNumber() + "]\n"); res.append("ColumnNumber:[" + diagnostic.getColumnNumber() + "]\n"); return res.toString(); } }
DynamicClassLoader.java
package com.ths.platform.framework.dynamic; import java.net.URLClassLoader; import java.net.URL; /** * 自定義類加載器 */ public class DynamicClassLoader extends URLClassLoader { public DynamicClassLoader(ClassLoader parent) { super(new URL[0], parent); } public Class findClassByClassName(String className) throws ClassNotFoundException { return this.findClass(className); } public Class loadClass(String fullName, JavaClassObject jco) { byte[] classData = jco.getBytes(); return this.defineClass(fullName, classData, 0, classData.length); } }
DynaCompTest.java(測試類,從myclass文件中讀出源碼並在內存中編譯)
package com.ths.platform.framework.dynamic; import sun.misc.IOUtils; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; public class DynaCompTest { public static void main(String[] args) throws Exception { String fullName = "com.seeyon.proxy.MyClass"; File file = new File("/Users/yangyu/Downloads/myclass"); InputStream in = new FileInputStream(file); byte[] bytes = IOUtils.readFully(in, -1, false); String src = new String(bytes); in.close(); System.out.println(src); DynamicEngine de = DynamicEngine.getInstance(); Object instance = de.javaCodeToObject(fullName,src.toString()); System.out.println(instance); } }
/Users/yangyu/Downloads/myclass文件(這裡使用文件,實際也可以在程序中直接拼湊String)
package com.seeyon.proxy; public class MyClass { public String say(String str){ return "hello"+str; } }