JAvaでScriptEngineを使用してjavascriptスクリプトコードを実行し、使用中に注意すべき事項

12508 ワード

elasticsearchを使用するとscriptのような構成ができることはよく知られています.scoreこのスクリプトを実行してドキュメントのスコアを変更します.script_scoreはlangパラメータを指定できます.groovy(デフォルト)、javascript、native(javaによってインタフェースルールに従って実現されます).nativeの実行性能が最も優れているのは、esサービスを再起動することです.
スクリプトでは、事前に入力された変数、入力_を使用できます.score、docなどは、現在のスコアを取得したり、元のドキュメントの情報を取得してスコアを変更したりすることができます.つまり、構成されたスクリプトコードが実行されます.
コードに書くのに適していない場合もあり、スクリプト実行のような方法で実行することで、下位コードをより安定させ、柔軟に具体的なポリシーとデカップリングすることができ、javaがスクリプト言語をどのように実行するかを研究しました.JDK 1.6から、javascriptなどのgroovyスクリプトコードを実行するために使用できるScriptEngineが付属していることがわかりました.
しかし、実際のシーンでは、servletでスクリプトを実行して推奨結果リストを二次変換してフロントエンド結果に戻すなど、マルチスレッド環境で基本的に使用されます.
そこでscriptEngineの使用に関する最適なアドバイスを検索します.ScriptEngineはマルチスレッド環境で使用されることにこだわり、groovyスクリプトはスレッドセキュリティであり、javascriptスクリプトはスレッドセキュリティではありません.コードを実行することで、現在使用しているjdkがサポートしているスクリプトのスレッドセキュリティを表示できます.
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;

public class ScriptEngineTest {
  public static void main(String[] args) {
    final ScriptEngineManager mgr = new ScriptEngineManager();
    for(ScriptEngineFactory fac: mgr.getEngineFactories()) {
      System.out.println(String.format("%s (%s), %s (%s), %s", fac.getEngineName(),
          fac.getEngineVersion(), fac.getLanguageName(),
          fac.getLanguageVersion(), fac.getParameter("THREADING")));
    }
  }
}

