Google Guice の使い方メモ。
Wiki の User's Guide をざっと試してみた。
#特徴とか
- 読みは「ジュース」
- Google が開発してる
- DI コンテナ
- ver 3.0 からは JSR330(Dependency Injection for Java)のリファレンス実装
- 設定は XML ではなく Java コード中に書く
- アノテーションと型引数をフル活用
- 2013/10/31 現在の最新は 3.0(4.0 の Beta 版が公開されてる)
#環境
##Java
1.7.0_40
##Google Guice
3.0
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version>
</dependency>
#使い方(基本)
package google.guice;
public class HelloWorld {
public void hello() {
System.out.println("Hello Google Guice!!");
}
}
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class GuiceMain {
@Inject
private HelloWorld helloWorld;
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {}
});
GuiceMain main = injector.getInstance(GuiceMain.class);
main.helloWorld.hello();
}
}
Hello Google Guice!!
まず、 Guice#createInjector(Module...)
メソッドで Injector
のインスタンスを取得する。
引数の Module
には AbstractModule
を継承したクラスのインスタンスを渡す。
configure()
メソッドで DI の設定を記述するが、今回の Hello World は単純なケースなので、特に設定は不要。
取得した Injector
から GuiceMain
クラスのインスタンスを取得すると、 @Inject
アノテーションが付与された helloWorld
フィールドに、勝手に HelloWorld
クラスのインスタンスがインジェクションされるので、そのまま hello()
メソッドを実行している。
#インターフェースの実装を指定する
package google.guice;
public interface Speaker {
void thankYou();
}
package google.guice;
public class JapaneseSpeaker implements Speaker {
@Override
public void thankYou() {
System.out.println("ありがとう");
}
}
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class GuiceMain {
@Inject
private Speaker speaker;
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(Speaker.class).to(JapaneseSpeaker.class);
}
});
GuiceMain main = injector.getInstance(GuiceMain.class);
main.speaker.thankYou();
}
}
ありがとう
DI の設定は configure()
メソッドの中で記述する。
bind(Speaker.class).to(JapaneseSpeaker.class);
がその設定部分。 Speaker
をインジェクションするときの具体的なクラスに JapaneseSpeaker
を指定している。
##@ImplementedBy アノテーションで実装クラスを指定する
Speaker
インターフェースに @ImplementedBy
アノテーションを付与すると、上記と同じ設定ができる。
package google.guice;
import com.google.inject.ImplementedBy;
@ImplementedBy(JapaneseSpeaker.class)
public interface Speaker {
void thankYou();
}
@ImplementedBy
と configure()
メソッドの両方で設定がされていると、 configure()
メソッドで設定した内容が優先される。
つまり、 @ImplementedBy
はデフォルトの実装を定義するのに利用できる。
ただし、コンパイル時に具体的な実装に依存してしまうので、注意して使ったほうが良い。
#インジェクションできる場所について
以下の3つ。
- フィールド
- メソッド
- コンストラクタ
##フィールドインジェクション
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class GuiceMain {
@Inject
private Speaker speaker;
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(Speaker.class).to(JapaneseSpeaker.class);
}
});
GuiceMain main = injector.getInstance(GuiceMain.class);
main.speaker.thankYou();
}
}
ありがとう
フィールドに @Inject
アノテーションを付与すれば、可視性が private でもインジェクションできる。
##メソッドインジェクション
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class GuiceMain {
private Speaker speaker;
@Inject
public void setSpeaker(Speaker speaker) {
System.out.println("setter injection");
this.speaker = speaker;
}
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(Speaker.class).to(JapaneseSpeaker.class);
}
});
GuiceMain main = injector.getInstance(GuiceMain.class);
main.speaker.thankYou();
}
}
setter injection
ありがとう
セッターメソッドに @Inject
アノテーションを付与すると、セッターメソッド経由でインジェクションされる。
##コンストラクタインジェクション
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class GuiceMain {
private Speaker speaker;
@Inject
public GuiceMain(Speaker speaker) {
System.out.println("constructer injection");
this.speaker = speaker;
}
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(Speaker.class).to(JapaneseSpeaker.class);
}
});
GuiceMain main = injector.getInstance(GuiceMain.class);
main.speaker.thankYou();
}
}
constructer injection
ありがとう
コンストラクタに @Inject
アノテーションを付与すると、そのコンストラクタ経由でインジェクションされる。
#アノテーションでインジェクションするクラスを指定する
package google.guice;
public class EnglishSpeaker implements Speaker {
@Override
public void thankYou() {
System.out.println("Thank you.");
}
}
package google.guice;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.google.inject.BindingAnnotation;
@BindingAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Japanese {
}
package google.guice;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.google.inject.BindingAnnotation;
@BindingAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface English {
}
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class GuiceMain {
@Inject @Japanese
private Speaker japaneseSpeaker;
@Inject @English
private Speaker englishSpeaker;
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(Speaker.class).annotatedWith(Japanese.class).to(JapaneseSpeaker.class);
bind(Speaker.class).annotatedWith(English.class).to(EnglishSpeaker.class);
}
});
GuiceMain main = injector.getInstance(GuiceMain.class);
main.japaneseSpeaker.thankYou();
main.englishSpeaker.thankYou();
}
}
ありがとう
Thank you.
@BindingAnnotation
アノテーションを設定した自作のアノテーション(@Japanese
, @English
)を作り、 configure()
メソッド内で annotatedWith(Class<?>)
を使えば、インジェクションする具体的なクラスをアノテーションごとに指定できる。
#インジェクションするインスタンスを指定する
package google.guice;
public class User {
private String name;
public User(String name) {
this.name = name;
}
public void introduce() {
System.out.println("私の名前は" + this.name + "です。");
}
}
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class GuiceMain {
@Inject
private User user;
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(User.class).toInstance(new User("佐藤"));
}
});
GuiceMain main = injector.getInstance(GuiceMain.class);
main.user.introduce();
}
}
私の名前は佐藤です。
toInstance()
メソッドにインスタンスを渡すと、そのインスタンスがインジェクションされる。
#Provides メソッドでインジェクションするインスタンスを定義する
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provides;
public class GuiceMain {
@Inject
private User user;
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {}
@Provides
private User provideUser() {
return new User("鈴木");
}
});
GuiceMain main = injector.getInstance(GuiceMain.class);
main.user.introduce();
}
}
私の名前は鈴木です。
Module クラスに Provides メソッドを定義すると、このメソッドの戻り値がインジェクションに利用される。
Provides メソッドの定義は @Provides
アノテーションを付与すればいい。
#Provider クラスでインジェクションするインスタンスを定義する
package google.guice;
import com.google.inject.Provider;
public class UserProvider implements Provider<User> {
@Override
public User get() {
return new User("田中");
}
}
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class GuiceMain {
@Inject
private User user;
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(User.class).toProvider(UserProvider.class);
}
});
GuiceMain main = injector.getInstance(GuiceMain.class);
main.user.introduce();
}
}
私の名前は田中です。
com.google.inject.Provider
インターフェースを実装したクラスを作成して、 configure()
メソッドの中で toProvider(Class<Provider>)
に指定すると、 Provider クラスの get()
メソッドが返した値がインジェクションに利用されるようになる。
##@ProvidedByアノテーションで Provider クラスを指定する
インターフェースに @ProvidedBy
アノテーションを付与すると、上記と同じ設定ができる。
package google.guice;
import com.google.inject.ProvidedBy;
@ProvidedBy(SpeakerProvider.class)
public interface Speaker {
void thankYou();
}
@ImplementedBy
と同じで、 configure()
メソッドで Provider クラスが設定されている場合は、そちらが優先される。
#Provider クラス自体をインジェクションする
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
public class GuiceMain {
@Inject
private Provider<User> speakerProvider;
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(User.class).toProvider(UserProvider.class);
}
});
GuiceMain main = injector.getInstance(GuiceMain.class);
User user = main.speakerProvider.get();
user.introduce();
}
}
私の名前は田中です。
#Provider クラスの get() メソッドでチェック例外をスローする
Provider#get()
メソッドは例外をスローしない定義になっているので、そのままだと非チェック例外しかスローできない。
何らかの理由で get()
メソッドでチェック例外をスローしたい場合は、 Throwing Providers という Guice の拡張機能を利用する。
Throwing Providers を使用する場合は、 guice-throwingproviders-3.0.jar
をクラスパスに追加する。
pom.xml は以下。
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-throwingproviders</artifactId>
<version>3.0</version>
</dependency>
まず、 CheckedProvider<T>
インターフェースを拡張した独自の Provider インターフェースを定義する。
package google.guice;
import com.google.inject.throwingproviders.CheckedProvider;
public interface CheckedSpeakerProvider extends CheckedProvider<Speaker> {
@Override Speaker get() throws MyException;
}
package google.guice;
public class MyException extends Exception {
}
次に、 CheckedSpeakerProvider
インターフェースを実装した Provider クラスを作成する。
package google.guice;
public class CheckedEnglishSpeakerProvider implements CheckedSpeakerProvider {
@Override
public Speaker get() throws MyException {
return new EnglishSpeaker();
}
}
最後に、 configure()
メソッドの中で Provider クラスを設定する。
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.throwingproviders.ThrowingProviderBinder;
public class GuiceMain {
@Inject
private CheckedSpeakerProvider provider;
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
ThrowingProviderBinder.create(binder())
.bind(CheckedSpeakerProvider.class, Speaker.class)
.to(CheckedEnglishSpeakerProvider.class);
}
});
GuiceMain main = injector.getInstance(GuiceMain.class);
try {
Speaker speaker = main.provider.get();
speaker.thankYou();
} catch (MyException e) {
e.printStackTrace();
}
}
}
ThrowingProviderBinder
クラスのメソッドを使って Provider クラスの設定をしている。
#インジェクションするインスタンスを生成するときのコンストラクタを指定する
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class GuiceMain {
@Inject
private User user;
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(String.class).toInstance("高橋");
try {
bind(User.class).toConstructor(User.class.getConstructor(String.class));
} catch (NoSuchMethodException | SecurityException e) {
addError(e);
}
}
});
GuiceMain main = injector.getInstance(GuiceMain.class);
main.user.introduce();
}
}
私の名前は高橋です。
toConstructor(Constructor)
メソッドで、インスタンスを生成するときのコンストラクタを指定できる。
#シングルトン
Guice は、デフォルトだとインジェクションの度に新しいインスタンスを生成している。
シングルトンにしたい場合は、以下のいずれかの方法を用いる。
クラスに @Singleton
アノテーションを設定する
package google.guice;
import com.google.inject.Singleton;
@Singleton
public class SingletonScope {
public int count = 1000;
}
configure()
で in(Singleton.class)
を指定する
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(SingletonScope.class).in(Singleton.class);
}
});
Provide メソッドに @Singleton
アノテーションを設定する
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
}
@Provides @Singleton
private SingletonScope provideSingleton() {
return new SingletonScope();
}
});
##動作確認
package google.guice;
import com.google.inject.Singleton;
@Singleton
public class SingletonScope {
public int count = 1000;
}
package google.guice;
public class DefaultScope {
public int count = 100;
}
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class GuiceMain {
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {}
});
SingletonScope singletonScope = injector.getInstance(SingletonScope.class);
DefaultScope defaultScope = injector.getInstance(DefaultScope.class);
System.out.printf("singleton.count = %d\n", singletonScope.count);
System.out.printf("default.count = %d\n", defaultScope.count);
singletonScope.count = 9999;
defaultScope.count = 999;
singletonScope = injector.getInstance(SingletonScope.class);
defaultScope = injector.getInstance(DefaultScope.class);
System.out.printf("singleton.count = %d\n", singletonScope.count);
System.out.printf("default.count = %d\n", defaultScope.count);
}
}
singleton.count = 1000
default.count = 100
singleton.count = 9999
default.count = 100
##起動時にシングルトンのインスタンスを生成させる
package sample.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class Main {
public Main() {
System.out.println("Main Constructor.");
}
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(Main.class).asEagerSingleton();
}
});
}
}
通常、シングルトンのオブジェクトは、初めてインジェクションされるときにインスタンスが生成される。
起動時にあらかじめ初期化をしておきたい場合は、 asEagerSingleton()
を configure()
の中で指定する。
#依存関係が解決できないときにエラーを無視する
@Inject
アノテーションの optional
属性に true を指定すると、依存関係が解決できないときにエラーを無視できる。
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class GuiceMain {
@Inject(optional=true) @English
private Speaker speaker;
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
// 設定をコメントアウトしているので、 optional=false だとエラーになる
// bind(Speaker.class).annotatedWith(English.class).to(EnglishSpeaker.class);
}
});
GuiceMain main = injector.getInstance(GuiceMain.class);
System.out.println(main.speaker);
}
}
null
インジェクションは実行されず、デフォルト値(この場合 null) になる。
#オンデマンドでインジェクションする
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class GuiceMain {
@Inject
private Speaker speaker;
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(Speaker.class).to(EnglishSpeaker.class);
}
});
GuiceMain main = new GuiceMain();
System.out.println(main.speaker);
injector.injectMembers(main);
main.speaker.thankYou();
}
}
null
Thank you.
Injector#injectMembers(Object)
メソッドを使えば、好きなタイミングで、引数に渡したインスタンスに依存関係をインジェクションできる。
実行されるインジェクションはフィールドインジェクションかメソッドインジェクションで、コンストラクタインジェクションは無視される。
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class GuiceMain {
private Speaker speaker;
@Inject
public GuiceMain(Speaker speaker) {
this.speaker = speaker;
}
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(Speaker.class).to(EnglishSpeaker.class);
}
});
GuiceMain main = new GuiceMain(null);
System.out.println(main.speaker);
injector.injectMembers(main);
System.out.println(main.speaker);
}
}
null
null
#static フィールドにインジェクションする
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
public class GuiceMain {
@Inject
private static Speaker speaker;
public static void main(String[] args) {
Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(Speaker.class).to(EnglishSpeaker.class);
requestStaticInjection(GuiceMain.class);
}
});
GuiceMain.speaker.thankYou();
}
}
Thank you.
configure()
メソッドのなかで requestStaticInjection(Class<?>)
を実行すると、 static フィールドに依存関係をインジェクションできる。
ただし、 Google Guice はこの使い方を推奨していない(テストしづらいし、グローバルな参照にほかならないとかとか)。
既存の実装が static な Factory を使用している場合に、 Guice による実装に移行するときに "繋ぎ" として使用することを想定しているらしい。
#インターセプター
package google.guice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("before proceed");
Object result = invocation.proceed();
System.out.println("after proceed");
return result;
}
}
package google.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.matcher.Matchers;
public class GuiceMain {
@Inject
private Speaker speaker;
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(Speaker.class).to(EnglishSpeaker.class);
bindInterceptor(Matchers.any(), Matchers.any(), new MyInterceptor());
}
});
GuiceMain main = injector.getInstance(GuiceMain.class);
main.speaker.thankYou();
}
}
before proceed
Thank you.
after proceed
まず、 MethodInterceptor
インターフェースを実装した、独自のインターセプターを実装する。
次に、 configure()
メソッドでインターセプターの設定を記述する。
インターセプターの設定は、 bindInterceptor(Matcher<? super Class<?>>, Matcher<? super Method>, MethodInterceptor...)
を使用する。
1つ目と2つ目の Matcher はインターセプターを適用するクラスを限定するための引数で、3つ目の引数に具体的なインターセプターのインスタンスを渡す。
Matcher.any()
はなんでも OK を意味するので、前述の実装だと全てのクラスの全てのメソッドに対してインターセプターが適用される。
以下、 Matcher による絞り込みの実装例。
##特定のパッケージ直下にあるクラスのみ対象
bindInterceptor(Matchers.inPackage(Hoge.class.getPackage()), Matchers.any(), new MyInterceptor());
Hoge
クラスのあるパッケージ直下にあるクラスが対象。
##特定のパッケージ以下にあるクラスのみ対象(サブパッケージも含む)
bindInterceptor(Matchers.inSubpackage("google.guice.aop.hoge"), Matchers.any(), new MyInterceptor());
google.guice.aop.hoge
以下にあるクラス(サブパッケージも含む)が対象。
##特定のパッケージ以外にあるクラスのみ対象
bindInterceptor(Matchers.not(Matchers.inPackage(Hoge.class.getPackage())), Matchers.any(), new MyInterceptor());
Matchers#not(Matcher)
メソッドで引数に渡した Matcher を否定した条件が指定できる。
上記の場合は、 Hoge
クラスのあるパッケージ以外のパッケージに含まれるクラスが対象になる。
##特定のアノテーションが付与されたクラス(メソッド)のみ対象
package google.guice.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface AopTarget {
}
bindInterceptor(Matchers.annotatedWith(AopTarget.class), Matchers.any(), new MyInterceptor());
@AopTarget
アノテーションを付与したクラスのみが対象になる。
bindInterceptor(Matchers.any(), Matchers.annotatedWith(AopTarget.class), new MyInterceptor());
@AopTarget
アノテーションを付与したメソッドのみが対象。
##特定のクラス、およびそのサブクラスのみ対象
bindInterceptor(Matchers.subclassesOf(HogeClass.class), Matchers.any(), new MyInterceptor());
HogeClass
と、それを継承したクラスのみが対象。
##Matcher を自作する
AbstractMatcher
というクラスがあるので、それを継承すれば任意のクラス(メソッド)にマッチする Matcher を自作できる。
package google.guice.aop;
import com.google.inject.matcher.AbstractMatcher;
public class MyMatcher extends AbstractMatcher<Class<?>> {
@Override
public boolean matches(Class<?> clazz) {
return clazz.getSimpleName().endsWith("Impl");
}
}
bindInterceptor(new MyMatcher(), Matchers.any(), new MyInterceptor());
名前が Impl
で終わるクラスが対象になる。
#参考