Java实现动态编译加载外部类,适用于动态模板生成java文件编译执行场景
最近工作中遇到一个业务场景:通过编辑器编辑html及java代码保存到文件中,类似传统的jsp方式只不过文件名为html,文件编辑完成后需要动态将该文件编译运行生成html内容并保存到文件中。
首先整理一下思路:
1.可执行java代码是在html中嵌套的;
2.文件是html类型的文件
3.需要将html文件以java class方式运行并返回结果
根据以上确定大致方案
1. 需要借鉴jsp解析方式将html内容另存为java文件格式
2. 动态编译该java文件生成class文件
3. 动态加载class并初始化,通过反射方式执行方法
此处也存在几个问题,html内容中并没有可执行方法,我们加载class并初始化后如何反射方式执行;执行后的html内容如何获取
1. 首先我们定义接收html的容器,可以使用类似 buffer 或者 stream等可以字符串的对象
2.改写html内容保存java文件,模板jsp解析,将html输出到第一步定义的容器中,如果是java代码同样执行并输出到第一步定义的容器中,改写后的java文件需要有一个暴漏的方法及一个入参,这个参数就是我们第一步定义的容器
3.动态编译该java文件生成class文件
4.动态加载class并初始化,通过反射方式执行第二步暴漏的方法,入参为第一步定义的容器
下面主要介绍如何动态编译加载并执行
这一步google上面有很多例子,比如创建自定义classloader动态编译执行巴拉巴拉。。。。。。
之前有接触过阿里的arthas,了解到它可以实现动态编译加载,既然别人都造好了轮子哪有不使用的道理呢。当然我们需要做一些改造,arthas是基于agent方式启动我们项目中肯定不希望通过这种方式,而且arthas包含模块较多,直接照搬过来也有点浪费,所以我们只挑选自己能用到的模块,既然我们要实现动态编译和加载,那我们只需要把编译模块和动态加载模块拿过来就行了
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-memorycompiler</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>repackage-asm</artifactId>
<version>0.0.13</version>
</dependency>
我们没有通过agent方式来使用,但是arthas是依赖于agent启动时安装的Instrumentation , 我们需要通过代码方式嵌入
//无论是web服务或者微服务,都可以在服务启动后注册安装,然后我们就可以放心使用arthas了
ByteBuddyAgent.install();
下面我们就可以阅读arthas源码来实现自己的动态编辑加载了,首先我们看arthas如何做动态编译:
// MemoryCompilerCommand 类 下对应动态变异的处理
ClassLoader classloader = null; if (hashCode == null) { classloader = ClassLoader.getSystemClassLoader(); } else { classloader = ClassLoaderUtils.getClassLoader(inst, hashCode); if (classloader == null) { process.end(-1, "Can not find classloader with hashCode: " + hashCode + "."); return; } } DynamicCompiler dynamicCompiler = new DynamicCompiler(classloader); Charset charset = Charset.defaultCharset(); if (encoding != null) { charset = Charset.forName(encoding); } for (String sourceFile : sourcefiles) { String sourceCode = FileUtils.readFileToString(new File(sourceFile), charset); String name = new File(sourceFile).getName(); if (name.endsWith(".java")) { name = name.substring(0, name.length() - ".java".length()); } dynamicCompiler.addSource(name, sourceCode); } Map<String, byte[]> byteCodes = dynamicCompiler.buildByteCodes(); File outputDir = null; if (this.directory != null) { outputDir = new File(this.directory); } else { outputDir = new File("").getAbsoluteFile(); } List<String> files = new ArrayList<String>(); for (Entry<String, byte[]> entry : byteCodes.entrySet()) { File byteCodeFile = new File(outputDir, entry.getKey().replace('.', '/') + ".class"); FileUtils.writeByteArrayToFile(byteCodeFile, entry.getValue()); files.add(byteCodeFile.getAbsolutePath()); affect.rCnt(1); }
这里有很多arthas的监控及统计,我们将无用代码剔除后只剩下关键代码即可,大概代码如下
/** * 编译 * * @param javaFile * @param charSet * @return * @throws Exception */ public String compiler(DynamicCompiler dynamicCompiler, String parentPath, String javaFile, String charSet) throws Exception { File file = new File(parentPath, javaFile); String sourceCode = FileUtils.readFileToString(file, charSet); String name = file.getName(); if (name.endsWith(".java")) { name = name.substring(0, name.length() - ".java".length()); } dynamicCompiler.addSource(name, sourceCode); Map<String, byte[]> byteCodes = dynamicCompiler.buildByteCodes(); String className = null; for (Map.Entry<String, byte[]> entry : byteCodes.entrySet()) { className = entry.getKey(); File byteCodeFile = new File(parentPath, entry.getKey().replace('.', '/').concat(".class")); FileUtils.writeByteArrayToFile(byteCodeFile, entry.getValue()); } return className; }
通过上面实现了java文件动态编译为class文件后,下面我们在看如何动态加载执行class
// ClassLoaderCommand 类下的动态加载处理
private void processLoadClass(CommandProcess process, Instrumentation inst, ClassLoader targetClassLoader) { if (targetClassLoader != null) { try { Class<?> clazz = targetClassLoader.loadClass(this.loadClass); process.appendResult(new MessageModel("load class success.")); ClassDetailVO classInfo = ClassUtils.createClassInfo(clazz, false); process.appendResult(new ClassLoaderModel().setLoadClass(classInfo)); } catch (Throwable e) { logger.warn("load class error, class: {}", this.loadClass, e); process.end(-1, "load class error, class: "+this.loadClass+", error: "+e.toString()); return; } } process.end(); }
其实这里只有一句关键代码 targetClassLoader.loadClass(this.loadClass) ,其核心就是这个classloader上面
private Class<?> loadTargetClass(DynamicCompiler dynamicCompiler, String className) throws Exception { return dynamicCompiler.getClassLoader().loadClass(className); }
执行完上面的基本可以实现编译和加载,接着我们执行初始化并运行
// 载入类 try { templateObject = (BaseTemplate) clazz.newInstance(); } catch (Exception e) { log.error("初始化模板出错:{}", e.getMessage(), e); templateSystem.response.output("初始化模板出现了错误,详细请看以上信息!" + e.getMessage()); throw new BizException(ErrorCodeEnum.GENERATOR_ERROR, templateSystem.response.outputBuffer()); } try { templateObject.service(templateSystem); } catch (Exception e) { log.error("执行模板文件失败:{}", e.getMessage(), e); }
templateSystem中包含接受html输出的容器,这样我们就能获取到执行后的输出内容
到闲着我们已经完成了动态编译和加载运行并获取结果,但当模板内容变化该如何让其重新加载呢,这个arthas也提供了对应的处理
Map<String, byte[]> bytesMap = new HashMap<String, byte[]>(); for (String path : paths) { RandomAccessFile f = null; try { f = new RandomAccessFile(path, "r"); final byte[] bytes = new byte[(int) f.length()]; f.readFully(bytes); final String clazzName = readClassName(bytes); bytesMap.put(clazzName, bytes); } catch (Exception e) { logger.warn("load class file failed: "+path, e); process.end(-1, "load class file failed: " +path+", error: " + e); return; } finally { if (f != null) { try { f.close(); } catch (IOException e) { // ignore } } } } ....... try { if (definitions.isEmpty()) { process.end(-1, "These classes are not found in the JVM and may not be loaded: " + bytesMap.keySet()); return; } inst.redefineClasses(definitions.toArray(new ClassDefinition[0])); //这里是核心,调用安装的 Intsrumentation 进行class的redefine process.appendResult(redefineModel); process.end(); } catch (Throwable e) { String message = "redefine error! " + e.toString(); logger.error(message, e); process.end(-1, message); }
参考arthas上面的处理我们实现自己的动态替换
/** * redefine * * @param clazz * @param classFile */ private void redefine(Class<?> clazz, String classFile) throws Exception { RandomAccessFile f = null; ClassDefinition definition = null; try { f = new RandomAccessFile(classFile, "r"); final byte[] bytes = new byte[(int) f.length()]; f.readFully(bytes); definition = new ClassDefinition(clazz, bytes); } catch (Exception e) { log.error("load class file {} failed {}", classFile, e.getMessage(), e); } finally { if (f != null) { try { f.close(); } catch (IOException e) { } } } getInstrumentation().redefineClasses(definition); }
最后我们看下所有的执行流程
Class<?> clazz = null;
//判断是否需要重新编译 if (needCompile(outputClass, physicalTemplate)) { try { parse(templateContent, physicalTemplate, pathInfo.className, outputFile, pathInfo._package, charSet); } catch (Exception e) { log.error("生成模板出现了错误,{}", e.getMessage(), e); templateSystem.response.output("生成模板出现了错误,详细请看以上信息!" + e.toString()); throw new BizException(ErrorCodeEnum.GENERATOR_ERROR, templateSystem.response.outputBuffer()); } try { clazz = compilerLoadClass(pathInfo, charSet); //redefine redefine(clazz, outputClass); } catch (Exception e) { log.error("编译模板出现了错误,{}", e.getMessage(), e); templateSystem.response.output("编译模板出现了错误,详细请看以上信息!" + e.toString()); throw new BizException(ErrorCodeEnum.GENERATOR_ERROR, templateSystem.response.outputBuffer()); } } else { clazz = getLoadClass(pathInfo.clazz); if (clazz == null) { try { clazz = compilerLoadClass(pathInfo, charSet); } catch (Exception e) { log.error("编译模板出现了错误,{}", e.getMessage(), e); templateSystem.response.output("编译模板出现了错误,详细请看以上信息!" + e.toString()); throw new BizException(ErrorCodeEnum.GENERATOR_ERROR, templateSystem.response.outputBuffer()); } } } // 载入类 try { templateObject = (BaseTemplate) clazz.newInstance(); } catch (Exception e) { log.error("初始化模板出错:{}", e.getMessage(), e); templateSystem.response.output("初始化模板出现了错误,详细请看以上信息!" + e.getMessage()); throw new BizException(ErrorCodeEnum.GENERATOR_ERROR, templateSystem.response.outputBuffer()); } try { templateObject.service(templateSystem); } catch (Exception e) { log.error("执行模板文件失败:{}", e.getMessage(), e); }

浙公网安备 33010602011771号