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