在java中使用javascript脚本

在java中执行js脚本,首先感觉这种操作有点无意义,但是存在即合理,作者要设计这个功能应该是有他的使用场景。java中支持使用jsp写网页,估计是这个原因才有了脚本引擎。

一、java支持的脚本语言查看

我们可以通过下面的代码查看目前支持的脚本语言:

import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import java.util.List;

/**
 * 腳本语言
 * @Author xingo
 * @Date 2023/11/10
 */
public class JavaScriptDemo {

    public static void main(String[] args) {
        // 支持的腳本引擎列表
        ScriptEngineManager manager = new ScriptEngineManager();
        List<ScriptEngineFactory> factories = manager.getEngineFactories();
        for (ScriptEngineFactory f : factories) {
            System.out.println("egine name : " + f.getEngineName() +
                    "\nengine version : " + f.getEngineVersion() +
                    "\nlanguage name : " + f.getLanguageName() +
                    "\nlanguage version : " + f.getLanguageVersion() +
                    "\nnames : " + f.getNames() +
                    "\nmime : " + f.getMimeTypes() +
                    "\nextension : " + f.getExtensions());
        }
    }
}

可以看到输出内容如下:

egine name : Oracle Nashorn
engine version : 1.8.0_161
language name : ECMAScript
language version : ECMA - 262 Edition 5.1
names : [nashorn, Nashorn, js, JS, JavaScript, javascript, ECMAScript, ecmascript]
mime : [application/javascript, application/ecmascript, text/javascript, text/ecmascript]
extension : [js]

二、简单入门示例

可以看到对js的支持,下面入门一个示例demo展示如何使用该api:

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

/**
 * 腳本语言
 * @Author xingo
 * @Date 2023/11/10
 */
public class JavaScriptDemo {

    public static void main(String[] args) throws ScriptException {
        // 1、创建一个脚本引擎管理器
        ScriptEngineManager manager = new ScriptEngineManager();

        // 2、从引擎管理器中获取一个引擎可以有下面三种方式:
        // getEngineByExtension()、getEngineByMimeType()、getEngineByName()。
        // 只要参数名能对上就可以,参数名称就是上面代码执行输出展示的内容
        // ScriptEngine engine = manager.getEngineByExtension("js");
        // ScriptEngine engine = manager.getEngineByMimeType("text/javascript");
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        // 3、执行js脚本,调用eval()方法
        String script = "var a = 1, b = 2;\n" +
                "var c = a + b;\n" +
                "print(c);";
        engine.eval(script);
    }
}
三、传递参数

上面的这种方式,参数已经在脚本中定义好了,但是在项目中,参数一般是通过方法调用时传递进去的,这时候就需要有方法将参数设置,脚本引擎是支持的,通过 put() 方法将参数传入:

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

/**
 * 腳本语言
 * @Author xingo
 * @Date 2023/11/10
 */
public class JavaScriptDemo {

    public static void main(String[] args) throws ScriptException {
        // 1、创建一个脚本引擎管理器
        ScriptEngineManager manager = new ScriptEngineManager();

        // 2、获取引擎
        ScriptEngine engine = manager.getEngineByName("js");

        // 3、设置参数
        engine.put("a", 1);
        engine.put("b", 2);

        // 4、执行脚本并获取结果输出
        String script = "function add(a, b) {\n" +
                "return a + b;\n" +
                "}\n" +
                "add(a, b);";
        Object val = engine.eval(script);
        System.out.println("sum : " + val);
    }
}

对于上面put的变量,它作用于自身engine范围内,也就是ScriptContext.ENGINE_SCOPE,put 的变量放到一个叫Bindings的Map中,可以通过 engine.getBindings(ScriptContext.ENGINE_SCOPE).get(“a”);得到put的内容。和 ENGINE_SCOPE相对,还有个ScriptContext.GLOBAL_SCOPE 作用域,其作用的变量是由同一ScriptEngineFactory创建的所有ScriptEngine共享的全局作用域。也可以将上面的代码调整为如下形式:

import javax.script.*;

/**
 * 腳本语言
 * @Author xingo
 * @Date 2023/11/10
 */
public class JavaScriptDemo {

    public static void main(String[] args) throws ScriptException {
        // 1、创建一个脚本引擎管理器
        ScriptEngineManager manager = new ScriptEngineManager();

        // 2、获取引擎
        ScriptEngine engine = manager.getEngineByName("js");

        // 3、设置参数:通过Bindings设置
        Bindings bind = engine.createBindings();
        bind.put("a", 1);
        bind.put("b", 2);
        // 绑定上下文,作用域为当前引擎范围
        engine.setBindings(bind, ScriptContext.ENGINE_SCOPE);

        // 4、执行脚本并获取结果输出
        String script = "function add(a, b) {\n" +
                "return a + b;\n" +
                "}\n" +
                "add(a, b);";
        Object val = engine.eval(script);
        System.out.println("sum : " + val);
    }
}
四、动态调用方法

在项目开发过程中一般都是定义好js脚本,然后在需要运行的时候才执行对应的方法,这时候用上面的方式就不行了,需要使用Invocable的invokeFunction()方法实现定义的js函数调用,Invocable可以多次调用脚本库中的函数,使用示例如下:

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

/**
 * 腳本语言
 * @Author xingo
 * @Date 2023/11/10
 */
public class JavaScriptDemo {

