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;
        }
    }
}





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM