本文的目的:
使用者在程序運行期間,可以動態的寫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;
}
}