2008年5月29日木曜日

Spring 2.5 AbstractWizardFormControllerを使ってみる(1)

AbstractWizardFormController(以下AWFC)を使ってみます。
(情報がないので苦労しました・・・)
以下のようなフローを実現します。

入り口画面 ⇒ 設定画面1 ⇒ 設定画面2 ⇒ 設定確認画面 (⇒ 最初に戻る)

まずは小手調べに超基本的な動きのみを実装します。

【始めに】
SimpleFormController(以下SFC)は、画面を"formBackingObject"で描画し、"onSubmit"で送信されたデータを処理するという言わば単一画面でのインプット/アウトプット処理に特化したコントローラです。
それに対してAWFCは複数画面に渡るインプットを内容チェックしながら溜め込んで行く、文字通りウィザード形式の画面遷移をサポートするコントローラです。
「ウィザード形式」という抽象化が秀逸ですね。言われてみればショッピングカートもまさにウィザード形式になっています。
SFCでゴリゴリとウィザード形式の画面遷移を作成するよりも以下の点で優れています。

- 個々の画面にBeanを準備する必要がありません
 ⇒ 複数画面に渡って単一のBeanを共有できます。
- 「戻る」「進む」機能が単一のAWFCで処理できます。
 ⇒ この例でいえば「設定画面1」設定画面2」「設定確認画面」の遷移を単一のコントローラで制御できます。素晴らしい。

【手順概要】
入り口画面を作成します。

入り口画面用のコントローラをSFCで実装します。Beanは作成しません。

上記のSpring サーブレットXML定義ファイルを作成します。

AWFCで参照するBeanを作成します。

AWFCを継承したコントローラを作成します。

設定画面1~設定確認画面を作成します。

上記のSpring サーブレットXML定義ファイルを作成します。

補足「include.jsp」「web.xml」

【手順詳細】
入り口画面を作成します。
<%@ include file="/WEB-INF/jsp/include.jsp" %>

<html>
<head><title>Hello :: Wizard controller test</title></head>
<body>
<h1>Hello - Spring Application</h1>
<p>Message: <c:out value="${model.message}"/></p>
<br><a href="<c:url value="page1form.htm"/>">Input values</a>
</body>
</html>

入り口画面用のコントローラをSFCで実装します。Beanは作成しません。
package springapp2.web;

import java.util.HashMap;
import java.util.Map;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class EntranceController implements Controller {

protected final Log logger = LogFactory.getLog(getClass());
@Override
public ModelAndView handleRequest(HttpServletRequest arg0,
HttpServletResponse arg1) throws Exception {
String message = "WizardForm sample";
Map<String, Object> myModel = new HashMap<String, Object>();
myModel.put("message", message);
return new ModelAndView("entrance", "model", myModel);
}

}

