JAVA的动态性之脚本语言支持API

发表于:2020-10-10 09:34

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:单守臣    来源:博客园

  JAVA语言是一种静态类型的编程语言。静态类型的含义是指在编译的时候进行类型检查。JAVA源代码中的每个每个变量的类型都需要显式地进行声明。所有的变量、方法的参数和返回值的类型在程序运行之前就必须是已知的。JAVA语言的这种静态类型特性使编译器可以在编译的时候执行大量的检查来发现代码中明显的类型错误,这样一来,代码中会包含很多不必要的类型声明,使用代码不够简洁灵活。与静态类型语言相对应的是动态类型语言,如JavaScript和Ruby等。动态类型语言的类型检查在运行中进行。源代码中不需要显式地声明类型。去掉了类型声明之后,使用动态类型语言写的代码更加简洁。近年来,动态类型语言的流行也反映了语言中动态性的重要性。适当的动态性对于提高开发的效率是有帮助的,可以减少开发人员需要编写的代码量。
  从Java6引入了脚本语言支持API,文本主要围绕脚本语言支持API讲解(反射将在下一文提起)
  随着JAVA平台的流行,很多的脚本语言(scripting language)都可以运行在JAVA虚拟机上,其中比较流行的有JavaScript、JRuby、Jython和Groovy等。对于这些运行在JAVA虚拟机平台上的脚本语言来说,并不需要为它们准备额外的运行环境,直接复用已有的JAVA虚拟机环境即可。在应用开发中使用脚本语言,实际上是“多语言开发”的一种很好的实践,即根据应用的需求和语言本身的特性来选择最合适的变成语言,以快速高效的解决应用中的问题,比如一个一用,可以用Groovy来编写用户界面,用Java编写核心业务逻辑,用Ruby来进行数据处理,不同的语言编写的代码可以同时运行在同一个JAVA虚拟机上。这些脚本语言于Java语言之间的交互,是由脚本语言支持API来完成的。
  1、脚本引擎
  来一个简单的例子(相当于java中System.out.println方法),了解一下脚本引擎的用法,代码如下:
public void greet() throws ScriptException{
        ScriptEngineManager manager=new ScriptEngineManager();
        ScriptEngine engine=manager.getEngineByName("JavaScript");
        if(engine==null)
            throw new RuntimeException("未找到JavaScript语言执行引擎");
        engine.eval("println('HelloWorld!');");
    }
  上面的代码中我们是通过脚本引擎的名称来查找的。实际上,脚本引擎管理器提供了三种查找脚本引擎的方式,分别是通过名称、文件扩展名和MIME类型来完成,如:
ScriptEngine engine=manager.getEngineByExtension("js");
ScriptEngine engine=manager.getEngineByMimeType("text/javascript");
ScriptEngine engine=manager.getEngineByName("JavaScript");
  2、语言绑定
  所谓的语言绑定对象就是一个简单的哈希表,用来存放和获取需要共享的数据。所有的数据都对应这个哈希表中的一个条目,是简单的键值对。接口javax.script.Bindings定义了语言绑定对象的接口,它继承自java.util.Map接口。一个脚本引擎在执行过程中可能会使用多个语言绑定对象,用来存放在执行过成功产生的全局对象等。ScriptEngine类提供了put和get方法对脚本引擎中特定作用域的默认语言绑定对象进行操作。程序可以直接使用这个默认的语言绑定对象,也可以使用自己的语言绑定对象。在脚本语言的执行过程中,可以将语言绑定对象看成是一个额外的变量映射表。
  脚本引擎默认的语言绑定对象示例:
  首先通过ScriptEngine的put方法向脚本引擎默认的语言绑定对象中添加一个名为name的字符串,接着在脚本中直接根据名称来引用这个对象。同样,在脚本中创建的全局变量message也可以通过ScriptEngine的get方法来获取。这样就实现了Java程序与脚本之间的双向数据传递。数据传递过程中的类型转换是有脚本引擎类完成的,转换规则取决于具体的脚本语言的语法。
public void useDefaultBinding() throws ScriptException{
        ScriptEngine engine=getScriptEngine();
        engine.put("name","andy");
        engine.eval("var message='Hello,'+name;");
        engine.eval("println(message);");
        Object obj=engine.get("message");
        System.out.println(obj);
    }
  自定义语言绑定对象的示例:
  如果希望使用自己的语言绑定对象,可以调用脚本引擎的createBindings方法或者创建一个javax.script.SimpleBindings对象,并传递给脚本引擎的eval方法。通过eval方法传递的语言绑定对象,仅在当前eval调用中生效,并不会改变引擎默认的语言绑定对象。
