スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Spring MVC 例外ハンドリング

今回は例外ハンドリングを実装したいと思う。

Spring MVCでは、例外ハンドリングのスコープとして、
①アプリケーション単位
②コントローラ単位
で設定できるので、それぞれで動作確認まで行ってみる。

なお、題材は以前実装したファイルアップロードを対象としている。

■ファイルアップロードに関する過去の記事
Spring MVC ファイルアップロードを実装する[入力チェック編]

◎動作検証にあたっての各バージョンは以下の通り
  • SpringFramework 3.2.8.RELEASE
  • Java 1.7
  • Tomcat 7.0


1.SystemException.java

package jp.co.sample.exception;

public class SystemException extends RuntimeException {

private static final long serialVersionUID = 1L;

public SystemException(){
super();
}

public SystemException(String message) {
super(message);
}

public SystemException(String message, Throwable cause) {
super(message, cause);
}

public SystemException(Throwable cause) {
super(cause);
}
}

「①アプリケーション単位」用の例外クラスを追加。


2.BookException .java

package jp.co.sample.exception;

public class BookException extends RuntimeException {

private static final long serialVersionUID = 1L;

public BookException(){
super();
}

public BookException(String message) {
super(message);
}

public BookException(String message, Throwable cause) {
super(message, cause);
}

public BookException(Throwable cause) {
super(cause);
}
}

「②コントローラ単位」用の例外クラスを追加。


3.exception.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<link href="<c:url value="/css/bootstrap.min.css" />" rel="stylesheet">
<link href="<c:url value="/css/bootstrap-theme.min.css" />" rel="stylesheet">
<script src="<c:url value="/js/bootstrap.min.js" />"></script>
<title>exception</title>
</head>
<body>
<div class="well">
<font color="red">Exception</font>
</div>
<br>GlobalException : ${exception.message}
<br>BookException : ${message}
</body>
</html>

例外用のJSPを用意。
GlobalExceptionが「①アプリケーション単位」で例外発生した場合のメッセージエリアで、BookExceptionが「②コントローラ単位」で例外発生した場合のメッセージエリアとして、どちらで発生したのか分かるようにしている。
ちなみに${exception.message}の「exception」は、「①アプリケーション単位」で暗黙的に使えるオブジェクトとなる。

まずは、「①アプリケーション単位」の例外ハンドリング設定を行いたいと思う。

4.mvc-config.xml(関連箇所のみ掲載)




exception
exception




「①アプリケーション単位」の例外ハンドリングは、Bean定義ファイルで設定できる。
「prop」タグで例外クラスと対応するView名(今回の場合、「excepion.jsp」)を定義する。

5.BookController.java(関連箇所のみ掲載)

package jp.co.sample.book.controller;

import jp.co.sample.exception.BookException;
import jp.co.sample.exception.SystemException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
//他のimport文省略

@Controller
@RequestMapping("/book")
@SessionAttributes("bookSearchForm")
public class BookController {

//変数定義省略

@RequestMapping(value = "/edit/update", method = RequestMethod.POST)
public String update(@Valid BookForm form, BindingResult result, Model model,
@RequestParam(value="uploadFile",required=false) MultipartFile file) {

logger.info("update start");
if (file.getSize() > uploadFileSize) {
result.rejectValue("cover", "error.fileSize",
new Object[]{uploadFileSize},"File size error");
}
if (result.hasErrors()) {
return "editBook";
}

if (!file.isEmpty()) {
byte[] fileContent = null;
InputStream is = null;
try {
is = file.getInputStream();
fileContent = IOUtils.toByteArray(is);
form.setCover(fileContent);
//動作確認用に一時的に追加
//throw new IOException("test");
} catch (IOException e) {
//「SystemException」「BookException」の一方をコメント化して動作確認
//「1.アプリケーション単位」
throw new SystemException(e);
//「2.コントローラ単位」
//throw new BookException(e);
} finally {
IOUtils.closeQuietly(is);
}
}

BookEntity entity = BookUtil.copyProperties(form);
//登録・編集の判定
if (entity.getBookId() > 0) {
bookService.editEntity(entity);
} else {
bookService.addEntity(entity);
}
return "redirect:/book/edit/" + entity.getBookId() + "/update";
}

@ExceptionHandler
public ModelAndView bookException(BookException e){
logger.info("bookException start");
ModelAndView mav = new ModelAndView("exception");
mav.addObject("message", e.getMessage() + " [bookController]");
return mav;
}
//他のメソッド省略
}

