はじめに
本稿では、Google GuiceのInjectorおよびそれから構成されるInjectorHolderがOpenAMでどのように使われているかについて説明します。関連するコードはForgeRock社のGuice Coreというパッケージにあります。ソースコードはここから参照可能です。まずは、Guice Injectorの基本的な説明から始めます。
Guice Injector の基本
もうすぐクリスマス/年末なのでSeasonsGreeting
インターフェイスを実装するChristmasGreeting
とNewYearGreeting
という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
を結びつけています。それに従って生成されたinjector
はChristmasGreeting
のインスタンスを返すようになります。
Merry Christmas!
上記の例では、Guice#createInjector(Module...)
の引数はひとつだけでしたが、以下のように複数指定することも可能です。
public static Injector createInjector(java.lang.Iterable<? extends Module> modules)
これを使えばModule
を拡張したクラスで指定された複数の設定を一気に読み込むことが可能です。こうして生成されたInjector
のインスタンスを繰り返し使うために一定の「置き場所」を提供するのが、次に説明するInjectorHolderになります。
InjectorHolderについて
InjectorHolderの「ホールダー」という名前からは、生成したInjector
のインスタンスを保存しておき、必要に応じて取り出して使うという感じがします。しかし、実際にはInjector
はInjectorHolder
の内部で生成され、内部で使用されます。外に出されることは無いため、ラッパーと呼んだ方が実態に近いかもしれません。
以下のコードでは、冒頭から列挙型が出てきます。ただひとつの要素(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は
-
@GuiceModule
というアノテーションの付いたModule
の拡張クラスをクラスパス上で検索して - それらをインスタンス化し
-
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コンソーシアムで公開しているソースコードを見て下さい。