先週JSR 223 Scripting for Java Platformを使用して,Compiler APIを簡単に使う方法を紹介しました。Javaに対応したスクリプトエンジンを使用することで,すべてオンメモリーでソースをコンパイルし,クラスロードまで行うことができます。

今週はこのスクリプトエンジンについて調べることにより,オンメモリーでコンパイル,クラスロードを行う方法を考えてみましょう。

スクリプトエンジンのソース取得

スクリプトエンジンについて調べるには,まずそのソースを取得しなくてはなりません。先週,Scripting ProjectからダウンロードしたのはJARファイルだけで,ソースは含まれていませんでした。そこで,ソースをダウンロードしてみましょう。

ソースはScripting ProjectのCVSよりダウンロードできます。詳しくはScripting ProjectのCVSのページをご覧ください。また,このページからソースのブラウズもできます。

Javaのスクリプトエンジンは次の5つのソースファイルから構成されています。

  • JavaCompiler.java
  • JavaScriptEngine.java
  • JavaScriptEngineFactory.java
  • MemoryClassLoader.java
  • MemoryJavaFileManager.java

次章からスクリプトエンジンの動作を調べていきましょう。

スクリプトエンジンを調べる

上記のクラスの内,JavaScriptEngineFactoryは名前からしても,ファクトリクラスだということがわかります。また,スクリプトエンジンの本体はJavaScriptEngineクラスであることもわかります。

そこで,まず,JavaScriptEngineクラスを見ていきましょう。

先週,紹介したようにスクリプトエンジンでスクリプトを実行するのはevalメソッドです。そこで,まずevalメソッドを見ていくことにします。evalは6種類オーバーロードされていますが,ここでは第1引数がStringクラス,第2引数がjavax.script.ScriptContentクラスのものを示します。

    public Object eval(String str, ScriptContext ctx)
                       throws ScriptException {
        Class clazz = parse(str, ctx);
        return evalClass(clazz, ctx);
    }

たった2行の単純なメソッドです。はじめにparseメソッドをコールし,戻り値としてClassオブジェクトを取得しています。そして,そのClassオブジェクトを引数にしてevalClassをコールしています。

引数や戻り値から推定すると,parseメソッドがソースをコンパイルし,クラスロードまで行っているようです。そして,evalClassメソッドが引数のClassオブジェクトのmainメソッドを実行していると予想できます。

単純そうなevalClassメソッドから片付けてしまいましょう。

    private static Object evalClass(Class clazz, ScriptContext ctx) 
                            throws ScriptException {
        // JSR-223 requirement
        ctx.setAttribute("context", ctx, ScriptContext.ENGINE_SCOPE);
        if (clazz == null) {
            return null;
        }
        try {
            boolean isPublicClazz = Modifier.isPublic(clazz.getModifiers());
 
            // find the setScriptContext method
            Method setCtxMethod = findSetScriptContextMethod(clazz);
            // call setScriptContext and pass current ctx variable
            if (setCtxMethod != null) {
                if (! isPublicClazz) {
                    // try to relax access
                    setCtxMethod.setAccessible(true);
                }
                setCtxMethod.invoke(null, new Object[] { ctx });
            }
 
            // find the main method
            Method mainMethod = findMainMethod(clazz);
            if (mainMethod != null) {
                if (! isPublicClazz) {
                    // try to relax access
                    mainMethod.setAccessible(true);
                }        
 
                // get "command line" args for the main method
                String[] args = getArguments(ctx);
 
                // call main method
                mainMethod.invoke(null, new Object[] { args });
            }
 
            // return main class as eval's result
            return clazz;
        } catch (Exception exp) {
            throw new ScriptException(exp);
        }
    }

少し長いですが,中心となる部分は単純です。予想したとおり,Classオブジェクトのmainメソッドをコールするためのメソッドだということがわかります。

つまり,青字の部分でClassオブジェクトで表されるクラスのmainメソッドに対応するMethodオブジェクトを取得しています。そして,mainメソッドの引数は,先週紹介したとおりScriptContextオブジェクトに保持させてあるので,オレンジ字の部分でScriptContextオブジェクトのctxより取りだしています。

後は,赤字に示したようにMethodオブジェクトに対してinvokeメソッドをコールしています。Methodクラスのinvokeメソッドの第1引数は,コールするメソッドを持つオブジェクトですが,mainメソッドはstaticメソッドなのでnullになっっています。

この先は会員の登録が必要です。有料会員(月額プラン)は初月無料!

日経 xTECHには有料記事(有料会員向けまたは定期購読者向け)、無料記事(登録会員向け)、フリー記事(誰でも閲覧可能)があります。有料記事でも、登録会員向け配信期間は登録会員への登録が必要な場合があります。有料会員と登録会員に関するFAQはこちら