39~43行目がファイルアップロードで発生した「IOException」をラッピングして、リスローしている箇所となる。(今までは「e.printStackTrace();」としていた。)

「①アプリケーション単位」の場合、41行目「throw new SystemException(e);」とすれば、「4.mvc-config.xml」の設定により、exception.jspに遷移してくれる。

「②コントローラ単位」の場合、43行目「throw new BookException(e);」とすると、59行目「@ExceptionHandlerアノテーション」を付与したbookExceptionメソッドが実行される。引数の「BookException e」は自動でバインドされる。そして、今回のポイントは、「public String bookException(BookException e, Model model)」と定義できない点となる。(「ERROR ExceptionHandlerExceptionResolver - Failed to invoke @ExceptionHandler method: public java.lang.String jp.co.sample.book.controller.BookController.bookException(jp.co.sample.exception.BookException,org.springframework.ui.Model) java.lang.IllegalStateException: No suitable resolver for argument [1] [type=org.springframework.ui.Model]」の例外が発生。) 例外メッセージを表示しないのであれば、@ExceptionHandler(BookException.class) として、「public String bookException()」とすればよいのだが、「①アプリケーション単位」は表示できて「②コントローラ単位」ができないわけがないと思い、あれこれ試してみる。

辿り着いたのが、ModelAndViewクラスとなる。62行目で、コンストラクタの引数にView名を渡して、63行目「addObject」メソッドを使って、JSP側で例外メッセージを「message」で参照するよう設定している。(Modelの「addAttribute」メソッドと同じような使い方。)なお、例外メッセージは意図的に「①アプリケーション単位」と異なるようにしている。あとは、ModelAndViewを返せばよい。以前からModelAndViewクラスの存在は知っていたが、今までString型のView名を返していたので、特に使うことがなかった。今回、Modelが使えず困っていたので、ModelAndViewクラスの便利さを知ることができた。


6.動作確認
①アプリケーション単位
Exception_1.png

②コントローラ単位
Exception_2.png

それぞれのスコープに応じて、例外ハンドリングされていることが確認できる。

■過去のSpring関連記事
Spring関連記事 Index

スポンサーサイト

Spring MVC 検索にソート条件を追加する(JSP編)

3回に渡ったソート条件の追加も、今回が最後となる。JSPを中心に説明していきたいと思う。

また、ボリュームが多いため、記事を3回に分けて掲載している。
 ①DBアクセス編
  ⇒Spring MVC 検索にソート条件を追加する(DBアクセス編)
 ②コントローラ編
  ⇒Spring MVC 検索にソート条件を追加する(コントローラ編)
 ③JSP編
  ⇒本記事

◎動作検証にあたっての各バージョンは以下の通り
  • SpringFramework 3.2.8.RELEASE
  • Java 1.7
  • Tomcat 7.0

