作成 2004/8/6
えー、巷で話題のSpringに触ってみます。
Spring Frameworkは J2EEシステムデザインの作者でもあるロッド・ジョンソンさんとかが作ったフレームワークです。
Spring自体の機能はなんだかいろいろたくさんあるみたいですが、とりあえず、いろいろは後で調べるとして、まずは、IoCとAOPの基本部分にさわってみたいと思います。
Webアプリケーションでも使えますが、IoCなどの機能はmainからはじまるJavaアプリケーションでも利用できます。ここでは、まず、mainからはじまるJavaアプリケーションをいくつか作っていきます。まず、その前に、Springをゲットして、設定です。
ここでは以下の環境で実験を行いました。
Springは以下のURLからダウンロードします。
http://www.springframework.org/
ここでは、現在の最新版の1.1RC1を利用しました。非常に基本的なことしか行っていないので、Springは1.0でも同じだと思いますが、まあなんとなく。
本筋とは関係ないですが、ここではEclipse3を利用しました。以降の説明も一部Eclipseベースになっています。Spring用のEclipseプラグインもありますが、現状Eclipse3で動かなかったので、Springプラグインの利用はやめました(設定が悪かったのかもしれませんが、M7って書いてるし)。XMLBuddyのコード補完でガマンです。
まずJavaプロジェクトを作ります。libディレクトリを作り必要なJARをコピーします。以下のものです(リソースパースペクティブ)。
画面 必要なJAR
JAR | 説明 | springを解凍してどこにあるか |
---|---|---|
spring.jar | スプリング本体全部JAR | dist/ |
aopalliance.jar | AOP用のインターフェイス (Springに限らずAOP共通っぽい) | lib/aopaaliance/ |
commons-logging.jar | ログ用 | lib/jakarta-commons/ |
log4j-1.2.8.jar | ログ用 | lib/log4j/ |
これらのJARをクラスパスに通します。
ではさっそく作っていきましょう。まずは基本のIoCな機能をためします。
IFoo.java
package bean; public interface IFoo { void doSomething(); }
Foo.java
package bean; public class Foo implements IFoo{ private String message = ""; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public void doSomething() { System.out.println("doSomething " + message); } }
インターフェイスは必須ではありませんが、後で必要になってくるので、先に作っておきます。
まず最も?簡単そうなサンプルです。bean.xml(ファイル名は任意)というSpringのbean定義ファイルを作成します。ここでは、クラスパス上の/ioc/bean.xmlに作っています。bean定義ファイルでは、bean.Fooクラスにfooというidをふっています。
bean.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="foo" class="bean.Foo"/> </beans>
で、これをJavaから呼びます。こんな感じ。
Main.java
package ioc; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; import bean.Foo; public class Main { public static void main(String[] args) { BeanFactory factory = new ClassPathXmlApplicationContext("/ioc/bean.xml"); Foo foo = (Foo)factory.getBean("foo"); foo.doSomething(); } }
実行結果
doSomething
ClassPathXmlApplicationContextクラスでbean定義ファイルをクラスパスから読み込んでます。いろいろ読み込み方があるようですが、まあとりあえず。facotoryから"foo"のidで取得できるのがFooクラスのインスタンスです。コンテナ(Spring)が設定ファイルを元にインスタンスの生成をやってくれたことがわかります。
あ、実行するときはlog4jの設定ファイルもどこか置いてください(クラスパスのルートにlog4j.propertiesを置くとか)。
fooが作成されるときに値をセットできます。こんな感じで書きます。
bean2.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="foo" class="bean.Foo"> <property name="message"><value>hello</value></property> </bean> </beans>
実行結果
doSomething hello
あとあとAOPをからますと、インターフェイスで扱った方がよいので、次はFooを返すのでなく、Fooのプロキシのインターフェイスを返すことにします。
bean3.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="foo" class="bean.Foo"> <property name="message"><value>hello</value></property> </bean> <bean id="ifoo" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"><value>bean.IFoo</value></property> <property name="target"><ref local="foo"/></property> </bean> </beans>
ProxyFactoryBeanを使って、IFoo型のインターフェイスで、リアルなクラスはFooである、プロキシオブジェクトを指定しています。
Java側は、ifooで指定したオブジェクトをIFoo型のインターフェイスで受け取ります(FooがIFooになっただけです)。
Main3.java
IFoo foo = (IFoo)factory.getBean("ifoo"); foo.doSomething();
まあ、実行しても、プロキシ経由でFoo#doSomething()が実行されるだけなので、さっきと一緒ですけど。
実行結果
doSomething hello
お次はAOPを試します。SpringのAOPは、実行時に設定します(コンパイル時に特別な指定は不要)。
まず、プロキシを利用した簡単な例を作ってみます。
Main.java
package aop; import org.springframework.aop.framework.ProxyFactory; import bean.Foo; import bean.IFoo; public class Main { public static void main(String[] args) { Foo realFoo = new Foo(); ProxyFactory factory = new ProxyFactory(realFoo); IFoo proxy = (IFoo)factory.getProxy(); proxy.doSomething(); } }
Fooのインスタンスを作って、ProxyFactoryクラスをかまして、IFoo型でdoSomethingを実行してます。えーと、まだProxyFactoryの詳細がわかりませんが、とりあえず。実行結果はあいかわらず。
実行結果
doSomething
次にAdviceを指定します。Adviceとは、差し込むブツをあらわす(適当、、、)AOP用語およびインターフェイス名です。
MyIntercepter.java
package aop; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class MyIntercepter implements MethodInterceptor{ public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("before"); Object result = invocation.proceed(); System.out.println("after"); return result; } }
ここでは、Adviceだと思われる(画面↓)MethodInterceptorインターフェイスをimplementsしてMyIntercepterというクラスを作成しました。MyIntercepterでは、メソッドの前後でbefore、afterとSystem.outを行っています。
画面 Adviceのクラス階層
で、Java側では、このAdvice(MyIntercepter)をセットします。
リスト Main2.java
package aop; import org.springframework.aop.framework.ProxyFactory; import bean.Foo; import bean.IFoo; public class Main2 { public static void main(String[] args) { Foo realFoo = new Foo(); ProxyFactory factory = new ProxyFactory(realFoo); factory.addAdvice(new MyIntercepter()); IFoo proxy = (IFoo)factory.getProxy(); proxy.doSomething(); } }
実行すると、メソッド呼び出し時に、Adviceが割って入り、before、afterが出力されます。
実行結果
before doSomething after
お次はAdvisorを指定します。Advisorは、条件を判断してAdviceを返すモノをあらわすAOP用語およびインターフェイス名です(今の理解で説明すると)。ここでは、PointcutAdvisorを実装したMyAdvisorクラスを作成しました。
リスト MyAdvisor.java
package aop; import org.aopalliance.aop.Advice; import org.springframework.aop.Pointcut; import org.springframework.aop.PointcutAdvisor; public class MyAdvisor implements PointcutAdvisor{ public Pointcut getPointcut() { return Pointcut.TRUE; } public boolean isPerInstance() { return false; } public Advice getAdvice() { return new MyIntercepter(); } }
本来は、ここで、条件を指定すべきですが、めんどくさいので、常にTRUEのPointcutを使って、いつもMyInterceptorを返すAdvisorを作りました。
Advisorには、Introduction(合成系)とPointCut(条件系)があるみたいですが、詳しいことは知りません(TODO)。
画面 Advisorのクラス階層
なお、Advisorインターフェイスを直接実装してもいつも、Adviceを返すAdvisorができるかなぁ、と思ったのですが、Springでは、Advisorインターフェイスだけでは使えないようです(JavaDoc参照、ちょっとハマった)。
でもって、AdvisorはaddAdvisorでセットします。
Main3.java
public static void main(String[] args) { Foo realFoo = new Foo(); ProxyFactory factory = new ProxyFactory(realFoo); factory.addAdvisor(new MyAdvisor()); IFoo proxy = (IFoo)factory.getProxy(); proxy.doSomething(); }
実行結果は例によって同じです。
実行結果
before doSomething after
そして、お次はIoCとAOPの組み合わせです。IoCがAOPととうとう出会います。まあ、やることは、Javaコード中で、直接ビーンをnewしたり、アドバイスのセットを書かずに、設定ファイルに移動するって感じですが。既にIoCのところでプロキシをやってしまっているので、ここではadviceを追加するだけです。
bean.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="foo" class="bean.Foo"> <property name="message"><value>hello</value></property> </bean> <bean id="myadvisor" class="aop.MyAdvisor"/> <bean id="ifoo" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"><value>bean.IFoo</value></property> <property name="target"><ref local="foo"/></property> <property name="interceptorNames"> <value>myadvisor</value> </property> </bean> </beans>
Javaコードはこんな感じ(IoCのとこから変化なし)。Javaコードの中にはアドバイスうんぬんなどのを記述する必要はありません。
Main.java
package iocaop; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; import bean.IFoo; public class Main { public static void main(String[] args) { BeanFactory factory = new ClassPathXmlApplicationContext("/iocaop/bean.xml"); IFoo foo = (IFoo)factory.getBean("ifoo"); foo.doSomething(); } }
というわけで、SpringでIoCとAOPでした。実際、このサンプル見てもだからどう?って感じですが、これで、横断的関心事(cross-cutting concern)をコードから分離するのに役立つわけです。ログだー、トランザクションだー、なんだーとかそういう間違いやすくて、ロジック自体とは直接関係ない、あちこち出てくるコードをうまく分離できるってのがAOPの目的の一つらしいです。
AOPもSpringもど素人が書いているので、なんか変だったら教えていただければ幸い。そのうちSpringでWebアプリを作ってみたいと思います。今宵はここまで。
作ったサンプルソース→src.zip
Java World 2004.7 徹底解剖Spring
Spring-Java/J2EEアプリケーションフレームワークリファレンスドキュメント(訳)
http://www.andore.com/money/trans/spring_ref_ja.html
Spring Framework 入門記
http://www.wikiroom.com/koichik/?Spring%20Framework%20%C6%FE%CC%E7%B5%AD