V8はGoogleが開発するオープンソースのJavaScriptエンジンです。高速、最新ES適用など、いろいろほめられています。V8はC++のものですが、javaからV8から利用する場合、ラッパーが必要です。
- 一番有名なのは、J2V8です。だが更新が止まっているみたいで、windows向けのバージョンは2016年の4.6.0まで、async/awaitはサポートしていないです。詳細は以下のリンクから確認してください。
- 2番めは、javetです。V8の2023年10月のv11.8.172.15、node.jsも9月v20.8.0を利用されています。認知度がJ2V8よりちょっと低いかもしれません。後発ですからJ2V8より使い勝手がいいです。
<dependency>
<groupId>com.caoccao.javet</groupId>
<artifactId>javet</artifactId>
<version>3.0.0</version>
</dependency>
J2V8とJavetの使い勝手の違い
testa.js
global.myVar="aaaaaaaaaa";
testb.js
global.myVar="bbbbbbbbbb";
testJ2V8.java
import java.io.File;
import java.io.IOException;
import com.caoccao.javet.exceptions.JavetException;
import com.eclipsesource.v8.NodeJS;
public class TestJ2V8 {
public static void main(String[] args) throws IOException, InterruptedException, JavetException {
final NodeJS nodeJS = NodeJS.createNodeJS();
File testa=new File("testa.js");
nodeJS.exec(testa);
File testb=new File("testb.js");
nodeJS.exec(testb);
while (nodeJS.isRunning()) {//実行完了まで待つ
nodeJS.handleMessage();
}
System.out.println(nodeJS.getRuntime().getString("myVar"));
nodeJS.release();
}
}
TestJavet.java
import java.io.File;
import java.io.IOException;
import com.caoccao.javet.exceptions.JavetException;
import com.caoccao.javet.interop.V8Host;
import com.caoccao.javet.interop.V8Runtime;
import com.caoccao.javet.interop.executors.IV8Executor;
public class TestJavet {
public static void main(String[] args) throws IOException, InterruptedException, JavetException {
try (V8Runtime v8Runtime = V8Host.getNodeInstance().createV8Runtime()) {
IV8Executor iV8Executor=v8Runtime.getExecutor(new File("testa.js"));
iV8Executor.executeVoid();
v8Runtime.await();//実行完了まで待つ
iV8Executor=v8Runtime.getExecutor(new File("testb.js"));
iV8Executor.executeVoid();
v8Runtime.await();//実行完了まで待つ
System.out.println(v8Runtime.getGlobalObject().getString("myVar"));
}
}
}
javetのv8Runtime.await()呼び出しは、j2v8のwhile (nodeJS.isRunning()) と類似役割の目的ですが、v8Runtime.await()は何回呼び出しても構わないです。while (nodeJS.isRunning())は1回しか呼び出せなくて、実行後V8は停止状態になってこれ以上jsファイルを実行できません。j2v8には、v8Runtime.await()に相当するものがありません。だって2016年のものですから。
そして、j2v8は、efwのloadWithGlobalPool関数の内蔵javaScriptエンジンとして利用することは不可能です。プールから取得して利用して返すことは、エンジンの利用は1回限りではなく、断続で何回にもなるからです。
また、javetは、Primitive ConverterとObject Converterの仕組みがあって、javaとjavascript間の変換が自動的に行ってくれます。j2v8にはPrimitive Converterの類似仕組みがありますが、Object Converterと同等するものがありません。そして、Byte[]をjavaからエンジンに渡す場合、手動でV8Arrayに変換してエンジンに渡しています。その分の勉強と手間が発生しなければなりません。
J2V8とJavetのマルチスレッドの違い
結論からいうと、J2V8はマルチスレッド禁止です。JavetはマルチスレッドがOKです。
- まずJ2V8とJavetのラインタイムをstatic変数に定義します。
package test;
import com.eclipsesource.v8.V8;
public class TestJ2V82 {
public static V8 runtime = V8.createV8Runtime();
static {
runtime = V8.createV8Runtime();
runtime.executeVoidScript("var c=0;function hello(){c++;return c;};");
}
}
package test;
import com.caoccao.javet.exceptions.JavetException;
import com.caoccao.javet.interop.V8Host;
import com.caoccao.javet.interop.V8Runtime;
public class TestJavet2 {
public static V8Runtime v8Runtime;
static {
try {
v8Runtime = V8Host.getNodeInstance().createV8Runtime();
v8Runtime.getExecutor("var c=0;function hello(){c++;return c;};").executeVoid();
} catch (JavetException e) {
e.printStackTrace();
}
}
}
それぞれのstatic変数を利用するjspを作成します。
test_subJ2V8.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="test.TestJ2V82"%>
<%@ page import="com.eclipsesource.v8.V8"%>
<%
V8 runtime =TestJ2V82.runtime;
out.println(runtime.executeStringScript("''+hello();"));
out.println("<br>");
out.println(runtime.executeStringScript("''+hello();"));
%>
test_subJavet.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="test.TestJavet2"%>
<%@ page import="java.io.File"%>
<%@ page import="java.io.IOException"%>
<%@ page import="com.caoccao.javet.exceptions.JavetException"%>
<%@ page import="com.caoccao.javet.interop.V8Host"%>
<%@ page import="com.caoccao.javet.interop.V8Runtime"%>
<%@ page import="com.caoccao.javet.interop.executors.IV8Executor"%>
<%
V8Runtime v8Runtime=TestJavet2.v8Runtime;
out.println(v8Runtime.getExecutor("hello()").executeInteger());
out.println("<br>");
out.println(v8Runtime.getExecutor("hello()").executeInteger());
%>
上記jspを同時10回を呼び出すテスト画面を作ります。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="efw" uri="efw" %>
<!DOCTYPE HTML>
<HTML>
<HEAD>
<META HTTP-EQUIV="CONTENT-TYPE"CONTENT="TEXT/HTML;CHARSET=UTF-8">
<TITLE>efw Output Test</TITLE>
<!-- Efwクライアントの取り込み-->
<efw:Client lang="jp"/>
<script>
function testJavetM(flg){
$("#pdf0")[0].src="";
$("#pdf1")[0].src="";
$("#pdf2")[0].src="";
$("#pdf3")[0].src="";
$("#pdf4")[0].src="";
$("#pdf5")[0].src="";
$("#pdf6")[0].src="";
$("#pdf7")[0].src="";
$("#pdf8")[0].src="";
$("#pdf9")[0].src="";
setTimeout(function(){
$("#pdf0")[0].src="test_sub"+flg+".jsp";
$("#pdf1")[0].src="test_sub"+flg+".jsp";
$("#pdf2")[0].src="test_sub"+flg+".jsp";
$("#pdf3")[0].src="test_sub"+flg+".jsp";
$("#pdf4")[0].src="test_sub"+flg+".jsp";
$("#pdf5")[0].src="test_sub"+flg+".jsp";
$("#pdf6")[0].src="test_sub"+flg+".jsp";
$("#pdf7")[0].src="test_sub"+flg+".jsp";
$("#pdf8")[0].src="test_sub"+flg+".jsp";
$("#pdf9")[0].src="test_sub"+flg+".jsp";
},100);
}
</script>
</HEAD>
<BODY>
<button onclick="testJavetM('Javet');">マルチJavet</button>
<button onclick="testJavetM('J2V8');">マルチJ2V8</button>
<br>
<iframe id="pdf0" src="" style="width:400px;height:300px"></iframe>
<iframe id="pdf1" src="" style="width:400px;height:300px"></iframe>
<iframe id="pdf2" src="" style="width:400px;height:300px"></iframe>
<iframe id="pdf3" src="" style="width:400px;height:300px"></iframe>
<iframe id="pdf4" src="" style="width:400px;height:300px"></iframe>
<iframe id="pdf5" src="" style="width:400px;height:300px"></iframe>
<iframe id="pdf6" src="" style="width:400px;height:300px"></iframe>
<iframe id="pdf7" src="" style="width:400px;height:300px"></iframe>
<iframe id="pdf8" src="" style="width:400px;height:300px"></iframe>
<iframe id="pdf9" src="" style="width:400px;height:300px"></iframe>
</BODY>
</HTML>
実行の結果はこれです。
J2V8は、1回目成功、その後は全部失敗です。失敗の理由は、org.apache.jasper.JasperException: javax.servlet.ServletException: java.lang.Error: Invalid V8 thread access です。
- マルチスレッドについて、Graaljsもだめです。QuickJSもだめです。逆にむかしのNashornとRhinoは可能です。ようやくES2015以上サポート且つマルチ可能のエンジンを見つかりました。
結論
Javetはいいね。これから利用してみましょう。