2008-06-25

SpringでJNDIを提供する 続き

以前のエントリで、XBeanの成果物を拝借して Springで JNDIを提供させる方法を示したのだが、この org.apache.xbean.spring.jndi.SpringInitialContextFactoryは Beanファクトリとして ApplicationContextのバリアントを使っていないためそのままだと PropertyPlaceholderConfigurerなどを使って Bean定義ファイル内の値をパラメタ化することが出来ない。
(ApplicationContextの子孫は、BeanFactoryPostProcessorを自動処理するのだが、それ以外の BeanFactoryではプログラムコードで明示的に postProcessBeanFactoryを呼んでやらなければ処理されない)
ApplicationContextはあくまで「アプリケーションの」コンテキストであるため JNDIの提供に適用しない事自体は正しいと思うが、せめてpostProcessBeanFactoryを呼んで欲しいところだ。
この問題(?)は2005年に報告されているが完全に無視という形のようだ。こんなのただの副産物だから本来の用途にさえ使えればよくてそれ以外興味ないねということだろうか。

仕方がないのでSpringInitialContextFactoryの派生クラスを作って BeanFactoryの生成を行っているメソッドをオーバーライドし、postProcessBeanFactoryの呼び出しを追加することにした。

package net.stbbs.xbean.spring.jndi;

import java.util.Map;

import org.apache.xbean.spring.context.impl.XBeanXmlBeanFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.core.io.Resource;

public class SpringInitialContextFactory extends
org.apache.xbean.spring.jndi.SpringInitialContextFactory {

@Override
protected BeanFactory createContext(Resource resource) {
BeanFactory factory = super.createContext(resource);
Map<String,Object> postProcessors =
((XBeanXmlBeanFactory)factory)
.getBeansOfType(BeanFactoryPostProcessor.class);
for (Map.Entry pp:postProcessors.entrySet()) {
((BeanFactoryPostProcessor)pp.getValue())
.postProcessBeanFactory((XBeanXmlBeanFactory)factory);
}
return factory;
}
}

jndi.propertiesは
java.naming.factory.initial=org.apache.xbean.spring.jndi.SpringInitialContextFactory

java.naming.factory.initial=net.stbbs.xbean.spring.jndi.SpringInitialContextFactory
に書き換えた。

ラベル:

2008-06-08

Springベースのユニットテストに DbUnitを組み合わせる方法

DbUnitは、JUnitにデータベース入出力のテストを行うための各種便利機能を提供するための拡張である。
ここでは、フィクスチャのロードを行う機能にスコープを絞って DbUnitを取り上げる。
フィクスチャとは、ユニットテストの事前条件となるテストデータである。

DbUnitを使うには、クラスパスに下記のjarファイルを追加する。

dbunit-*.jar
slf4j-api-*.jar
slf4j-jcl-*.jar

フィクスチャを使うユニットテストは、各テストメソッド毎に下記のような流れで実行される。

a) トランザクション開始

b) フィクスチャをデータベースにINSERT

c) テスト対象メソッドの呼び出し

d) 実行結果のチェック

e) トランザクションをロールバック

a, e は Springの機能で自動的に行われる。
c, d はテストケースの実装者がコーディングするとして、残る b を DbUnitで行うということになる。

テストケースの各トランザクションを常にロールバックする設定にしておけば、データベースは各テストメソッドが終了するたびにテストを開始する前の状態へ戻るため、INSERTされたフィクスチャも取り消される。そのため、固定のテストデータを用いても2回目以降のテストで重複キーの問題が発生するということは無い。

前回のエントリSpringとユニットテストで解説したような、Springによって自動的にトランザクション管理が行われるように構成された、つまり

@RunWith(SpringJUnit4ClassRunner.class)
@TransactionConfiguration
@Transactional
@ContextConfiguration

といったアノテーションの付加されたテストケースをさらに DbUnitへ対応させるには、テストケースに下記のような追加を行う。

1) DataSourceBasedDBTestCaseを継承させる
2) DataSource型のプロパティを持たせ、Springでインジェクトさせる
3) protected DataSource getDataSource()メソッドを実装する (単に 2のデータソースオブジェクトを返すだけで良い)。このメソッドはDbUnitが利用する。
4) @Beforeアノテーションを付けて、public void setUp()を実装する。単に super.setUp()をコールするだけで良い。
5) protected IDataSet getDataSet()をオーバーライドする。ここには、フィクスチャとなるデータセットオブジェクトを返す処理を記述する。このメソッドはDbUnitが利用する。

注意しておきたいのは 2 のデータソースである。Springと DbUnitを一緒に使うためのデータソースは TransactionAwareDataSourceProxyでなくてはならない
DbUnitはデータソースから取得した物理コネクションをフィクスチャのINSERTが完了した後に一度クローズしてしまう。Springのトランザクション管理下ではこれは不正な操作であり(DbUnitは Springの事など知らないのでやむを得ないのだが)、このため素のデータソースを DbUnitへ渡すとテスト本体が正常に実行できなかったりする。
Springの TransactionAwareDataSourceProxyでデータソースを包んでおけば、物理コネクションが適切な Proxyオブジェクトによって保護されるため、この問題を防ぐことができる。

デフォルトでは、DbUnitはフィクスチャをデータベースに INSERTする際にテーブルを一旦クリアしてしまう。もし接続先のデータベースがユニットテスト専用ならばこの動作は妥当だが、そうでない場合(同じデータベースを結合テストにも使用するなど)はテーブルをクリアしてほしくないだろう。ユニットテスト実行時にテーブルをクリアせずフィクスチャの INSERTだけを行うようにするには、protected DatabaseOperation getSetUpOperation() メソッドをオーバーライドして DatabaseOperation.INSERT を返すようにすると良い。
(但し、できれば結合テスト用のデータベースとユニットテスト用のデータベースは別々に設けたいところである。例えば Ruby on Railsではそのような習わしになっている)

フィクスチャは Excelで記述することが出来る(私が DbUnitを使いたいと思った唯一の理由がこれだ)。
Excelの各シートがデータベースのテーブルに対応する。シート名としてテーブル名を用いることによって、シートがテーブルと対応づけられる。
各シートの最上行にはカラム名を記載し、2行目以降に INSERTしたいデータを並べる。このようにして作成した Excelファイルを fixtures.xls として保存したなら、先の 5番にある protected IDataSet getDataSet() の実装はこのようになるだろう。

@Override
protected IDataSet getDataSet() throws Exception {
return new XlsDataSet(new File("fixtures.xls"));
}

ラベル:

2008-06-04

Springとユニットテスト

テストケースくらいはなるべく特定のフレームワークに依存させたくないという気持ちはあるものの、さすがに Hibernateやらといったヘヴィな奴をインスタンス化する長い道のりを手でコーディングしたり、単一責任の原則に基づいて真面目に分割されたコンポーネント群のワイヤリングを手でコーディングしたりするのはかなり厳しいものがあるので、そこは目をつぶってテストケースもやはり Springの助けを借りて実装するのが現実的だ。

なお JUnitと Springを連携させるには、spring.jarの他に spring-test.jar が必要となる。

Eclipseについてる JUnitを捨てる


Eclipseについている JUnitはちょっと古くて、Springに対応するために必要なインターフェイスJUnit4ClassRunnerが存在していない。なので Eclipseに標準で付属している JUnitをビルドパスから外し、最新の JUnitへビルドパスを通す必要がある。その場合でも "Run as"→"JUnit TestCase"はちゃんと動くので心配ない。

テストケースに Beanをインジェクトさせる


テストケースに Springサポートを適用する一番シンプルな方法は、下記のふたつのアノテーションをつけてやることである。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration

これらのアノテーションがついたテストケースを走らせると、最初にテストケースと同じパッケージ階層とクラス名+"-context.xml"という名前を持つ Bean定義ファイルが読み込まれる。
テストケースに @Autowired (もしJava EEのアノテーションが使えるなら @Resource でも良い)の付いたプロパティがあれば、それぞれのプロパティと同じ型の Beanが自動的にインジェクトされる。

<!-- src/test/com/acme/DataImporterTest-context.xml -->
<beans>
<!-- 中略 -->
<bean id="dataImporter" class="com.acme.DataImporterImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>

package com.acme;
// 中略
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class DataImporterTest {
@Autowired
DataImporter dataImporter;

public void testDoImport()
{
dataImporter.doImport();
}
}

これなら、テストケースの中に何らインスタンス化やワイヤリングの処理を記述しなくても良く非常に簡潔である。
読み込むBean定義ファイルを変更したり名称を指定してプロパティに Beanをインジェクトしたり(@Qualifierアノテーションを使う)も出来るので、詳しくは Spring 2.5のドキュメント 8.3.7.2. Context management and caching あたりを参照されたし。

トランザクション制御も自動化する


典型的には、データベースアクセスを行うコンポーネントに適用するテストケースの各メソッドはそれぞれがひとつのトランザクション単位として考えられるだろう。したがって、各テストメソッドの開始時点で自動的にトランザクションがBEGINされ、終了時には自動で COMMIT又は ROLLBACKが行われると便利である。
そうしたい場合は、先の @RunWith, @ContextConfigurationアノテーションに加えて下記のアノテーションをつけてやると良い。

@TransactionConfiguration
@Transactional

各テストメソッドの実行にあたり、Bean定義ファイル内で定義された transactionManagerという名前のトランザクションマネージャを勝手に使ってトランザクションの制御を行ってくれる。
(使用するトランザクションマネージャの名前は@TransactionConfigurationアノテーションのオプションで変更が可能)

デフォルトでは、各テストメソッドの終了時にはトランザクションがロールバックされるようになっている。つまりテストの実行によってデータベースの内容を変更しないということだ。ユニットテストは外部リソースの状態から独立しているべきなのでこの動作が理想的なのではあるが、どうしてもテストケースを回した後に Microsoft Accessやら Navicatやらといった GUIツールでテーブルの中身を確認してみたい場合もある。そういう時は @TransactionConfigurationアノテーションのオプションで defaultRollback=falseを与えてやるか、テストメソッドへ明示的に @Rollback(false) のようにアノテーションでロールバックを行わないことを記してやる必要がある。

<bean id="transactionManager"
class=
"org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TransactionConfiguration
@Transactional
public class DataImporterTest {
@Autowired
DataImporter dataImporter;

@Rollback(false)
public void testDoImport()
{
dataImporter.doImport();
}
}

気になったのだが、テストメソッドがロールバックを行わない設定になっていると、テストが失敗した(キャッチされない例外が上がった、アサーションに引っかかった)場合でもトランザクションは容赦なくコミットされてしまうようだ。中途半端なデータをデータベースに残さないよう、実装の初期段階ではロールバックを行う設定でユニットテストを行う方が良さそうである。

Beforeと After


Springによるトランザクション管理が適用されたテストケースでは、JUnitの標準的な @Before, @Afterメソッドの他に @BeforeTransaction, @AfterTransactionメソッドを持つことが出来る。
@Before, @Afterはトランザクション内で実行されるが、@BeforeTransactionはトランザクションの開始前、@AfterTransactionはトランザクションの終了後に実行される。

少しだけ記述を減らす+α


もしテストケースが他のクラスを継承していないのであれば、@RunWith, @TransactionConfiguration, @Transactionalアノテーションを付ける代わりに AbstractTransactionalJUnit4SpringContextTests を継承させることもできる。

アノテーションの記述を省略できることに加え、このクラスにはデータベース操作のユニットテストを行う際に便利かもしれない下記のものが装備されている。
・Beanファクトリへのアクセス (applicationContext)
・ログAPIへのアクセス (logger)
・SimpleJdbcTemplateのインスタンス (simpleJdbcTemplate)
・データベース操作のテストに便利かもしれないいくつかのメソッド(countRowsInTable,deleteFromTables,executeSqlScript,setSqlScriptEncoding)

