4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

OpenAMにおけるInjectorHolderの使われ方

Last updated at Posted at 2019-12-12

はじめに

本稿では、Google GuiceのInjectorおよびそれから構成されるInjectorHolderがOpenAMでどのように使われているかについて説明します。関連するコードはForgeRock社のGuice Coreというパッケージにあります。ソースコードはここから参照可能です。まずは、Guice Injectorの基本的な説明から始めます。

Guice Injector の基本

もうすぐクリスマス/年末なのでSeasonsGreetingインターフェイスを実装するChristmasGreetingNewYearGreetingという2つのクラスを使って説明します。

注目していただきたいのは、Guice#createInjector(Module...)メソッドで Injector のインスタンスを取得している部分です。

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;

public class Example01 {

    public interface SeasonsGreeting {
        void greeting();
    }

    public static class ChristmasGreeting implements SeasonsGreeting {
        @Override
        public void greeting() {
            System.out.println("Merry Christmas!");
        }
    }

    public static class NewYearGreeting implements SeasonsGreeting {
        @Override
        public void greeting() {
            System.out.println("Happy New Year!");
        }
    }

    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new AbstractModule() {
            @Override protected void configure() {
                bind(SeasonsGreeting.class).to(ChristmasGreeting.class);
            }
        });

        SeasonsGreeting sg = injector.getInstance(SeasonsGreeting.class);
        sg.greeting();
    }
}

configure() メソッドでSeasonsGreetingインターフェイスとその実装であるChristmasGreetingを結びつけています。それに従って生成されたinjectorChristmasGreetingのインスタンスを返すようになります。

実行結果
Merry Christmas!

上記の例では、Guice#createInjector(Module...)の引数はひとつだけでしたが、以下のように複数指定することも可能です。

public static Injector createInjector(java.lang.Iterable<? extends Module> modules)

これを使えばModuleを拡張したクラスで指定された複数の設定を一気に読み込むことが可能です。こうして生成されたInjectorのインスタンスを繰り返し使うために一定の「置き場所」を提供するのが、次に説明するInjectorHolderになります。

InjectorHolderについて

InjectorHolderの「ホールダー」という名前からは、生成したInjectorのインスタンスを保存しておき、必要に応じて取り出して使うという感じがします。しかし、実際にはInjectorInjectorHolderの内部で生成され、内部で使用されます。外に出されることは無いため、ラッパーと呼んだ方が実態に近いかもしれません。

以下のコードでは、冒頭から列挙型が出てきます。ただひとつの要素(INSTANCE)を持つ列挙型を使ってシングルトンを実装する方法は
Effective Java 第3版の項目3でも紹介されています。ここではそれを使ってInjectorHolderをシングルトンとして定義しています。

private コンストラクタ内でInjectorFactoryを使ってInjectorを生成している部分に注目してください。


public enum InjectorHolder {

    /**
     * The Singleton instance of the InjectorHolder.
     */
    INSTANCE;

    private Injector injector;

    /**
     * Constructs an instance of the InjectorHolder and initialises the Guice Injector.
     */
    private InjectorHolder() {
        InjectorFactory injectorFactory = new InjectorFactory(new GuiceModuleCreator(),
                new GuiceInjectorCreator(), InjectorConfiguration.getGuiceModuleLoader());

        try {
            injector = injectorFactory.createInjector(InjectorConfiguration.getModuleAnnotation());
        } catch (Exception e) {
            e.printStackTrace();
            throw new IllegalStateException(e);
        }
    }

    /**
     * @param clazz The class to get an instance of.
     * @param <T> The type of class to get.
     * @return A non-null instance of the class.
     */
    public static <T> T getInstance(Class<T> clazz) {
        return INSTANCE.injector.getInstance(clazz);
    }

最後のgetInstance(Class<T> clazz)メソッドはこのシングルトン自身やシングルトンに登録されたInjectorインスタンスを返すのではなく、それを使って生成されたclazzのインスタンスを返すようになっています。

それでは、InjectorFactoryがどのようにしてInjectorのインスタンスを生成しているか見てみることにしましょう。

InjectorFactory について

既に説明したようにInjectorの生成時には、Moduleを拡張したクラスのインスタンスを複数指定することができます。それぞれの拡張クラスは異なる設定でConfigure()メソッドをオーバーライドしています。それらのうちどの拡張クラスをロードの対象とするかの判定には@GuiceModuleというアノテーションを使います。具体的には、InjectorFactoryは

  1. @GuiceModuleというアノテーションの付いたModuleの拡張クラスをクラスパス上で検索して
  2. それらをインスタンス化し
  3. Guice#createInjector()に引数として渡してInjectorを生成する

という処理を行っています。以下のコードはその導入部分です。その他の部分は長くなるので省略しています。詳しくは公開されているコードを見てください。

final class InjectorFactory {

    /**
     * Creates a new Guice injector which is configured by all modules found by the {@link GuiceModuleLoader}
     * implementation.
     *
     * @param moduleAnnotation The module annotation.
     * @return A non-null Guice injector.
     */
    Injector createInjector(Class<? extends Annotation> moduleAnnotation) {
        /*
         This does not need to by synchronized as it is only ever called from the constructor of the
         InjectorHolder enum, which is thread-safe so no two threads can create an injector at the same time.

         This does mean that this method MUST not be called/used by another other class!
         */
        return injectorCreator.createInjector(createModules(moduleAnnotation));
    }

InjectorHolderの仕組みについて見てきました。以下では、OpenAMでどのように使われているかについて見ていくことにします。

OpenAMでの使われ方

OpenAMをGoogle Guice対応にするという作業はかなり前に終わっています。一般的に言って、OpenAMのような大規模なソフトウェアをGoogle Guiceに対応させることは容易ではありません。まずは、依存関係ツリーの枝葉に近い単純なサブツリーから開始して、より複雑なオブジェクトの方向に向かってツリーを辿っていくことになります。複雑なツリーでは、一気に書き換えられない場合もあるかもしれません。そうした場合には、書き換えが完了したクラスと未完了のクラスの「境目」にInjectorHolderを配置します。未完了のクラスのなかでInjectorHolderを使い書き換えが完了したクラスのオブジェクトを生成します。その先の依存関係の解決は、Google Guiceに任せるという方法です。書き換えが進むに従ってInjectorHolderも動かしていきます。こうすることにより複雑なソフトウェアでも漸進的に書き換えていくことが可能になります。

そもそもGoogle Guiceに対応させることができない、もしくは対応させるメリットがないということもあります。OpenAMの場合は、認証サービスがそれにあたります。OpenAMは多要素認証を行うために、様々な認証モジュールのインスタンスを独自に管理する仕組みを持っています。これをGoogle Guiceに対応させることは難しいため、InjectorHolderは個々の認証モジュールのレベルで使っています。

おわりに

OpenAMでは"non-Guice World"と"Guice World"の「境目」にInjectorHolderを配置していることを見てきました。興味があれば、OpenAMコンソーシアムで公開しているソースコードを見て下さい。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?