美文网首页
java直接编译字符串生成class字节码对象

java直接编译字符串生成class字节码对象

作者: TinyThing | 来源:发表于2020-03-17 19:13 被阅读0次

    现在没时间,先把代码贴上:

    package com.fly.architecture.code;
    
    import com.google.common.collect.Lists;
    import lombok.extern.slf4j.Slf4j;
    
    import javax.tools.*;
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.lang.reflect.Method;
    import java.net.URI;
    import java.net.URL;
    import java.net.URLClassLoader;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.util.*;
    import java.util.stream.Collectors;
    
    /**
     * <p></p>
     *
     */
    @Slf4j
    public class CompileUtils {
        public static final String JAVA_CODE = 
                "public class Man {\n" +
                "\tpublic void hello(){\n" +
                "\t\tSystem.out.println(\"hello world\");\n" +
                "\t}\n" +
                "}";
    
        public static void main(String[] args) throws Exception {
            String sourcePath = "C:\\Users\\IdeaProjects\\architecture\\src\\main\\resources\\code";
            ClassLoader classLoader = compile(sourcePath, true);
    
            Class<?> aClass = classLoader.loadClass("com.fly.HelloWorld");
            Object instance = aClass.newInstance();
            Method hello = aClass.getMethod("hello");
            hello.invoke(instance);
        }
    
    
        /**
         * @param sourcePath        源码路径
         * @param generateClassFile 是否生成类文件
         * @return
         * @throws IOException
         */
        public static ClassLoader compile(String sourcePath, boolean generateClassFile) throws IOException {
    
    
            Path root = Paths.get(sourcePath);
            List<String> files = Files.walk(root)
                    .filter(path -> path.toFile().isFile())
                    .filter(path -> path.toString().endsWith(".java"))
                    .map(Path::toString)
                    .collect(Collectors.toList());
    
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(null, null, null);
            ClassJavaFileManager memoryJavaFileManager = new ClassJavaFileManager(standardFileManager);
            Iterable<? extends JavaFileObject> javaFileObjects = standardFileManager.getJavaFileObjectsFromStrings(files);
    
            JavaFileObject javaFileObject = new MemoryJavaFileObject("Man", JAVA_CODE);
            ArrayList<JavaFileObject> list = Lists.newArrayList(javaFileObjects);
            list.add(javaFileObject);
    
            ArrayList<String> options = new ArrayList<>();
            JavaFileManager manager = memoryJavaFileManager;
    
            //是否生成class文件
            String targetPath = sourcePath + File.separator + "target_dir";
            if (generateClassFile) {
                manager = standardFileManager;
                generateClassFile(options, targetPath);
            }
    
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, list);
            Boolean taskSuccess = task.call();
            System.out.println("compile " + (taskSuccess ? "success" : "fail"));
    
            standardFileManager.close();
            memoryJavaFileManager.close();
    
            if (generateClassFile) {
                URL url = Paths.get(targetPath).toFile().toURI().toURL();
                return new URLClassLoader(new URL[]{url}, CompileUtils.class.getClassLoader());
            }
            return new MemoryClassLoader(memoryJavaFileManager, CompileUtils.class.getClassLoader());
        }
    
        /**
         * 生成类文件到磁盘
         * @param options       编译命令行
         * @param targetPath    目标路径
         * @throws IOException
         */
        public static void generateClassFile(ArrayList<String> options, String targetPath) throws IOException {
            options.add("-d");
            options.add(targetPath);
            //清空输出路径
            Path target = Paths.get(targetPath);
            if (Files.exists(target)) {
                Files.walk(target)
                        .sorted(Comparator.reverseOrder())
                        .map(Path::toFile)
                        .forEach(File::delete);
            }
    
            Files.createDirectory(target);
        }
    
    
        /**
         * 自定义fileManager
         */
        static class ClassJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
    
    
            final Map<String, JavaFileObject> classMap = new HashMap<>();
    
            public ClassJavaFileManager(JavaFileManager fileManager) {
                super(fileManager);
            }
    
            public Map<String, JavaFileObject> getClassMap() {
                return classMap;
            }
    
            //这个方法一定要自定义
            @Override
            public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
                JavaFileObject classJavaFileObject = new MemoryJavaFileObject(className, kind);
                classMap.put(className, classJavaFileObject);
                return classJavaFileObject;
            }
            
        }
    
    
        /**
         * 自定义的JavaFileObject,包含源码和字节码byte[]
         */
        public static class MemoryJavaFileObject extends SimpleJavaFileObject {
            private String source;
            private ByteArrayOutputStream outPutStream;
            // 该构造器用来输入源代码
            public MemoryJavaFileObject(String name, String source) {
                // 1、先初始化父类,由于该URI是通过类名来完成的,必须以.java结尾。
                // 2、如果是一个真实的路径,比如是file:///test/demo/Hello.java则不需要特别加.java
                // 3、这里加的String:///并不是一个真正的URL的schema, 只是为了区分来源
                super(URI.create("String:///" + name + Kind.SOURCE.extension), Kind.SOURCE);
                this.source = source;
            }
            // 该构造器用来输出字节码
            public MemoryJavaFileObject(String name, Kind kind){
                super(URI.create("String:///" + name.replace(".", "/") + kind.extension), kind);
                source = null;
            }
    
            @Override
            public CharSequence getCharContent(boolean ignoreEncodingErrors){
                if(source == null){
                    throw new IllegalArgumentException("source == null");
                }
                return source;
            }
    
            @Override
            public OutputStream openOutputStream() throws IOException {
                outPutStream = new ByteArrayOutputStream();
                return outPutStream;
            }
    
            // 获取编译成功的字节码byte[]
            public byte[] getCompiledBytes(){
                return outPutStream.toByteArray();
            }
        }
    
    
        //自定义classloader
        static class MemoryClassLoader extends URLClassLoader {
    
            // class name to class bytes:
            Map<String, JavaFileObject> classBytes = new HashMap<>();
    
            public MemoryClassLoader(ClassJavaFileManager manager, ClassLoader classLoader) {
                super(new URL[0], classLoader);
                this.classBytes.putAll(manager.getClassMap());
            }
    
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                MemoryJavaFileObject classObject = (MemoryJavaFileObject) classBytes.get(name);
                if (classObject == null) {
                    return super.findClass(name);
                }
                classBytes.remove(name);
                byte[] bytes = classObject.getCompiledBytes();
                return defineClass(name, bytes, 0, bytes.length);
            }
    
        }
    
    }
    

    相关文章

      网友评论

          本文标题:java直接编译字符串生成class字节码对象

          本文链接:https://www.haomeiwen.com/subject/aorxyhtx.html