最後のはフィクスチャのロードなどに使うことを想定した機能かもしれないが、そのあたりを本格的にやるなら DbUnitを組み合わせて使うのが良いと思う。
言うまでもなく Javaは多重継承ができないので、DbUnitの DatabaseTestCaseを継承するかこのAbstractTransactionalJUnit4SpringContextTestsを継承するかの選択を迫られることになるのだが。

ラベル:

2008-06-03

SpringでJNDIを提供する

「え、それって<jee:jndi-lookup/> のこと?」
・・・だったらわざわざブログのエントリなぞ起こさんわい。JNDIを参照する側ではなく、提供する側の話。

Springは JNDIを参照することは出来るけど、JNDIサービス自体は提供していない。
ふつう JNDIはアプリケーションサーバが提供するものなので(ついでに言えば Java EEのサービスなので) Springの守備範囲ではないのだが、何らかの理由でユニットテストのターゲットが JNDIを参照しているとか(レガシーなSLSBを使い回してるとか、中のJRubyで activerecord-jdbc-adapterを使ってるとかね)、ユニットテスト用の Bean定義ファイル内でもデータソースの参照は JNDI経由で行いたいとか、Webアプリケーションではなくstatic public void main()から始まるスタンドアロンのプログラムなんだけどやっぱり Bean定義ファイル内でのデータソース設定は JNDIを参照する形の記述にしたいとか、そういう特殊な事情のある人またはマニアックな人は Springベースの JNDI実装が欲しくなったりもするのである。そして、やっぱりそういうものが存在する。

Springベースの JNDI実装 org.apache.xbean.spring.jndi.SpringInitialContextFactory は Apache Geronimoのサブプロジェクト XBeanの成果物である xbean-spring-*.jarに含まれている。これを拝借して自分のユニットテスト用クラスパスに追加してやろう。

JNDIの実装クラスを指定する方法には下記のようなものがある。
・システムプロパティjava.naming.factory.initial にクラス名をセットする
・クラスパスに jndi.propertiesというファイルを置き、java.naming.factory.initial=行にクラス名を記述する
どちらでも良いが Eclipseで Runする時に VM引数を設定するのは面倒なので私は後者にしている。

jndi.propertiesの内容は下記のようになる。
java.naming.factory.initial=org.apache.xbean.spring.jndi.SpringInitialContextFactory

SpringInitialContextFactoryは jndi.xmlという名前の Bean定義ファイルをクラスパス上で探しだし、それを JNDIの設定ファイルとして用いる(jndi.propertiesと同じ場所にでも置いておけば良いだろう)。この Bean定義ファイルには少なくとも、jndi という名称で org.apache.xbean.spring.jndi.DefaultContext クラスの Beanを定義しておく必要がある。空の JNDIツリーを提供するだけならばこの Beanに何らプロパティをセットする必要はないが、典型的にはここにデータソースをバインドすることになるだろう。データソースをJNDIにバインドしたければ、jndi.xmlは下記のような記述になる。例はMySQLの場合だが、DBベンダの独自な DataSource実装を使うのが嫌であれば Springの DriverManagerDataSourceなどといった汎用のDataSource実装を使えば良いと思う。
(個人的には、ことユニットテストに用いる場合などコネクションプーリングの必要ないケースに限って言うならば DataSourceの実装が DBベンダのものか Springのものかは大した問題ではないと考えている。アプリケーションの利用するDBMSが差し替わるというのは、いろんな意味でよほどのことだ)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans">
<bean id="dataSource"
class=
"com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
<property name="url" value="jdbc:mysql://localhost/mydb"/>
<property name="user" value="username"/>
<property name="password" value="secret"/>
<property name="zeroDateTimeBehavior" value="convertToNull"/>
<property name="useUnicode" value="true"/>
<property name="characterEncoding" value="UTF8"/>
</bean>

<bean id="jndi"
class=
"org.apache.xbean.spring.jndi.DefaultContext">
<property name="entries">
<map>
<entry key="java:/comp/env/jdbc/dataSource">
<ref bean="dataSource"/>
</entry>
</map>
</property>
</bean>
</beans>

ずるをする


Springのトランザクション管理下で JDBCの呼び出しをしたい場合、下記のいずれかの条件を満たしている必要がある。

1)常に Springの JdbcTemplateを経由してJDBCの呼び出しを行う
2)常に SpringのDataSourceUtilsを経由してデータソースからコネクションを取得する
3)DataSourceを Springの TransactionAwareDataSourceProxyでラップし、常にそれを使う
4)JtaTransactionManagerを使う

JNDIから直接データソースを取得するように実装されているレガシーなJEEコードも Springのトランザクション管理下で動作させたい場合、Java EEコンテナ上で動作させる時は 4の方法でコンテナ管理のトランザクションマネージャとくっつけてやれば裏でうまく処理してくれそうな気がするが、Java SEベースの開発環境でユニットテストをするときはその方法が使えない。

では、TransactionAwareDataSourceProxyでラップ済みのデータソースを JNDIに登録してはどうかということで、やってみたところ一応期待通りの動作をするようだ。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans">
<bean id="dataSource"
class=
"org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
<constructor-arg>
<bean class="com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
<property name="url" value="jdbc:mysql://localhost/mydb"/>
<property name="user" value="username"/>
<property name="password" value="secret"/>
<property name="zeroDateTimeBehavior" value="convertToNull"/>
<property name="useUnicode" value="true"/>
<property name="characterEncoding" value="UTF8"/>
</bean>
</constructor-arg>
</bean>

<bean id="jndi" class="org.apache.xbean.spring.jndi.DefaultContext">
<property name="entries">
<map>
<entry key="java:/comp/env/jdbc/dataSource">
<ref bean="dataSource"/>
</entry>
</map>
</property>
</bean>
</beans>

Springのドキュメントによると、DataSourceTransactionManagerにセットする dataSourceは TransactionAwareDataSourceProxyでは駄目・・・なんだけど、そういう場合は勝手に TransactionAwareDataSourceProxyから中の本物データソースを取り出して使うよ。ということになっていて、実際ソースを確認したところそうなっていたので、JNDIからもってきたデータソースが既に TransactionAware...でラップされていても大丈夫みたい。

