雨翔河
首页
列表
关于
JAVA 中动态编译 JAVA 代码
2019-01-24 15:09
公司项目有个小需求,需要在java代码中读取mysql或者其他渠道来的java代码来执行一段业务逻辑,也就是动态编译然后执行java代码。 常见的这种需求有'热部署'。 > 在业务系统中动态编译执行java代码是很危险的操作,搞不好容易把自己搭进去。 为了让代码不从java文件中加载,直接从各种渠道得到字符代码,从字符中加载,需要自己继承 `SimpleJavaFileObject` 类来实现。 ``` public class GenericJavaFileObject extends SimpleJavaFileObject { final private String content; public GenericJavaFileObject(String className, String content) throws Exception { super(URI.create("string:///" + className.replace('.', File.separatorChar) + JavaFileObject.Kind.SOURCE.extension), JavaFileObject.Kind.SOURCE); this.content = content; } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return content; } } ``` 动态编译和运行的时候如果要使用不属于 `java.lang.*` 的包里的对象,需要在classpath中说明jar所在的目录。 在这里我随便拿了个外部的 `Page` 对象扔进去测试。 类编译加载运行的代码如下: `page` 为我假设传入到java脚本内部的变量,动态编译 `scriptContent` 里的java代码。 ``` public void runScript(Page page, String scriptContent) { URLClassLoader classLoader = null; try { if (StringUtils.isBlank(scriptContent)) { LOG.warn("scriptContent is blank."); return; } // 1.获得JavaCompiler,final static // 2.创建一个DiagnosticCollector对象,用于获取编译输出信息 DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>(); // 3.获得JavaFileManager对象,用于管理需要编译的文件 StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnosticCollector, null, null); // 4.生成一个JavaFileObject对象,表示需要编译的源文件 GenericJavaFileObject fileObject = new GenericJavaFileObject("ScriptClass", scriptContent); // 5.组成一个JavaFileObject的遍历器 Iterable<GenericSpiderJavaFileObject> fileObjects = Collections.singletonList(fileObject); // 6.编译后文件输出地址 String compileToPath = "E:\\test\\temp"; //传入对象的包所在的位置 String path = Page.class.getProtectionDomain().getCodeSource().getLocation().getFile(); Iterable<String> options = Arrays.asList("-d", compileToPath, "-classpath", path); // 7.获得编译任务 JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnosticCollector, options, null, fileObjects); // 8.编译 Boolean result = task.call(); if (result) { // 编译成功 LOG.info("compile success"); } else { // 编译失败,打印错误信息 StringBuilder errorInfo = new StringBuilder(); for (Diagnostic<?> diagnostic : diagnosticCollector.getDiagnostics()) { errorInfo.append("编译错误。 Code:").append(diagnostic.getCode()) .append("\r\n").append("Kind:").append(diagnostic.getKind()) .append("\r\n").append("StartPosition:").append(diagnostic.getStartPosition()) .append("\r\n").append("EndPosition:").append(diagnostic.getEndPosition()) .append("\r\n").append("Position:").append(diagnostic.getPosition()) .append("\r\n").append("Source:").append(diagnostic.getSource()) .append("\r\n").append("Message:").append(diagnostic.getMessage(null)) .append("\r\n").append("ColumnNumber:").append(diagnostic.getColumnNumber()) .append("\r\n").append("LineNumber:").append(diagnostic.getLineNumber()); } LOG.error("diagnostic:" + errorInfo.toString()); throw new RuntimeException("compile error"); } // 9.关闭JavaFileManager fileManager.close(); // 运行 // 10.创建ClassLoader,并设置目录为编译时的输出目录 File file1 = new File(compileToPath); File file2 = new File(path); URL[] urls = {file1.toURI().toURL(),file2.toURI().toURL()}; classLoader = new URLClassLoader(urls, this.getClass().getClassLoader()); // 11.构建类的Class对象 Class<?> scriptClass = classLoader.loadClass("net.yuxianghe.generic.script.ScriptClass"); // 12.获得类的方法 Method scriptClassMethod = scriptClass.getDeclaredMethod("run", Page.class); // 13.创建一个类的实例 Object object = scriptClass.newInstance(); // 14.运行函数 scriptClassMethod.invoke(object, page); classLoader.close(); } catch (Exception e) { LOG.error("JavaExecuteServiceImpl.runScript Exception:", e); throw new RuntimeException("JavaExecuteServiceImpl.runScript Exception"); } finally { if (classLoader != null) { try { classLoader.close(); } catch (IOException e) { e.printStackTrace(); } } } } ``` 上面的方法执行后会在 `compileToPath` 定义的目录生成编译后的class文件,所以在运行时,`URLClassLoader` 在进行类加载的时候,你需要告诉它字节码文件的位置和依赖的jar包的位置。 测试例子: ``` @Test public void testJavaExecuteServiceImpl() { String javaCode = "package net.yuxianghe.generic.script; \n" + "import us.codecraft.webmagic.Page; \n" + "public class ScriptClass {\n" + " public void run(Page page) {\n" + " page.setRawText(\"12312312\");\n" + " System.out.println(\"ScriptClass.run success\");\n" + " }\n" + "}\n"; Page page = new Page(); //不要在意这个接口,重点在执行的这个 `runScript` 方法 System.out.println("page.getRawText: "+page.getRawText()); ExecuteService executeService = new JavaExecuteServiceImpl(); executeService.runScript(page, javaCode); System.out.println("page.getRawText: "+page.getRawText()); } ``` 输出结果: ``` 12312312 ScriptClass.run success ``` 看了看java的类加载机制,也看到很多网上的文章。 各种操作,各种转载,各种报错,还不如自己动手试一试来的快。
类型:工作
标签:动态编译,java
Copyright © 雨翔河
我与我周旋久
独孤影
开源实验室