JDK1.6开始,Java引入了jsr223,就是可以用一致的形式在JVM上执行一些脚本语言,如js脚本,本文详细说明了java脚本引擎的使用方式,并贴出了大量的经过实际测试的java源代码,请各位参考:

Java 脚本引擎技术相关内容,都已经在Java代码中以注释的形式说明了,请测评:



package jaas;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.script.*;
import java.io.File;
import java.util.*;

public class Test {

    /**
     * 文中的某些示例用到了 JNDI,需要提供 JNDI 实现者,参数:-Djava.naming.factory.initial=org.apache.naming.java.javaURLContextFactory
     * 使用了 Tomcat 的 JNDI 实现,添加依赖:
     * <dependency>
     * <groupId>org.apache.tomcat</groupId>
     * <artifactId>tomcat-catalina</artifactId>
     * <version>8.5.6</version>
     * </dependency>
     */
    public static void main(String[] args) throws Exception {
        // 获取脚本引擎管理器
        ScriptEngineManager manager = new ScriptEngineManager();
        List<ScriptEngineFactory> engineFactories = manager.getEngineFactories();
        System.out.println("当前 JVM 支持的脚本引擎:");
        for (ScriptEngineFactory sef : engineFactories) {
            System.out.println("引擎名称:" + sef.getEngineName());
            System.out.println("\t 别名:" + sef.getNames());
            System.out.println("\t MimeTypes:" + Arrays.asList(sef.getMimeTypes()));
        }
        System.out.println();
        // 获取指定的 js 脚本引擎
        ScriptEngine jsEngine = manager.getEngineByName("js");
//        ScriptEngine jsEngine = manager.getEngineByName("javascript");
//        ScriptEngine jsEngine = new ScriptEngineManager().getEngineByMimeType("text/javascript");

        // 引擎测试

        /**
         * 脚本语言支持API使用语言绑定对象实现Java语言编写的程序与脚本语言间的数据传递。
         * 语言绑定对象实际上就是一个简单的哈希表,用来存放和获取需要共享的数据,
         * 其定义的接口为javax.script.Bindings,继承自java.util.Map接口。
         * 一个脚本引擎在执行过程中可能会使用多个语言绑定对象,不同语言绑定对象的作用域不同。
         * ScriptEngine类提供out和get方法对脚本引擎中特定作用域的默认语言绑定对象进行操作。
         */
        testEngine(jsEngine);
        //
        testContext_Java_Params(jsEngine);
    }