ラベル:

2008-05-13

Springの lang:jrubyで日本語を通す

Spring Framerowkと JRubyを組み合わせると、Springの Beanを Rubyで実装することが出来る。
が、Bean定義ファイルにインラインで記述するにしろソースファイルに記述するにしろ、スクリプト中の文字列リテラルに日本語が含まれていると化けてしまう。仕方ないので Springと JRubyのソースを追ってみた。

まず結論として、Springの JRuby連携部分に対するその場しのぎのパッチを示す。
org/springframework/scripting/jruby/JRubyScriptFactory.java をこんな風に書き換えると、とりあえず日本語が通る。

96a97,98
>String iso8859_1edString =
> new String(scriptSource.getScriptAsString().getBytes("UTF-8"), "ISO-8859-1");
98c100
< scriptSource.getScriptAsString(), actualInterfaces, this.beanClassLoader);
---
> iso8859_1edString, actualInterfaces, this.beanClassLoader);

デバッガを使って文字の化ける箇所を特定→charを惜しげもなく byte へキャストしている箇所発見(日本語を含む文字列リテラルが化ける直接の原因)→なぜそうなっているか大体想像がついたので JRubyの mainメソッドからスクリプトのロードが行われる箇所までを追跡→やはりコマンドラインからの通常起動では ISO-8859-1固定でスクリプトファイルを読み込んでいる

つまりJRuby1.0 の Lexarは、スクリプトがどんなエンコーディングで記述されていようともそれを一旦 ISO-8859-1文字列としてパースすることを前提に実装されている。
「コマンドラインや$KCODEで文字コードの指定が出来るじゃないか!」と思うかもしれないが、指定された文字コードによって文字列の扱い方が影響を受けるのは、構文解析が終わった後である。通常のI18Nアプローチでは考えにくいことだが、そうしないともともとI18Nに対応していない(文字列とバイトストリームの区別がない) MRI(CRuby) 1.8との互換性を取ることが困難なのだろう。

上記の理由により、スクリプトを Correctly I18N'edな文字列として JRubyパーサに渡すと逆に文字化けが起こってしまう。だが、Springはそのようなことをお構いなしに「正しいエンコーディングで読み込まれた」スクリプト文字列を JRubyのパーサに渡してしまう。
上のパッチは、一度正しく読み込まれたI18N'edな文字列を UTF-8表現のバイトストリームにバラし、それをわざわざISO8859-1文字列であると偽って Rubyパーサに渡すためのクイックハックである。

・・・っていうか、Springで JRubyが使えるようになってからしばらく経つと思うんだけど、この問題についての情報を検索で見つけることができなかった。日本人は誰もこの機能使ってないの?

ラベル: ,

2008-04-30

BlazeDSとセッション変数、Springのsessionスコープ その2

前回からの続き

Flexアプリケーション向けのサービスをBlazeDSで構築するにあたり、認証をどのように扱うかという問題について、

a) FlexContext経由で HttpSessionを使ってセッション変数へアクセスできる
b) aを利用し、認証メソッドが一度呼び出され成功した際にセッション変数へ認証情報をセットする
c) aを利用し、各メソッドの先頭に認証状態のチェックを行う処理を入れる

というソリューションを解説した。しかし、この方法にはいくつかの問題点がある。最近の Java設計者であればすぐに下記の2点を解決したいと考えるはずだ。

α - ひとつは、FlexContextや HttpServletRequestといったコンテキスト依存の APIを使用することでコンポーネントの独立性が低下し(ひらたい言い方をすると、POJOでなくなる)、再利用の機会を喪失するうえユニットテストもやりにくくなること。

β - もうひとつは、全てのメソッドに同じ処理(この場合は認証状態のチェック処理)をコピー&ペーストで差し込む行為が一般的には忌避すべきものであるということ。

このような問題を解決するために、まさにDI/AOPコンテナが存在することは言うまでもない。
幸い、BlazeDSは Spring Frameworkと一緒に使うことが出来る(以前のブログエントリを参照)。

αの問題を解決するにあたっては、DIを用いる。
「臭いものに蓋をする」の考え方で、FlexContext, HttpServletRequestといったものを隠蔽し純粋に必要なデータのみの入出力を定義するためのインターフェイスを抽出する。このインターフェイスは BlazeDSや Servlet APIに依存しない。

interface SessionData {
public void setUserId(String userId);
public String getUserId();
}

ユニットテスト用の実装を、特別なAPIを一切用いないで作成する。

public class SessionDataMock implements SessionData {
private String userId;

public void setUserId(String userId)
{
this.userId = userId;
}
public String getUserId()
{
return this.userId;
}
}

同じインターフェイスでデプロイ用の実装を作成する。

public class SessionDataImpl implements SessionData {
private HttpSession getSession()
{
return FlexContext.getHttpRequest().getSession();
}

public void setUserId(String userId)
{
getSession().setAttribute("userId", userId);
}
public String getUserId()
{
return (String)getSession().getAttribute("userId");
}
}

SessionDataの実装は下記のように使われるだろう。

// 認証処理が成功したら、セッションにユーザIDをセットする
if (doAuth(userId,password)) sessionData.setUserId(userId);

// セッションからユーザIDを取得する。認証されていない場合は例外を送出する。
String userId = sessionData.getUserId();
if (userId == null) throw new AuthenticationRequiredException();

ユニットテストの際にはSessionDataMock、デプロイの際にはSessionDataImplをビジネスコンポーネントへ注入してやるような使い分けをすれば、「セッションデータへアクセスするためのAPIが実行環境に依存してしまうためビジネスコンポーネントのユニットテストが出来ない」という事態を避けることが出来る。

と、ここまでは Spring 1.xまでの場合である。
Spring 2.0以降では、Webアプリケーションの場合に限って利用可能な Beanのスコープとして "request" "session" が追加されている。
ここではセッションに対して値をバインドしたいので、

public class SessionData {
private String userId;

public void setUserId(String userId)
{
this.userId = userId;
}
public String getUserId()
{
return this.userId;
}
}