1.pageList.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec"%>
<link href="<c:url value="/css/bootstrap.min.css" />" rel="stylesheet">
<link href="<c:url value="/css/bootstrap-theme.min.css" />" rel="stylesheet">
<script src="<c:url value="/js/bootstrap.min.js" />"></script>
<title>pageList</title>
</head>
<body>
<spring:url value="/book/list/page" var="varBookListUrl"/>
<spring:url value="/book/edit/cover" var="varBookCoverUrl"/>
<spring:url value="/book/edit/new" var="varBookAddUrl"/>
<spring:url value="/book/edit" var="varBookEditUrl"/>
<spring:message code="bookId" var="varBookId"/>
<spring:message code="bookName" var="varBookName"/>
<spring:message code="price" var="varPrice"/>
<spring:message code="cover" var="varCover"/>
<spring:message code="button.search" var="varButtonSearch"/>
<spring:message code="link.add" var="varLinkAdd"/>
<spring:message code="link.edit" var="varLinkEdit"/>
<spring:message code="title.order" var="varTitleOrder"/>
<spring:message code="radio.asc" var="varRadioAsc"/>
<spring:message code="radio.desc" var="varRadioDesc"/>

<div class="container-fluid">
<form:form
action="${pageContext.request.contextPath}/book/list/search"
method="get" modelAttribute="bookSearchForm">
<div class="well">
<div class="row">
<form:label path="bookName" class="col-sm-3 text-right">${varBookName}</form:label>
<form:input path="bookName" class="col-sm-6"/>
<div class="col-sm-3"></div>
</div>
<div class="row">
<form:label path="sortId" class="col-sm-3 text-right">${varTitleOrder}</form:label>
<div class="col-sm-5">
<form:select path="sortId" name="select1" items="${sortList}"
itemLabel="label" itemValue="data" multiple="false"/>
<form:radiobutton path="sortDirection" name="radio1" label="${varRadioAsc}" value="asc" checked="true"/>
<form:radiobutton path="sortDirection" name="radio1" label="${varRadioDesc}" value="desc"/>
</div>
<div class="col-sm-4">
<input type="submit" value="${varButtonSearch}" class="btn btn-info btn-sm"/>
</div>
</div>
</div>
<form:errors path="*" cssStyle="color:red"/>
</form:form>
<hr/>
<div class="row">
<div class="col-sm-9"></div>
<div class="col-sm-3 text-right">
<sec:authorize ifAllGranted="ROLE_ADMIN,ROLE_USER">
<a href="${varBookAddUrl}">${varLinkAdd}</a>
</sec:authorize>
</div>
</div>
<table class="table table-striped table-bordered">
<tr>
<th>${varBookId}</th>
<th>${varCover}</th>
<th>${varBookName}</th>
<th>${varPrice}</th>
<th></th>
</tr>
<c:forEach items="${bookPage.books}" var="book">
<tr>
<td><c:out value="${book.bookId}"/></td>
<c:set value="${fn:length(book.cover)}" var="varCoverLen"/>
<c:if test="${varCoverLen > 0}">
<td><img src="${varBookCoverUrl}/${book.bookId}"></td>
</c:if>
<c:if test="${varCoverLen == 0}">
<td><img src="<c:url value="/img/blank.jpg" />"/></td>
</c:if>
<td><c:out value="${book.bookName}"/></td>
<td><c:out value="${book.price}"/></td>
<td>
<sec:authorize ifAllGranted="ROLE_ADMIN,ROLE_USER">
<a href="${varBookEditUrl}/${book.bookId}">${varLinkEdit}</a>
</sec:authorize>
</td>
</tr>
</c:forEach>
</table>
<div class="row">
<div class="col-sm-3">
<c:choose>
<c:when test="${not empty bookPage}">
<c:set value="Page ${bookPage.currentPage} of ${bookPage.totalPages}" var="varPage"/>
</c:when>
<c:otherwise>
<c:set value="Page 0 of 0" var="varPage"/>
</c:otherwise>
</c:choose>
</div>
<div class="col-sm-6" align="center">
<c:choose>
<c:when test="${bookPage.hasPreviousPage && not bookPage.hasNextPage}">
<a href="${varBookListUrl}/1"><img src="<c:url value="/img/first.gif" />"/></a> |
<a href="${varBookListUrl}/${bookPage.currentPage -1}"><img src="<c:url value="/img/prev.gif" />"/></a> |
${varPage} |
<img src="<c:url value="/img/off-next.gif" />"/> |
<img src="<c:url value="/img/off-last.gif" />"/>
</c:when>
<c:when test="${not bookPage.hasPreviousPage && bookPage.hasNextPage}">
<img src="<c:url value="/img/off-first.gif" />"/> |
<img src="<c:url value="/img/off-prev.gif" />"/> |
${varPage} |
<a href="${varBookListUrl}/${bookPage.currentPage +1}"><img src="<c:url value="/img/next.gif" />"/></a> |
<a href="${varBookListUrl}/${bookPage.totalPages}"><img src="<c:url value="/img/last.gif" />"/></a>
</c:when>
<c:when test="${bookPage.hasPreviousPage && bookPage.hasNextPage}">
<a href="${varBookListUrl}/1"><img src="<c:url value="/img/first.gif" />"/></a> |
<a href="${varBookListUrl}/${bookPage.currentPage -1}"><img src="<c:url value="/img/prev.gif" />"/></a> |
${varPage} |
<a href="${varBookListUrl}/${bookPage.currentPage +1}"><img src="<c:url value="/img/next.gif" />"/></a> |
<a href="${varBookListUrl}/${bookPage.totalPages}"><img src="<c:url value="/img/last.gif" />"/></a>
</c:when>
<c:otherwise>
<img src="<c:url value="/img/off-first.gif" />"/> |
<img src="<c:url value="/img/off-prev.gif" />"/> |
${varPage} |
<img src="<c:url value="/img/off-next.gif" />"/> |
<img src="<c:url value="/img/off-last.gif" />"/>
</c:otherwise>
</c:choose>
</div>
<div class="col-sm-3 text-right">
<c:choose>
<c:when test="${not empty bookPage}">
View ${bookPage.startRecords} - ${bookPage.endRecords} of ${bookPage.totalRecords}
</c:when>
<c:otherwise>
View 0 - 0 of 0
</c:otherwise>
</c:choose>
</div>
</div>
</div>
</body>
</html>