上記のSpring サーブレットXML定義ファイル(springapp2-servlet.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<!-- the application context definition for the springapp DispatcherServlet -->

<bean name="/entrance.htm" class="springapp2.web.EntranceController" />

<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView">
</property>
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

</beans>

AWFCで参照するBeanを作成します。(まさにNothing specialです)
package springapp2.service;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class AllValueForm {
protected final Log logger = LogFactory.getLog(getClass());
private int firstId;
private String firstValue;
private int nextId;
private String nextValue;

public String getNextValue() {
return nextValue;
}

public void setNextValue(String nextValue) {
this.nextValue = nextValue;
logger.info("nextValue set:" + nextValue);
}

public int getNextId() {
return nextId;
}

public void setNextId(int nextId) {
this.nextId = nextId;
logger.info("nextId set:" + nextId);
}

public int getFirstId() {
return firstId;
}

public void setFirstId(int firstId) {
this.firstId = firstId;
logger.info("firstId set:" + firstId);
}

public String getFirstValue() {
return firstValue;
}

public void setFirstValue(String firstValue) {
this.firstValue = firstValue;
logger.info("firstValue set:" + firstValue);
}
}

AWFCを継承したコントローラを作成します。
(本当にこれだけ!)
(5/30追記:processFinish(..)で返すのはforwardではなくredirectがよいです。forwardだとURLがpage1form画面になってしまい、リロードされると変なことになるので・・・)
package springapp2.web;

import java.util.HashMap;
import java.util.Map;

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

import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractWizardFormController;
import org.springframework.web.servlet.view.RedirectView;

import springapp2.service.AllValueForm;
import springapp2.service.InputValidator;

public class SimpleWizardController extends AbstractWizardFormController {

private static String[] commands = new String[] { "page1form", "page2form",
"confirmform" };

public SimpleWizardController() {
setPages(commands);
}

@Override
protected ModelAndView processFinish(HttpServletRequest arg0,
HttpServletResponse arg1, Object arg2, BindException arg3)
throws Exception {
String message = "Input done. (WizardForm sample)";
Map myModel = new HashMap();
myModel.put("message", message);
// return new ModelAndView("entrance", "model", myModel);
return new ModelAndView(new RedirectView("entrance.htm"), "model",
myModel);
}

@Override
protected void validatePage(Object command, Errors errors, int page,
boolean finish) {
AllValueForm form = (AllValueForm) command;
InputValidator validator = (InputValidator) getValidator();
// errors.setNestedPath("order");
switch (page) {
case 0:
validator.validateFirstPage(form, errors);
break;
case 1:
validator.validateSecondPage(form, errors);
}
}

}

設定画面1~設定確認画面を作成します。
それぞれのページのsubmitボタンにご注目ください。
<input type="submit" value="Next" name="_target1">
<input type="submit" value="Next" name="_target2">
<input type="submit" value="Finish" name="_finish">

'_targetN'のNが、SimpleWizardController.setPages(String[])した文字列配列の添え字を指します。
commands[0] ⇒ page1form
commands[1] ⇒ page2form
commands[2] ⇒ confirmform

また_finishボタンを押すとSimpleWizardController.processFinish(..)が呼ばれます。

page1form.jsp
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<html>
<head>
<title>Page1</title>
<style>
.error { color: red; }
</style>
</head>
<body>
<h1>Page 1</h1>
<form:form method="post" commandName="allValueForm">
<table width="95%" bgcolor="f8f8ff" border="0" cellspacing="0" cellpadding="5">
<tr>
<td align="right" width="20%">First id:</td>
<td width="20%">
<form:input path="firstId"/>
</td>
<td width="60%">
<!-- form:errors path="percentage" cssClass="error"/ -->
</td>
</tr>
<tr>
<td align="right" width="20%">First value:</td>
<td width="20%">
<form:input path="firstValue"/>
</td>
<td width="60%">
<!-- form:errors path="percentage" cssClass="error"/ -->
</td>
</tr>
</table>
<br>
<input type="submit" value="Next" name="_target1">
</form:form>
<a href="<c:url value="entrance.htm"/>">Home</a>
</body>
</html>

page2form.jsp
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<html>
<head>
<title>Page2</title>
<style>
.error { color: red; }
</style>
</head>
<body>
<h1>Page 2</h1>
<form:form method="post" commandName="allValueForm">
<table width="95%" bgcolor="f8f8ff" border="0" cellspacing="0" cellpadding="5">
<tr>
<td align="right" width="20%">Next id:</td>
<td width="20%">
<form:input path="nextId"/>
</td>
<td width="60%">
<!-- form:errors path="percentage" cssClass="error"/ -->
</td>
</tr>
<tr>
<td align="right" width="20%">Next value:</td>
<td width="20%">
<form:input path="nextValue"/>
</td>
<td width="60%">
<!-- form:errors path="percentage" cssClass="error"/ -->
</td>
</tr>
</table>
<br>
<input type="submit" value="Next" name="_target2">
</form:form>
<a href="<c:url value="entrance.htm"/>">Home</a>
</body>
</html>

confirmform.jsp
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<html>
<head>
<title>Confirm</title>
<style>
.error { color: red; }
</style>
</head>
<body>
<h1>Confirm</h1>
<form:form method="post" commandName="allValueForm">
<table width="95%" bgcolor="f8f8ff" border="0" cellspacing="0" cellpadding="5">
<tr>
<td align="right" width="20%">First id:</td>
<td width="20%">
<c:out value="${allValueForm.firstId}"/>
</td>
<td width="60%">
<!-- form:errors path="percentage" cssClass="error"/ -->
</td>
</tr>
<tr>
<td align="right" width="20%">First value:</td>
<td width="20%">
<c:out value="${allValueForm.firstValue}"/>
</td>
<td width="60%">
<!-- form:errors path="percentage" cssClass="error"/ -->
</td>
</tr>
<tr>
<td align="right" width="20%">Next id:</td>
<td width="20%">
<c:out value="${allValueForm.nextId}"/>
</td>
<td width="60%">
<!-- form:errors path="percentage" cssClass="error"/ -->
</td>
</tr>
<tr>
<td align="right" width="20%">Next value:</td>
<td width="20%">
<c:out value="${allValueForm.nextValue}"/>
</td>
<td width="60%">
<!-- form:errors path="percentage" cssClass="error"/ -->
</td>
</tr>
</table>
<br>
<input type="submit" value="Finish" name="_finish">
</form:form>
<a href="<c:url value="entrance.htm"/>">Home</a>
</body>
</html>

上記のspringapp2-servlet.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<!-- the application context definition for the springapp DispatcherServlet -->

<bean name="/entrance.htm" class="springapp2.web.EntranceController" />

<bean id="simpleWizardController"
class="springapp2.web.SimpleWizardController">
<property name="pages">
<list>
<value>page1form</value>
<value>page2form</value>
<value>confirmform</value>
</list>
</property>
</bean>

<bean name="/page1form.htm"
class="springapp2.web.SimpleWizardController">
<property name="sessionForm" value="true" />
<property name="commandName" value="allValueForm" />
<property name="commandClass" value="springapp2.service.AllValueForm" />
</bean>
<bean name="/page2form.htm"
class="springapp2.web.SimpleWizardController">
<property name="sessionForm" value="true" />
<property name="commandName" value="allValueForm" />
<property name="commandClass" value="springapp2.service.AllValueForm" />
</bean>
<bean name="/confirmform.htm"
class="springapp2.web.SimpleWizardController">
<property name="sessionForm" value="true" />
<property name="commandName" value="allValueForm" />
<property name="commandClass" value="springapp2.service.AllValueForm" />
</bean>

<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView">
</property>
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

</beans>

補足「include.jsp」
<%@ page session="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

補足「web.xml」
<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.4" 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
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>springapp2</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>springapp2</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>

</web-app>

以上。

【参考】
Springに付属のJpetstoreサンプルから、OrderFormController.javaのソースと設定ファイル
The Spring Framework (Alan P Sexton)

0 件のコメント: