アノテーションによるSpring設定方法を勉強

前回までで、Hibernate EntityManagerとの連携部分はだいたいわかったので、次はSpringのBean設定方法について学習しています。最近のSpringはアノテーションによる設定が主流のようですので、それを主体に調べてみました。
今回の例は、StrutsのActionにServiceオブジェクトをDIし、ServiceオブジェクトでEntityManagerを利用する、というもの。Serviceオブジェクトの利用メソッドにはトランザクションをかけます。
まずはstruts-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
          "http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd">
<struts-config>
	<form-beans>
		<form-bean
			name="loginForm"
			type="springexample.form.LoginForm"/>
	</form-beans>

	<action-mappings>
		<action
			path="/login"
			name="loginForm"
			scope="request">
			<forward name="success" path="/WEB-INF/jsp/login.jsp"/>
		</action>
	</action-mappings>

	<controller
		processorClass="org.springframework.web.struts.DelegatingTilesRequestProcessor" />

	<message-resources parameter="messages" />

	<plug-in className="org.apache.struts.tiles.TilesPlugin">

		<!-- Path to XML definition file -->
		<set-property property="definitions-config"
			value="/WEB-INF/tiles-defs.xml" />
		<!-- Set Module-awareness to true -->
		<set-property property="moduleAware" value="true" />
	</plug-in>

	<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
		<set-property property="pathnames"
			value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml" />
	</plug-in>

	<plug-in
		className="org.springframework.web.struts.ContextLoaderPlugIn" />

</struts-config>

DelegatingTilesRequestProcessorによって連携するので、struts-configにActionクラス名は記述しません。
この/loginパスに対応するActionクラスは以下の通り

package springexample.action;

import java.util.List;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.springframework.stereotype.Controller;

import springexample.entity.MapContents;
import springexample.service.MapContentsService;

@Controller
public class LoginAction extends Action {

	@Resource
	private MapContentsService mapContentsService;

	@Override
	public ActionForward execute(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		List<MapContents> list = mapContentsService.getMapContents();
		request.setAttribute("list", list);
		return mapping.findForward("success");
	}

}

登録コンポーネントの目印として@Controllerを使用。DI対象のServiceには@Resourceを記述しています。
次にServiceオブジェクト

package springexample.service;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import springexample.entity.MapContents;

@Service
@Transactional
public class MapContentsServiceImpl implements MapContentsService {
	
	@PersistenceContext
	private EntityManager em;

	@SuppressWarnings("unchecked")
	public List<MapContents> getMapContents() {
		return (List<MapContents>) em.createQuery(
			"SELECT mc FROM MapContents mc").getResultList();
	}

}

@Serviceアノテーションが登録の目印。@Transactionalによってメソッドにトランザクションがかかります。EntityManagerは@PersistenceContextをつけることによってDIされます。
これらのアノテーションを有効にする為のSpringの定義がこちら

<?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:tx="http://www.springframework.org/schema/tx"
	xmlns:jee="http://www.springframework.org/schema/jee"
	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/jee
		http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
		http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-2.5.xsd">

	<jee:jndi-lookup id="s2TxManager"
		jndi-name="jta/TransactionManager">
		<jee:environment>
			java.naming.factory.initial=org.seasar.extension.j2ee.JndiContextFactory
		</jee:environment>
	</jee:jndi-lookup>

	<jee:jndi-lookup id="dataSource" jndi-name="jdbc/dataSource">
		<jee:environment>
			java.naming.factory.initial=org.seasar.extension.j2ee.JndiContextFactory
		</jee:environment>
	</jee:jndi-lookup>

	<bean id="transactionManager"
		class="org.springframework.transaction.jta.JtaTransactionManager">
		<constructor-arg ref="s2TxManager" />
	</bean>
	<tx:annotation-driven />

	<bean id="pum"
		class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
		<property name="defaultDataSource" ref="dataSource" />
	</bean>

	<bean id="emf"
		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="persistenceUnitManager" ref="pum" />
	</bean>

	<bean
		class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

	<context:annotation-config/>
	<context:component-scan base-package="springexample.action" use-default-filters="false"
		name-generator="springexample.factory.support.ActionBeanNameGenerator"
		scope-resolver="springexample.annotation.PrototypeScopeMetadataResolver">
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	</context:component-scan>
	
	<context:component-scan base-package="springexample.service" use-default-filters="false">
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
	</context:component-scan>
	
	<!-- 
	<bean name="/login" class="springexample.action.LoginAction"
		scope="prototype" />
	 -->
</beans>

によって@Transactionalアノテーションが有効になります。
JPAの設定は前回までの内容で済んでいるので、PersistenceContextアノテーションも有効です。
を設定することによって、@Resourceアノテーションが有効になります。
フィールド名をBean名として、合致する名前のBeanを自動登録します。
context:component-scanタグによって、指定したパッケージ配下のクラスを検索し、条件に合致するClassを自動的に登録します。ここではアノテーションをキーとして登録しています。@Controllerを指定したクラスはPrototypeで登録されるように設定しています。
今回のLoginActionクラスのbean名は"/login"となるように自動設定したかったので、簡単なBeanNameGeneratorを自作して対応しました。
以上の設定によって、アノテーション主体によるDI+AOP定義が可能になりました。トランザクションの設定が自動的に@Serviceアノテーションをつけたクラスにかかるようになったら、より便利かも
RailsPHP系のフレームワークはアプリのディレクトリ階層構造がフラットで解りやすいので、CoCによる自動定義がかなり有効です。しかしJavaの場合はパッケージによる深い階層構造が存在するので、CoCの定義が複雑になってしまって、返ってよくわからなくなってしまう危険があります。Javaの場合、まずはアノテーションを対象クラスに目印としてつけておき、細かい設定についてはデフォルト値の指定によって簡略化を目指すようなやり方がより合っている気がします。JPAのEntityの定義は正にそうですね。EJB3のSessionBeanの定義も同じ方向性です。Springのアノテーションによる設定機能も、JPAEJB3と極めて近い考え方なので、親和性は高いと思います。拡張性もあるので、しっかり定義さえしてあげれば、かなり快適な開発環境を整備できるのではないでしょうか