その5以降、その8まで設定ファイル(classworlds.conf)の読み込み、解析処理の旅をしてきた。ようやく設定ファイルから設定内容を読み込み終えたことになるので、ここからは設定内容に従い、起動処理を行うことになるはずだ。

public static int mainWithExitCode( String[] args )
    throws Exception
{
/* 転載省略 */

    launcher.configure( is );

    is.close();

    try
    {
        launcher.launch( args );
    }
    catch ( InvocationTargetException e )
    {
        ClassRealm realm = launcher.getWorld().getRealm( launcher.getMainRealmName() );

        URL[] constituents = realm.getURLs();

        System.out.println( "---------------------------------------------------" );

        for ( int i = 0; i < constituents.length; i++ )
        {
            System.out.println( "constituent[" + i + "]: " + constituents[i] );
        }

        System.out.println( "---------------------------------------------------" );

        // Decode ITE (if we can)
        Throwable t = e.getTargetException();

        if ( t instanceof Exception )
        {
            throw (Exception) t;
        }
        if ( t instanceof Error )
        {
            throw (Error) t;
        }

        // Else just toss the ITE
        throw e;
    }

    return launcher.getExitCode();
}

上記のコードはLauncher#mainWithExitCode()メソッドの後半部分の抜粋。luncher.configure( is );の部分まではすでに見てきた処理になる。その次は、launcher.launch( args );で、このコードはtry ~ catchで囲まれている。もしこのメソッドで例外が発生すると、標準出力にレルムのURLを出力するようですね。例外ハンドリングはあまり面白そうなことはないので、素通りしておきましょう。

で、肝心要なのは、launcher.launch( args );ですね。

public void launch( String[] args )
    throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException,
    NoSuchRealmException
{
    try
    {
        launchEnhanced( args );

        return;
    }
    catch ( NoSuchMethodException e )
    {
        // ignore
    }

    launchStandard( args );
}

launchEnhanced( args );を実行して例外が発生しなければ、リターン。発生した例外がNoSuchMethodExceptionの場合は、それをキャッチだけしておいて、無視してlaunchStandard( args );を実行するという流れ。

protected void launchEnhanced( String[] args )
    throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException,
    NoSuchRealmException
{
    ClassRealm mainRealm = getMainRealm();

    Class<?> mainClass = getMainClass();

    Method mainMethod = getEnhancedMainMethod();

    ClassLoader cl = mainRealm;

    // ----------------------------------------------------------------------
    // This is what the classloader for the main realm looks like when we
    // boot from the command line:
    // ----------------------------------------------------------------------
    // [ AppLauncher$AppClassLoader ] : $CLASSPATH envar
    //           ^
    //           |
    //           |
    // [ AppLauncher$ExtClassLoader ] : ${java.home}/jre/lib/ext/*.jar
    //           ^
    //           |
    //           |
    // [ Strategy ]
    // ----------------------------------------------------------------------

    Thread.currentThread().setContextClassLoader( cl );

    Object ret = mainMethod.invoke( mainClass, new Object[]{args, getWorld()} );

    if ( ret instanceof Integer )
    {
        exitCode = ( (Integer) ret ).intValue();
    }

    Thread.currentThread().setContextClassLoader( systemClassLoader );
}

ざっと見ると、メインメソッドを取得して、リフレクションを使ってそのメソッドを呼び出している、ってことかな。

で、その時にクラスローダを意識している点がポイントのようですな。最初の2行はそれぞれ、mainRealmとmainClassをゲッターを通じて取得しているだけ。で、次のgetEnhancedMainMethod()は初めて見るから、中身が気になる。

protected Method getEnhancedMainMethod()
    throws ClassNotFoundException, NoSuchMethodException, NoSuchRealmException
{
    Class<?> cwClass = getMainRealm().loadClass( ClassWorld.class.getName() );

    Method m = getMainClass().getMethod( "main", new Class[]{String[].class, cwClass} );

    int modifiers = m.getModifiers();

    if ( Modifier.isStatic( modifiers ) && Modifier.isPublic( modifiers ) )
    {
        if ( m.getReturnType() == Integer.TYPE || m.getReturnType() == Void.TYPE )
        {
            return m;
        }
    }

    throw new NoSuchMethodException( "public static void main(String[] args, ClassWorld world)" );
}

一見すると、このメソッドもメソッドを取得して、そのシグニチャを確認しているだけに見える。が、やはりここでもクラスローダがポイントになるようだ。

最初の2行はなぜこんな回り道をしているんだろか。この2行は、素直に次の1行にまとめられそうだが。。

Method m = getMainClass().getMethod( "main", new Class[]{String[].class, ClassWorld.class} );

だーけーどー、当たり前だが意味があってやってるんだろうね。それを探らないとですね、先に進めませんな。

よーく考えてみると、次の2つの違いが本質ってことになりそうだ。

ClassWorlds.classgetMainRealm().loadClass( ClassWorld.class.getName() )の違い。getMainRealm()はクラスローダを返すので、指定したクラスローダからクラスを取得することになる。この時に取得できるクラスローダは、

classworlds.confから取得したローダということになりますね。m2.confを前提とすると、