    public static void testEngine(ScriptEngine engine) throws Exception {
        engine.put("name", "张三");// 参数
        engine.eval("var msg = '你好, ' + name;");// 使用某作用域的参数
        engine.eval("println('js println: '+msg);");// 使用某作用域的参数
        Object obj = engine.get("msg");// java 可获取到 js 脚本返回的对象
        System.out.println("java println: " + obj);
        System.out.println();

        // 自定义语言绑定对象(如语言绑定对象中包含程序自己独有的数据等情形……)
        Bindings bindings = new SimpleBindings();
        bindings.put("fruit", "Apples");
        engine.eval("println('I like ' + fruit);", bindings);
        System.out.println();

        ScriptContext context = engine.getContext();// 获取引擎默认的上下文

        // 重定向输出到文件
//        context.setWriter(new FileWriter("/home/conquer/Desktop/a.txt"));
        engine.eval("println('Hello World, I am from Js script.');");
        System.out.println();

        // 绑定对象作用域1
        context.setAttribute("name", "张三", ScriptContext.GLOBAL_SCOPE);//从同一引擎工厂中创建的所有脚本引擎对象
        context.setAttribute("name", "李四", ScriptContext.ENGINE_SCOPE);//当前的脚本引擎对象
        Object name = context.getAttribute("name");//值按优先级为 李四
        System.out.println(name);
        System.out.println();

        // 绑定对象作用域2
        Bindings bindings1 = engine.createBindings();
        bindings1.put("name", "张三");
        context.setBindings(bindings1, ScriptContext.GLOBAL_SCOPE);
        Bindings bindings2 = engine.createBindings();
        bindings2.put("name", "李四");
        context.setBindings(bindings2, ScriptContext.ENGINE_SCOPE);
        engine.eval("println('脚本按优先级得到参数:'+name);");    //李四
        System.out.println();

        Bindings bindings3 = context.getBindings(ScriptContext.ENGINE_SCOPE);// 直接获取当前引擎作用域参数集合
        Bindings bindings4 = context.getBindings(ScriptContext.GLOBAL_SCOPE);// 直接获取所有引擎作用域参数集合

        context.setAttribute("name", "张三", ScriptContext.GLOBAL_SCOPE);// 属性设置也具有作用域的选择
//        engine.eval("println(name);");

        // b编译运行,提升脚本运行效率
        if (engine instanceof Compilable) {
            String performanceTest = "var a = 100;\n" +
                    "var b = 100+a;\n" +
                    "var c = a+b;\n" +
                    "println(c);" +
                    "println('');";
            Compilable compilable = (Compilable) engine;
            CompiledScript compiledScript = compilable.compile(performanceTest);
            compiledScript.eval();
        }

        // 脚本中 调用 java 方法
        File file = new File("/home/conquer/mine/work_space/idea/Eden/jse/src/main/java/jaas/loginscript.js");
        engine.put("file", file);

        Bindings engineBindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
        engineBindings.put("file2", file);

        engine.eval("println('脚本中执行java对象方法:'+file.getAbsoluteFile())");
        engine.eval("println('脚本中执行java对象方法2:'+file.getAbsoluteFile())");
        engine.eval("println(file.getAbsolutePath());");
        Object eval1 = engine.eval("file.getAbsolutePath();");
        System.out.println("打印js从java得到的数据:" + eval1);
        engine.eval("println(file.isHidden());");

        Properties temp = new Properties();
        temp.setProperty("aaa", "aaaaa");
        engineBindings.put("properties", temp);
        engine.eval("println(properties.toString());");
        Object eval2 = engine.eval("properties.toString();");
        System.out.println("打印js从java中读取Properties:" + eval2);
        engine.eval("println(properties.getProperty('aaa'));");
        Object eval3 = engine.eval("properties.getProperty('aaa');");
        System.out.println("打印js从java中读取Properties,key 为 aaa:" + eval3);

        SayHello sayHello = new SayHello();
        engine.put("sayHello1", sayHello);
        engine.getBindings(ScriptContext.ENGINE_SCOPE).put("sayHello2", sayHello);
        engine.eval("println(sayHello1.hello('张三111'))");// 注意类必需声明为 public的
        Object hello = engine.eval("sayHello2.hello('张三222')");// 注意类必需声明为 public的
        System.out.println("js 获取到的java返回:" + hello);// ok

        // 也可以通过下面的方式将 实参 放到 jndi 上下文中获取,脚本中仍然使用形式参数
        ScriptContext scriptContext = new SimpleScriptContext();
        Bindings bindings5 = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE);
        bindings5.put("name", "张三");
        Context ctx = new InitialContext();// 这里是启动之前设置的 tomcat的 jndi 实现
        ctx.bind("jndiObj", sayHello);

        Object eval = engine.eval("var ctx = new javax.naming.InitialContext();\n" +
                "var myBean = ctx.lookup(\"jndiObj\");\n" +
                "myBean.hello(name);", scriptContext);
//                "myBean.hello('name');", scriptContext);// 使用 jndi 保存参数,这里不需要传递 实际参数
        System.out.println("js 调用 java 方法返回值:" + eval);
        System.out.println();

        // 方法调用,Java 调用 脚本中定义的方法
        /**
         * 虚拟机支持脚本的意义在于实现函数式的编程,脚本中最重要的便是方法,
         * 脚本引擎允许使用者单独调用脚本中的某个方法,
         * 支持此操作的脚本引擎可以通过实现javax.script.Invocable接口。
         * JavaSE中的JavaScript引擎已实现了Invocable接口。
         */
        if (engine instanceof Invocable) {
            String scriptText = "function greet(name) { println('Hello, ' + name); } ";
            engine.eval(scriptText);// 这里是在定义 greet 方法,只有定义以后才能进行调用:invokeFunction
            Invocable invocable = (Invocable) engine;
            invocable.invokeFunction("greet", "张三");// 调用方法,并传递参数

            // 调用成员方法
            String scriptText2 = "var obj = { getGreeting : function(name) { return 'Hello, ' + name; } }; ";
            engine.eval(scriptText2);// 定义脚本对象,并且定义对象成员方法
            Object scriptObj = engine.get("obj");//获取到脚本中的实例化的对象
            Object result = invocable.invokeMethod(scriptObj, "getGreeting", "张三");   //第一个参数为方法所属对象,后面调用对象的方法和参数
            System.out.println(result);

            // 脚本方法 Java 化 !!! 是指将 java 的调用自动映射到脚本中方法执行上,
            // 需要用到java 接口,接口的方法定义和脚本中方法定义一致,即:脚本作为 接口 的实现类
            String scriptText3 = "function getGreeting(name) { " +
                    "println('我是 Java 定义的 Greet 接口的具体实现哦...');" +
                    "return '你好, ' + name; } ";
            engine.eval(scriptText3);
            Greet greet = invocable.getInterface(Greet.class);
            System.out.println(greet.getGreeting("Alex"));
        }
    }

    interface Greet {
        String getGreeting(String name);
    }

    public static void testContext_Java_Params(ScriptEngine engine) throws Exception {
        Map<String, String> params = new HashMap() {{
            // 脚本中需要的形参名称及对应的实参值
            put("user", "admin");// 必需包含 js 脚本中调用的LoginBean.authenticate类方法需要的参数
            put("password", "pwd");// 必需包含 js 脚本中调用的LoginBean.authenticate类方法需要的参数
        }};

        // 脚本需要的 ScriptContext 对象,脚本中引起的参数都需要从这个对象里面获取
        ScriptContext scriptContext = new SimpleScriptContext();//也可以在这里直接 new 出来
//        ScriptContext scriptContext = engine.getContext();// 构造参数里面也是直接 new 出来的

        /**
         * 绑定参数两个作用域:
         * ScriptContext.ENGINE_SCOPE(当前的脚本引擎)
         * ScriptContext.GLOBAL_SCOPE(从同一引擎工厂中创建的所有脚本引擎对象)
         */

        // 绑定方式1
        Bindings bindings = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE);
        // 脚本中需要的形参名称及对应的实参值
