Java動態(tài)編譯那些事

jdk1.6中添加了編譯API, 我們可以在java代碼中調用編譯API動態(tài)編譯JAVA源文件, 也就是運行時編譯. 下面對JAVA動態(tài)編譯API的用法進行簡單介紹:

一. 編譯API概要

下面是動態(tài)編譯API相關類的類圖:

編譯API.png

關鍵組件介紹:

  • JavaCompiler - 表示java編譯器, run方法執(zhí)行編譯操作. 還有一種編譯方式是先生成編譯任務(CompilationTask), 讓后調用CompilationTask的call方法執(zhí)行編譯任務
  • JavaFileObject - 表示一個java源文件對象
  • JavaFileManager - Java源文件管理類, 管理一系列JavaFileObject
  • Diagnostic - 表示一個診斷信息
  • DiagnosticListener - 診斷信息監(jiān)聽器, 編譯過程觸發(fā). 生成編譯task(JavaCompiler#getTask())或獲取FileManager(JavaCompiler#getStandardFileManager())時需要傳遞DiagnosticListener以便收集診斷信息

二. 用法

  • (1) 下面的demo, 先構建一個類的源文件, 再使用流寫入到磁盤中, 接著調用編譯api, 編譯磁盤上的源文件, 最后使用反射調加載并調用編譯后的字節(jié)碼. 代碼如下:
package com.example.dynamic_compiler;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

public class DynamicCompileDemo {

    static void eval(String source) {
        //構建一個類的源代碼
        StringBuffer sourceCode = new StringBuffer();
        sourceCode.append("public class Temp {").append("\r\n")
                .append("public static String call(String args) {").append("\r\n")
                .append("System.out.println(\""+source+"\");").append("\r\n")
                .append("return \"Hello, " + source + "\";").append("\r\n")
                .append("}").append("\r\n")
                .append("}");
        try {
            //將源文件寫入到磁盤中
            String javaFileName = "Temp.java";
            //生成的Java源文件存放到<module>/build/generated/source/java目錄下  (開發(fā)工具為Android Studio, java-demo是我的module名稱)
            File sourceDir = new File("java-demo/build/generated/source/java");
            if (!sourceDir.exists()) {
                sourceDir.mkdirs();
            }
            File javaFile = new File(sourceDir, javaFileName);
            PrintWriter writer = new PrintWriter(new FileWriter(javaFile));
            writer.write(sourceCode.toString());
            writer.flush();
            writer.close();

            //動態(tài)編譯磁盤中的代碼
            //生成的字節(jié)碼文件存放到<module>/build/classes/main目錄下
            File distDir = new File("java-demo/build/classes/main");
            if (!distDir.exists()) {
                distDir.mkdirs();
            }
            JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
            //JavaCompiler最核心的方法是run, 通過這個方法編譯java源文件, 前3個參數(shù)傳null時, 
            //分別使用標準輸入/輸出/錯誤流來 處理輸入和編譯輸出. 使用編譯參數(shù)-d指定字節(jié)碼輸出目錄.
            int compileResult = javac.run(null, null, null, "-d", distDir.getAbsolutePath(), javaFile.getAbsolutePath());
            //run方法的返回值: 0-表示編譯成功, 否則表示編譯失敗
            if(compileResult != 0) {
                System.err.println("編譯失敗!!");
                return;
            }

            //動態(tài)執(zhí)行 (反射執(zhí)行)
            Class klass = Class.forName("Temp");
            Method evalMethod = klass.getMethod("call", String.class);
            String result = (String)evalMethod.invoke(klass.newInstance(), source);
            System.out.println("eval(" + source + ") = " + result);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        eval("Lucy");
    }
}

運行后輸出結果如下:

運行結果
  • (2) 下面的demo, 使用編譯api編譯內存中的java源代碼. 代碼如下:
    a. 自定義一個JavaFileObject對象, 用于表示Java源代碼對象, 如下:
package com.example.dynamic_compiler;

import java.net.URI;

import javax.tools.SimpleJavaFileObject;


public class JavaFileObjectFromString extends SimpleJavaFileObject {
    //Uri uri; //uri為源碼的資源定位符, 在父類中定義

    /**
     * Java源文件的內容
     */
    final String code;

    /**
     * @param className 類的完整名稱
     * @param code
     */
    public JavaFileObjectFromString(String className, String code) {
        //uri為類的資源定位符號, 如: com/stone/generate/Hello.java
        super(URI.create(className.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
        //uri值為com.stone.generate.Hello時, 下面注釋的代碼導致異常 => com.stone.generate.Hello:1: 錯誤: 類Hello是公共的, 應在名為 Hello.java 的文件中聲明
//        super(URI.create(className), Kind.SOURCE);
        this.code = code;
    }

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

(b) 使用一個類的源碼字符串構造一個JavaFileObject, 讓后調用編譯API編譯此JavaFileObject對象, 并生成字節(jié)碼, 最后使用放射調用編譯結果. 代碼如下:

package com.example.dynamic_compiler;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;


public class Demo1 {
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //構建java源文件
        StringBuilder sourceCode = new StringBuilder();
        sourceCode.append("package com.stone.generate;")
                .append("public class Hello {")
                .append("public static void main(String[] args) {")
                .append("System.out.println(\"Hello World !\");")
                .append("}")
                .append("}");

//        //將代碼寫入到內存
//        StringWriter writer = new StringWriter(); //寫入到內存中
//        PrintWriter out = new PrintWriter(writer);
//        out.println(sourceCode.toString());
//        out.flush();
//        out.close();

        //動態(tài)編譯內存中的代碼
        File distDir = new File("java-demo/build/classes/main");
        if (!distDir.exists()) {
            distDir.mkdirs();
        }
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//        JavaFileObject javaFileObject = new JavaFileObjectFromString("Hello", writer.toString());
        JavaFileObject javaFileObject = new JavaFileObjectFromString("com.stone.generate.Hello", sourceCode.toString());
        JavaCompiler.CompilationTask task = compiler.getTask(null, null, null,
                Arrays.asList("-d", distDir.getAbsolutePath()), null,
                Arrays.asList(javaFileObject));
        boolean compileSuccess = task.call();
        if (!compileSuccess) {
            System.out.println("編譯失敗");
        } else {

            //動態(tài)執(zhí)行 (反射執(zhí)行)
            System.out.println("編譯成功");
            URL[] urls = new URL[] {new URL("file:/" + distDir.getAbsolutePath())};
            URLClassLoader classLoader = new URLClassLoader(urls);
            Class dynamicClass = classLoader.loadClass("com.stone.generate.Hello");
            Method method = dynamicClass.getDeclaredMethod("main", String[].class);
            String[] arguments = {null};
            method.invoke(dynamicClass, arguments);
        }
    }
}

運行后輸出結果如下:

運行結果
  • (3) 編譯編譯一個錯誤的Java類. 使用診斷信息監(jiān)聽器手機診斷信息. 代碼如下:
package com.example.dynamic_compiler;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

/**
 * @author stone
 * @date 16/9/6
 */
public class Demo2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //構建java源文件
        String javaFileName = "Demo.java";
        StringBuilder sourceCode = new StringBuilder();

        //正常的java源代碼
//        sourceCode.append("package com.stone.generate;").append("\r\n")
//                .append("public class Demo {").append("\r\n")
//                .append("public static void main(String[] args) {").append("\r\n")
//                .append("System.out.println(\"Hello World !\");").append("\r\n")
//                .append("}").append("\r\n")
//                .append("}");

        //錯誤的java源代碼
        sourceCode.append("package com.stone.generate;").append("\r\n\n")
                .append("public class Demo ").append("\r\n")  //此處少了一個: `{`
                .append("public static void main(String[] args) {").append("\r\n")
                .append("System.out.println(\"Hello World !\");").append("\r\n")
                .append("}").append("\r\n")
                .append("}");

        //見文件寫到磁盤
        File sourceDir = new File("java-demo/build/generated/source/java");
        if (!sourceDir.exists()) {
            sourceDir.mkdirs();
        }
        File javaFile = new File(sourceDir, javaFileName);
        PrintWriter out = new PrintWriter(new FileWriter(javaFile));
        out.write(sourceCode.toString());
        out.flush();
        out.close();


        //獲取編譯器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

        //創(chuàng)建診斷信息監(jiān)聽器, 用于手機診斷信息
        DiagnosticCollector<JavaFileObject> diagnosticListeners = new DiagnosticCollector<>();

        //獲取FileManager
        StandardJavaFileManager javaFileManager = compiler.getStandardFileManager(diagnosticListeners, null, null);
        Iterable it = javaFileManager.getJavaFileObjects(javaFile);
        File distDir = new File("java-demo/build/classes/main");
        if (!distDir.exists()) {
            distDir.mkdir();
        }

        //生編譯任務
        JavaCompiler.CompilationTask task = compiler.getTask(null, javaFileManager, diagnosticListeners,
                Arrays.asList("-d", distDir.getAbsolutePath()), null, it);

        //執(zhí)行編譯任務
        task.call();

        //輸出診斷信息
        for(Diagnostic<? extends JavaFileObject> diagnostic : diagnosticListeners.getDiagnostics()) {
            //可以在此處自定義編譯診(錯誤)斷信息的輸出格式
            System.out.format("Error on line %d in %s%n",
                    diagnostic.getLineNumber(),
                    diagnostic.getSource().toUri());
        }

        //關閉FileManager
        javaFileManager.close();

        //動態(tài)執(zhí)行
        Class klass = Class.forName("com.stone.generate.Demo");
        Method method = klass.getDeclaredMethod("main", String[].class);
        method.invoke(klass, new String[]{null});
    }
}

運行結果如下:

運行結果
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子心肪,更是在濱河造成了極大的恐慌,老刑警劉巖咖城,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異呼奢,居然都是意外死亡宜雀,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門握础,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辐董,“玉大人,你說我怎么就攤上這事禀综〖蚝妫” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵定枷,是天一觀的道長孤澎。 經(jīng)常有香客問我,道長欠窒,這世上最難降的妖魔是什么亥至? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮贱迟,結果婚禮上,老公的妹妹穿的比我還像新娘絮供。我一直安慰自己衣吠,他們只是感情好,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布壤靶。 她就那樣靜靜地躺著缚俏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贮乳。 梳的紋絲不亂的頭發(fā)上忧换,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機與錄音向拆,去河邊找鬼亚茬。 笑死,一個胖子當著我的面吹牛浓恳,可吹牛的內容都是我干的刹缝。 我是一名探鬼主播碗暗,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼梢夯!你這毒婦竟也來了言疗?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤颂砸,失蹤者是張志新(化名)和其女友劉穎噪奄,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡卫病,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年冶匹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叙谨。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖保屯,靈堂內的尸體忽然破棺而出手负,到底是詐尸還是另有隱情,我是刑警寧澤姑尺,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布竟终,位于F島的核電站,受9級特大地震影響切蟋,放射性物質發(fā)生泄漏统捶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一柄粹、第九天 我趴在偏房一處隱蔽的房頂上張望喘鸟。 院中可真熱鬧,春花似錦驻右、人聲如沸什黑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽愕把。三九已至,卻和暖如春森爽,著一層夾襖步出監(jiān)牢的瞬間恨豁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工爬迟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留橘蜜,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓付呕,卻偏偏與公主長得像扮匠,于是被迫代替她去往敵國和親捧请。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,110評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理棒搜,服務發(fā)現(xiàn)疹蛉,斷路器,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • 那是一個原本并不明媚的午后力麸,似乎一切都被蒙上了一層陰沉的面紗可款。我站在手機維修的柜臺前面,握著發(fā)熱到似乎馬上要爆炸的...
    PP_沛沛閱讀 800評論 5 49
  • 文明從定居開始 地球北半球上有一條文明帶克蚂。這條文明帶從北回歸線往北到北緯45度闺鲸,是人類文明的搖籃,也是現(xiàn)代文明最繁...
    許秀江智庫的平方閱讀 857評論 0 0
  • 如果我辦培訓班埃叭,我會做什么樣的培訓了摸恍? 我會進行國學培訓。這是最主要的事情赤屋。 首先立镶,我先把自己的想法和自己的合伙人...
    我心我愿秀閱讀 150評論 3 1