main is org.apache.maven.cli.MavenCli from plexus.core

となっていたので、plexus.coreがクラスローダってことになりますな。でも。plexus.coreってなんだ?これがクラスローダクラス??以前パースしてた時に、どう扱ってたんだっけか?ずっと先延ばしにしてた、レルムってやつの正体を掴まないと先に進めなそうだな。

ここで落ち着いて考えてみると、plexus.coreというのは、レルムの名前な訳だ。

その7:設定ファイルを解析する(前編) で設定ファイルから main is org.apache.maven.cli.MavenCli from plexus.core

という行を解析し、mainクラスがこのplexus.coreというレルムから読み込まれるという意味に解釈していた。

さらには、その8:設定ファイルを解析する(後編) で [plexus.core]という行を解析し、plexus.coreというレルムを追加していた。

そんでもって、レルムというのは具体的にはClassRealmというクラスで表現されていて、以下のように定義されていることが判明した。
public class ClassRealm extends URLClassLoader

つまりは、URLClassLoaderを継承しているので、レルムはクラスローダでもあるわけだな。

このレルムというクラスローダを使用して、設定ファイルに記載のあるクラスやらjarやらリソースファイルをロードしているんだな。

そして

Class<?> cwClass = getMainRealm().loadClass( ClassWorld.class.getName() );

というのは、そのレルムから明示的に「ClassWorld.class.getName()」という名前のクラスをロードしている。

これは、このレルム内にちゃんとこのクラスが属しているか(ちゃんとロードできるか)を確認しているんだろう。

つまり、「最初の2行はなぜこんな回り道をしているんだろか。」という疑問点に関しては、この2行は関連がありそうに見えるが、実はそうでもなくて、1行目の役割はクラスの存在確認だと解釈した。getEnhancedMainMethod()はClassNotFoundExceptionをスローするという仕様になっているので、おそらく間違い無いだろう。

結局のところ、getEnhancedMainMethod()というメソッドは、「public static void main(String[] args, ClassWorld world)」というシグニチャを持つメソッドを探すメソッドであると言える。

ま、ちょっと深読みしたけど、あんまり難しく考える必要はなかったかもしれんな。

で、もう一度launchEnhanced()に戻ると、めでたくmainMethodが取得できたので、実際にそのメソッドの呼び出しを行うことになる。が、その前に、Thread.currentThread().setContextClassLoader( cl );を実行してスレッドのコンテキストローダを設定している。

protected void launchEnhanced( String[] args )
    throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException,
    NoSuchRealmException
{
    ClassRealm mainRealm = getMainRealm();

    Class<?> mainClass = getMainClass();

    Method mainMethod = getEnhancedMainMethod();

    ClassLoader cl = mainRealm;

    // ----------------------------------------------------------------------
    // This is what the classloader for the main realm looks like when we
    // boot from the command line:
    // ----------------------------------------------------------------------
    // [ AppLauncher$AppClassLoader ] : $CLASSPATH envar
    //           ^
    //           |
    //           |
    // [ AppLauncher$ExtClassLoader ] : ${java.home}/jre/lib/ext/*.jar
    //           ^
    //           |
    //           |
    // [ Strategy ]
    // ----------------------------------------------------------------------

    Thread.currentThread().setContextClassLoader( cl );

    Object ret = mainMethod.invoke( mainClass, new Object[]{args, getWorld()} );

    if ( ret instanceof Integer )
    {
        exitCode = ( (Integer) ret ).intValue();
    }

    Thread.currentThread().setContextClassLoader( systemClassLoader );
}

設定しているのは、mainRealmだ。これは何を意味しているかと言うと、今から呼び出すmainメソッド、つまりは今回のぶらコードの本当の目的である、mavenの呼び出しにおいて、ここで指定したクラスローダを用いて実行を開始するということになる訳だ。実際にはこのmainメソッドの先でも任意のクラスローダを指定しているかもしれないが、少なくともそれまでの間は、ここで指定したクラスローダを用いてクラスをロードしていくことになる。

さらに良く考えてみると、今回呼び出しているmainMethodは引数にClassWorldを取っているので、そのClassWorldからは今までに設定ファイルから読み込んだ、各種クラスとか設定ファイルとか、クラスローダなどにアクセスできることになる。

ふーむ。なかなか巧妙な仕掛けですなぁ。

そして、mainメソッド実効後に、その戻り値をexitCodeに設定し、コンテキストローダをsystemClassLoaderに再設定している。

ちなみに、解説を端折っていたが、launch()メソッドの中でlaunchEnhanced()の呼び出しに失敗すると、launchStandard()を呼び出しているが、こちらはその名の通り、ClassWorldを引数に取らない通常のmainメソッド呼び出しを行う。

では、本日の最後に今回の旅の目的である、mavenのエントリポイントを覗いてみましょ〜。

public static int main( String[] args, ClassWorld classWorld )
{
    MavenCli cli = new MavenCli();
    return cli.doMain( new CliRequest( args, classWorld ) );
}

ようやく本当の入り口にたどり着いたけど、意外とあっさりしてますな。次からいよいよmavenの中に入って行きまっす!!

results matching ""

    No results matching ""