心地良すぎるDependency Injectionライブラリ Guice

etc9さんの"心地良すぎるモックライブラリ Mockito"がとても勉強になったので、似たような形式でGuiceの紹介をしてみます。

Dependency Injection

きちんと勉強したわけではないので間違っていたらごめんなさい。Dependency Injection (DI)はユニットテストを書きやすくするためのクラスの書き方の一つです。クラスAが内部でクラスBを使うとき、Aの中でBをnewする代わりに、AのコンストラクタやsetterでBのインスタンスを外部から渡せるしておきます。こうしておくとAのユニットテストを書くとき、Bの動作を真似るモックオブジェクトを渡すことで、Bの中身を考えずにAをのテストを書けるようになります。これはAとBの動作を同時に考えてテストを書くよりずっと楽だと思います。依存性注入とかレポジトリパターンとも呼ばれているっぽいです。
GuiceはDIの補助をしてくれるライブラリです。Guice Moduleと呼ばれるクラスにインスタンス生成ルールを記述しておくと、依存関係を解析した上でインスタンスを作成してくれます。Guiceの内部ではリフレクションで依存クラスの解析とインスタンスの作成をしているんだと思います。多分。単にインスタンスを作成するだけでなく、Singletonにしてくれたり、Factoryクラスを自動実装してくれたり、色々やってくれます。

簡単な例

以下の例ではInjectorを作成したあと、Injector経由でクラスAのインスタンスを作成しています。デフォルトのルールでインスタンス生成ができるので、Moduleは使っていません。

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

public class Sample00 {
  public static class A {
    public void run() {
      System.out.println("A#run() is called.");
    }
  }

  public static void main(String[] args) {
    Injector injector = Guice.createInjector();
    A a = injector.getInstance(A.class);
    a.run();
  }
}

出力

A#run() is called.

Aのインスタンスを作るよう指示しました。するとinjectorが内部でインスタンスを作って返してくれます。

引数なしコンストラク

引数なしのコンストラクタは特別な指定なしで呼び出してくれます。

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

public class Sample01 {
  public static class A {
    public A() {
      System.out.println("A() is called.");
    }

    public void run() {
      System.out.println("A#run() is called.");
    }
  }

  public static void main(String[] args) {
    Injector injector = Guice.createInjector();
    A a = injector.getInstance(A.class);
    a.run();
  }
}

出力

A() is called.
A#run() is called.

Aのインスタンスを作るよう指示しました。するとinjectorはAの引数なしコンストラクタを呼び出してインスタンスを作ってくれます。

Constructor Bindings

コンストラクタが引数を持つ場合、引数の型に応じたインスタンスを生成して渡した上で、対象のインスタンスを生成してくれます。ただし、引数付きのコンストラクタには@Injectアノテーションを付けておかないとエラーになってしまいます。クラス間の依存関係がありますが、まだModuleは使わなくて大丈夫です。
バグ防止のためguavaライブラリのPreconditions.checkNotNull()でnullチェックをしています。

import com.google.common.base.Preconditions;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;

public class Sample02 {
  public static class A {
    public A() {
      System.out.println("A() is called.");
    }

    public void run() {
      System.out.println("A#run() is called.");
    }
  }

  public static class B {
    private final A a;

    @Inject
    public B(A a) {
      this.a = Preconditions.checkNotNull(a);
      System.out.println("B() is called.");
    }

    public void run() {
      a.run();
    }
  }

  public static void main(String[] args) {
    Injector injector = Guice.createInjector();
    B b = injector.getInstance(B.class);
    b.run();
  }
}

出力

A() is called.
B() is called.
A#run() is called.

Bのインスタンスを作るよう指示しました。するとinjectorはBのコンストラクタの引数にAが入っているのを見つけ、先にAのインスタンスを作ります。そしてそれをBのコンストラクタに渡して、Bのインスタンスを作ってくれます。出力からBにAのインスタンスが渡されているのが分かります。
ちなみに@Injectを付けないとこんなエラーが出ます。

Exception in thread "main" com.google.inject.ConfigurationException: Guice configuration errors:

1) Could not find a suitable constructor in Sample02$B. Classes must have either one (and only one) constructor annotated with @Inject or a zero-argument constructor that is not private.
  at Sample02$B.class(Sample02.java:20)
  while locating Sample02$B

1 error
  at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1004)
  at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:961)
  at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1013)
  at Sample02.main(Sample02.java:32)

こういったエラーがコンパイル時に分からないのがGuiceの難点の一つです。どうにかならないのでしょうか・・・。
Constructor Bindingsの他にもsetterによるbindingやfield bindingといった方法もありますが、Constructor Bindings推奨だったように思います。

Linked Bindings

Module初登場です。Moduleを使って、Aをinjectする代わりにAを継承したBをinjectするよう指定します。これにはbind()メソッドを使います。"bind(A.class).to(B.class);"は、"型Aの引数にはBのインスタンスを結び付けろ"という意味です。ModuleはcreateInjector()の引数に渡します。
Moduleのconfigure()メソッドの中ではinstall()というメソッドも使えます。これは別のModuleの定義を取り込むというものです。別のパッケージで定義されているModuleの内容を取り込む特に使うと良いでしょう。

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

