Java动态编译

文章讲述了如何在分库分表中间件控制台中动态加载业务系统的类和方法,以测试验证Groovy配置。作者使用JavaCompiler和自定义的MemoryJavaFileManager、MemoryClassLoader来实现类的动态编译和隔离,以避免影响控制台系统性能。
摘要由CSDN通过智能技术生成

背景

个人目前从事分库分表中间件的开发,用户有个需求:期望在中间件控制台提前去测试验证“逻辑表”的拆分路由情况。其中 “逻辑表” 的拆分规则配置,支持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 动态编译在项目中的实践

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值