    public static void main(String[] args) throws Exception {
        // 1、创建一个脚本引擎管理器
        ScriptEngineManager manager = new ScriptEngineManager();

        // 2、获取引擎
        ScriptEngine engine = manager.getEngineByName("js");

        // 3、加载需要运行的脚本代码
        String script = "function add(a, b) {\n" +
                "return a + b;\n" +
                "}";
        engine.eval(script);

        // 4、动态调用函数并获取结果
        Invocable invocable = (Invocable) engine;
        Object result = invocable.invokeFunction("add", 1, 2);
        System.out.println("sum : " + result);
    }
}

Invocable不但可以调用js中定义的方法,还可以调用java中的接口,在接口中定义方法名,方法的实现使用js代码开发,只要保证接口中的方法名和js的方法名一致就可以调用了。
比如我定义了一个java接口如下:

/**
 * 定义一个接口,接口的实现在js中
 * @Author wangxixin
 * @Date 2023/11/10
 */
public interface JavaScriptMethod {

    /**
     * 求两个数的和
     * @param a     参数1
     * @param b     参数2
     * @return
     */
    int add(int a, int b);

    /**
     * 求两个数的最大值
     * @param a     参数1
     * @param b     参数2
     * @return
     */
    int max(int a, int b);
}

这时我要调用方法就可以改成下面这种方式:

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

/**
 * 腳本语言
 * @Author xingo
 * @Date 2023/11/10
 */
public class JavaScriptDemo {

    public static void main(String[] args) throws Exception {
        // 1、创建一个脚本引擎管理器
        ScriptEngineManager manager = new ScriptEngineManager();

        // 2、获取引擎
        ScriptEngine engine = manager.getEngineByName("js");

        // 3、加载需要运行的脚本代码
        String script = "function add(a, b) {\n" +
                "return a + b;\n" +
                "}\n" +
                "function max(a, b) {\n" +
                "return a >= b ? a : b;\n" +
                "}";
        engine.eval(script);

        // 4、动态调用函数并获取结果
        Invocable invocable = (Invocable) engine;
        JavaScriptMethod jsMethod = invocable.getInterface(JavaScriptMethod.class);
        System.out.println("sum : " + jsMethod.add(1, 2));
        System.out.println("max : " + jsMethod.max(1, 2));
    }
}
五、脚本中使用java对象

脚本引擎不但可以在java代码中调用js开发的脚本代码,也可以将java中的对象交给脚本执行,但js是解释型语言,跟java这种编译型语言的执行性能还是有很大差距的,作为知识点还是要了解一下:

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

/**
 * 腳本语言
 * @Author xingo
 * @Date 2023/11/10
 */
public class JavaScriptDemo {

    public static void main(String[] args) throws Exception {
        // 1、创建一个脚本引擎管理器
        ScriptEngineManager manager = new ScriptEngineManager();

        // 2、获取引擎
        ScriptEngine engine = manager.getEngineByName("js");

        // 3、使用java中的对象
        String[] arr = { "kafka", "RocketMQ", "RabbitMQ" };
        engine.put("arr", arr);
        String script = "for(var i = 0; i < arr.length; i++) {\n" +
                "print(arr[i]);\n" +
                "}";
        engine.eval(script);
    }
}

脚本引擎默认是解释执行的,如果需要反复执行脚本,可以使用它的可选接口Compilable来编译执行脚本,以获得更好的性能:

import javax.script.*;

/**
 * 腳本语言
 * @Author xingo
 * @Date 2023/11/10
 */
public class JavaScriptDemo {

    public static void main(String[] args) throws Exception {
        // 1、创建一个脚本引擎管理器
        ScriptEngineManager manager = new ScriptEngineManager();

        // 2、获取引擎
        ScriptEngine engine = manager.getEngineByName("js");

        // 3、编译脚本代码
        Compilable compilable = (Compilable) engine;
        String script = "function add(a, b) {\n" +
                "return a + b;\n" +
                "}";
        CompiledScript compiledScript = compilable.compile(script);
        compiledScript.eval();

        // 4、调用方法获取执行结果
        Invocable invocable = (Invocable) engine;
        Object result = invocable.invokeFunction("add", 1, 2);
        System.out.println("sum : " + result);
    }
}
六、加载外部脚本文件

上面的示例程序都是将js脚本代码嵌入到java代码中,如果要修改js代码就必须要重新打包发布项目,其实我们可以将js代码写入文件中,在代码中加载该文件,这时如果要修改js代码就不用重新编译java代码:
比如在我的E盘下有一个demo.js文件:

function add(a, b) {
  return a + b;
}

在java代码中加载该js文件并调用js中的方法:

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.FileReader;

/**
 * 腳本语言
 * @Author xingo
 * @Date 2023/11/10
 */
public class JavaScriptDemo {

    public static void main(String[] args) throws Exception {
        // 1、创建一个脚本引擎管理器
        ScriptEngineManager manager = new ScriptEngineManager();

        // 2、获取引擎
        ScriptEngine engine = manager.getEngineByName("js");

        // 3、加载js文件
        engine.eval(new FileReader("E:\\demo.js"));

        // 4、调用方法获取执行结果
        Invocable invocable = (Invocable) engine;
        Object result = invocable.invokeFunction("add", 1, 2);
        System.out.println("sum : " + result);
    }
}

以上总结的就是在java中脚本引擎的常用API,使用场景其实并不多。我在做项目过程中就有一个需求用到过:用户想自定义一些计算公式,这些计算公式也不复杂,都是一些加减乘除运算,在写这个需求时我就把用户的公式保存下来,然后在调用这些公式时使用了脚本引擎,用很少的代码就完成了这个功能,还是很好用的。

你可能感兴趣的:(java,javascript,开发语言)