心地良すぎる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.
引数なしコンストラクタ
引数なしのコンストラクタは特別な指定なしで呼び出してくれます。
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
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
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
あたりをやるかもしれません。
リンク
- google-guice - Guice (pronounced 'juice') is a lightweight dependency injection framework for Java 5 and above, brought to you by Google. - Google Project Hosting http://code.google.com/p/google-guice/
- 心地良すぎるモックライブラリ Mockito 〜その1〜 - etc9 http://d.hatena.ne.jp/Naotsugu/20101108/1289218176
結合テスト向けGuice補助ライブラリGuiceBerry
GuiceBerryの目的と使い方が分かった気になったので備忘録として書いてみる。GuiceBerryはGuiceを使用しているコードツリー内で、JUnitやTestNGを使用して結合テストを書いている場合、テスト内でのインスタンスの生成の補助をしてくれるライブラリっぽい。ニッチだなぁ・・・。
まず、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>
リンク
- Hotchpotch Society http://hayamiz.com/~hotchpotch/
- Munin http://munin-monitoring.org/
- サーバ監視ツール「munin」の使い方 〜 その1 〜 | さぶみっと!JAPAN http://www.submit.ne.jp/821
- munin-node-configureコマンドを覚えた - ヌキのやる気のないエンジニアブログ http://d.hatena.ne.jp/editnuki/20110726/1311649985
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)
同時実行性や負荷耐久などは調べていないが、トップページにアクセスする人は少ないのでこれで良しとする。
リンク
- apache の FastCGI(mod_fastcgi) + PHP-FPM で phpを動かしてみる | レンタルサーバー・自宅サーバー設定・構築のヒント http://server-setting.info/centos/apache-mod_fastcgi-php-fpm.html
- Apache module mod_fastcgi http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html
- FastCGI | FastCGI - http://www.fastcgi.com/drupal/
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経由でページに投稿することができます。
リンク
- Authentication - Facebook開発者 https://developers.facebook.com/docs/authentication/
- Removal of offline_access permission - Facebook開発者 https://developers.facebook.com/roadmap/offline-access-removal/
- Graph APIでFacebookページに投稿すると、「ページからの投稿」ではなく「個人ユーザからの投稿」として表示されてしまう - QA@IT http://qa.atmarkit.co.jp/q/46
Apache Commons DbUtils
Apache CommonsのDbUtilsはJDBCの補助ライブラリで、よく書くテンプレを簡略化することができます。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; } };
初期化
DbUtilsでJDBCを扱うときは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()の内部では
- Connectionの確立
- PreparedStatementへのパラメーター指定
- SQL文の実行
- 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()と同様に
- Connectionの確立
- PreparedStatementへのパラメーター指定
- バッチ追加
- バッチ実行
- Connectionのclose
が行われています。
SELECT文
SELECT文はQueryRunner#query()を使って実行します。ResultSetHandler
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()の内部では
- Connectionの確立
- PreparedStatementへのパラメーター指定
- SQL文の実行
- ResultSetのclose
- Connectionのclose
が行われています。ResultSetHandlerを使用する場合、handle()には何も操作がされてない状態のResultSetが渡されます。
AbstractListHandler
query()の戻り値をList
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; } });
AbstractListHandler
AbstractKeyedHandler
query()の戻り値をMap
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
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 Utility Component http://commons.apache.org/dbutils/
- DbUtilsめも http://www008.upp.so-net.ne.jp/kikuta/dbu/
- @IT:Java TIPS -- DbUtilsで結果セットをJavaBeansにマッピングする http://www.atmarkit.co.jp/fjava/javatips/101java013.html
- 「薄い」JavaのO/Rマッパーの紹介 - DbUtils、Persist、Butterfly Persistence - public static void main http://d.hatena.ne.jp/Kishi/20081212/1229076102
- DbUtils でアンダバー区切りをハンガリアン記法にマッピング | もじゃの日記 http://blog.utils.jp/2008/04/dbutils.html
透過型HTTPプロキシ
透過型HTTPプロキシを使うとブラウザのプロキシ設定を使用しなくても自動的にHTTPプロキシを使うことができるそうなのでやってみた。IEの設定でプロキシを無効にして、Google ChromeのinspecterからHTTPアクセスのヘッダを確認したところ、自宅サーバーのsquidからのヘッダがあることを確認した。
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