首页 > 代码库 > Java--自定义Class并且在内存中编译,加载,实例化

Java--自定义Class并且在内存中编译,加载,实例化

本文的目的:

使用者在程序运行期间,可以动态的写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 =http://www.mamicode.com/ 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;    }}

 

Java--自定义Class并且在内存中编译,加载,实例化