アメリカではほとんどデフェクトスタンダードとなっている「」の覚書きです。Spring は簡単に言うと、IoC (制御の反転)、またの名を DI (依存性注入) という仕組みを取り入れた軽量コンテナです。
Springが他のIoCコンテナと差別化を計っている点として、「ビジネスオブジェクトを管理する方法にフォーカスを当てている」という点があります。また、「レイヤー化されたアーキテクチャであるため、局所的にSpringアーキテクチャを利用できる」、「テストドリブンプロジェクトに理想的なフレームワークになるように設計されている」という利点があります。
Springの詳細については、ほかにもっとよいサイト(Springフレームワークの紹介)があるので、そちらを参考にしてください。ここでは、Springを使っていて、ハマった点や気になった点などをメモしていこうと思います。随時更新していくつもりです。間違っている可能性が高いので、気になる点があればコメントをお願いします。
参考
IoCコンテナ
- IoCコンテナとは
- IoC で使われる主なクラス / インターフェース
- XmlBeanFactory を使った、サンプルコード
- ApplicationContext を使った場合
- bean-context.xml (Bean定義ファイル)の中身
- CustomEditorConfigurer を使った、特殊型へのマッピング
- MessageResource の取り扱い
- ApplicationContextの取得方法その2
- BeanFactoryLocator が必要な理由
- Beanのプロパティを別ファイルに定義する方法
- メソッドの実行結果をBeanのプロパティに設定する
- ハマった点
IoCコンテナとは
Spring の IoC コンテナはXMLで定義された Bean定義ファイル に基づいて、オブジェクトの生成や初期化、関連付けを行います。
オブジェクトの管理に使うこともできます。例えば、Singleton (インスタンスが常に一つしか存在しないクラス) として扱いたい場合にも、Bean定義ファイルに設定することで Singleton として扱えるようになります。
IoC で使われる主なクラス / インターフェース
IoCコンテナを利用する場合に使われる主なクラスやインターフェースです。
org.springframework.context.ApplicationContext
このクラスはIoCの核となるものです。以下のような機能を ApplicationContext は備えています。
- BeanFactory 機能
- 国際化機能
- リソースファイルのロード機能
- 複数コンテキストの扱い
- イベントの通知
org.springframework.beans.factory.BeanFactory
IoCコンテナの核となるインターフェース。このインターフェースの実装クラスが、Beanインスタンスを生成し、初期値を設定してくれる。ただし、メモリ容量などの縛りがない限り通常は ApplicationContext の使用が推奨されています。
通常は、ApplicationContext を使用します。
org.springframework.context.support.FileSystemXmlApplicationContext
XMLファイルを読み込んで Beanインスタンスを生成します。おそらく、一般的な ApplicationContext の実装だと思います。
org.springframework.beans.factory.xml.XmlBeanFactory
Beanの定義ファイル(XML)を読み込んでインスタンスの生成や初期化を行うクラス。XMLのDTDは「http://www.springframework.org/dtd/spring-beans.dtd」が利用できます。BeanFactory を利用する場合にはこのクラスを使います。
org.springframework.beans.factory.InitializingBean
このインターフェースを実装しているクラスは、IoC コンテナが BeanFactoryがインスタンスを生成して初期値を設定した後に呼び出されるようになります。このインターフェースに定義されている afterPropertiesSet() で初期化処理を行うことが出来るようになります。これは、Bean定義の init-method を指定したのと同じ動作になります。
POJOのクラスは、Springに依存させたくない場合がほとんどでしょう。通常、このインターフェースを実装するよりも、Bean定義に init-method を指定したほうが幸せになれます。
org.springframework.beans.factory.DisposableBean
このインターフェースを実装しているクラスは、BeanFactory のデストラクタ処理の中で呼ばれるようになります。通常、Bean中のリソースを開放したい場合に使います。destroy() メソッドを実装します。 Bean定義の destroy-method を指定したのと同じ動作です。
POJOのクラスは、Springに依存させたくない場合がほとんどでしょう。通常、このインターフェースを実装するよりも、Bean定義に destroy-method を指定したほうが幸せになれます。
org.springframework.beans.factory.config.CustomEditorConfigurer
Bean定義の中で、プロパティの型が特殊なもの (独自クラスや Timestamp型など) は、Bean定義ファイルの中に直接値を書いても、BeanCreationException が発生してしまいます。原因は型が一致しないからです。このクラスは、指定した型に独自のコンバート処理を施すことが出来るようになります。サンプルコードを参考にしてください。
デフォルトで用意されている主なカスタムエディター
- org.springframework.beans.propertyeditors.CustomBooleanEditor
true, false という文字列を Boolean に変換します。
- org.springframework.beans.propertyeditors.CustomDateEditor
日付文字列を日付型に変換します。日付文字列は、コンストラクタに渡すフォーマッタで指定できます。
- org.springframework.web.multipart.support.ByteArrayMultipartFileEditor
マルチパートファイルをバイトの配列に変換します。
- org.springframework.beans.propertyeditors.CustomNumberEditor
-
Integer, Long, Short, Double, Float 等の数値型への変換を行います。
Webアプリケーションで使用する場合は、通例では BaseCommandController の initBinder メソッドで、binder.registerCustomEditor を呼び出すようにする。
- org.springframework.beans.propertyeditors.StringTrimmerEditor
空文字 を null に変換するエディターです。Webアプリケーションで使えるかも。
org.springframework.beans.factory.access.BeanFactoryLocator
BeanFactory クラスをルックアップするためのインターフェースです。このインターフェースの実装クラスは、主に Singleton で使われます。Singleton で使われる理由は「BeanFactoryLocator が必要な理由」を見てください。
XmlBeanFactory を使った、サンプルコード
ここでは、まず XmlBeanFactory を作成しています。これが IoC コンテナの本体です。
XmlBeanFactory のサンプル
package com.hamasyou.spring;
import java.io.FileInputStream;
import java.math.BigDecimal;
import junit.framework.TestCase;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
public class ExsampleBeanTest extends TestCase {
public void testDataAccess() throws Exception {
BeanFactory factory =
new XmlBeanFactory(new FileInputStream("bean-context.xml"));
PersonAccessDAO dao = (PersonAccessDAO)factory.getBean("personAccessDAO");
Person person = dao.findPerson(BigDecimal.valueOf(1));
assertEquals("IDは 1 のはず", 1, person.getId().intValue());
}
}
ApplicationContext を使った場合
ApplicationContext は、BeanFactory に国際化メッセージ機能や、イベント通知機能を備えたものです。複数のコンテキストを扱え、Bean定義ファイルを分割することが出来ます。BeanFactory と使い方はほとんどかわりません。プロジェクトでは、BeanFactory よりも ApplicationContext を利用するほうが多いと思います。
BeanFactory よりも ApplicationContext をつかう
BeanFactory よりも ApplicationContext を主に使いましょう。ApplicationContext は、AOP のサポートや分散トランザクションのサポートなど、 BeanFactory よりも高機能を備えています。
ApplicationContext のサンプル
public void testApplicationContext() throws Exception {
FileSystemXmlApplicationContext context =
new FileSystemXmlApplicationContext(
new String[] { "bean-context.xml",
"bean-context2.xml",
"custom-edit.xml" } );
PersonAccessDAO dao =
(PersonAccessDAO)context.getBean("personAccessDAO");
Person person = dao.findPerson(BigDecimal.valueOf(1));
assertEquals("IDは 1 のはず", 1, person.getId().intValue());
}
bean-context.xml (Bean定義ファイル)の中身
Bean定義ファイルには、Beanの設定を書いておきます。プロジェクトで使い始めたら、あっという間にBeanファイルが大きくなりすぎてしまう気がします。ApplicationContext なら、複数のコンテキストを扱えるので、Bean 定義を複数ファイルに分割することが出来ます。
ApplicationContext クラスを使うと、Bean 定義ファイルを分割することができます。ApplicationContext を使う場合、bean-context.xml ではなく applicationContext.xml というファイル名を使うのが慣習です。
bean-context.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="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>org.hsqldb.jdbcDriver</value>
</property>
<property name="url">
<value>jdbc:hsqldb:hsql://localhost</value>
</property>
<property name="username">
<value>sa</value>
</property>
<property name="password">
<value></value>
</property>
</bean>
<bean id="personAccessDAO"
class="jp.dip.xlegend.spring.dao.SimpleAccessDAO">
<property name="dataSource">
<ref local="dataSource"/>
</property>
</bean>
</beans>
CustomEditorConfigurer を使った、特殊型へのマッピング
例えば Timestamp 型を Bean に設定したい場合、ファイルに直接値を書いても、BeanCreationException が発生してしまいます。特殊型へのマッピングには、いくつか方法がありますが、下記はそのうちの一つです。
[参考]
Timestamp型へのマッピングがあるBean定義
<?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="employee" class="jp.dip.xlegend.spring.Employee">
<constructor-arg index="0" type="java.math.BigDecimal">
<value>1</value>
</constructor-arg>
<property name="name">
<value>Employee Syougo Hamada</value>
</property>
<property name="birthday">
<value>2004-11-04 00:48:32.00</value>
</property>
</bean>
<bean id="customEditorConfig"
class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="java.sql.Timestamp">
<bean class="jp.dip.xlegend.spring.custom.TimestampEditor"/>
</entry>
</map>
</property>
</bean>
</beans>
これが 独自コンバータの実装です。java.beans.PropertyEditorSupport クラスを継承して作ると簡単になります。
TimestampEditor クラスの実装
package com.hamasyou.spring.custom;
import java.beans.PropertyEditorSupport;
import java.sql.Timestamp;
public class TimestampEditor extends PropertyEditorSupport {
public String getAsText() {
return getValue().toString();
}
public void setAsText(String text) throws IllegalArgumentException {
setValue(Timestamp.valueOf(text));
}
}
MessageResource の取り扱い
ApplicationContext は MessageResource を扱えるようになっています。実装は StaticMessageSource やResourceBundleMessageSource が使えるようです。 Bean定義ファイルに 「messageSource」という ID で Bean を定義することで、MessageResource の実装を切り替えられるようです。
この方法を使うと、MessageResource を使うクラスが ApplicationContext に依存してしまいます。依存しないようにするには、アダプターとか使うと良いかもしれません。Beanクラスに ApplicationContext を渡すためには、 ApplicationContextAware インターフェースを実装すれば良いです。setApplicationContext というメソッドを使い、ApplicationContext にアクセスできます。
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>application_messages</value>
</list>
</property>
</bean>
ApplicationContextの取得方法その2
BeanFactory や ApplicationContext は 直接 new
することもできますが、設定ファイルに実装クラスを書いておきたい場合もあるでしょう。そんな場合には、「SingletonBeanFactoryLocator」、「ContextSingletonBeanFactoryLocator」を使えます。詳しくは、邪悪な Singleton』を参考にするといいと思います。
BeanFactory や ApplicationContext は Singleton オブジェクトとして扱うべきです。大きく2つの理由があります。
- サードパティのコードが BeanFactory や ApplicationContext を使って、オブジェクトをインスタンス化したときにも、こちら側で設定した Bean定義を生かせるようにしたい
- Webアプリケーションのようなリソースが制限されるアプリのときに、最小のリソースですむようにしたい
BeanFactoryLocator
は XML 定義ファイルを読み込んでコンテナを生成してくれます。読み込む定義ファイル名は定義されているようです。
- SingletonBeanFactoryLocator
BeanFactory コンテナを生成するクラス。定義ファイル名は「beanRefFactory.xml」
- ContextSingletonBeanFactoryLocator
ApplicationContext コンテナを生成するクラス。定義ファイル名は「beanRefContext.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="context"
class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg>
<list>
<value>bean-factory.xml</value>
<value>bean-context.xml</value>
</list>
</constructor-arg>
</bean>
</beans>
そして、BeanFactory、ApplicationContext を生成するコードはこんな感じになります。
BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance();
BeanFactoryReference ref = locator.useBeanFactory("context");
ApplicationContext context = (ApplicationContext) ref.getFactory();
Person person = (Person)context.getBean("personBean");
ref.release();
BeanFactoryLocator が必要な理由
Spring Pad 「邪悪なSingleton」 の中でこんな部分がありました。
なぜこういうものが必要かというと,コンテナの外でインスタンス化されたオブジェクトからコンテナを使いたい場合があるから,ということです.世の中にはすでに様々なフレームワークがあって,インスタンス化はそれらにおまかせというのはIoCなコンテナに限った話ではありません.そのようなフレームワークをいろいろと組み合わせて使う場合,あっちでインスタンス化されたオブジェクトからこっちのコンテナでDependency Injectionされたオブジェクトを使いたいということもあるでしょう.そのような場合のために,Spring Frameworkが用意してくれているのが BeanFactoryLocator です.だから”Glue code”なんですね.
つまり、SpringFramework の外にあるオブジェクトから、SpringFramework が管理しているオブジェクトに触りたいときに使うのが BeanFactoryLocator というわけです。
Beanのプロパティを別ファイルに定義する方法
Beanのプロパティを別ファイルに定義しておいて、実行時に設定させたい場合があります。例えば、データベース接続情報など、Bean定義ファイルの中身は直接触らせたくないけど、お客様にデータベースの接続情報を変更して欲しい場合などです。そんなときは、データベースの接続情報だけをリソースファイルとして定義しておき、置換処理を行うことで、Beanのプロパティを設定できます。
置換処理でプロパティを設定する
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>jdbc.properties</value>
</list>
</property>
</bean>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>${jdbc.driverClassName}</value>
</property>
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
</bean>
PropertyPlaceholderConfigurer クラスは、設定ファイル中の 「${置換文字列}」とマッチするキーが、locations で指定したリソースファイルの中にあれば、置換します。結構使えると思います。
メソッドの実行結果をBeanのプロパティに設定する
コンテナ管理される Bean のプロパティに、メソッドの実行結果を設定したい場合があります。例えば、データベースの値であったり、設定ファイルを読み込んだ値だったりするものです。その場合は、org.springframework.beans.factory.config.MethodInvokingFactoryBean という FactoryBean をつかう事ができます。詳しくは、「メソッド起動の結果にビーンプロパティを設定する」を参考に。
MethodInvoke の実行結果はキャッシュされるようです。キャッシュされないようにするには、setSingleton プロパティに false を設定すればよさそうです。
ハマった点
インスタンスの初期化をコンテナの外から渡すには
コンテナ管理のオブジェクトは、必要なときに生成され、初期化されます。生成時に必要な引数や、初期化時の値は通常 Bean 定義ファイルに書きます。
コンテナ管理されるオブジェクトの初期化に、コンテナ管理されていないオブジェクトを渡したい場合はどうするのでしょうか?例えば、GUI の入力値保持するオブジェクトをコンテナ管理されるオブジェクトの初期化に使いたい場合などです。
おそらく、「コンテナ管理されるオブジェクトをコンテナ管理されないオブジェクトに依存させることは出来ない」はずです。そもそも、DIコンテナで扱うべきものというのは、コンポーネント の単位であるはずです。コンポーネントとして切り出すときの指針として、設定ファイルとして書けるか?というのがあります。GUI の入力は設定ファイルとして書けるはずありませんから、DIで扱うべきものではないと言うのが、僕の意見です。
Seasar2 フレームワークでは、こういう用途にも対応できる機能が備わっています。もしかしたら、SpringFramework にも後のバージョンで可能になるかもしれません。
[参考]
参考
Spring Framework の本家です。 Spring Framework
Spring Framework の 日本語 Wiki です。大量の情報があります。 Spring Pad
Spring-Java/J2EEアプリケーションフレームワークリファレンスドキュメント (日本語訳) Spring-Java/J2EEアプリケーションフレームワークドキュメント
Spring フレームワークに関しての概要です。TECHSCORE さんの記事は読みやすいなぁ (^^ ; TECHSCORE - Spring Framework
Spring を含めた、軽量コンポーネントのお話です。
- Spring の ロッドジョンソンが贈る、J2EE技術者のためのバイブル
- Spring のロッドジョンソンによる Spring ユーザのための本 (洋書)
- SpringでWebアプリケーションを作りながら、Springの全体像がわかりやすく解説されています。