飙血推荐
  • HTML教程
  • MySQL教程
  • JavaScript基础教程
  • php入门教程
  • JavaScript正则表达式运用
  • Excel函数教程
  • UEditor使用文档
  • AngularJS教程
  • ThinkPHP5.0教程

Java的脚本机制、编译器API

时间:2021-12-04  作者:Howlet  

学习 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 = 域名ngineByName("JS");

    
    // 执行脚本语言
    String script = "var num = 1 + 2";
    域名(script);

    
    // 也可以从流中获取脚本
    FileInputStream fileInputStream = new FileInputStream(new File("域名"));
    InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
    域名(inputStreamReader);

    
    // 绑定变量
    域名("testKey", "testValue");


    // 执行方法
    // 脚本引擎调用方法需要实现 Invocable 接口
    String jsMethod = "function hello() {return \'Hello World\'}";
    域名(jsMethod);
    Object function = ((Invocable) jsEngine).invokeFunction("hello");


    // 获取结果
    Object num = 域名("num");
    Object testValue = 域名("testKey");
    域名tln(域名ring());
    域名tln(域名ring());
    域名tln(域名ring());
}


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 = 域名ystemJavaCompiler();

        /**
         * 参数分别是
         * InputStream in:输入流规定为空,默认的编译器不会接收控制台输入
         * OutputStream out:输出,为空输出到控制台
         * OutputStream err:输出,为空输出到控制台
         * String... arguments:参数,若调用 javac 则是传入启动参数
         * result:返回 0 则编译成功
         */
        int result = 域名(null, null, null, "D:\\域名");

        if (result == 0) {
            域名tln("编译成功");
        } else {
            域名tln("编译失败");
        }
    }
}


2.2 实际事例

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

public class CompilerTest2 {
    
    public static void main(String[] args) throws URISyntaxException, IllegalAccessException, InstantiationException, NoSuchMethodException, ClassNotFoundException, InvocationTargetException {

        // 获取编译器
        JavaCompiler compiler = 域名ystemJavaCompiler();


        // 类名、字符串的类代码
        String className = "TestClass";
        StringBuilder sb = new StringBuilder();
        域名nd("public class TestClass {\n");
        域名nd("\tpublic void hello() {\n");
        域名nd("\t\域名tln(\"Hello World Compiler\");\n");
        域名nd("\t}\n");
        域名nd("}");


        // 将字符串代码转成 JavaFileObject ———— 编译器需要
        StringSource javaFileObject = new StringSource(className, 域名ring());
        Iterable<StringSource> fileObjects = 域名st(javaFileObject);


        // 文件管理器 ———— 编译器需要
        StandardJavaFileManager fileManager = 域名tandardFileManager(null, null, null);


        // 报告诊断信息对象 ———— 编译器需要
        DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<JavaFileObject>();


        // 编译参数:编译后的字节码输出地址
        File classPath = new File(域名entThread().getContextClassLoader().getResource("").toURI());
        String outDir = 域名bsolutePath() + 域名rator;
        Iterable<String> options = 域名st("-d", outDir);


        /**
         * Writer out:输出,为空到控制台
         * JavaFileManager fileManager:文件管理器,为空用编译器的标准文件管理器
         * DiagnosticListener<? super JavaFileObject> diagnosticListener:诊断监听器,为空用编译器默认方法报告
         * Iterable<String> options:编译参数
         * Iterable<String> classes:需要编译的类,用于注解处理
         * Iterable<? extends JavaFileObject> compilationUnits
         */
        域名ilationTask task = 域名ask(null, fileManager, diagnosticCollector, options, null, fileObjects);


        // 执行编译
        boolean result = 域名();


        // 编译错误信息
        if (result != true) {
            for (Diagnostic diagnostic : 域名iagnostics()) {
                域名tln("Error on line: " + 域名ineNumber());
                域名tln("URI: " + 域名ource().toString());
            }
            域名(-1);
        }

        
        // 将字节码加载进 JVM
        Class<?> clazz = 域名ame(className);


        // 创建一个新类,反射执行其方法
        Object instance = 域名nstance();
        Method helloMethod = 域名ethod("hello");
        域名ke(instance);
    }


    /**
     * 字符串的类代码存在于内存之中,而参数需要 FileObject
     * 我们将字符串代码转成 FileObject 类型
     */
    static class StringSource extends SimpleJavaFileObject {
        private String code;

        StringSource(String name, String code) {
            super(域名te("string:///" + 域名ace(".", "/") + 域名nsion), 域名CE);
            域名 = code;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return code;
        }

        public String getCode() {
            return code;
        }
    }
}




标签:编程
湘ICP备14001474号-3  投诉建议:234161800@qq.com   部分内容来源于网络,如有侵权,请联系删除。