のようなバリューオブジェクトを作成して scope="session" のBeanとして定義し、必要なコンポーネントへ注入してやれば良い。例えば Bean定義ファイルは下記のようになる。

<bean id="sessionData" class="com.acme.SessionData" scope="session">
<aop:scoped-proxy/>
</bean>

<bean id="myService" class="com.acme.MyService">
<property name="sessionData" ref="sessionData"/>
</bean>

sessionスコープで定義された Beanのインスタンスは、HTTPセッション毎にそれぞれ1個生成される。つまりこの Beanのクライアント(利用側)である myServiceは、そのまま sessionDataの setter/getterを呼び出すことでセッションデータの読み書きと同等のことが出来ることになる。これならば FlexContextも HttpServletRequestも HttpSessionも全く自分のコードに登場させる必要がないうえ、この SessionDataクラスはコンテキストに依存しないため単純に new して使用することも可能であり、ユニットテストの際にも一切変更することなく使える(=デプロイ用とユニットテスト用に複数の実装を用意する必要がない)のである。

賢明なエンジニアであれば、ライフサイクルの異なる Bean同士をワイヤリングする、つまりsingleton(デフォルト)スコープのBeanへ sessionスコープの Beanを注入することに無理があると感じるかもしれないが、実は Spring 2以降では、<aop:scoped-proxy/>要素を Bean定義内に配置することによりライフサイクルの差異を吸収する Proxyオブジェクトを自動的に生成してくれるようになっておりコーディング時には意識する必要がない(もっとも、意識しはじめてしまうと気持ちの悪い動作に思えてくるかもしれないが・・・)。

なお Springで Webアプリケーション特有のスコープを用いる場合、web.xmlに下記のリスナーを登録する必要がある。

<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>

</listener>


以上のように BlazeDSと Springを一緒に使うことによって、”BlazeDS、サーブレットAPI、ましてやSpringのAPIをも一切使わずに" Flexアプリケーションの認証情報をセッションへバインドすることが出来るのである。

続く

ラベル: ,

2008-04-27

BlazeDSとセッション変数、Springのsessionスコープ その1

一般的に、RIAではその設計上サーバサイドでユーザの状態を持つ必要がほとんどない。例えば、Flexなら、ショッピングカートを実装するためにわざわざサーバ側でセッション変数を使わなくとも、Flexアプリケーションの内部変数、つまり ActionScriptの変数でカートの中身を保持すれば良い。

しかしながら、RIAであってもセッション変数を使ってサーバ側で保持せざるを得ないデータがひとつだけある。それは認証に関するものだ。認証はユーザがアプリケーションを起動した際に一度だけ行われ、それ以降はIDやパスワードのやりとりをせずとも済むのが好ましい。そのためには、認証情報をリモートオブジェクトのコンテナたるサーバ側でセッションに関連づけて保持する必要がある。

もし認証情報をサーバ側で保持しないとなれば、ユーザの特定や正当性検証を行うためリモートオブジェクトのあらゆるメソッドに引数としてID/パスワードが必要となってしまうことになり大変不経済であるのは想像に難くないだろう。

ここでは、BlazeDSを使って Flex向けのサービスを構築する際にセッション変数の扱いをどのように行うことが出来るかをまず説明する。

BlazeDS経由で呼び出されるリモートメソッドの中では、FlexContextクラスのstaticメソッド getHttpRequest() で HttpServletRequestのインスタンスを得ることが出来る。ここからさらに getSession()をコールすることによりセッションオブジェクトへアクセス出来るため、通常のサーブレットプログラミングと同様のやり方でセッション変数を保持したり読み出したりすることが可能だ。

HttpSession session = FlexContext.getHttpRequest().getSession();
session.setAttribute("mysessionobject", value);

クライアント)
ID/パスワードを引数とし、サーバ側の認証メソッドをコールする

サーバ)
ID/パスワードの検証を行い、正当であればセッション変数にユーザIDを保持しtrueを返す

クライアント)
認証メソッドがtrueを返してきたら、ステートを切り替えてユーザの操作を可能にする。

クライアントはサーバ側の様々なメソッドを必要に応じて呼び出す
サーバは「セッション変数にユーザIDが入っているか確認し、もし未認証状態であれば例外をスローする(この場合、Flex側ではFaultイベントが発生することになる)」という処理をあらゆるメソッドの先頭で行う。

この方法で、認証処理をアプリケーション起動時の1回だけに抑えつつも、認証を通過していないセッションからの不正なメソッド呼び出しを防ぐことが出来る。・・・・ひとまずは。

しかし、この方法はいくつかの問題を抱えている。
続く。

ラベル: ,

2008-01-25

JSPからSpringを使う

普通そういうことはしないのだが、ちょっとしたトライandエラーに使うと便利な時があるかもしれない。

<%@page contentType="text/html"%>
<%
org.springframework.web.context.WebApplicationContext wac =
org.springframework.web.context.support.
WebApplicationContextUtils.getRequiredWebApplicationContext(
pageContext.getServletContext());
com.acme.MyBean myBean = (com.acme.MyBean)wac.getBean("myBean");
%>
<html>
<body>
ここで myBeanを使っていろいろやる
</body>
</html>

ラベル:

2008-01-24

BlazeDS経由で呼び出されるサービスメソッドの例外ログをAOPで出力する

以前のエントリでこんなことを言った。
BlazeDSを介したメソッド呼び出しでは、メソッドが例外をスローしても BlazeDSがそれを捕まえて Flexアプリケーションに Faultオブジェクトとして(アプリケーションサーバから見れば)正常に返してしまうため、アプリケーションサーバ側では例外ログが出力されない。

アプリケーションサーバ側のログファイル(開発作業中の場合はコンソール)にスタックトレースが出力されないのではデバッグが困難なので、Springの AOPを使って例外をサーバ側でも出力させておくことにする。

必要なjarファイルを追加


Springの配布に同梱されている。
asm-2.2.3.jar
asm-commons-2.2.3.jar
asm-util-2.2.3.jar
aspectjrt.jar
aspectjweaver.jar

