1. 程式人生 > >Zeppelin 使用JShell實現java直譯器,從此用notebook寫java

Zeppelin 使用JShell實現java直譯器,從此用notebook寫java

REPL

互動式直譯器環境
Read(取值)-> Evaluation(求值)-> Print(列印)-> Loop(迴圈)
python,scala都提供原生的REPL ,例如在scala命令列內,鍵入scala程式碼,會直接返回結果
既可以作為一個獨立的程式執行,也可以包含在其他程式中作為整體程式的一部分使用

Zeppelin0.7.2目前不支援java的原因

當前spark直譯器只支援scala,雖然提供了javaSparkContext的一個例項jsc,但並沒有為它提供初始化,
我對spark直譯器的原始碼進行了閱讀,通過呼叫scala的REPL執行scala程式碼

scala呼叫REPL的關鍵程式碼:使用反射,將程式碼扔到repl中執行

/**
 * intp - org.apache.spark.repl.SparkIMain (scala 2.10)
 * intp - scala.tools.nsc.interpreter.IMain; (scala 2.11)
 */
private Results.Result interpret(String line) {
  return (Results.Result) Utils.invokeMethod(
      intp,
      "interpret",
      new Class[] {String.class},//方法的引數列表
new Object[] {line});//方法執行時要用的實參 }

程式碼的執行結果直接通過控制檯輸出,在這之前做了輸出重定向,所以直接重定向到InterpreterOutputStream輸出(提供了write方法,以位元組陣列的形式輸出)。並不做攔截或返回,這也是為什麼dataset.show的輸出沒有直接提供Zeppelin強大的視覺化功能,不過提供了其他解決辦法:zeppelincontext類封裝了各種輸出形式的實現,包括input,chexkbox等表單,show方法封裝了dataframe的視覺化圖表形式輸出等,如果想讓dataset擁有和select語句在Zeppelin中一樣強大的視覺化效果,自己呼叫z.show方法即可

Scala REPL

IMain
http://www.scala-lang.org/api/2.12.1/scala-compiler/scala/tools/nsc/interpreter/IMain.html
scala程式碼直譯器
compile()載入一個完整的Scala檔案
interpret()根據使用者的請求執行一行Scala程式碼
bind()將物件繫結到一個變數,然後可以被稍後解釋的程式碼使用
整體方法基於:編譯所請求的程式碼,然後使用Java類載入器和Java反射來執行程式碼並訪問其結果

細節:一個單獨的編譯器例項用於累積所有成功編譯或解釋的Scala程式碼
為了“解釋”一行程式碼,編譯器將生成一個新物件,其中包含程式碼,和公共成員(以匯出由該程式碼定義的所有變數)
要提取解釋的結果顯示給使用者,將建立第二個“結果物件”,匯入由上述物件匯出的變數,然後匯出名為“eval print”的成員
優缺點
主要的優點是解釋程式碼的行為與編譯程式碼完全一樣,包括全速執行
主要的缺點是重新定義類和方法不能正確處理,因為在Java級別的重新繫結在技術上是困難的
這裡寫圖片描述

這裡寫圖片描述

JShell

從Java9開始,java也可以原生支援repl,這就是JShell

可以直接在bin目錄下啟動JShell,體驗強大功能

這裡寫圖片描述

JShell為我們提供了良好API,可以實現我們自己的直譯器
http://download.java.net/java/jdk9/docs/api/jdk/jshell/package-summary.html
簡單概括:把程式碼丟進JShell裡,JShell會生成一系列snippet流,每一個snippet都有自己的狀態標記,eval方法會執行一句程式碼,並返回該snippet的狀態和初始化的變數值等資訊,實際功能十分強大,還需自己看API文件
這裡寫圖片描述

為Zeppelin0.7.2實現java直譯器

重點在open,interpret方法,關鍵點有輸出重定向,程式碼完整判斷,source,remaining的使用

public class JavaInterpreter extends Interpreter {
  public static Logger logger = LoggerFactory.getLogger(JavaInterpreter.class);

  private JShell j;

  private InterpreterOutputStream outputStream;//zeppelin的輸出流,目的是重定向JShell向控制檯輸出為向web頁面輸出

  public JavaInterpreter(Properties property) {
    super(property);
  }

  public void open() {
    //輸出重定向第一步,JShell中System.out這類輸出會預設輸出到控制檯,在zeppelin上顯示不出,需要重定向JShell輸出到zeppelin的輸出流
    outputStream = new InterpreterOutputStream(logger);
    PrintStream ps = new PrintStream(outputStream);
    //此處out為更改JShell輸出流,err為更改錯誤資訊輸出流,但並沒有得到想要的錯誤資訊,問題暫未解決
    j = JShell.builder().err(ps).out(ps).build();
  }

  public void close() {

  }

  public InterpreterResult interpret(String input, InterpreterContext interpreterContext) {
    //這裡真正結束了重定向,interpreterContext.out為當前段落的輸出流,將outputStream流的輸出定向為interpreterContext.out
    outputStream.setInterpreterOutput(interpreterContext.out);

    InterpreterResult.Code code = InterpreterResult.Code.SUCCESS;
    StringBuffer sb = new StringBuffer();
    while (!input.isEmpty()) {
      SourceCodeAnalysis.CompletionInfo c =
          j.sourceCodeAnalysis().analyzeCompletion(input);
      //source返回程式碼的第一個Snippet,比如以第一個分號為界,eval一次只會執行一個Snippet
      List<SnippetEvent> events = j.eval(c.source());
      for (SnippetEvent e : events) {
        sb.append(e.value() + "\n");
        if (e.causeSnippet() == null) {
          if (e.status() == Snippet.Status.REJECTED) {
            try {
            //向輸出流寫出錯誤程式碼
              interpreterContext.out.write("ERROR: " + c.source() + "\n");
              code = InterpreterResult.Code.INCOMPLETE;
            } catch (IOException e1) {
              e1.printStackTrace();
            }
          }

        }
      }
      //remaining返回程式碼除去source的剩餘部分,執行eval後剩餘的部分,也就是還未被執行的Snippet
      input = c.remaining();
    }
    return new InterpreterResult(code);
  }

  public void cancel(InterpreterContext interpreterContext) {

  }

  public FormType getFormType() {
    return FormType.NATIVE;
  }//三種可選NATIVE,SIMPLE,NONE,具體差異並沒有搞清楚,跟具體邏輯實現無關,普遍遇到過這裡報錯,但還沒有搞清原因

  public int getProgress(InterpreterContext interpreterContext) {
    return 0;
  }
}

java9打包

在pom檔案中引入外掛,其實3.1版本即可

    <properties>
<maven-compiler-plugin.version>3.6.1</maven-compiler-plugin.version>
    </properties>
<build>
        <plugins>
            <plugin>
<groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-compiler-plugin.version}</version>
             </plugin>
        </plugins>
    </build>