3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

時代遅れかもしれないSpring Web Flow入門 4

Last updated at Posted at 2014-12-31

アクションについて

WebFlowでは、フロー定義中に書かれた処理(Javaオブジェクトのメソッドコール)をActionと読んでいます。基本はevaluate要素にメソッドコールを記述すれば、実行できます。実行できるJavaオブエジェクトは、Springのコンテナ管理下のオブジェクトならばなんでも可能です。
例:

アクション記述の例
<evaluate expression="sampleAction.execute(sampleForm,messageContext)" result="requestScope.Result"/>		    
アクション記述の例
public class SampleAction {

	public Object execute(Form form,MessageContext messageContext){
		return null;
	}
	
}	    

アクションが実行可能な場所は多岐に渡ります。
詳細はドキュメントを参照してください。

実行結果はevaluate要素のresult属性で、各種のスコープに保存できます。

ActionはPOJOでも可能ですが、フロー定義上で使いやすいベースクラスであるAbstractActionMultiAction,また、戻り値としてEventクラスが提供されています。

Actionのレイヤーについて

参考1
やはり達人
基本は、フロー上のActionはアプリケーションレイヤだと思います。メッセージをはじめ、Viewやフレームワークに依存せざるを得ないからです。

ですから、Domain層のサービスをフロー上で直接呼びだすことはせず、上記のActionのサブクラスで統一してしまうのも手だと思います。
※Eventを戻り値とすると、少し面倒な部分もあるのですが、IFを統一してしまった方が、後々メンテナンス性も良くなると思います。

今回は、Actionクラスのexecuteメソッドは引数が全てのモデルにアクセスできるものですので、独自の基底クラスを作ってみます。

基底クラス

flowapp/common/action/BaseActionInterface.java
package flowapp.common.action;

import org.springframework.binding.message.MessageContext;
import org.springframework.webflow.execution.Event;

public interface BaseActionInterface<P> {
	public Event execute(P param,MessageContext messageContext);
}
flowapp/common/action/BaseAction.java
package flowapp.common.action;

import org.springframework.webflow.action.EventFactorySupport;
import org.springframework.webflow.execution.Event;

public abstract class BaseAction<P>  implements
		BaseActionInterface<P> {
	private EventFactorySupport eventFactorySupport = new EventFactorySupport();

	protected Event successO() {
		return eventFactorySupport.success(this);
	}
	protected Event success(Object result) {
		return eventFactorySupport.success(this, result);
	}
	protected Event error() {
		return eventFactorySupport.error(this);
	}
	protected Event error(Exception e) {
		return eventFactorySupport.error(this, e);
	}
}

検索アクションの定義

内部実装はダミーとします。

エンティティ

flowapp/entity/Book.java
package flowapp.entity;

import java.io.Serializable;

public class Book implements Serializable{

	private static final long serialVersionUID = 7525223989361280844L;
	private String isbn;
	private String title;
	
	public Book(String isbn, String title) {
		super();
		this.isbn = isbn;
		this.title = title;
	}
	
	public String getIsbn() {
		return isbn;
	}
	public String getTitle() {
		return title;
	}
	
}

フォーム

flowapp/search/form/SearchForm
package flowapp.search.form;

import java.io.Serializable;

public class SearchForm implements Serializable{
	

	private static final long serialVersionUID = 8170913021455253413L;
	
	private String isbn;
	private String title;
	private String detailIsbn;
	
	public String getIsbn() {
		return isbn;
	}
	public void setIsbn(String isbn) {
		this.isbn = isbn;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getDetailIsbn() {
		return detailIsbn;
	}
	public void setDetailIsbn(String detailIsbn) {
		this.detailIsbn = detailIsbn;
	}
	
	
}

検索アクション

flowapp/search/action/SearchAction.java
package flowapp.search.action;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.stereotype.Component;
import org.springframework.webflow.execution.Event;

import com.google.common.base.Strings;

import flowapp.common.action.BaseAction;
import flowapp.entity.Book;
import flowapp.search.form.SearchForm;

@Component
public class SearchAction extends BaseAction<SearchForm> {

	private List<Book> books = Arrays.asList(new Book("1234567890", "普通の本"),
			new Book("1234567891", "難しい本"));

	@Override
	public Event execute(SearchForm form, MessageContext messageContext) {

		String fIsbn = Strings.nullToEmpty(form.getIsbn());
		String ftitl = Strings.nullToEmpty(form.getTitle());

		List<Book> result = books
				.stream()
				.filter(b -> b.getIsbn().startsWith(fIsbn)
						|| b.getTitle().contains(ftitl))
				.collect(Collectors.toList());

		if (result.isEmpty()) {
			messageContext.addMessage(new MessageBuilder().defaultText(
					"検索結果が0件です。").build());
		}
		return success(result);
	}

}

テスト

SearchActionSpec.groovy
package flowapp.search.action;
class SearchActionSpec extends Specification {
	@Unroll
	def "正常系"(){
		setup:
		MessageContext msgs = Mock();
		SearchAction sut = new SearchAction()
		
		when:
		sut.books =[new Book("1234567890", "普通の本"),new Book("1234567891", "難しい本")]
		def form = new SearchForm(isbn:p_isbn,title:p_title)
		Event result=sut.execute(form, msgs)
		
		then:
		msgCount * msgs.addMessage( _ )
		with(result) {
			id == "success"
			attributes.result.size == resultSize
		}
		
		where:
		p_isbn      |p_title|resultSize|msgCount
		"1234567890"|"xxx"  |1         |0
		"123456789" |"xxx"  |2         |0
		"x23456789" |"本"  |2         |0
		"x23456789" |"普通"  |1         |0
		"1234567890"|"難しい"  |2        |0
		"0"         |"xx"  |0        |1
		""          |""  |2        |0
		"x"          |""  |2        |0
		""          |"x"  |2        |0
	}
}

フロー定義

変更点

  • フォームの初期化(先頭のvar要素)
  • フォームバインディングの追加。(view-stateのmodel要素)
  • アクションの呼び出しの追加(action-state)
  • 追記:詳細から戻った時に結果が表示されている(詳細押せないのに)ので、詳細画面から戻るボタン押下で、フローを終了させるように変更。
flow-search.xml
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
	
	<var name="searchForm" class="flowapp.search.form.SearchForm"/>
	<!-- 検索トップ画面 -->
	<view-state id="top" view="/search/top" model="searchForm">
		<transition on="search" to="searchAction" />
	</view-state>

	<!-- 検索トップ画面(検索結果表示状態) テンプレートは同じ -->
	<view-state id="result" view="/search/top" model="searchForm">
		<transition on="search" to="searchAction" />
		<transition on="detail" to="detail" />
	</view-state>

	<!-- 詳細画面 -->
	<view-state id="detail" view="/search/detail">
		<transition on="back" to="end" />
	</view-state>
	<!-- フローの完了全てのフロースコープがクリアされる。 -->
	<end-state id="end">
	</end-state>

	<!-- 検索処理 -->         
	<action-state id="searchAction">
		<evaluate expression="searchAction.execute(searchForm,messageContext)"/>
		<transition to="result">
		  <set name="flowScope.searchResult" value="currentEvent.attributes.result"></set>
		</transition>
	</action-state>
</flow>

最後に画面に、searchResultを参照するテーブルと、メッセージを表示するリストを追加

top.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:th="http://www.thymeleaf.org"
	xmlns:tiles="http://www.thymeleaf.org"
	xmlns:sec="http://www.thymeleaf.org" lang="jp">
<head>
<meta charset="UTF-8" />
<title>検索画面</title>
</head>
<body>
    <!-- ステートIDを表示 -->
	<span th:text="${flowRequestContext.currentState.id}"></span>
	<form action="#" th:action="${flowExecutionUrl}" method="POST">
		<ul
            th:unless="${#lists.isEmpty(flowRequestContext.messageContext.allMessages)}">
            <li th:each="msg : ${flowRequestContext.messageContext.allMessages}"
                th:text="${msg.severity}+':'+${msg.text}">Input is incorrect</li>
        </ul>
		
		<table>
			<tr>
				<td>ISBN</td>
				<td><input type="text" name="isbn" /></td>
			</tr>
			<tr>
				<td>タイトル</td>
				<td><input type="text" name="title" /></td>
			</tr>
		</table>
		<input type="submit" name="_eventId_search" value="検索" />
		<table th:unless="${#lists.isEmpty(searchResult)}">
            <tr>
                <th>ISBN</th>
                <th>タイトル</th>
            </tr>
            <tr th:each="book : ${searchResult}">
                <td th:text="${book.isbn}">Onions</td>
                <td th:text="${book.title}">Onions</td>
                <td><a href=""
                    th:href="@{${flowExecutionUrl}(_eventId='detail',detailIsbn=${book.isbn})}">詳細</a></td>
            </tr>
        </table>
	</form>
</body>
</html>

これで検索結果が表示される。

バリデーション

hibernate validation/JSR303を使える。やりかたはmodel属性に設定したクラスにvalidation ruleを設定するだけ。

SearchForm.java
package flowapp.search.form;

import java.io.Serializable;

import javax.validation.constraints.Max;

public class SearchForm implements Serializable{
	
	
	@Max(value=10)
	private String isbn;
	
	private String title;
	
	private String detailIsbn;
	

一部の値だけbindしたい場合はbind属性で設定可能。戻るボタンなど、bindやvalidationをしたくない場合はtransition要素の属性で設定可能

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?