applicationContext.xmlにネームスペースを追加(まだ無ければ)


xmlns:aop="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"

自動weavingの設定を追加


aopネームスペースの aspectj-autoproxy要素を入れておくと、アスペクトのWeaving周りで面倒な設定をしなくても自動で色々やってくれる模様。詳しく知らないけどとにかく面倒だったものが簡単に。

<aop:aspectj-autoproxy/>

ワンポイントアドバイス:Spring 1.2の頃はこういう自動化がまだ無く、手で XMLをいっぱい書かなければならなかった。Spring AOPのことについてググる時は、ヒットした記事が Spring 1.2時代のものでないことを都度確認しないとひどい遠回りをするはめになるよ。

アスペクトの作成


例外発生時に実行される処理を担当するアスペクトを作る。こんな感じ。

package com.acme.aspect;

import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;

@Aspect // このクラスはアスペクトだよ。という印
public class ExceptionLoggingAspect {
// "com.acme.servicesパッケージ内にある全てのクラス"
// "全てのメソッド"
// "例外発生直後" を対象にこの処理が行われる
@AfterThrowing(
pointcut="execution(* com.acme.services.*.*(..))"
,throwing="ex")
public void logException(Throwable ex)
{
// 本当は commons-loggingを使うなりもっとマシな方法で
ex.printStackTrace();
}
}
こうして作ったアスペクトを applicationContext.xmlにBeanとして登録する(自動で適用されるためIDをつけて他のBeanから参照する必要は特にない)。

アスペクトの登録


<bean class="com.acme.aspect.ExceptionLoggingAspect"/>

これで FlexのRemoteObjectには Faultを返しつつサーバ側のコンソールにも例外が出力されるようになった。

ラベル:

2008-01-10

BlazeDSと Springを統合する

以前のエントリで予告したとおり、Springと一緒に BlazeDSを使う方法を書きとどめておく。Springを使って組み立てられた既存の Webアプリケーションを Flexで RIA対応化へ導くのであれば、この組み合わせは避けて通れないはずだ。

ここではSpringが既に Webアプリケーションに組み込まれているものとして解説する。
つまり、web.xmlにContextLoaderListenerが登録されており、Bean定義ファイル (デフォルトでは WEB-INF/applicationContext.xml) がロードされるよう構成されていること。

Springと BlazeDSを統合するとはつまり何かというと、BlazeDSのオブジェクトファクトリとして Springを利用するよう構成するということだ。デフォルトで BlazeDSのオブジェクトファクトリは BlazeDS自体の内蔵している簡易な物だが、これを Springに置き換えれば DIや AOPの強力なサポートが得られるわけだ。

BlazeDSとSpringを接続するアダプタとなるモジュールは今のところ下記からダウンロード出来る。
http://www.igenko.org/archiva/repository/igenko/com/adobe/flex/blazeds-spring/
ここから適当に一番新しそうな blazeds-spring-*.jar を持ってきて WEB-INF/lib に入れるといいだろう。

WEB-INF/flex/services-config.xml に、オブジェクトファクトリとして Springを使えるように factory要素を登録する( services-config要素下 )

<factories>
<factory id="spring"
class=
"flex.messaging.service.SpringFactory"/>
</factories>
続いてサービス定義の中、destination要素のプロパティに注目。

<properties>
<source>com.acme.MyService</source>
</properties>
のように、クラス名を直接指定している部分を

<properties>
<factory>spring</factory>
<source>myService</source>
</properties>
のように、Springの Bean IDを使ってオブジェクトを取得するように書き換える。この際、Springの WEB-INF/applicationContext.xmlには下記のような Bean定義が必要となる(むろん実際にはこの Beanに対して property 要素で依存コンポーネントをあれこれ注入する形になるだろう)。
<bean id="myService" class="com.acme.MyService"/>
こうすることで、Springによってインスタンス生成と依存性注入が行われ完成したオブジェクトを BlazeDSを通じて Flexアプリケーションに向けて公開することが出来る。

日付範囲から勤務時間表を出力する Flexアプリケーションの例。


Java側
/**
* Java側メソッド - 指定した日付範囲の勤務時間を抽出する
* from: 開始日付
* to: 終了日付
* 前提条件: JDBCデータソース dataSource が セットされている
*/

public List getWorks(java.sql.Date from, java.sql.Date to)
{
/*
JdbcTemplate#queryForList でSQLを実行し、結果(MapのList)を
そのまま Flex側に返す(ObjectのArrayCollectionへとうまい具合に変換される)。
*/

return new
JdbcTemplate(dataSource).queryForList(
"select 日付,勤務時間 from 勤務表 where 日付>=? and 日付<=?",
new Object[] {from, to});
}
Flex側

destinationやendpointをどう設定するかは以前のエントリを参照
<mx:RemoteObject id="srv" destination="amftest" 
endpoint="http://localhost:8080/amftest/messagebroker/amf"/>
<mx:HBox>
<mx:DateField id="from"/><mx:Label text="から"/>
<mx:DateField id="_to"/><mx:Label text="まで"/>
<mx:Button label="検索実行"
click="srv.getWorks(from.selectedDate, _to.selectedDate)"/>
</mx:HBox>

<mx:DataGrid dataProvider="{srv.getWorks.lastResult}">
<mx:columns>
<!-- ActionScriptでは日本語名のプロパティも通るようだ
勧めはしないが -->
<mx:DataGridColumn dataField="日付" width="300"/>
<mx:DataGridColumn dataField="勤務時間" width="60"/>
</mx:columns>
</mx:DataGrid>
実行画面


テストは Spring 2.5.1と Flex Builder3 Beta3で行った。
日付のフォーマットをきちんとしたければ DataGridColumn.labelFunctionと mx:dateFormatterを使えば良い。

メモ


