Archive for 11月 25th, 2006

WicketとSpringの連携法

さて、前回のエントリで予告した通り、今回はWicketとSpringとHibernateの連携について書いていきます。

ただし、SpringとHibernate(というかJPA)の連携に関してはWicket独自の部分っていうのはないので、このエントリではWicketとSpringの連携法について説明します。SpringとHibernate(JPA)の連携に関しては他のサイトの記事を参照してください。
また、DBを使わないWicketアプリケーションの作成法についても、初歩的なこと(ここで解説している程度のこと)を理解していることを前提としています。

まず、WicketとSpringの連携における問題点についてざっと説明をし、その解決策を御紹介します。

WicketとSpringの連携における問題点

WicketとSpringの連携においてはいくつかの問題があります。それはWicketのもつ以下の二つの特性が原因としてあげられます。

  1. Wicketは管理されていないフレームワーク(unmanaged framework)である
  2. Wicketのコンポーネントとモデルはシリアライズされることがある

それぞれについて簡単に説明します。

1.Wicketは管理されていないフレームワークである

Wicketは、コンポーネントのライフサイクルを管理しておりません。つまり、ページやコンポーネントはコード中のどこでも単純にnewするだけで作ることができます。なので、コンポーネントの作成時に依存性を注入することが難しいのです。解決法としては依存性を注入するsingleton factoryを用意し、コンポーネントを作成するときにはそれを使って依存性を注入するという方法があります。が、この方法だと、コンポーネントを作成するときには必ずデフォルトコンストラクタによって作られ、その後必要な変数をセットするという形になるため、記述がめんどくさくなってしまいます。

2.Wicketのコンポーネントとモデルはシリアライズされることがある

Wicketは、コンポーネントツリーをセッション中に保存します。クラスタ構成環境では、セッション情報は他のクラスタに複製される必要があります。これはあるクラスタノードでオブジェクトをシリアライズし、他のクラスタノードでデシリアライズされることで実現されます。この際、依存性までシリアライズされるとは期待できないことが、依存性の注入における問題になってしまいます。

解決策

これらの問題を回避し、WicketとSpringを連携させる方法は以下の3種類あります。

  1. Application Object Approach
  2. Proxy-based Approach
  3. Annotation-based Approach

それぞれについて簡単に説明します。

1.Application Object Approach

WicketアプリケーションはApplicationクラスのサブクラスであるグローバルapplicationオブジェクトをただ一つ持っています。このグローバルapplicationオブジェクトは、ただ一度だけ作られ、(ユーザ固有情報を保持していない限り)シリアライズされることはありません。この特性によって、applicationオブジェクトは残りのアプリケーションに対するサービスロケータとして働かせることができます。applicationオブジェクトを作る際には、自分好みのfactoryクラスを用意することができます。wicket-contrib-springプロジェクトではそうしたfactoryクラス(SpringWebApplicationFactory)を提供しております。このクラスは、applicationオブジェクトのインスタンスを作成するかわりに、Springのアプリケーションコンテキストから持ってきます。Wicketはthreadlocal変数にapplicationオブジェクトのインスタンスを保持し、コンポーネント中からそのインスタンスを得るためのメソッドを提供するので、Wicketコンポーネント中の依存性を受け取ることが簡単にできるのです。
実際のコンフィグファイルとコードは以下のようになります。
web.xml:

...
<servlet>
    <servlet-name>wicket</servlet-name>
    <servlet-class>wicket.protocol.http.WicketServlet</servlet-class>

    <init-param>
        <param-name>applicationFactoryClassName</param-name>
        <param-value>wicket.spring.SpringWebApplicationFactory</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
...
<!-- 以下はSpringWebApplicationFactoryが利用するSpringアプリケーションコンテキストの設定例-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
...

applicationContext.xml:

...
<!-- setup wicket application -->
<bean id="wicketApplication" class="project.MyApplication">
    <property name="contactDao" ref="contactDao"/>
</bean>
...

code:

class MyApplication extends WebApplication {
   private ContactDao dao;
   public void setContactDao(ContactDao dao) { this.dao=dao; }
   public ContactDao getContactDao() { return dao; }
}

class BasePage extends WebPage {
    ContactDao getContactDao() {
        return ((MyApplication)getApplication()).getContactDao();
}

class EditContact extends BasePage {
    public EditContact(long id) {
        Form form=new Form("form",...) {
           public void onSubmit() {
               Contact contact=getContact();
               ...
               getContactDao().save(contact); // < ================
               ...
            }
        }
    }
}

良い点:

  1. シンプルである
  2. コンポーネントに依存性を保持しないため、シリアライズ問題を避けることができる

悪い点:

  1. アプリケーションが多くの依存性を保持するようになると、applicationクラスはクラスタ構成しするほうが良い
  2. 偶然にも依存性の参照を保持してしまうことが起こりやすいので、それゆえシリアライズ問題が発生するかもしれない

2.Proxy-based Approach

シリアライズ/デシリアライズを安全に行える、依存性のためのダイナミックプロキシを作成することもできます。プロキシは必要に応じて依存性を捜し出すための必要な情報を含める必要があり、またセッションのサイズに影響を与えすぎない程度に小さい必要があります。このプロキシは、以後LazyInitProxyと表現します。この手法は、シリアライズ問題を解決しますが、依存性注入問題は解決しません。のちほど依存性注入問題をどうするかを示します。wicket-contrib-springプロジェクトは、必要なプロキシを作成するためのツールを提供しています。
LazyInitProxyはその他のプロキシと同様とてもシンプルです。invocation handlerは以下のようになります。

// this is only an example implementation
class LazyInitProxy implements InvocationHandler {

