Springメモ

作成 2004/8/6

えー、巷で話題のSpringに触ってみます。

Springとは?

Spring Frameworkは J2EEシステムデザインの作者でもあるロッド・ジョンソンさんとかが作ったフレームワークです。

Spring自体の機能はなんだかいろいろたくさんあるみたいですが、とりあえず、いろいろは後で調べるとして、まずは、IoCとAOPの基本部分にさわってみたいと思います。

Springの設定

Webアプリケーションでも使えますが、IoCなどの機能はmainからはじまるJavaアプリケーションでも利用できます。ここでは、まず、mainからはじまるJavaアプリケーションをいくつか作っていきます。まず、その前に、Springをゲットして、設定です。

ここでは以下の環境で実験を行いました。

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を試す

ではさっそく作っていきましょう。まずは基本の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を試す

お次は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を指定します。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を指定します。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の組み合わせです。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


Top | Back