BlazeDSを介したメソッド呼び出しでは、メソッドが例外をスローしても BlazeDSがそれを捕まえて Flexアプリケーションに Faultオブジェクトとして(アプリケーションサーバから見れば)正常に返してしまうため、アプリケーションサーバ側では例外ログが出力されない。このため、例外の追跡を行えるような何らかの仕掛けをしておくのが望ましいと思う。
(例えば例外のロギングといった地味なAOPの用法はここで大きく役立つと思われる)

ラベル: ,

2007-12-20

Springのトランザクションマネージャを明示的に使う

Springの入門記事は大抵AOPでトランザクション境界を定義するやりかたを紹介している。それはそれでKeep POJOの観点から理想的なのだが、初心者は設定間違いなどにより実際にはトランザクション制御が効いていないようなケースでもそれに気付かず過ごしてしまうかもしれない。AOPを使わず明示的にトランザクション境界をコーディングしてみて、その後にAOPでの解決法へ移行する方がそういったミスを見過ごすことがないという意味では入門に適していると思う。
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);

try {
// ここでトランザクションの中身を実行
transactionManager.commit(status);
}
finally {
if (!status.isCompleted()) transactionManager.rollback();
}
transactionManagerは PlatFormTransactionManager(実体は DataSourceTransactionManagerなど)で、これは Spring Framework独自のトランザクションマネージャである(JEEのトランザクション管理機構を使いたい場合はこのトランザクションマネージャでラップして使う)ため、これを明示的に使用したコードは Springに依存することになる。

注意しておかなくてはならないのは、データベースアクセスを伴う処理を Springのトランザクションマネージャによるトランザクション制御の影響下に収めるためには守らなければならない約束事があるということだ。それは、少なくとも下記のうちいずれかの方法でデータベースへアクセスすることである。

・dataSource.getConnection() のかわりに、 DataSourceUtil.getConnection(dataSource) でJDBCコネクションを得る。コードはSpring依存になる。
・全てのSQLを JdbcTemplate クラス経由で実行する。コードは Spring依存になる。
・DataSourceオブジェクトを TransactionAwareDataSourceProxy で包んでおき、それに対して getConnection() が行われるようにする。

後々 Keep POJOでAOPなトランザクション制御へ移行する予定なら最後の方法を使うと良い。

ラベル:

2007-12-07

Spring 2.5 MVC コントローラ アノテーション

あまり話題になることのない Spring MVCだが、私は「DIが可能でちょっと便利なサーブレット」として使うことがある。Spring 2.5では、アノテーションでコントローラクラスの使われ方を設定できるようになり楽になったので情報をメモしておく。

参考: 13.12. Annotation-based controller configuration

web.xmlの設定

Spring 2.0の時と同じで、DispatcherServletをservlet登録し、URIにマッピングする。ただし Spring 2.5の配布では、DispatcherServletは spring.jarに入っておらず spring-webmvc.jarに収録されているため、spring.jarの他に spring-webmvc.jarもプロジェクトに加えること。