//        bindings.put("user", "admin");
//        bindings.put("password", "pwd");
        bindings.putAll(params);

        // 绑定方式2
//        for (Map.Entry<String, String> e : params.entrySet()) {
//            scriptContext.setAttribute(e.getKey(), e.getValue(), ScriptContext.GLOBAL_SCOPE);
//        }


        // 写自己的这个 JNDI 上下文,是因为 js 脚本中的内容要用到 应用自定义的对象,而在脚本中不能创建 非 java 的对象
        // 所以我们事先创建好,放到 JNDI 上下文,以供脚本中 lookup 查找并使用对象
        InitialContext context = new InitialContext();
        context.bind("script_ref_obj", new LoginBean());

        String scriptText = getScriptText();
        System.out.println("脚本内容:");
        System.out.println(scriptText);
        System.out.println();

        List<String> myGroups = (List<String>) engine.eval(scriptText, scriptContext);// 脚本的最后以后具有返回值的可以在这里获取到!!
        System.out.println("脚本执行后的返回值:");
        System.out.println(myGroups);
    }

    /**
     * Scanner 类用于扫描、接收、分段处理字符串,可以指定分割符号,
     * 通过 一系列的 nextXXX() 可以依次获取到分割的内容片段
     * <p>
     * Scanner 可以接入一个 InputStream 或者一个 File(内部转为FileInputStream)以作为字符输入端
     */
    private static String getScriptText() throws Exception {
//        测试的话,可以将脚本内容直接硬编码写在这里:
        String jsText = "println(\"js begin invoke...\");\n" +
                "var ctx = new javax.naming.InitialContext();\n" +
                "var myBean = ctx.lookup(\"script_ref_obj\");\n" +
                "myBean.authenticate(user, password);";
        return jsText;


//        从文件中读取脚本
//        Scanner 可以采用正则表达式分段读取字符串内容,分段符合可以使用 useDelimiter 方法指定
//        如果不写,默认是空格分割 scanner.useDelimiter(",") 表示以“,”分割读取内容
//        File script = new File("/home/conquer/mine/work_space/idea/Eden/jse/src/main/java/jaas/loginscript.js");
//        Scanner scanner = new Scanner(script).useDelimiter("\\Z");
//        return scanner.next();
    }
}



package jaas;

import javax.security.auth.login.FailedLoginException;
import java.util.Arrays;
import java.util.List;

public class LoginBean {

    public List<String> authenticate(String user, String password) throws FailedLoginException {
        System.out.println("从 js 脚本调入进来的 java 方法开始执行...");
        System.out.println("user:" + user);
        System.out.println("password:" + password);
        if (true) {// test
            return Arrays.asList("adminRole", "tomcatRole");
        }

        if ("paul".equals(user) && "michelle".equals(password)) {
            return Arrays.asList("Manager", "rockstar", "beatle");
        }

        if ("eddie".equals(user) && "jump".equals(password)) {
            return Arrays.asList("Employee", "rockstar", "vanhalen");
        }

        throw new FailedLoginException("Bad user or password!");
    }
}



package jaas;

public class SayHello {
    public String hello(String name) {
        System.out.println("java 方法执行");
        return "你好," + name;
    }
}



loginscript.js 脚本文件内容:


println("js begin invoke...");
var ctx = new javax.naming.InitialContext();
var myBean = ctx.lookup("script_ref_obj");
myBean.authenticate(user, password);