Zeppelin 使用JShell实现java解释器,从此用notebook写java

来源:互联网 发布:ubuntu软件源不能更新 编辑:程序博客网 时间:2024/05/21 05:59

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

scala REPL文档
http://docs.scala-lang.org/overviews/repl/overview.html

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

目前java9还未正式发布,不过功能已经相对完善,可以在这里下载与体验http://blog.csdn.net/nougats/article/details/76219357

可以直接在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>

github上一些其他复杂的手段,使用toolchains等,我并没有用上
https://muyinchen.github.io/2017/07/19/%E5%A6%82%E4%BD%95%E5%9C%A8Maven%E9%A1%B9%E7%9B%AE%E4%B8%AD%E8%AE%BE%E7%BD%AEJava%209/
https://github.com/cfdobber/maven-java9-jigsaw
https://cwiki.apache.org/confluence/display/MAVEN/Java+9+-+Jigsaw