• 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() + "]
    ");
            res.append("Kind:[" + diagnostic.getKind() + "]
    ");
            res.append("Position:[" + diagnostic.getPosition() + "]
    ");
            res.append("Start Position:[" + diagnostic.getStartPosition() + "]
    ");
            res.append("End Position:[" + diagnostic.getEndPosition() + "]
    ");
            res.append("Source:[" + diagnostic.getSource() + "]
    ");
            res.append("Message:[" + diagnostic.getMessage(null) + "]
    ");
            res.append("LineNumber:[" + diagnostic.getLineNumber() + "]
    ");
            res.append("ColumnNumber:[" + diagnostic.getColumnNumber() + "]
    ");
            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;
        }
    }
  • 相关阅读:
    SQL面试题:有A B C三列,用SQL语句实现:当A列大于B列时选择A列否则选择B列
    Centos下Yum安装PHP5.5
    docker 容器内服务自启动
    centos6.6系统初始化脚本
    不重启linuxVMWare虚拟机添加虚拟磁盘
    linux(centos6)搭建ftp服务器
    记一次扩容操作
    mongodb数据迁移的两种方式
    mongodb 数据库操作--备份 还原 导出 导入
    关于PHP参数的引用传递和值传递
  • 原文地址:https://www.cnblogs.com/eoss/p/6136943.html
Copyright © 2020-2023  润新知