美文网首页
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);
        }

    }

}

相关文章

  • 03 java字节码文件

    java源码经过编译,生成class字节码文件,JVM加载class文件执行。字节码文件将java语言与JVM解耦...

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

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

  • jvm概述

    .java文件通过jdk中的javac编译工具编译生成.class字节码文件,.class文件才能被jvm识别运行...

  • JVM常量池

    1 Class常量池 .java文件通过编译器编译后会生成.class(字节码)文件。class文件中除了包含类的...

  • java Day01

    Day01 Java编译过程 1 编译期:java源文件经过编译,生成.class字节码文件 2 运行期 JVM加...

  • java程序运行原理分析

    什么是class文件 是jvm编译java代码后生成的字节码文件,包含java程序执行的字节码;数据严格按照格式紧...

  • class对象

    class对象是由编译器编译java文件生成的字节码,里面保存了对象的信息。当程序运行需要此类时,加载器首先改类的...

  • Java 对象生命周期

    Java代码编译后生成的字节码.class文件从文件系统中加载到虚拟机之后,便有了JVM上的Java对象,Java...

  • Java学习心得(三)

    一、Java 的跨平台性源文件(.java)经过编译器(javac)编译后生成.class文件(字节码文件),这个...

  • java基础概念

    编译运行过程 1.java源文件经过编译生成.class文件(字节码文件)2.JVM加载.class文件并且运行....

网友评论

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

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