public class Sample03 {
  public interface A {
    void run();
  }

  public static class B implements A {
    public B() {
      System.out.println("B() is called.");
    }

    public void run() {
      System.out.println("B#run() is called.");
    }
  }

  public static class SampleModule extends AbstractModule {
    @Override
    protected void configure() {
      bind(A.class).to(B.class);
    }
  }

  public static void main(String[] args) {
    Injector injector = Guice.createInjector(new SampleModule());
    A a = injector.getInstance(A.class);
    a.run();
  }
}

出力

B() is called.
B#run() is called.

Aのインスタンスを作るよう指示しました。injectorはModuleの中に"型Aの引数にはBのインスタンスを結び付けろ"と指示があるので、Bのインスタンスを作って返してくれます。
Aがクラスだったり抽象クラスでも動きます。Aが別のクラスの引数に指定されていても動きます。

Scopes

即別な指定をしない場合、Guiceインスタンスをinjectのたびに新しいインスタンスを生成します。ModuleでScopes.SINGLETONを指定すると、インスタンスをinjectするときに最初の一回だけインスタンスを作ってinjector内で使いまわすようになります。いわゆるSingletonパターン的な使い方ができます。

import com.google.common.base.Preconditions;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Scopes;

public class Sample04 {
  public static class A {
    public A() {
      System.out.println("A() is called.");
    }

    public void run() {
      System.out.println("A#run() is called.");
    }
  }

  public static class B {
    private final A a;

    @Inject
    public B(A a) {
      this.a = Preconditions.checkNotNull(a);
      System.out.println("B() is called.");
    }

    public void run() {
      System.out.println("B#run() is called.");
      a.run();
    }
  }

  public static class C {
    private final A a;

    @Inject
    public C(A a) {
      this.a = Preconditions.checkNotNull(a);
      System.out.println("C() is called.");
    }

    public void run() {
      System.out.println("C#run() is called.");
      a.run();
    }
  }

  public static class SampleModule extends AbstractModule {
    @Override
    protected void configure() {
      bind(A.class).in(Scopes.SINGLETON);
    }
  }

  public static void main(String[] args) {
    Injector injector = Guice.createInjector(new SampleModule());
    B b = injector.getInstance(B.class);
    b.run();
    C c = injector.getInstance(C.class);
    c.run();
  }
}

出力

A() is called.
B() is called.
B#run() is called.
A#run() is called.
C() is called.
C#run() is called.
A#run() is called.

BとCのインスタンスを作るよう指示しました。それぞれのコストラクタの引数にAが入っているので、injectorはAのインスタンスを生成して渡そうとします。ここでModuleの中でScopes.SINGLETONが指定されているので、injectorはAを1回だけ生成し、それを2回目以降はそのインスタンスを返します。出力からAのコンストラクタが1回しか呼ばれてないのが分かります。
@Singletonアノテーションを使っても同様のことができますが、Scopeをどこで指定したのかわからなくなってしまうためやめたほうが良いと思います。Linked Bindingsと組み合わせることもできます。

Binding Annotations

同じ型の引数に@Namedアノテーションで別の名前をつけることで、別のインスタンスをinjectするよう指定できます。

import com.google.common.base.Preconditions;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.name.Named;
import com.google.inject.name.Names;

public class Sample05 {
  public interface A {
    void run();
  }

  public static class B implements A {
    public B() {
      System.out.println("B() is called.");
    }

    @Override
    public void run() {
      System.out.println("B#run() is called.");
    }
  }

  public static class C implements A {
    public C() {
      System.out.println("C() is called.");
    }

    @Override
    public void run() {
      System.out.println("C#run() is called.");
    }
  }

  public static class D {
    private final A a0;
    private final A a1;

    @Inject
    public D(@Named("BBB") A a0, @Named("CCC") A a1) {
      this.a0 = Preconditions.checkNotNull(a0);
      this.a1 = Preconditions.checkNotNull(a1);
      System.out.println("D() is called.");
    }

    public void run() {
      System.out.println("D#run() is called.");
      a0.run();
      a1.run();
    }
  }

  public static class SampleModule extends AbstractModule {
    @Override
    protected void configure() {
      bind(A.class).annotatedWith(Names.named("BBB")).to(B.class);
      bind(A.class).annotatedWith(Names.named("CCC")).to(C.class);
    }
  }

  public static void main(String[] args) {
    Injector injector = Guice.createInjector(new SampleModule());
    D d = injector.getInstance(D.class);
    d.run();
  }
}

出力

B() is called.
C() is called.
D() is called.
D#run() is called.
B#run() is called.
C#run() is called.

