Java动态编译

2023-12-13 11:48:29

背景

个人目前从事分库分表中间件的开发,用户有个需求:期望在中间件控制台提前去测试验证“逻辑表”的拆分路由情况。其中 “逻辑表” 的拆分规则配置,支持groovy方式配置,部分业务会通过groovy引用到“业务系统”代码里面的某个类#方法。但“中间件控制台系统”里面是没有“业务系统”的代码。

如果要实现上面的需求,我们需要有一种手段,让控制台系统能够动态加载到业务系统的类#方法,方便分库分表中间件加载到对应类,继而执行拆分规则。

一种方式是将整个应用jar包上传到控制台系统,该方式评估不可行,一方面是应用jar太大,本身只需要其中某一个或者几个类(一般情况下只会引用到一个类),全部加载没有必要,反而可能会导致测试效率“慢”。

另外一种方式,就是在控制台,让用户将使用到的类的code粘贴进去,我们将对应的类动态编译到中间件控制台系统,让分库分表中间件能够加载到就行,我们目前选择的就是此种方式。

无论是哪种方式都需要注意下类隔离,避免影响这中间件控制台系统,同时也需要注意类卸载的问题,避免中间件控制台系统内存爆炸。

实现

我们使用JavaCompiler来实现动态加载java code的功能,其中:MemoryJavaFileManager、MemoryFileObject代码是从github里面复制而来,核心实现如下:

public void testDynamicLoad(String fqn,String sourceCode){
	JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
	StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(null, null, null);
	//自定义JavaFileManager,将sourceCode转换成class bytes
	MemoryJavaFileManager memoryJavaFileManager = new MemoryJavaFileManager(standardFileManager);
	
	Map<String, byte[]> classMap = new HashMap<>();
	//自定义ClassLoader,用作类隔离
	MemoryClassLoader memoryClassLoader = new MemoryClassLoader(classMap);
	
	JavaFileObject javaFileObject = memoryJavaFileManager.makeStringSource(fqn, sourceCode);
	
	StringWriter stringWriter = new StringWriter();
	JavaCompiler.CompilationTask compilerTask = compiler.getTask(stringWriter, memoryJavaFileManager, null, null, null, Arrays.asList(javaFileObject));
	if (compilerTask.call()) {
	    Map<String, byte[]> classBytes = memoryJavaFileManager.getClassBytes();
	    classMap.putAll(classBytes);
	} else {
	    throw new RuntimeException(stringWriter.toString());
	}
	
	memoryClassLoader.findClass(fqn);
}
public class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {

    // compiled classes in bytes:
    final Map<String, byte[]> classBytes = new HashMap<String, byte[]>();

    public MemoryJavaFileManager(JavaFileManager fileManager) {
        super(fileManager);
    }

    public Map<String, byte[]> getClassBytes() {
        return new HashMap<String, byte[]>(this.classBytes);
    }

    @Override
    public void flush() throws IOException {
    }

    @Override
    public void close() throws IOException {
        classBytes.clear();
    }

    @Override
    public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className,
                                               Kind kind, FileObject sibling) throws IOException {
        if (kind == Kind.CLASS) {
            return new MemoryOutputJavaFileObject(className);
        } else {
            return super.getJavaFileForOutput(location, className, kind, sibling);
        }
    }

    public JavaFileObject makeStringSource(String name, String code) {
        return new MemoryInputJavaFileObject(name, code);
    }

    static class MemoryInputJavaFileObject extends SimpleJavaFileObject {

        final String code;

        MemoryInputJavaFileObject(String name, String code) {
            super(URI.create("string:///" + name), Kind.SOURCE);
            this.code = code;
        }

        @Override
        public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
            return CharBuffer.wrap(code);
        }
    }

    class MemoryOutputJavaFileObject extends SimpleJavaFileObject {
        final String name;

        MemoryOutputJavaFileObject(String name) {
            super(URI.create("string:///" + name), Kind.CLASS);
            this.name = name;
        }

        @Override
        public OutputStream openOutputStream() {
            return new FilterOutputStream(new ByteArrayOutputStream()) {
                @Override
                public void close() throws IOException {
                    out.close();
                    ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
                    classBytes.put(name, bos.toByteArray());
                }
            };
        }

    }
}
public class MemoryClassLoader extends URLClassLoader {

    private Map<String, byte[]> classBytes = new HashMap<>();

    public MemoryClassLoader(Map<String, byte[]> classBytes) {
        super(new URL[0], MemoryClassLoader.class.getClassLoader());
        this.classBytes = classBytes;
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] buf = classBytes.get(name);
        if (buf == null) {
            return super.findClass(name);
        }
        classBytes.remove(name);
        return defineClass(name, buf, 0, buf.length);
    }
}

参考

Java 动态编译在项目中的实践

文章来源:https://blog.csdn.net/wt_better/article/details/134893697
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。