首页 > 代码库 > Scripting Java(一):Java中执行脚本

Scripting Java(一):Java中执行脚本

Many implementations of scripting and dynamically typed languages generate Java bytecodes so that programs can be run on the Java Platform, just as are actual Java programs. Implementing a language in this way (or as a Java interpreter class for the scripting language) provides all the advantages of the Java platform: scripting implementations can take advantage of the Java platform‘s binary portability, security, and high performance bytecode execution.

上面这段话可以很好的回答为什么会有这么多基于Java平台的脚本语言。anyway,今天我们只是来看看怎么在我们的Java代码中执行脚本语言。下面使用两种语言来演示,Groovy 2.4.0和Scala 2.11.5。

有两种方式可以在Java中执行脚本,

Java SE includes JSR 223: Scripting for the Java Platform API. This is a framework by which Java applications can "host" script engines. The scripting framework supports third-party script engines using the JAR service discovery mechanism. It is possible to drop any JSR-223 compliant script engine in the CLASSPATH and access the same from your Java applications (much like JDBC drivers, JNDI implementations are accessed).

所以我们既可以使用JSR223的API来执行脚本,当然也可以直接使用脚本语言提供的API来执行了。下面分别来看看。

JSR-223

通过ScriptEngineManager#getEngineByName这个API我们可以拿到实现了JSR-223的脚本引擎,那么我们怎么知道传什么参数才能拿到Groovy跟Scala的引擎呢?冲进去看看代码就知道了,

    public ScriptEngine getEngineByName(String shortName) {
        if (shortName == null) throw new NullPointerException();
        //look for registered name first
        Object obj;
        if (null != (obj = nameAssociations.get(shortName))) {
            ScriptEngineFactory spi = (ScriptEngineFactory)obj;
            try {
                ScriptEngine engine = spi.getScriptEngine();
                engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE);
                return engine;
            } catch (Exception exp) {
                if (DEBUG) exp.printStackTrace();
            }
        }

        for (ScriptEngineFactory spi : engineSpis) {
            List<String> names = null;
            try {
                names = spi.getNames();
            } catch (Exception exp) {
                if (DEBUG) exp.printStackTrace();
            }

            if (names != null) {
                for (String name : names) {
                    if (shortName.equals(name)) {
                        try {
                            ScriptEngine engine = spi.getScriptEngine();
                            engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE);
                            return engine;
                        } catch (Exception exp) {
                            if (DEBUG) exp.printStackTrace();
                        }
                    }
                }
            }
        }

        return null;
    }
    /**
     * Registers a <code>ScriptEngineFactory</code> to handle a language
     * name.  Overrides any such association found using the Discovery mechanism.
     * @param name The name to be associated with the <code>ScriptEngineFactory</code>.
     * @param factory The class to associate with the given name.
     * @throws NullPointerException if any of the parameters is null.
     */
    public void registerEngineName(String name, ScriptEngineFactory factory) {
        if (name == null || factory == null) throw new NullPointerException();
        nameAssociations.put(name, factory);
    }

所以可以看到有两种方式可以找到ScriptEngine,

  • 如果已经通过调用ScriptEngineManager#registerEngineName注册了名字,那么需要知道注册的是啥名字;
  • 如果没有,那就需要知道具体的ScriptEngineFactory实现的getNames方法返回了啥;

ScriptEngineFactory是通过ServiceLoader机制来进行加载的,看下Groovy跟Scala各自的实现都是啥,

技术分享

技术分享

javax.script.ScriptEngineFactory文件内容分别是,

org.codehaus.groovy.jsr223.GroovyScriptEngineFactory
scala.tools.nsc.interpreter.IMain$Factory

没有找到有调用registerEngineName方法,那就看下各自的getNames方法吧,GroovyScriptEngineFactory的,

    private static final String SHORT_NAME = "groovy";

    private static final String LANGUAGE_NAME = "Groovy";
    static {
        List<String> n = new ArrayList<String>(2);
        n.add(SHORT_NAME);
        n.add(LANGUAGE_NAME);
        NAMES = Collections.unmodifiableList(n);

        n = new ArrayList<String>(1);
        n.add("groovy");
        EXTENSIONS = Collections.unmodifiableList(n);

        n = new ArrayList<String>(1);
        n.add("application/x-groovy");
        MIME_TYPES = Collections.unmodifiableList(n);
    }

所以可以通过"groovy"或者"Groovy"来拿到Groovy的ScriptEngine。看下IMain,

    @BeanProperty
    val names: JList[String] = asJavaList("scala")

alright,那就是通过"scala"来拿到了。代码在后面,两种方式一起上。

Shell

通过groovy.lang.GroovyShell和IMain都可以直接执行脚本,这两个就是上面我们说的脚本语言提供的API。

我们可以在脚本语言提供的shell环境执行试试,

$ sudo groovy/bin/groovysh
[sudo] password for blues: 
Feb 3, 2015 12:36:43 AM org.codehaus.groovy.runtime.m12n.MetaInfExtensionModule newModule
WARNING: Module [groovy-nio] - Unable to load extension class [org.codehaus.groovy.runtime.NioGroovyMethods]
Groovy Shell (2.4.0, JVM: 1.6.0_33)
Type ':help' or ':h' for help.
-----------------------------------------------------------------------------
groovy:000> import groovy.lang.GroovyShell
===> groovy.lang.GroovyShell
groovy:000> groovySh = new GroovyShell()
===> groovy.lang.GroovyShell@1e74f8b
groovy:000> groovySh.evaluate("import java.util.Random; random = new Random(); random.nextInt();")
===> -1378717507
$ scala
Welcome to Scala version 2.11.5 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_51).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import scala.tools.nsc.interpreter._
import scala.tools.nsc.interpreter._

scala> val iMain = new IMain()
iMain: scala.tools.nsc.interpreter.IMain = scala.tools.nsc.interpreter.IMain@65adfc7b

scala> iMain.eval("import java.util.Random; val random = new Random(); random.nextInt();")
res0: Object = 1320282527

都没有问题。

最后,回到最开始的问题,怎么在Java代码中执行脚本?下面就把两种方式的代码一起贴上,

import groovy.lang.GroovyShell;
import scala.tools.nsc.interpreter.IMain;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class Main {

    public static void main(String[] args) throws Exception {
        ScriptEngineManager sem = new ScriptEngineManager();

        String script_groovy =
                "import java.util.Random; random = new Random(); random.nextInt();";

        ScriptEngine se_groovy = sem.getEngineByName("groovy");
        System.out.println(se_groovy.eval(script_groovy));

        GroovyShell groovyShell = new GroovyShell();
        System.out.println(groovyShell.evaluate(script_groovy));

        String script_scala =
                "import java.util.Random; val random = new Random(); random.nextInt();";

        ScriptEngine se_scala = sem.getEngineByName("scala");
        System.out.println(se_scala.eval(script_scala));

        IMain iMain = new IMain();
        System.out.println(iMain.eval(script_scala));

    }

}
1424725324
1691255297
459327395
1109624277

本来想玩下JDK自带的JSR223实现,也就是Nashorn,which is an implementation of the ECMAScript Edition 5.1 Language Specification,但是要到JDK8才有得玩,改天吧^_^ 轻拍。

参考资料

  • http://docs.oracle.com/javase/8/docs/technotes/guides/scripting/

Scripting Java(一):Java中执行脚本