Spring2.5とClickで開発

今のプロジェクトはこの組み合わせで進めてる。
せっかくSpringの2.5を使うんだから新機能を使いたいよねーってわけで、今採用してる方法を書いてみる。

まず、applicationContext.xml はこんな感じでやってる。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-2.5.xsd">

  <!-- ========== RESOURCE DEFINITIONS ========== -->
  <context:annotation-config/> 
  <context:property-placeholder location="classpath:jdbc.properties"/> 
  <!-- DataSource  -->
  <bean id="dataSource"
    class="org.apache.commons.dbcp.BasicDataSource"
    p:driverClassName="${jdbc.driverClassName}" p:url="${jdbc.url}"
    p:username="${jdbc.username}" p:password="${jdbc.password}"
    p:defaultAutoCommit="false" p:maxActive="3" p:maxIdle="1"
    p:initialSize="1" p:maxWait="60000" />

  <!-- Transaction manager -->
  <bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource">
      <ref local="dataSource" />
    </property>
  </bean>
  <tx:annotation-driven transaction-manager="transactionManager" />
  <!-- ========== BUSINESS OBJECT DEFINITIONS ========== -->
  <context:component-scan base-package="sample.service"
    use-default-filters="false">
    <context:include-filter type="annotation"
      expression="org.springframework.stereotype.Service" />
  </context:component-scan>
  <!-- ========== Data Access OBJECT DEFINITIONS ========== -->
  <context:component-scan base-package="sample.dao"
    use-default-filters="false">
    <context:include-filter type="annotation"
      expression="org.springframework.stereotype.Repository" />
  </context:component-scan>
  <!-- ========== PAGE OBJECT DEFINITIONS ========== -->
  <context:component-scan base-package="sample.page"
    use-default-filters="false" name-generator="sample.PageNameGenerator" 
      scope-resolver="sample.PageScopeMetadataResolver">
    <context:include-filter type="annotation"
      expression="org.springframework.stereotype.Component" />
  </context:component-scan>
</beans>

パッケージとしてはPageとDaoとServiceにわけた。
PageパッケージのPageクラスにはComponentアノテーションを、Daoパッケージの実装クラスにはRepositoryアノテーションを、Serviceパッケージの実装クラスにはServiceアノテーションを付けたものだけSpringに自動登録されるようにした。こいつらはSpringの標準アノテーション
問題はPageなんだけど、通常Springで自動登録したら、たとえばsample.HogePageだとsample.HogePage#0って名前になっちゃう。Clickは完全クラス名でApplicationContextに登録されている事が前提だから、独自のBeanNameGeneratorを作ってやった。

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;

public class PageNameGenerator implements BeanNameGenerator {
  public String generateBeanName(BeanDefinition definition,
      BeanDefinitionRegistry registry) {
    return definition.getBeanClassName();
  }
}

こいつらは通常だとSingletonなので、scope-resolverで一括でprototypeにしてやる。これはScopeMetadataResolverを独自実装したものを使ったけど、この程度のクラス、デフォルトで用意されてないのかなぁ。あんまり注意して探してないけど。

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ScopeMetadata;
import org.springframework.context.annotation.ScopeMetadataResolver;
import org.springframework.context.annotation.ScopedProxyMode;

public class PageScopeMetadataResolver implements ScopeMetadataResolver {
  public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
    ScopeMetadata scopeMetadata = new ScopeMetadata();
    scopeMetadata.setScopedProxyMode(ScopedProxyMode.NO);
    scopeMetadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE);
    return scopeMetadata;
  }
}

もちろん各Pageクラスで@Scopeを指定してやってもいいけど、忘れちゃやだからねー。
ClickとSpring2.5って、すごく相性がいいと思うけど、あんまり情報ないなー。ってゆーかClick自体の情報がないじゃーん。

追記

あ、ClassPathBeanDefinitionScanner見たら、@Service、@Repository、@Component、@Controllerの4つはもともと自動で登録されるんだ。いちいちuse-default-filtersをfalseにせんでもよかった。