<?xml version="1.0"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://java.sun.com/xml/ns/j2ee web-app_2_4.xsd"
version=
"2.4">
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>hello/*</url-pattern>
</servlet-mapping>
</web-app>
WEB-INF直下には、servlet-name要素で指定したサーブレット名の後ろに-servlet.xml を付けた名前で Springのbean定義ファイルを配置する。これも Spring 2.0と同様。

アノテーションで設定をする場合、コントローラは bean要素でただロードさせるだけで良い。

<bean class="com.acme.hello.web.HelloController" />

また、「このパッケージ以下にあるコントローラクラスを全部」のような指定もできる。
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
...
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
..."
:
<context:component-scan base-package="com.acme.hello.web" />
:
context:component-scan要素は base-packageで指定されたパッケージ以下に存在する @ComponentアノテーションのついたクラスをsingletonなBeanとして自動的に登録してくれる。(後述する @Controllerは @Componentの特殊化)

コントローラクラスには @Controllerアノテーション(org.springframework.stereotype.Controller)をつけてやる。
@Controller
public class HelloController {
:
}
URIに処理をマッピングするには、@RequestMappingアノテーションを使う。
1種類のURIに1個のコントローラを割り当てる場合はコントローラクラスに、1個のコントローラで様々なURIの処理を行うにはコントローラクラスのpublicメソッドにそれぞれ @ResultMappingアノテーションでURIを指定する。ここでは後者を例にとる。

@RequestMapping("/hello.do")
public String hello() throws Exception
{
return "hello.jsp"; // hello.jspへ処理を渡す
}
リクエスト毎にインスタンスの生成される Struts2(aka WebWork)のActionクラスと違い、Spring MVCのコントローラクラスはシングルトンなので、リクエストパラメータやビュー層へ渡す値についてはメソッドの引数を使う。メソッドの引数とリクエストパラメータの対応付けもアノテーションで指定できる。また、引数に Mapがあるとそこにはビューに渡す値のマップとして使える Mapのインスタンスが渡される。

public String hello(@RequestParam("name") String name, Map results)
{
results.put("message", "Hello, " + name + "!");
return "hello.jsp";
}
他にも
HttpServletRequest(IN)
HttpServletResponse(OUT)
org.springframework.web.context.request.WebRequest (IN)
java.util.Locale (IN)
java.io.InputStream(java.io.Reader) (IN)
java.io.OutputStream(java.io.Writer) (OUT)
org.springframework.ui.ModelMap (OUT)
などをメソッドの引数に含めることが出来る。

HTTPリクエストに対しバイトストリームをレスポンスしたい場合 OutputStreamを引数に含めておけばそれを使うことが出来るが、content-typeや content-lengthをセットできないためいくつかのケースでは実用的ではないかもしれない。そのような場合、代わりに HttpServletResponseを使うことになると思う。

■おまけ■

Spring 2.5では、プライベートフィールドに @Resourceアノテーションをつけると setterメソッドが無くても beanを autowireしてくれる。(Resourceアノテーションは JEEのものなので、Java SEベースの環境であれば Springの配布に含まれる lib/j2ee/common-annotations.jar をクラスパスに追加すること)

@Resource private DataSource dataSource;
@Resource private PlatformTransactionManager transactionManager;

setterメソッド(フィールドに値を代入するコード)が無いためコンパイラが警告を出すが・・・。

ラベル:

2007-09-01

すぐに始める Spring + OpenJPA 1.0.0

クラスパスに追加したもの

commons-collections-3.2.jar
commons-lang-2.2.jar
commons-logging-1.0.4.jar
openjpa-1.0.0.jar
geronimo-jpa_3.0_spec-1.0.jar
geronimo-jta_1.1_spec-1.1.jar
serp-1.13.1.jar

他にも要るかもしれないが、なんにせよ OpenJPAの配布物に含まれている。
あれ、0.9.7の時はgeronimo-j2ee-connectorも必要だったような気が・・・まあいいか。

Java VMに与えたオプション

-javaagent:path/to/openjpa-1.0.0.jar

Eclipseの場合は Run.. から Arguments → VM arguments: に書く。

このオプションを与えることによって、永続クラスをOpenJPA用にエンハンスする処理が自動で行われる。
(Hibernate-JPAの時は何もしなくても勝手にやってくれていた気がする)
他にも、classファイルを静的にエンハンスする方法(ビルドタイムエンハンス)などがある。

Springの Bean定義ファイルに追加したもの

(dataSourceの定義は既にあるものとする)

<bean id="entityManagerFactory"
class=
"org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter"/>
</property>
</bean>

(0.9.7の時にはつけていたかもしれない)loadTimeWeaverプロパティは、1.0.0ではつけないこと。さもないとランタイムエンハンスに失敗する?

クラスパス以下に作成したファイル

META-INF/persistence.xml

<?xml version="1.0"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="適当な名前" transaction-type="RESOURCE_LOCAL"/>
</persistence>

JPAの入門記事などでは、persistence.xml内にデータソースなど様々な設定を書き込むように解説されているのが普通だが、Spring LocalContainerEntityManagerFactoryBeanを使って EntityManagerFactoryを定義する場合にはそうする必要がない。
但し、永続クラスのリストをpersistence-unit要素内に持たないと OpenJPAのランタイムエンハンサが全てのクラスをエンハンス対象かどうか調べなければならないためクラスのロードがやたらと遅くなるので、上記の例では省略しているものの必要な永続クラスの数分だけ class 要素を並べるのだけはやっておいた方がよい。

JPAを使用するコードの例

EntityManager em = entityManagerFactory.createEntityManager();
// 主キー指定でDBから行をフェッチし、MyEntityのインスタンスとして得る
MyEntity e = em.find(MyEntity.class, 123);
// ...など、いろいろ処理
em.close(); // 使い終わったら片付ける
MyEntityはDBのテーブルに対応づけられるべくアノテーション漬けにされた永続クラス。

その他

永続クラスのキーになるクラスは、下記の条件を満たしていないとOpenJPAでは使えないことに注意。

equals()が実装されている
hashCode()が実装されている

永続クラス本体側では、キーとなるフィールドに@Idアノテーションが付いていること。

RDBMSの種類は設定で教えてあげなくてもドライバの種類を見て勝手に判断するようだ(少なくとも PostgreSQLではそうだった)

ラベル: ,

2007-06-11

Springで JPAの共有EntityManagerを利用する

EntityManagerFactoryにさえアクセスできるようセットアップされていれば、いちおう JPAが利用出来ることになるのだが、毎度ファクトリから EntityManagerを取り出して使い終わったらクローズするといった処理の記述を省略できればさらに良い。

Java EEでは @PersistenceContext アノテーション (javax.persistenceパッケージにある) を用いることによって、コンテナから Beanに対して自動的に EntityManager (Factoryじゃなく、さっそく使える方)をインジェクトしてもらえるらしい(ちゃんと調べていないが多分そう)。これならファクトリから EntityManagerのインスタンスをもらったり、使った後に閉じたりする処理を記述しなくて済む分プログラムが少し短くできる(どれほどのもんよ?という疑問はあるが)。

実はこの機能、SpringでもSharedEntityManagerBeanとPersistenceAnnotationBeanPostProcessorの組み合わせで実現できるらしいので試してみた。

Bean定義ファイルに追加する内容

<bean id="entityManager"
class=
"org.springframework.orm.jpa.support.SharedEntityManagerBean">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

EntityManagerをインジェクトされる側

private EntityManager entityManager;
@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
インジェクトされる側に必要なのはアノテーションのみで、Bean定義ファイルに property定義をする必要はない模様。

以上で EntityManagerがインジェクトされてくるようになるのを確認できた。
それって本当にスレッドセーフなのか?という疑問が当然のごとく湧いてくるかと思うが、そのあたりは SharedEntityManagerBeanや AbstractEntityManagerFactoryBeanがうまくやっているに違いないと信じている。

とりあえず JPAのAPIを通じて進行中のトランザクションに干渉してみるのはどうだろう、と考え、entityManager.getTransaction().setRollbackOnly() を試してみたところ、あえなく拒否された(それは使えないから SpringのPlatformTransactionManagerを使え、といったメッセージで)。

OpenJPAでの注意事項


上記のようにしてインジェクトされてきたEntityManagerをトランザクションの外で使おうとすると OpenJPAが例外をスローする。
org.apache.openjpa.persistence.InvalidStateException: The context has been closed. The stack trace at which the context was closed is available if Runtime=TRACE logging is enabled.
従って、共有EntityManagerを使う際は宣言的もしくはプログラム的トランザクションの内側で行うこと。

ソースコードがSpring依存にならない Keep POJOな 宣言的トランザクション
ソースコードはSpring依存になるが細かい制御の可能なプログラム的トランザクション

なおOpenJPA以外のJPAプロバイダではどうなるか試していない。

ラベル: ,

2007-06-10

Spring JDBCとPostgreSQLでSqlRowSet使用時にエラー

Spring-JDBCとPostgreSQLを組み合わせているときに、JdbcTemplate#queryForRowSet()など SqlRowSetを使うファンクションで "Invalid column display size. Cannot be less than zero"なる SQLExceptionが出る場合、PostgreSQLの JDBCドライバが古いので最新版に差し替えれば直る。

ラベル: