• 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 = 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 实际事例

    项目中编译的情况相对来说是复杂些,需要发起编译任务来对编译过程有更多的控制

    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<StringSource> fileObjects = Arrays.asList(javaFileObject);
    
    
            // 文件管理器 ———— 编译器需要
            StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
    
    
            // 报告诊断信息对象 ———— 编译器需要
            DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<JavaFileObject>();
    
    
            // 编译参数:编译后的字节码输出地址
            File classPath = new File(Thread.currentThread().getContextClassLoader().getResource("").toURI());
            String outDir = classPath.getAbsolutePath() + File.separator;
            Iterable<String> options = Arrays.asList("-d", outDir);
    
    
            /**
             * Writer out:输出,为空到控制台
             * JavaFileManager fileManager:文件管理器,为空用编译器的标准文件管理器
             * DiagnosticListener<? super JavaFileObject> diagnosticListener:诊断监听器,为空用编译器默认方法报告
             * Iterable<String> options:编译参数
             * Iterable<String> classes:需要编译的类,用于注解处理
             * Iterable<? extends JavaFileObject> 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;
            }
        }
    }
    




  • 相关阅读:
    ARM的体系结构与编程系列博客——ARM体系版本
    eclipse快捷键
    ARM的体系结构与编程系列博客——ARM的历史与应用范围
    基于LINUX的多功能聊天室
    CC2530自动安全联网
    python3元组
    Python3 列表
    Python3 数字(Number)
    Python3 注释
    python3解释器
  • 原文地址:https://www.cnblogs.com/Howlet/p/15585504.html
Copyright © 2020-2023  润新知