マルチスレッド環境でのScriptEngineの使用に関する推奨事項もいくつか用意されています.https://blogs.oracle.com/nashorn/nashorn-multithreading-and-mt-safety
So, our agenda is two fold.  The first is to provide a "workers"library (timeline is not tied to JDK8) which uses an onevent model that JavaScripters are familiar with.  No synchronization/locking constructs to be added to the language.  Communication between threads (and potentially nodes/servers) is done using JSON (under the covers.) Going this route allows object isolation between threads, but also allows maximal use of CPU capacity.
The second part is, we will not guarantee MT-safe structures, but we have to face reality.  Developers will naturally be drawn to using threads.  Many of the Java APIs require use of threads.  So, the best we can do is provide guidelines on how to not shoot yourself in the foot.  These guidelines will evolve and we'll post them 'somewhere' after we think them through.  In the meantime,  I follow some basic rules;
  • Avoid sharing script objects or script arrays across threads (this includes global.)  Sharing script objects is asking for trouble.  Share only primitive data types, or Java objects.
  • If you want to pass objects or arrays across threads, use JSON.stringify(obj) andJSON.parse(string) to transport using strings.
  • If you really really feel you have to pass a script object, treat the object as a constant and only pass the object to new threads (coherency.)  Consider using Object.freeze(obj).
  • If you really really really feel you have to share a script object, make sure the object's properties are stabilized.  No adding or removing properties after sharing starts.  Consider using Object.seal(obj).
  • Given enough time, any other use of a shared script object will eventually cause your app to fail.

  • スクリプト内のオブジェクトを共有しないで、オリジナルのデータ型やjavaオブジェクトを共有することができます.例えば、scriptでvar i=0を定義すると、この変数iはグローバルな変数であり、すべてのスレッドが共有する変数であり、マルチスレッドの場合var i=0を実行します.i=i+1;の場合、各スレッドで得られる結果は1ではありません.もちろん、scriptのfunctionで定義された変数であれば、共有されません.たとえば、script stringはfunction addone(){var i=0;i=i+1;return i;}です.多くのスレッドがこのfunctionを同時に呼び出すと、返される結果は1になります.
    ここで注意すべき点はfunctionでvarで変数を再定義しなければならないことであり、そうでなければグローバルな変数である.
    もう1つは、オブジェクトを共有したい場合は、JSON.stringfyとJSON.parseでjsonシーケンス化と逆シーケンス化で共有できます.これにより、内部にjavaが生成されたStringオブジェクトがあり、Stringオブジェクトは新しい割り当てメモリで保存されているので、スレッドごとに異なるオブジェクトインスタンスがあり、変更は互いに影響しません.
    sofでは多くのコメントを見て、スクリプト言語をjava実行可能なバイトコードにコンパイルして実行するので、多重化しないと毎回スクリプトをコンパイルしてバイトコードを生成し、固定的なスクリプトであれば効率が低く、https://stackoverflow.com/questions/27710407/reuse-nashorn-scriptengine-in-servletこのディスカッションでは、servletでScriptEngineを多重化する方法についても説明します.
    public class MyServlet extends HttpServlet {
    
      private ThreadLocal engineHolder;
    
      @Override
      public void init() throws ServletException {
        engineHolder = new ThreadLocal() {
          @Override
          protected ScriptEngine initialValue() {
            return new ScriptEngineManager().getEngineByName("nashorn");
          }
        };
      }
    
      @Override
      public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
        try (PrintWriter writer = res.getWriter()) {
          ScriptContext newContext = new SimpleScriptContext();
          newContext.setBindings(engineHolder.get().createBindings(), ScriptContext.ENGINE_SCOPE);
          Bindings engineScope = newContext.getBindings(ScriptContext.ENGINE_SCOPE);
          engineScope.put("writer", writer);
          Object value = engineHolder.get().eval("writer.print('Hello, World!');", engineScope);
          writer.close();
        } catch (IOException | ScriptException ex) {
          Logger.getLogger(MyServlet.class.getName()).log(Level.SEVERE, null, ex);
        }
      }
    }

    ThreadLocalを使用してScriptEngineを共有することで、その後のリクエストでこのScriptEngineインスタンスを多重化できます.つまり、各スレッドには独自のScriptEngineインスタンスがあります.スレッドプールが存在するため、スレッドは多重化され、ThreadLocalのScriptEngineも多重化される.
    また、マルチスレッドをテストする例を示します.
    import jdk.nashorn.api.scripting.NashornScriptEngine;
    import jdk.nashorn.api.scripting.ScriptObjectMirror;
    
    import javax.script.ScriptEngineManager;
    import javax.script.ScriptException;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collection;
    import java.util.concurrent.*;
    
    public class JavaExecScriptMTDemo {
    
        public static void main(String[] args) throws Exception {
    
            ScriptEngineManager sem = new ScriptEngineManager();
            NashornScriptEngine engine = (NashornScriptEngine) sem.getEngineByName("javascript");
            String script = "function transform(arr){" +
                    " var arr2=[]; for(var i=0;i addition = new Callable() {
                @Override
                public Collection call() {
                    try {
                        ScriptObjectMirror mirror= (ScriptObjectMirror)engine.invokeFunction("transform", Arrays.asList(1, 2, 3));
    
                        return  mirror.values();
                    } catch (ScriptException | NoSuchMethodException e) {
                        throw new RuntimeException(e);
                    }
                }
            };
    
            ExecutorService executor = Executors.newCachedThreadPool();
            ArrayList> results = new ArrayList<>();
    
            for (int i = 0; i < 50; i++) {
                results.add(executor.submit(addition));
            }
    
            int miscalculations = 0;
            for (Future result : results) {
                Collection jsResult = result.get();
                System.out.println(jsResult);
    //            if (jsResult != 2) {
    //                System.out.println("Incorrect result from js, expected 1 + 1 = 2, but got " + jsResult);
    //                miscalculations += 1;
    //            }
    
            }
    
            executor.awaitTermination(1, TimeUnit.SECONDS);
            executor.shutdownNow();
    
    //        System.out.println("Overall: " + miscalculations + " wrong values for 1 + 1.");
        }

    スクリプトの内容は、整数配列を入力し、配列内の各要素を+1し、新しい配列を返します.50スレッドを起動して同時にこの関数を実行すると、出力の結果は[2,3,4]であることがわかります.スクリプト変数を共有していないため、スレッドが安全であることを示します.
    スクリプトの内容を変更すると、次のように変更されます.
     String script = "function transform(arr){" +
                    " var arr2=[]; for( i=0;i

    forループ上のvar i=0をi=0に変更してテストコードを実行する.放出配列の境界異常が見つかります.この場合、マルチスレッドは安全ではありません.このiは共有スクリプト変数であり、各スクリプトで表示されるためです.
    scriptパッケージの下で最も重要なのは、ScriptEngineManager、ScriptEngine、CompiledScript、Bindingsの4つのクラスまたはインタフェースです.
    ScriptEngineManagerは、スクリプトのファクトリをnameまたはtagで取得して生成できるファクトリの集合です.現在javascriptのファクトリのみです.ファクトリ関数でScriptEngineを取得すると、このオブジェクトでスクリプト文字列を解析でき、Object obj=を直接呼び出すことができます. ScriptEngine.eval(String script)で、返されるobjは、true、false、intなどの式の値です.
    CompiledScriptは、ScriptEngineがスクリプトを解析した結果を保存し、複数回呼び出すのに便利です.ScriptEngineをCompilableインタフェースで強制的に変換すると、compile(String script)を呼び出すとCompilledScriptオブジェクトが返されます.使用するときはCompilledScript.eval()を呼び出すたびにjs関数の使用に適しています.
    Bindingsの概念は少し複雑で、Bindingsはデータを格納するためのコンテナであることを理解しています.Globalクラス、Engineクラス、Localクラスの3つのレベルがあり、上位2つはScriptEngine.getBindings()で獲得され、唯一のオブジェクトであり、Local BindingはScriptEngine.createBindings()で獲得され、よく理解され、毎回新しいものが生成されます.Globalはファクトリに対応し,EngineはScript Engineに対応し,この2つに任意のデータやコンパイルされたスクリプト実行オブジェクトを加え,新たに生成されたLocal Bindingごとに存在する.
    コードの例をあげて、その中のfunctionScriptは標準入力stdinあるいはプロファイルなどから得ることができて、このようにJavaコードの運行結果を動的に制御することができます
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    try {
    	ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
    	Compilable compilable = (Compilable) engine;
    	Bindings bindings = engine.createBindings(); //Local   Binding
    	String script = "function add(op1,op2){return op1+op2} add(a, b)"; //       
    	CompiledScript JSFunction = compilable.compile(script); //        
    	bindings.put("a", 1);bindings.put("b", 2); //  Bindings    
    	Object result = JSFunction.eval(bindings);
    	System.out.println(result); //            ,Bindings        
    }
    catch (ScriptException e) {}

    他にもScriptContextという概念がありますが、これはあまり使われていないのではないでしょうか.ScriptEngineとBindingsを接続するためのツールです.JDKの説明によれば、インタフェースの実装クラスは、ScriptEngineとホストアプリケーション内のオブジェクト(例えば、範囲のあるBindings)を接続するために使用される.