在Java中运行ES6代码
Java自带Nashorn引擎,可以在Java程序中运行JavaScript代码,从而实现可灵活配置动态接口。然而Nashorn引擎只支持老旧的语法,不支持ES6或更新的东西,而且在较新的Java中也已停止支持,因此本文选择替代品Javet。
Javet是一个能够整合Java与Node.js的库,使开发人员可以很方便地调用ES6的代码。
引入Javet
官方仓库指出了具体的方法。例如使用Maven时需要在pom.xml中加入:
<!-- Linux and Windows (x86_64) -->
<dependency>
<groupId>com.caoccao.javet</groupId>
<artifactId>javet</artifactId>
<version>2.1.2</version>
</dependency>
<!-- Linux (arm64) -->
<dependency>
<groupId>com.caoccao.javet</groupId>
<artifactId>javet-linux-arm64</artifactId>
<version>2.1.2</version>
</dependency>
<!-- Mac OS (x86_64 and arm64) -->
<dependency>
<groupId>com.caoccao.javet</groupId>
<artifactId>javet-macos</artifactId>
<version>2.1.2</version>
</dependency>
调用JavaScript代码
普通调用
public Object runJsCode(String code) throws JavetException {
Object result = null;
try (V8Runtime v8Runtime = V8Host.getV8Instance().createV8Runtime()) {
JavetProxyConverter javetProxyConverter = new JavetProxyConverter();
v8Runtime.setConverter(javetProxyConverter);
// TODO 可在这里引入Java对象,以便作为js的全局变量使用
v8Runtime.getGlobalObject().set("XXXUtil", xxxUtil);
V8Value val = v8Runtime.getExecutor(code).execute();
result = parseV8Value(val);
}
return result;
}
private Object parseV8Value(V8Value val) throws JavetException {
if (val instanceof V8ValueString) {
return ((V8ValueString) val).getValue();
} else if (val instanceof V8ValueBigInteger) {
return ((V8ValueBigInteger) val).getValue();
} else if (val instanceof V8ValueBoolean) {
return ((V8ValueBoolean) val).getValue();
} else if (val instanceof V8ValueDouble) {
return ((V8ValueDouble) val).getValue();
} else if (val instanceof V8ValueInteger) {
return ((V8ValueInteger) val).getValue();
} else if (val instanceof V8ValueLong) {
return ((V8ValueLong) val).getValue();
} else if (val instanceof V8ValueArray) {
// TODO 根据需要进行处理
return ((V8ValueArray) val).toArray();
} else if (val instanceof V8ValueObject) {
// TODO 根据需要进行处理
return ((V8ValueObject) val);
}
// TODO 根据需要进行处理
return val;
}
使用时调用runJsCode("js代码")
即可,返回值为Java对象。但由于JS的数组和Object灵活性较强,本程序仍返回Javet的对象,编码时应考虑根据实际需求进行处理。
将对象转为JSON
如果用于JSON,例如配置动态接口,将上述代码返回的对象直接转成JSON,结果可能会出错,因此需要在转JSON之前做一些加工处理。
为便于处理JSON,下面引入了Hutool,因此需要在pom.xml中引入:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.6.1</version>
</dependency>
上面代码的runJsCode不变,对parseV8Value进行修改,替换成下面的代码:
private Object parseV8Value(V8Value val) throws JavetException {
try (V8Scope v8Scope = val.getV8Runtime().getV8Scope()) {
v8Scope.add(val);
val = parseV8ValueToBasic(val, v8Scope);
if (val instanceof V8ValueString) {
return ((V8ValueString) val).getValue();
} else if (val instanceof V8ValueBigInteger) {
return ((V8ValueBigInteger) val).getValue();
} else if (val instanceof V8ValueBoolean) {
return ((V8ValueBoolean) val).getValue();
} else if (val instanceof V8ValueDouble) {
Double d = ((V8ValueDouble) val).getValue();
// 无穷大和NaN转JSON会报错,这里直接返回null
if (d.isInfinite() || d.isNaN()) {
d = null;
}
return d;
} else if (val instanceof V8ValueInteger) {
return ((V8ValueInteger) val).getValue();
} else if (val instanceof V8ValueLong) {
return ((V8ValueLong) val).getValue();
} else if (val instanceof V8ValueArray) {
String json = ((V8ValueArray) val).toJsonString();
if (json == null) {
return null;
}
if (json.startsWith("TypeError")) {
throw new RuntimeException(json);
}
return JSONUtil.parseArray(json, false);
} else if (val instanceof V8ValueTypedArray) {
String json = ((V8ValueTypedArray) val).toJsonString();
if (json == null) {
return null;
}
if (json.startsWith("TypeError")) {
throw new RuntimeException(json);
}
return JSONUtil.parseObj(json, false);
} else if (val instanceof V8ValueObject) {
String json = ((V8ValueObject) val).toJsonString();
if (json == null) {
return null;
}
if (json.startsWith("TypeError")) {
throw new RuntimeException(json);
}
return JSONUtil.parseObj(json, false);
}
// 其他类型无法转成JSON,直接返回null
return null;
}
}
private V8Value parseV8ValueToBasic(V8Value val, V8Scope v8Scope) throws JavetException {
if (val instanceof V8ValueString) {
return val;
} else if (val instanceof V8ValueBigInteger) {
return val;
} else if (val instanceof V8ValueBoolean) {
return val;
} else if (val instanceof V8ValueDouble) {
return val;
} else if (val instanceof V8ValueInteger) {
return val;
} else if (val instanceof V8ValueLong) {
// 处理js中Number与Java中Double的差异
Long u = ((V8ValueLong) val).getValue();
if (u == null) {
return v8Scope.createV8ValueNull();
} else {
long v = u.longValue();
if (v <= 9007199254740991L && v >= -9007199254740991L) {
return v8Scope.createV8ValueDouble(v);
} else {
return val;
}
}
} else if (val instanceof V8ValueProxy) {
// 将Java对象实例变成JS对象,进而方便转JSON
V8ValueProxy proxy = (V8ValueProxy) val;
if (proxy.has("getClass") && proxy.has("hashCode") && proxy.has("equals") && proxy.has("toString") && proxy.has("notify") && proxy.has("notifyAll") && proxy.has("wait")) {
V8ValueObject v8ValueObject = v8Scope.createV8ValueObject();
Class clz = proxy.invokeObject("getClass");
// 获取对象的getter并进行调用
for (Method method : clz.getMethods()) {
if (method.getParameterCount() == 0 && !Modifier.isStatic(method.getModifiers())) {
String methodName = method.getName();
String propertyName = null;
if (methodName.startsWith(METHOD_PREFIX_IS) && !EXCLUDED_METHODS.contains(methodName)
&& methodName.length() > METHOD_PREFIX_IS.length()) {
propertyName = methodName.substring(METHOD_PREFIX_IS.length(), METHOD_PREFIX_IS.length() + 1).toLowerCase(Locale.ROOT)
+ methodName.substring(METHOD_PREFIX_IS.length() + 1);
} else if (methodName.startsWith(METHOD_PREFIX_GET) && !EXCLUDED_METHODS.contains(methodName)
&& methodName.length() > METHOD_PREFIX_GET.length()) {
propertyName = methodName.substring(METHOD_PREFIX_GET.length(), METHOD_PREFIX_GET.length() + 1).toLowerCase(Locale.ROOT)
+ methodName.substring(METHOD_PREFIX_GET.length() + 1);
}
if (propertyName != null) {
V8Value newVal = proxy.invoke(methodName);
if (newVal != null) {
v8Scope.add(newVal);
newVal = parseV8ValueToBasic(newVal, v8Scope);
v8ValueObject.set(propertyName, newVal);
}
}
}
}
return v8ValueObject;
}
} else if (val instanceof V8ValueArray) {
V8ValueArray arr = (V8ValueArray) val;
// 递归处理每个元素
for (int i = 0; i < arr.getLength(); i++) {
V8Value u = arr.get(i);
V8Value v = parseV8ValueToBasic(u, v8Scope);
if (u != v) {
arr.set(i, v);
}
}
return arr;
} else if (val instanceof V8ValueTypedArray) {
V8ValueTypedArray arr = (V8ValueTypedArray) val;
V8ValueArray arr2 = v8Scope.createV8ValueArray();
// 递归处理每个元素
for (int i = 0; i < arr.getLength(); i++) {
V8Value u = arr.get(i);
V8Value v = parseV8ValueToBasic(u, v8Scope);
arr2.push(v);
}
return arr2;
} else if (val instanceof V8ValueObject) {
V8ValueObject obj = (V8ValueObject) val;
// 递归处理每个元素
for (String key : obj.getOwnPropertyNameStrings()) {
V8Value u = obj.get(key);
V8Value v = parseV8ValueToBasic(u, v8Scope);
if (u != v) {
obj.set(key, v);
}
}
return obj;
}
// 其他类型(例如Function)无法转成JSON,直接返回null
return v8Scope.createV8ValueNull();
}
以上代码还有一些缺陷,例如Java对象不是VO类型的类,转换时候就会出问题,不过现在的代码对于转JSON这种需求来说够用了。
Docker报错处理
如果将应用部署到Docker中,代码可能会无法正常执行:
com.caoccao.javet.exceptions.JavetException: Javet library is not loaded because <null>
at com.caoccao.javet.interop.JavetClassLoader.load(JavetClassLoader.java:93)
at com.caoccao.javet.interop.V8Host.loadLibrary(V8Host.java:418)
at com.caoccao.javet.interop.V8Host.<init>(V8Host.java:67)
at com.caoccao.javet.interop.V8Host.<init>(V8Host.java:43)
at com.caoccao.javet.interop.V8Host$V8InstanceHolder.<clinit>(V8Host.java:459)
at com.caoccao.javet.interop.V8Host.getV8Instance(V8Host.java:119)
……
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.caoccao.javet.interop.JavetClassLoader.load(JavetClassLoader.java:88)
... 170 more
Caused by: com.caoccao.javet.exceptions.JavetException: Failed to read /tmp/javet/1/libjavet-v8-linux-x86_64.v.3.0.1.so
at com.caoccao.javet.interop.loader.JavetLibLoader.load(JavetLibLoader.java:375)
... 175 more
Caused by: java.lang.UnsatisfiedLinkError: /tmp/javet/1/libjavet-v8-linux-x86_64.v.3.0.1.so: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.33' not found (required by /tmp/javet/1/libjavet-v8-linux-x86_64.v.3.0.1.so)
at java.lang.ClassLoader$NativeLibrary.load(Native Method)
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1934)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1817)
at java.lang.Runtime.load0(Runtime.java:810)
at java.lang.System.load(System.java:1088)
at com.caoccao.javet.interop.loader.JavetLibLoader.load(JavetLibLoader.java:360)
... 175 more
这是因为选择了alpine或slim版本的映像,容器里面缺少glibc,因此需要将image换成完整版,例如openjdk:8
。