    // this is the cache for the dependency we are going to lookup on first access
    // notice this is declared as transient so that it will not be serialized with the proxy
    private transient target;

    public Object invocationHandler(Object proxy, Method method, Object[] args) {
        if (target==null) {
            target=lookupTarget();
        }
        return method.invoke(target, args);
    }
}

このプロキシが持つ必要があるただ一つの情報は、lookupTarget()メソッドです。このために、wicket-contrib-springプロジェクトはIProxyTargetLocatorを提供します。このインタフェースはとてもシンプルです。

public interface IProxyTargetLocator extends Serializable
{
	Object locateProxyTarget();
}

LazyInitProxyFactoryは、プロキシターゲットロケーターとプロキシターゲットクラスからLazyInitProxyを作ることができるfactoryクラスです。もしクラスがインタフェースなら、factoryはダイナミックJDKプロキシを作成し、そうでなければ、cglibプロキシを作成します。
例:
applicationContext.xml:

<!-- setup wicket application -->
<bean id="wicketApplication" class="project.MyApplication"/>

code:

class MyApplication extends SpringWebApplication {
}

class EditContact extends WebPage {
   private ContactDao dao=LazyInitProxyFactory.createProxy(ContractDao.class,
      new IProxyTargetLocator() {
         public Object locateProxyTarget() {
            return ((MyApplication)Application.get()).getSpringContext().getBean("contactDao");
         }
      }
   }
}

ここで、applicationクラスはwicket-contrib-springプロジェクトによって提供されているSpringWebApplicationクラスを継承しております。このWebApplicationクラスのサブクラスは、Springコンテキストを取得するための簡単な方法を提供するために、SpringのApplicationContextAwareインタフェースを実装しなくてはなりません。
問題は、その冗長な性質です。各々の依存性について、プロキシとオブジェクトロケーターをを作成しなければなりません。簡単にするために、wicket-contrib-springプロジェクトはInjectorクラスを提供しています。このクラスはIFieldValueFactoryインタフェースの実装を使うことで、自動的にアプリケーションオブジェクトに依存性を注入することができます。Injector.inject(Object object, IFieldValueFactory fieldValueLocator)メソッドは、引数のオブジェクトの全てのフィールドの値を、IFieldValueFactory.getFieldValue(Field field, Object fieldOwner)メソッドから得られる値にアサインします。このクラスを使うことで、フィールドと関連するいくつかのメタデータを使うプロキシを作ることができるIFieldValueFactoryを実装することができます。wicket-contrib-spring-jdk5プロジェクトが提供するAnnotProxyFieldValueFactoryクラスではSpringBeans JDK5 アノテーションを使って実装しています。
例:

class EditContact extends WebPage {
   @SpringBean
   private ContactDao dao;

   @SpringBean(name="userDao")
   private UserDao userDao;

   protected EditContact(long userId) {
       ...
   }

   public static EditContact create(long userId) {
       EditContact c=new EditContact(userId);
       Injector.inject(c, new AnnotProxyFieldValueFactory(Application.get()));
   }
...

上例では、ページを作成しその依存性を注入するfactoryメソッドを作っています。これは実際のIoCなしで、IoCの利点をほとんど持っています。

3.Annotation-based Approach

アノテーションで表現された依存性は、生成時に自動的に注入することができます。そのためには、SpringComponentInjectorをインストールする必要があります。
例:

class MyAppliaction extends WebApplication {
    public void init() {
        addComponentInstantiationListener(new SpringComponentInjector(this));
    }
}

class EditContact extends WebPage {
   @SpringBean
   private ContactDao dao;

   @SpringBean(name="userDao")
   private UserDao userDao;

   public EditContact(long userId) {
       ...
   }
}

この例では、ページ(もしくはWicketコンポーネントから生成されたものなら何でも)は生成時にその依存性を注入されます。[getApplication().notifyComponentInstantiationListeners(this);の呼び出しがあれば、スーパークラスのコンストラクタはComponent(final String id, final IModel model)コンストラクタに至るまでチェーンされます。]
この手法を用いるとき、例えば、private ContactDao dao=null;のように、依存性をnullやその他の値に初期化しないことを覚えておくことが重要です。注入は、サブクラスがフィールドを初期化する前に行われるため、dao=nullは作られたプロキシをnullで上書きしてしまいます。
wicket-contrib-spring-annotプロジェクトは、SpringComponentInjectorクラスを提供します。上記のようにアプリケーションの中でinjectorをインストールするだけで注入することができます。SpringComponentInjectorはまた、Wicket portlet アプリケーション中でも自動注入をサポートします。

結論

さて、もういかにも直訳っぽい文章が多くてお気付きだとは思いますが、上記の文章はこちらの翻訳です。

僕が最初にこれらの3つの解決策を見たときに、一番簡単そうに見えたのは3番目のアノテーションを使う手法です。なので、僕は3番めのアノテーションを使う手法で今後開発を行っていこうと思います。

次回は、実際にWicket-Spring-Hibernateを利用した例(たぶんDB内容をCRUDするようなもの)を紹介したいと思います。

それではまた。

次のページ »