Dのインスタンスを作るよう指示しました。injectorはDの引数に"BBB"と名付けられたAと"CCC"と名付けられたAが入っているのを見つけます。ModuleにそれぞれBとCを作るような指示が入っているため、BとCを生成してDのコンストラクタに渡し、Dのインスタンスを生成して返してくれます。
ただし、@Namedを使うとインスタンスの実際の型が分かりにくくなってしまうため、あまり使わないほうが良いかもしれません。

Instance Bindings

injectするインスタンスを手動で生成して直接結びつけます。必然的にSingletonになります。

import com.google.common.base.Preconditions;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;

public class Sample06 {
  public interface A {
    void run();
  }

  public static class B implements A {
    public B() {
      System.out.println("B() is called.");
    }

    @Override
    public void run() {
      System.out.println("B#run() is called.");
    }
  }

  public static class C {
    private final A a;

    @Inject
    public C(A a) {
      this.a = Preconditions.checkNotNull(a);
      System.out.println("C() is called.");
    }

    public void run() {
      System.out.println("C#run() is called.");
      a.run();
    }
  }

  public static class SampleModule extends AbstractModule {
    @Override
    protected void configure() {
      bind(A.class).toInstance(new B());
    }
  }

  public static void main(String[] args) {
    Injector injector = Guice.createInjector(new SampleModule());
    C c = injector.getInstance(C.class);
    c.run();
  }
}

Cのインスタンスを生成するよう指示しました。injectorはCのコンストラクタの引数にAが入っているのを見つけます。Moduleに型Aの引数にはconfigure()内で作られたインスタンスを渡すように支持があるため、そのインスタンスを渡してCのインスタンスを生成します。
@Named等と併用できます。

@Provides Methods

injectするクラスやインスタンスを指定する代わりに、インスタンスを作るメソッドをModule内に定義すると、そのメソッドを使ってインスタンスを作ってinjectしてくれます。戻り値の型をinjectしたい型にして@Providesをつけるだけです。メソッドに引数を加えると、そこにもinjectしてくれます。@Namedや@Singletonと併用できます。

import com.google.common.base.Preconditions;
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 Sample07 {
  public static class A {
    public A() {
      System.out.println("A() is called.");
    }

    public void run() {
      System.out.println("A#run() is called.");
    }
  }

  public interface B {
    void run();
  }

  public static class C implements B {
    private final A a;

    @Inject
    public C(A a) {
      this.a = Preconditions.checkNotNull(a);
      System.out.println("C() is called.");
    }

    @Override
    public void run() {
      System.out.println("C#run() is called.");
      a.run();
    }
  }

  public static class D {
    private final B b;

    @Inject
    public D(B b) {
      this.b = Preconditions.checkNotNull(b);
      System.out.println("D() is called.");
    }

    public void run() {
      System.out.println("D#run() is called.");
      b.run();
    }
  }

  public static class SampleModule extends AbstractModule {
    @Override
    protected void configure() {
    }

    @Provides
    private B provideA(A a) {
      return new C(a);
    }
  }

  public static void main(String[] args) {
    Injector injector = Guice.createInjector(new SampleModule());
    D d = injector.getInstance(D.class);
    d.run();
  }
}

出力

A() is called.
C() is called.
D() is called.
D#run() is called.
C#run() is called.
A#run() is called.

Dのインスタンスを生成するよう指示しました。Dのコンストラクタの引数はBですので、Bのインスタンスを生成して渡そうとします。Moduleに戻り値の型がBで@Providesアノテーションが付いているメソッドがあるので、これを使ってインスタンスを生成しようとします。providerメソッドの引数にAがあるので、先にAのインスタンスを生成します。Aのインスタンスを生成しproviderメソッドに渡し、Bのインスタンスを生成します。ここで実際にはCのインスタンスが生成されています。そのインスタンスをDのコンストラクタに渡し、Dのインスタンスを生成して返します。

TypeLiterals

bind()ではジェネリクスを伴ったクラスを直接bindすることができません。これはジェネリクスの型情報がコンパイル時に消えてしまうためだと思います。ジェネリクスを伴ったクラスをbind()する場合は、TypeLiteralで包んで指定します。

import com.google.common.base.Preconditions;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.TypeLiteral;

public class Sample08 {
  public interface A<T> {
    void run(T t);
  }

  public static class B<T> implements A<T> {
    public B() {
      System.out.println("B() is called.");
    }

    public void run(T t) {
      System.out.println("B#run() is called with \"" + t + "\".");
    }
  }

  public static class C {
    private final B<Integer> b;

    @Inject
    public C(B<Integer> b) {
      this.b = Preconditions.checkNotNull(b);
      System.out.println("C() is called.");
    }

    public void run() {
      System.out.println("C#run() is called.");
      b.run(42);
    }
  }

  public static class SampleModule extends AbstractModule {
    @Override
    protected void configure() {
      bind(new TypeLiteral<A<Integer>>() {
      }).to(new TypeLiteral<B<Integer>>() {
      });
    }
  }

  public static void main(String[] args) {
    Injector injector = Guice.createInjector(new SampleModule());
    C c = injector.getInstance(C.class);
    c.run();
  }
}

出力

B() is called.
C() is called.
C#run() is called.
B#run() is called with "42".

Cのインスタンスを生成するように指示しました。Cのコンストラクタの引数にはAが含まれているので、Aインスタンスを生成しようとします。Module内で"型Aの引数にはBのインスタンスを結び付けろ"と指示があるので、Bのインスタンスが生成され、Cのコンストラクタの引数に渡されます。最後にCのインスタンスが生成されます。
Javaはあまり詳しくないため、なぜTypeLiteralでくるむとジェネリクス情報が残るのかわかっていませんorz

AssistedInject

プログラム実行中にクラスのインスタンスの生成をするとき、コンストラクタの引数の一部を手動で渡したい場合があると思います。このような場合はFactoryクラスを経由するのが定石だと思います。FactoryModuleBuilderを使用するとFactoryクラスを自動的に実装してくれます。このとき、引数の一部は手動で、残りはGuiceが自動的にインスタンスを生成して渡す形になります。
FactoryModuleBuilderを使うときは、Factoryインタフェースと@Assistedアノテーションを使います。初めにFactoryインタフェースを定義し、内部にFactoryメソッドを定義しておきます。このとき引数にはコンストラクタにプログラム実行時に手動で渡したいものを書いておきます。また戻り値は生成したいインスタンスの型か、その親クラス/インタフェースを書いておきます。
次にFactoryクラスに生成させたいクラスのコンストラクタの引数のうち、手動で渡したいものに@Assistedアノテーションを付けます。Factoryクラスのメソッドの引数と@Assistedのついた引数が一致すると、FactoryModuleBuilderが内部でFactoryクラスを自動的に実装してくれます。
Factoryクラスのメソッドの戻り値が、欲しいクラスの親クラス/インタフェースの場合は、FactoryModuleBuilder#implement()を使ってどのクラスを生成するか指定します。

import com.google.common.base.Preconditions;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.FactoryModuleBuilder;

public class Sample09 {

  public static class A {
    public A() {
      System.out.println("A() is called.");
    }

    public void run() {
      System.out.println("A#run() is called.");
    }
  }

  public interface B {
    void run();
  }

  public static class C implements B {
    private final A a;

    @Inject
    public C(A a, @Assisted int number) {
      this.a = Preconditions.checkNotNull(a);
      System.out.println("C() is called with " + number + ".");
    }

    @Override
    public void run() {
      System.out.println("C#run() is called.");
      a.run();
    }
  }

  public interface BFactory {
    B create(int number);
  }

  public static class D {
    private final BFactory bFactory;

    @Inject
    public D(BFactory bFactory) {
      this.bFactory = Preconditions.checkNotNull(bFactory);
      System.out.println("D() is called.");
    }

    public void run() {
      System.out.println("D#run() is called.");
      B b = bFactory.create(42);
      b.run();
    }
  }

  public static class SampleModule extends AbstractModule {
    @Override
    protected void configure() {
      install(new FactoryModuleBuilder().implement(B.class, C.class).build(BFactory.class));
    }
  }

  public static void main(String[] args) {
    Injector injector = Guice.createInjector(new SampleModule());
    D d = injector.getInstance(D.class);
    d.run();
  }
}

出力

D() is called.
D#run() is called.
A() is called.
C() is called with 42.
C#run() is called.
A#run() is called.

Dのインスタンスを生成するように指示しました。Dのコンストラクタの引数にはBFactoryがありますので、BFactoryのインスタンスを生成しようとします。Moduleの中で「BFactoryの実装をしろ。型Bを戻り値に持つメソッドがあったらCのインスタンスを生成して返せ。」と指定されたModuleがinstallされているので、この通りにBFactoryのインスタンスを自動実装してDのコンストラクタに渡します。このとき、Bのコンストラクタが呼ばれていない点がポイントです。Bのコンストラクタは初期化が終わったあと、BFactory#create()を呼んだときに初めて呼ばれます。
続いてBFactory#create()が呼ばれBのインスタンスが生成されようとしています。BFactoryはBの代わりにCのインスタンスを作るように指定されていましたので、Cのコンストラクタが呼ばれます。CのコンストラクタにはAが指定されていますので、Aのインスタンスが生成されて渡されます。またCの引数numberには、BFactory#create()に渡されたものが渡されます。Cのインスタンスが生成され、BFactory#create()から返ってきます。
implement()にはTypeLiteralも渡せますので、ジェネリクスを含むクラスも指定することができます。またimplement()にはAnnotationが渡せるようになっており、一つのFactoryクラスの中で、@Namedで違う名前を付けた、同じ型の戻り値を持つFactoryメソッドを作ることもできます。ただし、これは複雑すぎるため避けたほうが良いと思います。

Multibindings

Multibindingを使うと、複数のインスタンスをSetに入れた状態でinjectすることができます。やりかたはMultibinderのインスタンスにaddBinding()でインスタンス生成ルールを追加していくだけです。あまり使用機会はないのではないかもしれません。

import java.util.Set;

import com.google.common.base.Preconditions;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.multibindings.Multibinder;

public class Sample10 {
  public interface A {
    void run();
  }

  public static class B implements A {
    public B() {
      System.out.println("B() is called.");
    }

    public void run() {
      System.out.println("B#run() is called.");
    }
  }

  public static class C implements A {
    public C() {
      System.out.println("C() is called.");
    }

    public void run() {
      System.out.println("C#run() is called.");
    }
  }

  public static class D {
    private final Set<A> as;

    @Inject
    public D(Set<A> as) {
      this.as = Preconditions.checkNotNull(as);
      System.out.println("D() is called.");
    }

    private void run() {
      System.out.println("D#run() is called.");
      for (A a : as) {
        a.run();
      }
    }
  }

  public static class SampleModule extends AbstractModule {
    @Override
    protected void configure() {
      Multibinder<A> multibinder = Multibinder.newSetBinder(binder(), A.class);
      multibinder.addBinding().to(B.class);
      multibinder.addBinding().to(C.class);
    }
  }

  public static void main(String[] args) {
    Injector injector = Guice.createInjector(new SampleModule());
    D d = injector.getInstance(D.class);
    d.run();
  }
}

出力

B() is called.
C() is called.
D() is called.
D#run() is called.
B#run() is called.
C#run() is called.

Dのインスタンスを生成するように指示しました。Dのコンストラクタの引数にはSetが含まれていますので、injectorはこの生成ルールを探します。Module内でMultibinderでSetBinderが作らていますので、これに従ってBとCのインスタンスを生成してSetに入れてDに渡し、Dのインスタンスを生成します。

次回予告?

  • Web and Servlets Integration
  • Test with Mockito
  • GuiceBerry

あたりをやるかもしれません。

結合テスト向けGuice補助ライブラリGuiceBerry

GuiceBerryの目的と使い方が分かった気になったので備忘録として書いてみる。GuiceBerryはGuiceを使用しているコードツリー内で、JUnitTestNGを使用して結合テストを書いている場合、テスト内でのインスタンスの生成の補助をしてくれるライブラリっぽい。ニッチだなぁ・・・。

まず、GuiceBerryを使わない場合の結合テストの例。自分の作っているQMACloneから抜粋。

@RunWith(JUnit4.class)
public class ChatManagerTest {
  private ChatManager manager;

  @Before
  public void setUp() throws Exception {
    manager = Guice.createInjector(new QMACloneModule()).getInstance(ChatManager.class);
  }

  @Test
  public void testWriteRead() {
    PacketChatData expected = new PacketChatData();
    expected.body = "body";

    manager.write(expected, "1.2.3.4");

    PacketChatDataList list = manager.read(0);
    PacketChatData actual = list.list.get(list.list.size() - 1);

    assertEquals(expected.body, actual.body);
  }

  @Test
  public void testGetChatDataListWebSocket() {
    assertNotNull(manager.getChatDataListWebSocket());
  }
}

ここではChatManagerクラスを手えすとしようとしている。setUp()で毎回GuiceのInjectorを作成して、ChatManagerクラスを生成している。ちょっとかっこ悪い気がする。これをGuiceBerryを使って書き直すとこうなる。

public class QMACloneTestEnv extends AbstractModule {
  @Override
  protected void configure() {
    install(new GuiceBerryModule());
    install(new QMACloneModule());
  }
}

@RunWith(JUnit4.class)
public class ChatManagerTest {
  @Rule
  public final GuiceBerryRule rule = new GuiceBerryRule(QMACloneTestEnv.class);
  @Inject
  private ChatManager manager;

  @Test
  public void testWriteRead() {
    PacketChatData expected = new PacketChatData();
    expected.body = "body";

    manager.write(expected, "1.2.3.4");

    PacketChatDataList list = manager.read(0);
    PacketChatData actual = list.list.get(list.list.size() - 1);

    assertEquals(expected.body, actual.body);
  }

  @Test
  public void testGetChatDataListWebSocket() {
    assertNotNull(manager.getChatDataListWebSocket());
  }
}

GuiceBerry用のGuiceモジュール(Env)を定義し、GuiceBerryModuleとテストに使いたいモジュールをインストールする。次にテスト本体に@Rule付きでGuiceBerryRuleのフィールドを追加して、先ほど作ったEnvを指定する。最後にテストしたいクラスを@Inject付きでフィールドに追加する。テストが少しだけスッキリした。

サーバー監視ツール munin

C83で入手したThe Database Times Vol. 2で@zembutsu氏が紹介されていたサーバー監視ツール"munin"が気になったので試してみた。

munin-nodeがサーバーの情報を集めて、muninがvisualizeをするらしい。

Ubuntuには標準でmuninのパッケージが用意されているようなので、apt経由でインストール。mysqlの監視にはperlのCache::Cacheが必要になるらしいので同時にインストールしておく。

sudo aptitude install munin munin-node munin-plugins munin-plugins-java munin-plugins-openstack libcache-cache-perl ruby libxml-simple-perl

muninはプラグインを追加することで様々なプログラムの監視ができるようになるらしい。有効になっているプラグインと推奨プラグインのリストを見てみる。