publicvoiduseCustomBinding()throwsScriptException{
        ScriptEngine engine=getScriptEngine();
        Bindings bindings=newSimpleBindings();
        bindings.put("hobby","playing gemes");
        engine.eval("println('I like '+hobby);",bindings);
    }
  3、脚本执行上下文
  与脚本引擎相关的另外一个重要的接口是javax.script.ScriptContext,其中包含脚本引擎执行过程中的相关上下文信息,可以与JavaEE中的servlet规范中的javax.servlet.ServletContext接口进行类型。脚本引擎通过此上下文对象来获取与脚本执行相关的信息,也允许开发人员通过次对象类配置脚本引擎的行为。该上下文对象中主要包括3类信息。
  3.1、输入输出
  其中包括脚本在执行中用来读取数据输入的java.io.Reader对象以及输出正确内容和错误信息的java.io.Writer对象。在默认情况下,脚本的输入输出都发生在标准控制台中,如果希望把脚本的输出写入到文件中,可以通过setWriter方法把脚本的输出重定向到一个文件中。通过ScriptContext的setReader和setErrorWriter方法可以分别设置脚本执行时的数据出入来源和产生错误时的出错信息的输出目的。代码如下:
public void useCustomBinding()throws ScriptException{
        ScriptEngine engine=getScriptEngine();
        Bindings bindings=new SimpleBindings();
        bindings.put("hobby","playing gemes");
        engine.eval("println('I like '+hobby);",bindings);
    }
  3.2、自定义属性
  ScriptContext中也有与ServletContext中类似的获取和设置属性的方法,即setAttribute和getAttribute。所不同的是ScriptContext中的属性是有作用于之分的。设置属性的时候需要显式地指定所在的作用于。在获取属性的时候,即可以选择指定的作用于中查找,也可以选择根据作用于优先级自动进行查找。脚本上下文实现中包含的作用域是固定的,开发人员不能随意定义自己的作用于。通过ScriptContext的getScopes方法可以得到所有可用的作用于列表。ScriptContext中预先定义了两个作用于:常量ScriptContext.GLOBAL_SCOPE表示的作用于对应的是从一个引擎工厂中创建出来的所有脚本引擎对象,而ScriptContext.ENGINE_SCOPE表示的作用域对应的是当前的脚本引擎。后者优先于前者。
  作用域影响同名属性查找的示例:
  ENGINE_SCOPE中的属性name隐藏了GLOBAL_SCOPE中的同名属性
public void scriptContextAttribute() throws ScriptException {
        ScriptEngine engine = getScriptEngine();
        ScriptContext context = engine.getContext();
        context.setAttribute("name", "shanshouchen", ScriptContext.ENGINE_SCOPE);
        context.setAttribute("name", "andy", ScriptContext.GLOBAL_SCOPE);
        engine.eval("println(name);");//值为shanshouchen
    }
  3.3、语言绑定对象
  脚本执行上下文中的最后一类信息是语言绑定对象。语言绑定对象也是与作用于相对应的。同样的作用域优先顺序对语言绑定对象也适用。这样的优先级顺序会对脚本执行时的变量解析产生影响。
  语言绑定对象的优先级顺序的示例:
  两个不同的语言绑定对象中都有名称为name的对象,而在脚本的执行过程中,作用域ENGINE_SCOPE的语言绑定对象的优先级较高,因此变量name的值是"andy”
public void scriptContextBindings() throws ScriptException {
        ScriptEngine engine = getScriptEngine();
        ScriptContext context = engine.getContext();
        Bindings b1 = engine.createBindings();
        b1.put("name", "shanshouchen");
        context.setBindings(b1, ScriptContext.GLOBAL_SCOPE);
        Bindings b2 = engine.createBindings();
        b2.put("name", "andy");
        context.setBindings(b2, ScriptContext.ENGINE_SCOPE);
        engine.eval("println(name);");
    }
  通过ScriptContext的setBindings方法设置的语言绑定对象会影响到ScriptEngine在执行脚本时的变量解析。ScriptEngine的put和get方法所操作的实际上就是ScriptContext的作用域为ENGINE_SCOPE的语言绑定对象。如下代码,从ScriptContext中得到语言绑定对象之后,可以直接对这个对象进行操作。如果在ScriptEngine中的eval方法中没有指明的语言绑定对象,实际上起作用的是ScriptContext中作用域为ENGINE_SCOPE的语言绑定对象
  通过脚本执行上下文获取语言绑定对象的示例:
