Java的脚本机制、编译器API

学习 xxl-job 定时任务时了解到基于 JVM 的 Grovvy 脚本语言、搭建 Jenkins 时知道了编译API

1. Java 脚本机制 Java 的脚本 API 可以让我们调用 JavaScript、Grovvy、Ruby 等脚本语言,它避免了编译和链接环节,具有如下优势:
  • 可快速变更,不断实验(Java 9 已经有 JShell 可以实验了)
  • 可修改运行着的程序行为
  • 支持程序定制化

1.1 使用示例
public static void main(String[] args) throws Exception {// 获取 JS 脚本引擎 ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); ScriptEngine jsEngine = scriptEngineManager.getEngineByName("JS"); // 执行脚本语言 String script = "var num = 1 + 2"; jsEngine.eval(script); // 也可以从流中获取脚本 FileInputStream fileInputStream = new FileInputStream(new File("script.txt")); InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream); jsEngine.eval(inputStreamReader); // 绑定变量 jsEngine.put("testKey", "testValue"); // 执行方法 // 脚本引擎调用方法需要实现 Invocable 接口 String jsMethod = "function hello() {return 'Hello World'}"; jsEngine.eval(jsMethod); Object function = ((Invocable) jsEngine).invokeFunction("hello"); // 获取结果 Object num = jsEngine.get("num"); Object testValue = https://www.it610.com/article/jsEngine.get("testKey"); System.out.println(num.toString()); System.out.println(testValue.toString()); System.out.println(function.toString()); }



1.2 思考
脚本语言不像 Java 修改代码后需要再次编译和部署,这样想想的话 xxl-job 定时任务框架可能是通过 RPC 调用传输了 Grovvy 脚本的流给执行器,那么 JVM 执行的定时任务都是最新的

脚本 API 允许从外部读取脚本且实时生效,那么就可以做插件式的功能接口,只需做一个公用接口或者上层抽象类来调用外部脚本,需定制化或修改时可替换外部脚本来实现






2. 编译器 API 在项目中也看到过用 Java 来写 Java 类然后编译放入项目中调用的,第一次见有点新鲜感。这个编译器 API 在测试和自动化构建中也会被调用

2.1 基本使用
默认编译之后的字节码在同级目录下
public class CompilerTest1 { public static void main(String[] args) {// 获取编译器 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); /** * 参数分别是 * InputStream in:输入流规定为空,默认的编译器不会接收控制台输入 * OutputStream out:输出,为空输出到控制台 * OutputStream err:输出,为空输出到控制台 * String... arguments:参数,若调用 javac 则是传入启动参数 * result:返回 0 则编译成功 */ int result = compiler.run(null, null, null, "D:\\CompilerTest.java"); if (result == 0) { System.out.println("编译成功"); } else { System.out.println("编译失败"); } } }



2.2 实际事例
【Java的脚本机制、编译器API】项目中编译的情况相对来说是复杂些,需要发起编译任务来对编译过程有更多的控制
public class CompilerTest2 {public static void main(String[] args) throws URISyntaxException, IllegalAccessException, InstantiationException, NoSuchMethodException, ClassNotFoundException, InvocationTargetException {// 获取编译器 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); // 类名、字符串的类代码 String className = "TestClass"; StringBuilder sb = new StringBuilder(); sb.append("public class TestClass {\n"); sb.append("\tpublic void hello() {\n"); sb.append("\t\tSystem.out.println(\"Hello World Compiler\"); \n"); sb.append("\t}\n"); sb.append("}"); // 将字符串代码转成 JavaFileObject ———— 编译器需要 StringSource javaFileObject = new StringSource(className, sb.toString()); Iterable fileObjects = Arrays.asList(javaFileObject); // 文件管理器 ———— 编译器需要 StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); // 报告诊断信息对象 ———— 编译器需要 DiagnosticCollector diagnosticCollector = new DiagnosticCollector(); // 编译参数:编译后的字节码输出地址 File classPath = new File(Thread.currentThread().getContextClassLoader().getResource("").toURI()); String outDir = classPath.getAbsolutePath() + File.separator; Iterable options = Arrays.asList("-d", outDir); /** * Writer out:输出,为空到控制台 * JavaFileManager fileManager:文件管理器,为空用编译器的标准文件管理器 * DiagnosticListener diagnosticListener:诊断监听器,为空用编译器默认方法报告 * Iterable options:编译参数 * Iterable classes:需要编译的类,用于注解处理 * Iterable compilationUnits */ JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnosticCollector, options, null, fileObjects); // 执行编译 boolean result = task.call(); // 编译错误信息 if (result != true) { for (Diagnostic diagnostic : diagnosticCollector.getDiagnostics()) { System.out.println("Error on line: " + diagnostic.getLineNumber()); System.out.println("URI: " + diagnostic.getSource().toString()); } System.exit(-1); }// 将字节码加载进 JVM Class clazz = Class.forName(className); // 创建一个新类,反射执行其方法 Object instance = clazz.newInstance(); Method helloMethod = clazz.getMethod("hello"); helloMethod.invoke(instance); }/** * 字符串的类代码存在于内存之中,而参数需要 FileObject * 我们将字符串代码转成 FileObject 类型 */ static class StringSource extends SimpleJavaFileObject { private String code; StringSource(String name, String code) { super(URI.create("string:///" + name.replace(".", "/") + Kind.SOURCE.extension), Kind.SOURCE); this.code = code; }@Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return code; }public String getCode() { return code; } } }





    推荐阅读