sudo munin-node-configure -suggest

推奨プラグインを有効にするには以下のコマンドを叩く。手動でシンボリックリンクを貼って設定する方法もあるらしいが、こちらのほうが楽。

sudo munin-node-configure -shell | sudo sh

muninが正しく設定されているかどうかチェックスクリプトを走らせてみる。

munin-check

以下のコマンドでmunin-nodeが正しく走るか試すことができるらしい。

sudo -u munin munin-cron

WordPressのURL書き換えのためにmod_rewriteを使用しており、muninのcgiが正しくは知らない場合がある。設定ファイルに以下を適宜追加して、mod_rewriteを無効化しておく。

  <IfModule mod_rewrite.c>
    RewriteEngine Off
  </IfModule>

リンク

Apache2 + MPM Worker + mod_fastcgi + php5-fpm でWordPressを動かす

今までQMACloneはTomcat6に付属のHTTP Connectorを使用してhttpをサーブしていた。つい先日、httpのサーブはApacheを使用し、AJP Connector等でTomcatと繋げたほうが通信が安定するという都市伝説をネット上で見かけた。なんでもSocket通信のエラーハンドリングがTomcat6のJVMのものよりApacheのモノの方がしっかり作られているらしい。ということで早速やってみた。

ところが・・・、Apache2のMPMをPreforkのままにしたため、一つのhttpコネクションに対して一つのプロセスが作られ、最終的には約800のApacheプロセスが立ち上がり、あわやスワップアウトを起こしかけるという状態になってしまった。

これを防ぐためにMPMをWorkerに切り替えることにした。ところが・・・、MPM Workerではmod_phpが使用できないらしく、トップページのWordPressが動かなくなってしまった。色々調べたところmod_fastcgi + php5-fpmならMPM Workerでも動かすことができ、かつキャッシュが効くのでそこそこの速度が出るという情報を得た。

以下に備忘録として作業内容を残しておく。

作業内容

php5-fpmとmod_fastcgiをインストールして、php5-fpmを起動しておく。

% sudo aptitude install php5-fpm libapache2-mod-fastcgi
% sudo update-rc.d php5-fpm enable
% sudo service php5-fpm restart

必要となるモジュールを有効にする

% cd /etc/apache2/mods-enabled
% sudo ln -s ../mods-available/actions.conf
% sudo ln -s ../mods-available/actions.load
% sudo ln -s ../mods-available/rewrite.load

cgiを有効にする

% sudoedit /etc/apache2/sites-enabled/000-default
OptionsにExecCGIを追加
AllowOverrideをAllに変更

Ubuntuに含まれるphp5-fpmパッケージに合わせてfastcgiの設定をする。標準設定ではバイナリの場所は"/usr/sbin/php5-fpm"、Socketのパスは"/var/run/php5-fpm.sock"とのこと。

% sudoedit fastcgi.conf
<IfModule mod_fastcgi.c>
  FastCgiExternalServer /usr/sbin/php5-fpm -socket /var/run/php5-fpm.sock
  AddHandler php-fastcgi .php
  ScriptAlias /fcgi-bin/ /usr/sbin/
  Action php-fastcgi /fcgi-bin/php5-fpm
</IfModule>

この時点でApacheを再起動してWordPressが正しく動くことを確認する。

% sudo service apache2 restart

最後にMPM PreforkからMPM Workerに切り替える。

% sudo aptitude install apache2-mpm-worker

以上でApache2 + MPM Worker + mod_fastcgi + php5-fpmが動くようになった。
レスポンスは以下の通り。

% ab -n 10 http://kishibe.dyndns.tv/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking kishibe.dyndns.tv (be patient).....done


Server Software:        Apache/2.2.22
Server Hostname:        kishibe.dyndns.tv
Server Port:            80

Document Path:          /
Document Length:        11589 bytes

Concurrency Level:      1
Time taken for tests:   0.684 seconds
Complete requests:      10
Failed requests:        0
Write errors:           0
Total transferred:      119100 bytes
HTML transferred:       115890 bytes
Requests per second:    14.62 [#/sec] (mean)
Time per request:       68.418 [ms] (mean)
Time per request:       68.418 [ms] (mean, across all concurrent requests)
Transfer rate:          170.00 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        7    8   1.3      8      11
Processing:    48   60   8.5     61      72
Waiting:       47   59   8.4     59      70
Total:         56   68   8.3     69      80

Percentage of the requests served within a certain time (ms)
  50%     69
  66%     72
  75%     77
  80%     79
  90%     80
  95%     80
  98%     80
  99%     80
 100%     80 (longest request)

同時実行性や負荷耐久などは調べていないが、トップページにアクセスする人は少ないのでこれで良しとする。

botプログラムからFacebookページにAPI経由で投稿するためのアクセストークンを取得する

自分の作っているQMACloneにはFacebookのページに問題を投稿する機能があるのですが、トークンの期限が切れたために動かなくなっていました。
トークンの取得方法を調べ直しましたので備忘録として書いておきたいと思います。

短期間用(1〜2時間)アクセストークンの取得

以下のURLにアクセスして短期間用のアクセストークンを取得します。アクセストークンはリダイレクト先のURLのフラグメントの部分に書かれます。

https://www.facebook.com/dialog/oauth?
	client_id=[App ID/API Key]&
	redirect_uri=https://www.facebook.com/connect/login_success.html&
	response_type=token&
	scope=publish_stream,manage_pages

長期間用(60日)アクセストークンの取得

以下のURLにアクセスして短期間用のアクセストークンを長期間用のアクセストークンに交換します。

https://graph.facebook.com/oauth/access_token?
	client_id=[App ID/API Key]&
	client_secret=[アプリのシークレットキー]&
	grant_type=fb_exchange_token&
	fb_exchange_token=[短期間用アクセストークン]

ページ投稿用アクセストークンの取得

以下のURLにアクセスしてページ投稿用のアクセストークンを取得します。

https://graph.facebook.com/me/accounts?access_token=[長期間用アクセストークン]

以上で60日間くらい使えるアクセストークンが手に入ります。このアクセストークンを使用してGraph API経由でページに投稿することができます。

リンク

Apache Commons DbUtils

Apache CommonsのDbUtilsJDBCの補助ライブラリで、よく書くテンプレを簡略化することができます。DbUtilsを使うことでConnectionのcloseし忘れを防止することができたりもします。自分はQMACloneのデータベース部分にDbUtilsを使っています。

準備

解説では以下のMySQLのテーブルとJavaのclassを使います。

CREATE TABLE person (
  id SERIAL PRIMARY KEY,
  name TEXT
);
public class Person {
  public long id;
  public String name;

  public long getId() { return id; }
  public void setId(long id) { this.id = id; }
  public String getName() { return name; }
  public void setName(String name) { this.name = name; }
};

初期化

DbUtilsJDBCを扱うときはQueryRunnerを使用します。自分はApache CommonsのDBCPで作成した接続プールと一緒に使っています。

QueryRunner runner = new QueryRunner(databaseConnectionPool.getDataSoure());

INSERT文・UPDATE文・REPLACE文・DELETE文

INSERT文・UPDATE文・REPLACE文・DELETE文はQueryRunner#update()を使って実行します。update()は第一引数にPreparedStatementに渡すものと同じ書式のSQL文、第二匹数以降にSQL文の"?"に当てはめる値を順に指定していきます。

runner.update("INSERT person SET name = ?", "hoge");
runner.update("UPDATE person SET name = ? WHERE id = ?", "fuga", 1);
runner.update("DELETE person WHERE id = ?", 1);

update()の内部では

  1. Connectionの確立
  2. PreparedStatementへのパラメーター指定
  3. SQL文の実行
  4. Connectionのclose

が行われています。自分でこれらの動作を書く必要がない分、コードが簡潔になっています。また、接続のcloseし忘れの心配もありません。

バッチ処理

INSERT文・UPDATE文・REPLACE文・DELETE文はQueryRunner#batch()を使ってバッチ処理することができます。batch()には第一引数にupdate()と同様にSQL文、第二引数にはSQL文の"?"に当てはめる値を順に格納したObjectの配列、Object[]を渡します。

List<Object[]> params = new ArrayList<Object[]>();
params.add(new Object[]{"hoge"});
params.add(new Object[]{"fuga"});
runner.batch("INSERT person SET name = ?", params.toArray(new Object[0][]));

batch()の内部ではupdate()と同様に

  1. Connectionの確立
  2. PreparedStatementへのパラメーター指定
  3. バッチ追加
  4. バッチ実行
  5. Connectionのclose

が行われています。

SELECT文

SELECT文はQueryRunner#query()を使って実行します。ResultSetHandlerインスタンスを渡して、handle()メソッドに渡されてきたResultSetから値を取り出します。handle()はT型を返すことができるようになっており、ここで返した値がquery()の返り値になります。

Person person = runner.query("SELECT * FROM person WHERE id = ?", new ResultSetHandler<Person>() {
  @Override
  public Person handle(ResultSet rs) throws SQLException {
    if (rs.next()) {
      Person person = new Person();
      person.id = rs.getLong("id");
      person.name = rs.getString("name");
      return person;
    }
    return null;
  }
}, 1);
List<Person> people = runner.query("SELECT * FROM person", new ResultSetHandler<List<Person>>() {
  @Override
  public List<Person> handle(ResultSet rs) throws SQLException {
    List<Person> people = new ArrayList<>();
    while (rs.next()) {
      Person person = new Person();
      person.id = rs.getLong("id");
      person.name = rs.getString("name");
      people.add(person);
    }
    return people;
  }
});

query()の内部では

  1. Connectionの確立
  2. PreparedStatementへのパラメーター指定
  3. SQL文の実行
  4. ResultSetのclose
  5. Connectionのclose

が行われています。ResultSetHandlerを使用する場合、handle()には何も操作がされてない状態のResultSetが渡されます。

AbstractListHandler

query()の戻り値をListにしたい場合、AbstractListHandlerで一部のコードを省略できる場合があります。

List<Person> people = runner.query("SELECT * FROM person", new AbstractListHandler<Person>() {
  @Override
  protected Person handleRow(ResultSet rs) throws SQLException {
    Person person = new Person();
    person.id = rs.getLong("id");
    person.name = rs.getString("name");
    return person;
  }
});

AbstractListHandlerArrayListインスタンスを生成し、handleRow()の戻り値をadd()し、query()の返り値とします。先ほどよりもコードが簡潔になっています。個人的にはAbstractCollectionHandlerという形で、自分で戻り値となる型を指定できるようにして欲しかったのですが、戻り値の方のキャストが必要になる場合が多いので一長一短かもしれません。createArray()でListのインスタンスを選べるようにするくらいはして欲しかったです。

AbstractKeyedHandler

query()の戻り値をMapにしたい場合、AbstractKeyedHandlerで一部のコードを省略できる場合があります。

Map<Long, Person> people = runner.query("SELECT * FROM person",
    new AbstractKeyedHandler<Long, Person>() {
      @Override
      protected Person createRow(ResultSet rs) throws SQLException {
        Person person = new Person();
        person.id = rs.getLong("id");
        person.name = rs.getString("name");
        return person;
      }

      @Override
      protected Long createKey(ResultSet rs) throws SQLException {
        return rs.getLong("id");
      }
    });

AbstractListHandlerはprotectedメソッドのcreateMap()でMapのインスタンスを生成し、ResultSetの各行についてcreateKey()とcreateRow()でキーと値を取り出し、Mapにput()していきます。こちらはcreateMap()をオーバーライドすることでMapのインスタンスを選べるようになっています。

ColumnListHandler

ある列のListが欲しい場合はColumnListHandlerを使うこともできます。ただしquery()の戻り値がListとなってしまうため、使いどころが難しいです。

List<Object> people = runner.query("SELECT * FROM person", new ColumnListHandler(name));

ColumnListHandlerはコンストラクタで指定した列の値のListを返してくれます。引数を省略した場合は1列目の結果のListを返してくれるので、以下のような簡潔な書き方もできます。

List<Object> people = runner.query("SELECT name FROM person", new ColumnListHandler());

SQLのint(10)型がJavaのLong型で返されてしまうため、自分は以下のようにカスタマイズしたColumnListHandlerを使っています。

public class ColumnListHandler<T> extends AbstractListHandler<T> {
  private final Class<T> c;
  public ColumnListHandler(Class<T> c) { this.c = c; }
  protected T handleRow(ResultSet rs) throws SQLException {
    Object object = rs.getObject(1);
    if (c == object.getClass()) {
      return c.cast(object);
    } else if (c == Integer.class && object.getClass() == Long.class) {
      // SQL の int(10) 型を Java の Long 型で返すため、特別処理
      return c.cast((Integer) (int) (long) (Long) object);
    } else {
      throw new IllegalArgumentException(String.format("%s から %s にキャストできません", object
          .getClass().toString(), c.toString()));
    }
  }
}

ScalarHandler

ResultSetの初めの一行の特定の列の値が欲しい場合はScalarHandlerを使うことができます。

long count = (long) runner.query("SELECT COUNT(*) FROM person", new ScalarHandler());

ScalarHandlerはResultSetの初めの行の特定の列の値を返してくれます。自分は上のようにCOUNT()やMAX()の値を取り出す時に使っています。

BeanHandler・BeanListHandler

Beansに対応したアクセサメソッドが定義されているclassであれば、自分でResultSetHandlerを書かずに値を取り出すことができます。

Person person = runner.query("SELECT * FROM person WHERE ID = ?", new BeanHandler<>(
    Person.class), 1);
List<Person> porple = runner.query("SELECT * FROM person", new BeanListHandler<>(
    Person.class));

BeanHandler・BeanListHandlerはBeanProcessorを使ってResultSetを指定したクラスのオブジェクトに変換して取り出してくれます。楽です。ただし、アクセサを書かなければならないのだ若干億劫です。

まとめ

JDBCを使うとき、Apache CommonsのDbUtilsを使うと定型文を省略して書くことができ、コーディングの効率を上げることができます。

リンク

透過型HTTPプロキシ

透過型HTTPプロキシを使うとブラウザのプロキシ設定を使用しなくても自動的にHTTPプロキシを使うことができるそうなのでやってみた。IEの設定でプロキシを無効にして、Google ChromeのinspecterからHTTPアクセスのヘッダを確認したところ、自宅サーバーのsquidからのヘッダがあることを確認した。

squid

/etc/squid/squid.confを書き換えて透過型プロキシのオプションを追加する。
http_port 3128 transparent

iptables

iptablesを使って80番ポートに来たHTTPアクセスを3128に転送する
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 3128
転送を有効にするために/etc/sysctl.confに以下の設定を追加する。
net.ipv4.ip_forward=1

リンク