互联网 > 正文
人工智能网热度:

Java 动态语言支持

JDK6.0之后提供了脚本引擎功能,让我们可以执行某些脚本语言,特别是javascript(javascript是一门解释性语言,动态性非常好),让JAVA的动态性得到更充分的体现,某些时候可以更加灵活的应对需求的变化。在 JDK1.7 之前,无论是 Java 语言还是 JVM 走得都是坚定的静态类型道路。其对动态类型支持的缺失主要是体现在方法调用上。JDK1.7 之前共有 4 条方法调用指令:

invokevirtual: 调用实例方法。会根据对象的实际类型进行动态单分派 (虚方法分派)

invokespecial: 以操作数栈栈顶 reference 类型的数据所指向的对象为方法的接收者,调用此对象的实例构造器方法,私有方法或超类构造方法。该指令的操作码之后会紧跟一个 u2 的操作数说明具体调用的是哪个方法,该参数指向常量池集合中的一个 CONSTANT_UTF8_info 类型的索引项,也就是该方法的方法符号引用

invokestatic: 调用类方法 (static 修饰的方法)

invokeinterface: 调用接口方法。运行期解释器会搜索一个实现了该接口方法的对象,并调用对应实现的接口方法

JDK1.7 中,这个真物体现在 JVM 层面就是新增的 invokedynamic 指令,该指令为动态类型而生,直接支持动态类型。而在 Java 语言层面的体现则是新增的 java.lang.invoke 包。1 脚本语言

Rhino 是一种使用 Java 语言编写的 JavaScript 的开源实现,原先由Mozilla开发,现在被集成进入JDK 6.0。基于脚本的动态编译可以有效的增加代码的扩展性和动态发布。

使用java执行一段JS脚本,具体的结果如下:String script = "print(123)";

ScriptEngineManager manager = new ScriptEngineManager();

ScriptEngine engine = manager.getEngineByName("JavaScript");

//开始对提交的脚本进行编译

CompiledScript compiled = ((Compilable) engine).compile(script);

//执行脚本

compiled.eval();

测试执行结果如下:

上面是执行一个print方法,如果仅仅支持这么简单的JS语法上的东西,对我们来说没有任何意义,是否可以和JAVA交互呢,使用Java执行JS这个肯定是可以支持和Java交互的,具体的逻辑如下:@Test

public void testJS() throws Exception{

String script = "out.println("asdfb")";

ScriptEngineManager manager = new ScriptEngineManager();

ScriptEngine engine = manager.getEngineByName("JavaScript");

//开始对提交的脚本进行编译

CompiledScript compiled = ((Compilable) engine).compile(script);

Bindings bindings = new SimpleBindings();

bindings.put("out",System.out);

compiled.eval(bindings);

}

其中执行JS脚本时,可以在脚本中加入自定义对象实例,在脚本中可以直接引用。脚本中可以直接引用相关对象的方法。具体的执行结果如下:

上面的两个过程中使用了ScriptEngineManager、ScriptEngine、CompiledScript、Bindings等几个对象,ScriptEngineManager 为 ScriptEngine 类实现一个发现和实例化机制,还维护一个键/值对集合来存储所有 Manager 创建的引擎所共享的状态。此类使用服务提供者机制枚举所有的 ScriptEngineFactory 实现。ScriptEngineManager 提供了一个方法,可以返回一个所有工厂实现和基于语言名称、文件扩展名和 mime 类型查找工厂的实用方法所组成的数组。

ScriptEngine是JS的执行规范,所有的JS的引擎执行都需要实现这个接口,当前ScriptEngine中定义了一些基本的脚本功能法法。

CompiledScript是编译器,将JS编译后端的结果类扩展

Bindings是一个kv的mapping关系表,用于映射js脚本中用到的相关依赖对象的信息。ScriptEngineManager的全部共享数据是通过构造一个Bindings来实现的。

上面的各个类和简单的使用介绍了以后,是否可以直接调用其中的方法呢,而不是直接执行脚本,具体的如下:@Test

public void testJS1() throws Exception{

String script = "function add(a,b){return a + b;}";

ScriptEngineManager manager = new ScriptEngineManager();

ScriptEngine engine = manager.getEngineByName("JavaScript");

//开始对提交的脚本进行编译

engine.eval(script);

Invocable invocable =(Invocable)engine;

Object rs = invocable.invokeFunction("add",1,2);

System.out.println(rs);

}

类似于反射的方式来实现调用其中的方法。

经过上面的测试可以使用JS脚本做一些简单的东西,当然也可以进行绑定JAVA对象实例来做一些复杂的事情,下面基于JS脚本做动态接口的实现:1 通过controller来调用某个接口查询某个接口的数据

2 不通的接口的具体实现通过JS的方式来动态提交编译

基于这个场景,编写的代码逻辑结构如下:

代码结构中部分内容未实现。下面看一下controller的代码:@GetMapping(value = "/**")

