Java动态编译并执行JavaCompiler动态编译并执行java代码片段

目录

1.创建自定义类加载器

2.创建自定义JAVA文件对象 

3.查找指定包中的所有类 

4.创建class字节码文件对象

5.创建自定义文件管理器

6.创建自定义源文件对象

7.编译方法,准备编译

8.开始编译并执行


1.创建自定义类加载器

public class DynamicClassLoader extends ClassLoader {
    /**
     * 已编译的class字节码
     */
    private final Map<String, ByteJavaFileObject> byteCodes = new HashMap<>();

    public DynamicClassLoader(ClassLoader classLoader) {
        super(classLoader);
    }


    @Override
    protected Class<?> findClass(String name) {
        Class<?> result = null;
        byte[] bytes = null;
        //从内存中获取
        ByteJavaFileObject fileObject = byteCodes.get(name);
        if (fileObject == null) {
            //查找class文件 编译完成后我这会将class文件保存下来,所以这里会查找指定路径的文件。
            File file = new File(String.format(Constants.DYNAMIC_CLASS_PATH, name.replace(".", "/")));
            if (file.exists()) {
                bytes = FileUtil.readBytes(file);
            }
        } else {
            bytes = fileObject.getByteCode();
        }
        if (bytes != null) {
            result = super.defineClass(name, bytes, 0, bytes.length);
        }
        return result;
    }

    public void addCompiledSource(ByteJavaFileObject byteCode) {
        byteCodes.put(byteCode.getClassName(), byteCode);
    }

    public Map<String, Class<?>> getClasses() {
        Map<String, Class<?>> classes = new HashMap<>(byteCodes.size());
        for (ByteJavaFileObject byteCode : byteCodes.values()) {
            classes.put(byteCode.getClassName(), findClass(byteCode.getClassName()));
        }
        return classes;
    }

    public Class<?> getClasses(String className) {
        return findClass(className);
    }

    public Map<String, byte[]> getByteCodes() {
        Map<String, byte[]> result = new HashMap<>(byteCodes.size());
        for (Map.Entry<String, ByteJavaFileObject> entry : byteCodes.entrySet()) {
            result.put(entry.getKey(), entry.getValue().getByteCode());
        }
        return result;
    }
}

2.创建自定义JAVA文件对象 

public class CustomJavaFileObject implements JavaFileObject {
    /**
     * 文件名称
     */
    private final String binaryName;
    /**
     * JAR URI
     */
    private final URI uri;
    
    private final String name;

    public CustomJavaFileObject(String binaryName, URI uri) {
        this.uri = uri;
        this.binaryName = binaryName;
        name = uri.getPath() == null ? uri.getSchemeSpecificPart() : uri.getPath();
    }

    public URI toUri() {
        return uri;
    }

    public InputStream openInputStream() throws IOException {
        return uri.toURL().openStream();
    }

    public OutputStream openOutputStream() {
        throw new UnsupportedOperationException();
    }

    public String getName() {
        return name;
    }

    public Reader openReader(boolean ignoreEncodingErrors) {
        throw new UnsupportedOperationException();
    }

    public CharSequence getCharContent(boolean ignoreEncodingErrors) {
        throw new UnsupportedOperationException();
    }

    public Writer openWriter() throws IOException {
        throw new UnsupportedOperationException();
    }

    public long getLastModified() {
        return 0;
    }

    public boolean delete() {
        throw new UnsupportedOperationException();
    }

    public JavaFileObject.Kind getKind() {
        return JavaFileObject.Kind.CLASS;
    }

    public boolean isNameCompatible(String simpleName, JavaFileObject.Kind kind) {
        String baseName = simpleName + kind.extension;
        return kind.equals(getKind())
            && (baseName.equals(getName())
            || getName().endsWith("/" + baseName));
    }

    public NestingKind getNestingKind() {
        throw new UnsupportedOperationException();
    }

    public Modifier getAccessLevel() {
        throw new UnsupportedOperationException();
    }

    public String binaryName() {
        return binaryName;
    }


    public String toString() {
        return this.getClass().getName() + "[" + this.toUri() + "]";
    }
}

3.查找指定包中的所有类 

public class PackageInternalsFinder {
    /**
     * 自定义类加载器
     */
    private final DynamicClassLoader classLoader;
    /**
     * 文件后缀
     */
    private static final String CLASS_FILE_EXTENSION = ".class";

    public PackageInternalsFinder(DynamicClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    /**
     * 查找指定包下所有的java文件对象
     * @param packageName 包路径
     * @return java文件对象
     * @throws IOException
     */
    public List<JavaFileObject> find(String packageName) throws IOException {
        String javaPackageName = packageName.replaceAll("\\.", "/");

        List<JavaFileObject> result = new ArrayList<>();

        Enumeration<URL> urlEnumeration = classLoader.getResources(javaPackageName);
        while (urlEnumeration.hasMoreElements()) { 
            //类路径上每个jar都有一个URL
            URL packageFolderURL = urlEnumeration.nextElement();
            //获取jar包里面的类
            result.addAll(listUnder(packageName, packageFolderURL));
        }

        return result;
    }

    private Collection<JavaFileObject> listUnder(String packageName, URL packageFolderURL) {
        File directory = new File(decode(packageFolderURL.getFile()));
        //是一个文件夹
        if (directory.isDirectory()) {
            return processDir(packageName, directory);
        } 
        //是一个jar包
        else { 
            return processJar(packageFolderURL);
        }
    }

    /**
     * 获取jar包里面的类
     * @param packageFolderURL jar url
     * @return java文件对象集合
     */
    private List<JavaFileObject> processJar(URL packageFolderURL) {
        List<JavaFileObject> result = new ArrayList<>();
        try {
            String jarUri = packageFolderURL.toExternalForm().substring(0, packageFolderURL.toExternalForm().lastIndexOf("!/"));
            //创建获取jar包资源对象
            JarURLConnection jarConn = (JarURLConnection) packageFolderURL.openConnection();
            //jar包根目录名称
            String rootEntryName = jarConn.getEntryName();
            int rootEnd = rootEntryName.length() + 1;
            //获取jar文件
            Enumeration<JarEntry> entryEnum = jarConn.getJarFile().entries();
            //遍历文件
            while (entryEnum.hasMoreElements()) {
                JarEntry jarEntry = entryEnum.nextElement();
                //资源名称
                String name = jarEntry.getName();
                //当前目录下的class文件
                if (name.startsWith(rootEntryName) && name.indexOf('/', rootEnd) == -1 && name.endsWith(CLASS_FILE_EXTENSION)) {
                    URI uri = URI.create(jarUri + "!/" + name);
                    String binaryName = name.replaceAll("/", ".");
                    binaryName = binaryName.replaceAll(CLASS_FILE_EXTENSION + "$", "");

                    result.add(new CustomJavaFileObject(binaryName, uri));
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Wasn't able to open " + packageFolderURL + " as a jar file", e);
        }
        return result;
    }

    /**
     * 获取文件夹里面的类
     * @param packageName 包名
     * @param directory 文件夹
     * @return java文件对象集合
     */
    private List<JavaFileObject> processDir(String packageName, File directory) {
        List<JavaFileObject> result = new ArrayList<JavaFileObject>();
        //当前目录的子级
        File[] childFiles = directory.listFiles();
        if (childFiles != null) {
            for (File childFile : childFiles) {
                if (childFile.isFile()) {
                    //是class文件
                    if (childFile.getName().endsWith(CLASS_FILE_EXTENSION)) {
                        String binaryName = packageName + "." + childFile.getName();
                        binaryName = binaryName.replaceAll(CLASS_FILE_EXTENSION + "$", "");

                        result.add(new CustomJavaFileObject(binaryName, childFile.toURI()));
                    }
                }
            }
        }

        return result;
    }

    private String decode(String filePath) {
        try {
            return URLDecoder.decode(filePath, "utf-8");
        } catch (Exception e) {
        }

        return filePath;
    }
}

4.创建class字节码文件对象

public class ByteJavaFileObject extends SimpleJavaFileObject {

    private static final char DIR_SEPARATOR = '/';
    private static final char PKG_SEPARATOR = '.';
    private ByteArrayOutputStream byteArrayOutputStream;

    public ByteJavaFileObject(String className) {
        super(URI.create("byte:///" + className.replace(PKG_SEPARATOR, DIR_SEPARATOR) + Kind.CLASS.extension), Kind.CLASS);
    }

    public String getClassName() {
        String className = getName();
        className = className.replace(DIR_SEPARATOR, PKG_SEPARATOR);
        className = className.substring(1, className.indexOf(Kind.CLASS.extension));
        return className;
    }

    @Override
    public OutputStream openOutputStream() {
        if (byteArrayOutputStream == null) {
            byteArrayOutputStream = new ByteArrayOutputStream();
        }
        return byteArrayOutputStream;
    }

    public byte[] getByteCode() {
        return byteArrayOutputStream.toByteArray();
    }
}

5.创建自定义文件管理器

public class DynamicJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
    /**
     * 运行环境中的资源路径
     */
    private static final String[] superLocationNames = {StandardLocation.PLATFORM_CLASS_PATH.name(), "SYSTEM_MODULES"};
    /**
     * 用于查找给定包中的内部类
     */
    private final PackageInternalsFinder finder;

    /**
     * 自定义类加载器
     */
    private final DynamicClassLoader classLoader;

    /**
     * 初始化
     * @param fileManager 文件管理器
     * @param classLoader 类加载器
     */
    public DynamicJavaFileManager(JavaFileManager fileManager, DynamicClassLoader classLoader) {
        super(fileManager);
        this.classLoader = classLoader;

        finder = new PackageInternalsFinder(classLoader);
    }

    /**
     * 获取一个用于输出的java文件对象
     * 当你编译一个Java源文件时,编译器会生成一个或多个输出文件(例如.class文件)
     * 方法用于获取这些输出文件的JavaFileObject,这样编译器就可以将编译的结果写入这些文件
     * @param location a location
     * @param className 类名
     * @param kind 文件类型 
     * {@link JavaFileObject.Kind#SOURCE 源文件后缀}
     * {@link JavaFileObject.Kind#CLASS class文件后缀}
     * @param sibling 保存提示的文件对象
     * @return java文件对象
     */
    @Override
    public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className,
                                               JavaFileObject.Kind kind, FileObject sibling) {
        ByteJavaFileObject byteJavaFileObject = new ByteJavaFileObject(className);
        //保存编译后的class字节码到内存中
        classLoader.addCompiledSource(byteJavaFileObject);
        return byteJavaFileObject;

    }

    /**
     * 获取自定义类加载器
     * @param location a location
     * @return 自定义类加载器
     */
    @Override
    public ClassLoader getClassLoader(JavaFileManager.Location location) {
        return classLoader;
    }

    /**
     * 推断二进制名称表示输出文件名称
     * 输出文件的名称通常是源文件名加上.class后缀。然而,有时候你可能希望使用不同的名称来保存输出文件
     * @param location a location
     * @param file java文件对象
     * @return 输出文件名称
     */
    @Override
    public String inferBinaryName(Location location, JavaFileObject file) {
        //类型是自定义文件对象,输出默认名称
        if (file instanceof CustomJavaFileObject) {
            return ((CustomJavaFileObject) file).binaryName();
        } else {
            return super.inferBinaryName(location, file);
        }
    }

    /**
     * 列出指定位置的所有Java文件对象
     * @param location     a location
     * @param packageName  包名
     * @param kinds        文件类型
     * @param recurse      是否包含子包
     * @return Java文件对象集合
     * @throws IOException
     */
    @Override
    public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds,
                                         boolean recurse) throws IOException {
        if (location instanceof StandardLocation) {
            String locationName = ((StandardLocation) location).name();
            for (String name : superLocationNames) {
                if (name.equals(locationName)) {
                    return super.list(location, packageName, kinds, recurse);
                }
            }
        }

        // 从指定的ClassLoader合并JavaFileObjects
        if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) {
            return new DynamicJavaFileManager.IterableJoin<>(super.list(location, packageName, kinds, recurse),
                finder.find(packageName));
        }