36~54行目が検索条件に関する箇所となる。

45行目「form:select」タグを使って、リストボックスを作成している。「items」属性にコントローラ側「model.addAttribute("sortList",getSortList());」の「sortList」を設定する。「itemLabel」「itemValue」属性には、それぞれListDataFormクラスのプロパティ「label」と「data」を設定している。最後の「multiple」属性には「false」を設定して、複数選択を不可としている。

47,48行目の「form:radiobutton」タグについては、「昇順」「降順」と今後も変更がないことより、リストボックスのようにコントローラ側で値を設定することはしていない。「name」属性をともに「radio1」と同じにすることでグルーピングしている。「value」属性には、それぞれソート順の「asc」「desc」を設定している。また、初期状態を昇順選択とするため、「checked」属性に「true」を設定している。ちなみに「true」ではなく、「checked」でも動作することを確認した。

76,84,85行目はXSS対策の記事では掲載しなかったが、「c:out」タグを使って、JavaScriptが実行されないようサニタイジング(無害化)している。

■XSS対策に関する過去の記事
Spring MVC XSS対策


2.動作確認
①タイトルと価格の降順で検索
Sort_2.png

レコードが3件取得(View 1 - 2 of 3)され、価格の高い順に表示されていることが確認できる。

②日本語の場合
Sort_3.png


③英語の場合
Sort_4.png

リストボックス内の項目も日本語、英語と切り替わっていることが確認できる。


■過去のSpring関連記事
Spring関連記事 Index

プロフィール

bookmount8

Author:bookmount8
システムエンジニア。サーバーサイドでjavaを扱うことが多い。最近は、ミドルやフロント周りも関心あり。

最新記事
カテゴリ
検索フォーム
最新コメント
月別アーカイブ
これまでの訪問者数
ブロとも申請フォーム

この人とブロともになる

RSSリンクの表示
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。