public RestDataResponse getMdataBasicData(HttpServletRequest request, HttpServletResponse response){

//从缓存队列中获取是否有对应的执行脚本。如果没有执行脚本,则抛出异常

JavaScript script = javaScriptManager.findScript(request.getRequestURI());

if(Objects.isNull(script)){

//抛出一个运行时的异常,用于在外部检查

throw new IllegalArgumentException("未查询到具体的执行脚本");

}

return new SimpleResponse(new SimpleMetaResponse(request.getRequestURI(),""),

basicApiService.executor(request.getRequestURI(),request.getParameterMap(),script));

}

在controller中自定义个方法处理所有的请求,请求过来后,首先从JS引擎集合中获取对应的脚本,如果如果获取到了,就可以进行后续的处理了,具体的处理是在BasicApiService汇总进行处理的,处理的方法为executor中,具体的逻辑为:/**

* 执行请求

*/

public Object executor(String uri,Map<String,String[]> params,JavaScript script){

//构造开始开始执行

BasicQueryHandler queryHandler = BasicQueryHandler.builder().script(script).uri(uri).params(params).build()

.checkRequestParameters() //处理参数,以及参数的校验

.builderBasicQueryParameters(); //构造查询数据库的对象

//开始执行请求

try {

return queryHandler.executor(basicQueryExecutor).filter().result();

}catch (Exception e){

e.printStackTrace();

}

}

具体的我们看下对应的JS的处理过程:/**

* 通过脚本执行请求

*/

public void resolveQueryDataSourceByScript(BasicQueryExecutor basicQueryExecutor){

try {

Map<String,Object> paramaters = Maps.newHashMap();

paramaters.put("mapper",basicQueryExecutor);

paramaters.put("requestParameter",requestParameters);

paramaters.put("handler",this);

paramaters.put("LOGGER",LOGGER);

paramaters.put("queryParameters", BasicQueryParametersBuilder.newInstance());

script.getCompiledScript().eval(new SimpleBindings(paramaters));

} catch (ScriptException e) {

throw new IllegalArgumentException("执行脚本出现错误");

}

script.getCompiledScript().getEngine().getBindings(ScriptContext.ENGINE_SCOPE).clear();

}

对应的JS脚本为:function runExtractor(){

var now = new Date().getTime();

handler.setBody(mapper.selectOne(queryParameters

.table("t")

.of("tid","1")

.limit(0,1)

.builder()));

LOGGER.info(" x -> " + (new Date().getTime() - now));

}

runExtractor();

截图中的为对应的JS,我们看下执行的结果:

数据库中的数据,我们也看一下:

可以看到明确的将数据查询出来了,并且打印出了查询结果,具体的测试代码如下:@Test

public void test() throws Exception{

//构造脚本对象

JavaScript script = builder("/api/test");

//提交加脚本

javaScriptManager.submit(script);

//下面开始编写测试用例

//从缓存队列中获取是否有对应的执行脚本。如果没有执行脚本,则抛出异常

JavaScript javaScript = javaScriptManager.findScript("/api/test");

if(Objects.isNull(javaScript)){

//抛出一个运行时的异常,用于在外部检查

.findVirtual(executorParameters.getObject().getClass(), executorParameters.getMethod(), mt)

.bindTo(executorParameters.getObject());

//执行方法

handle.invokeExact(1,2);

}

@Builder

@AllArgsConstructor

@NoArgsConstructor

public class MethodExecutorParameters {

@Setter @Getter private MethodTypeReferer type;

@Setter @Getter private Object object;

@Setter @Getter private String method;

}

@Builder

@AllArgsConstructor

@NoArgsConstructor

public class MethodTypeReferer {

@Setter @Getter private Class returnType;

@Setter @Getter private Class[] argsList;

}

其实冲例子上看,这个和反射基本上没什么区别。首先构造方法的类型(这里类型包括返回结果类型,参数类型),构造处理器绑定方法名称,实例对象。执行时给定参数列表即可。

MethodHandle 的使用方法和效果上与 Reflection 都有众多相似之处。不过,它们也有以下这些区别:

Reflection 和 MethodHandle 机制本质上都是在模拟方法调用,但是 Reflection 是在模拟 Java 代码层次的方法调用,而 MethodHandle 是在模拟字节码层次的方法调用。在 MethodHandles.Lookup 上的三个方法 findStatic()、findVirtual()、findSpecial() 正是为了对应于 invokestatic、invokevirtual & invokeinterface 和 invokespecial 这几条字节码指令的执行权限校验行为,而这些底层细节在使用 Reflection API 时是不需要关心的。

Reflection 中的 java.lang.reflect.Method 对象远比 MethodHandle 机制中的 java.lang.invoke.MethodHandle 对象所包含的信息来得多。前者是方法在 Java 一端的全面映像,包含了方法的签名、描述符以及方法属性表中各种属性的 Java 端表示方式,还包含有执行权限等的运行期信息。而后者仅仅包含着与执行该方法相关的信息。用开发人员通俗的话来讲,Reflection 是重量级,而 MethodHandle 是轻量级。

由于 MethodHandle 是对字节码的方法指令调用的模拟,那理论上虚拟机在这方面做的各种优化(如方法内联),在 MethodHandle 上也应当可以采用类似思路去支持(但目前实现还不完善)。而通过反射去调用方法则不行。

欢迎关注微信公众号:dcwlcm666;合作及投稿请联系:1519329887@qq.com

赞助商