        return super.list(location, packageName, kinds, recurse);
    }

    static class IterableJoin<T> implements Iterable<T> {
        private final Iterable<T> first, next;

        public IterableJoin(Iterable<T> first, Iterable<T> next) {
            this.first = first;
            this.next = next;
        }

        @Override
        public Iterator<T> iterator() {
            return new DynamicJavaFileManager.IteratorJoin<T>(first.iterator(), next.iterator());
        }
    }

    static class IteratorJoin<T> implements Iterator<T> {
        private final Iterator<T> first, next;

        public IteratorJoin(Iterator<T> first, Iterator<T> next) {
            this.first = first;
            this.next = next;
        }

        @Override
        public boolean hasNext() {
            return first.hasNext() || next.hasNext();
        }

        @Override
        public T next() {
            if (first.hasNext()) {
                return first.next();
            }
            return next.next();
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("remove");
        }
    }

}

6.创建自定义源文件对象

public class StringJavaFileObject extends SimpleJavaFileObject {
    /**
     * 代码片段
     */
    private String contents;

    public StringJavaFileObject(String className, String content) {
        super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
        this.contents = content;
    }

    @Override
    public CharSequence getCharContent(boolean ignoreEncodingErrors) {
        return contents;
    }
}

7.编译方法,准备编译

public class DynamicCompiler {

    /**
     * java编译器
     */
    private final JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();

    /**
     * 文件管理器
     */
    private final StandardJavaFileManager standardFileManager;
    /**
     * 编译参数
     */
    private final List<String> options = new ArrayList<String>();
    /**
     * 自定义类加载器
     */
    private final DynamicClassLoader dynamicClassLoader;
    /**
     * 需要被编译的源文件
     */
    private final Collection<JavaFileObject> compilationUnits = new ArrayList<>();
    /**
     * 编译错误信息
     */
    private final List<Diagnostic<? extends JavaFileObject>> errors = new ArrayList<>();
    /**
     * 编译警告信息
     */
    private final List<Diagnostic<? extends JavaFileObject>> warnings = new ArrayList<>();

    public DynamicCompiler(ClassLoader classLoader) {
        if (javaCompiler == null) {
            throw new RuntimeException("动态编译器初始化错误");
        }
        standardFileManager = javaCompiler.getStandardFileManager(null, null, null);
        options.add("-Xlint:unchecked");
        dynamicClassLoader = new DynamicClassLoader(classLoader);
    }

    /**
     * 添加源文件
     *
     * @param className  全路径类名
     * @param sourceCode 源代码
     */
    public void addSource(String className, String sourceCode) {
        compilationUnits.add(new StringJavaFileObject(className, sourceCode));
    }

    /**
     * 开始编译
     * @return 自定义类加载器
     */
    public DynamicClassLoader build() {
        //清空异常信息
        errors.clear();
        warnings.clear();
        //创建文件管理器
        DynamicJavaFileManager dynamicJavaFileManager = new DynamicJavaFileManager(standardFileManager, dynamicClassLoader);
        //创建编译监听器
        DiagnosticCollector<JavaFileObject> diagnosticListener = new DiagnosticCollector<>();
        //创建编译任务
        JavaCompiler.CompilationTask task = javaCompiler.getTask(null, dynamicJavaFileManager, diagnosticListener, options, null, compilationUnits);

        try {
            if (!compilationUnits.isEmpty()) {
                //开始编译
                boolean result = task.call();
                //编译失败或者存在异常信息
                if (!result || diagnosticListener.getDiagnostics().size() > 0) {
                    for (Diagnostic<? extends JavaFileObject> diagnostic : diagnosticListener.getDiagnostics()) {
                        switch (diagnostic.getKind()) {
                            case NOTE:
                            case WARNING:
                            case MANDATORY_WARNING:
                                //添加编译警告信息
                                warnings.add(diagnostic);
                                break;
                            case ERROR:
                            case OTHER:
                            default:
                                //添加编译错误信息
                                errors.add(diagnostic);
                                break;
                        }
                    }
                    if (!warnings.isEmpty()) {
                        log.warn("动态编译警告:{}", diagnosticToString(warnings));
                    }
                    if (!errors.isEmpty()) {
                        log.warn("动态编译失败:{}", diagnosticToString(warnings));
                    }
                }
            }
            return dynamicClassLoader;
        } catch (Throwable e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        } finally {
            compilationUnits.clear();
        }
    }


    /**
     * 编译异常信息转字符串
     *
     * @param diagnostics 编译异常信息
     * @return 异常信息字符串
     */
    private List<String> diagnosticToString(List<Diagnostic<? extends JavaFileObject>> diagnostics) {
        List<String> diagnosticMessages = new ArrayList<>();
        for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics) {
            diagnosticMessages.add(
                "line: " + diagnostic.getLineNumber() + ", message: " + diagnostic.getMessage(Locale.US));
        }
        return diagnosticMessages;
    }
}

8.开始编译并执行

String code = "package com.example public class Example1 {\n" +
            "    public void execute() {\n" +
            "        System.out.println(\"动态编译代码片段成功执行\");\n" +
            "     }\n" +
            "}";
DynamicCompiler dynamicCompiler = new DynamicCompiler(ClassLoader.getSystemClassLoader());
        dynamicCompiler.addSource("com.example.Example1", code);
        DynamicClassLoader classLoader = dynamicCompiler.build();

//执行方法
classLoader.getClasses().get("com.example.Example1")
​​​​​​​Example1 example = (Example1) aClass.newInstance();
example.execute();

如果部署到服务器上之后编译执行报 ​​​​​​​ClassNotFoundException找不到类,请点击查看​​​​​​​JAVA动态编译执行代码片段部署后报ClassNotFoundException找不到类​​​​​​​

尝试解决 

  • 12
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Java 中,你可以使用 Java Compiler API 和反射来实现运行时动态获取源码、插入新代码、再次编译执行的功能。以下是一个简单的示例: ```java import javax.tools.*; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public class DynamicCodeInjection { public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException { // 要插入的新代码 String newCode = "System.out.println(\"Hello, dynamic code injection!\");"; // 加载原始类 Class<?> originalClass = OriginalClass.class; // 获取原始类的源文件路径 String sourceFilePath = originalClass.getName().replace('.', '/') + ".java"; // 读取原始类的源代码 String sourceCode = new String(Files.readAllBytes(Paths.get(sourceFilePath)), StandardCharsets.UTF_8); // 在原始类的源代码中插入新代码 String modifiedCode = sourceCode + "\n" + newCode; // 将修改后的源代码写入一个临时文件 Path tempFilePath = Files.createTempFile("ModifiedClass", ".java"); Files.write(tempFilePath, modifiedCode.getBytes(StandardCharsets.UTF_8)); // 动态编译修改后的源代码 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(tempFilePath.toFile()); compiler.getTask(null, fileManager, null, null, null, compilationUnits).call(); fileManager.close(); // 加载修改后的类 Class<?> modifiedClass = Class.forName(originalClass.getName() + "Modified"); // 创建修改后的类的实例并执行 Object modifiedInstance = modifiedClass.newInstance(); modifiedClass.getMethod("run").invoke(modifiedInstance); } } class OriginalClass { public void run() { System.out.println("Hello, original code!"); } } ``` 在上面的示例中,我们首先加载原始类`OriginalClass`,然后读取它的源代码并插入新代码。然后将修改后的源代码写入临时文件,并使用 Java Compiler API 运行时动态编译修改后的源代码编译后,我们加载修改后的类`OriginalClassModified`,创建它的实例并执行其中的方法。 请注意,上述示例只是一个简单的演示,实际情况可能更复杂。还需要处理异常、处理依赖关系等。此外,动态编译和类加载可能会涉及到安全性和性能方面的考虑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值