public void useScriptContextValues() throws ScriptException {
        ScriptEngine engine = getScriptEngine();
        engine.put("name", "shanshouchen");
        ScriptContext context = engine.getContext();
        Bindings bindings = context.getBindings(ScriptContext.GLOBAL_SCOPE);
        bindings.put("name", "andy");
        engine.eval("println(name);");//输出andy
    }
  不直接操作语言绑定对象本身,通过ScriptContext的setAttribute类像语言绑定对象中添加数据。所添加的数据在脚本执行时也同样是可见的
  自定义属性保存在语言绑定对象中的示例:
public void attributeInBindings()throws ScriptException{
        ScriptEngine engine=getScriptEngine();
        ScriptContext context=engine.getContext();
        context.setAttribute("name","Andy",ScriptContext.GLOBAL_SCOPE);
        engine.eval("println(name);");//输出为Andy
    }
  4、方法调用
  在脚本中,最常见和最实用的就是方法。有些脚本引擎允许使用者单独调用脚本中的某个方法。支持这种方法调用方式的脚本引擎可以实现javax.script.Invocable接口。通过Invocable接口可以调用脚本中的顶层方法,也可以调用对象中的成员方法。如果脚本中顶层方法或者对象中的成员方法实现了JAVA中的接口,可以通过Invocable接口中的方法来调用脚本中相应的JAVA接口的实现对象。这样可以在JAVA语言中定义接口,在脚本中实现接口。程序中使用该接口的其他部分代码并不知道接口是由脚本来实现的。与Compilable接口一样,ScriptEngine对Invocable接口的实现也是可选的。
  在JAVA中调用脚本中顶层方法的示例:
  通过Invocable接口的invokeFunction来调用脚本中的顶层方法,调用时的参数会被传递给脚本中的方法。因为JAVA SE自带的JavaScript脚本引擎实现了Invocable接口,所以这里省去了对引擎是否实现了Invocable接口的判断

public void invokeFunction() throws ScriptException, NoSuchMethodException {
        ScriptEngine engine = getScriptEngine();
        String scriptText = "function greet(name){println('Hello,'+name);}";
        engine.eval(scriptText);
        Invocable invocable = (Invocable) engine;
        invocable.invokeFunction("greet", "Alex");
    }
  在JAVA中调用脚本中对象的成员方法的示例:
  如果被调用的方法是脚本中对象成员方法,就需要使用invokeMethod方法,代码中的getGreeting方法是属性对象obj的,在调用的时候把这个对象作为参数传递进来。
public void invokeMethod() throws ScriptException, NoSuchMethodException {
        ScriptEngine engine = getScriptEngine();
        String scriptText = "var obj={getGreeting:function(name){return 'Hello,'+name;}};";
        engine.eval(scriptText);
        Invocable invocable = (Invocable) engine;
        Object scrpe = engine.get("obj");
        Object result = invocable.invokeMethod(scrpe, "getGreeting", "Alex");
        System.out.println(result);
    }
  在脚本中实现JAVA接口的示例:
  在有些脚本引擎中,可以在JAVA语言中定i接口,并在脚本中编写接口的实现。这样程序中的其他部分可以只同JAVA接口交互,并不需要关心接口是由什么方式来实现的。代码清单中,Greet是用JAVA定义的接口,其中包含一个getGreeting方法。在脚本中实现这个接口。通过getInterface方法可以得到由脚本实现的这个接口的对象,并调用其中的方法。
public void useInterface() throws ScriptException{
        ScriptEngine engine=getScriptEngine();
        String scriptText="function getGreeting(name){return 'Hello,'+name;}";
        engine.eval(scriptText);
        Invocable invocable=(Invocable)engine;
        Greet greet=invocable.getInterface(Greet.class);
        System.out.println(greet.getGreeting("Alex"));
    }
  Greet.java
public interface Greet {
    String getGreeting(String name);
}

  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号