注目のセミナー

申込受付中!

危機に強い
プロマネ養成
実践トレーニング

火消しのプロが、 実践演習と個別の メンタリングを通じ、 強いPMを2ヵ月で育成!

ソフト開発

Java技術最前線

ITpro

「Java SE 6完全攻略」第93回 プログラムからコンパイル - Compiler API その5

2008/12/15
櫻庭 祐一=横河電機

先週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になっっています。

>> parseメソッド(前半)
次ページ以降はITpro会員(無料)の方のみお読みいただけます。
会員の方は、 ログインしてご覧ください。
まだ会員でない方は、ぜひ登録(無料)していただき、ITproの豊富なコンテンツをご覧ください。

この記事に対するfacebookコメント

nikkeibpITpro

読みましたか? 〜 未読記事をご紹介