はじめに
社内のWebアプリケーションのフレームワークをStrutsからSpringMVCに移行しました。
移行でやったことをさらっとまとめます。
##経緯
フレームワークにStrutsを採用していましたが、
StrutsはEOLを迎え、バグや脆弱性に対しての対応がされなくなりました。
社内とはいえ、そろそろ移行時期かと思い、フレームワークの検討をしました。結果、
5年後もメンテナンスが継続されてそう!
既存のコードの流用性が高そう!
ということでSpringMVCに移行することになりました。
##環境
- Struts 1.2
- Spring 4.2.3.RELEASE
- JDK 7.0
- JSP 2.2
- Apache Tomcat 7.0.55
##準備
急いては事を仕損じます。
Spring初心者の場合は、世界に向けてあいさつをするところから始めましょう。
私はEclipseにSpring Tool Suiteのプラグインを追加して色々触ってみました。
以下、SpringMVCを使ってHello Worldはできた前提で進めます。
#本番作業
大きく分けてやることは3つです。
1.JSPのタグを変更
2.画面遷移を変更
3.Validationを変更
Springは設定より規約なフレームワークです。
主にStrutsのstruts-config.xmlとvalidation.xmlに設定していた画面遷移まわりの内容を移行していきます。
##JSPのタグを変更
タグライブラリの指定
Struts
<%@ taglib prefix="html" uri="/WEB-INF/struts-html.tld" %>
<%@ taglib prefix="logic" uri="/WEB-INF/struts-logic.tld" %>```
**Spring**
``` <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>```
タグの変換表
| Struts | Spring |
|:-----------|:------------|
| bean:define|c:set|
| bean:message| spring:message|
| bean:write|c:out|
| logic:equal| c:if |
| logic:iterate|c:forEach|
| html:text | form:input |
| html:optionsCollection | form:options |
| html:radio | form:radiobutton |
| (上記以外の) html: | form: |
その他 `logic:messagesPresent`(エラーメッセージの存在チェック)は
`c:if test="${!empty エラー}"`のような感じにしました。
##画面遷移を変更
struts-config.xmlに設定していた内容をコントローラーに書き換えます。
**Struts**
```lang:struts-config.xml
<action path="/hello" type="jp.sample.action.HelloAction"
name="HelloForm" scope="request" parameter="start">
<forward name="success" path="/hello.jsp" />
</action>
package jp.sample.action;
public class HelloAction extends Action {
public ActionForward execute(ActionMapping mapping, HelloForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception{
・・・
return(mapping.findForward("success"));
}
}
Spring
package jp.sample.action;
@Controller
public class HelloAction {
@RequestMapping("/hello") // action pathの値
public String hello(Model model) {
・・・
return "hello"; // forward pathの値
}
}
##Validationを変更
validation.xmlに設定していた内容をBeanValidationに書き換えます。
javax.validationとHibernate Validatorのアノテーションを使用します。
別途ライブラリの追加が必要です。
Struts
<field property="name" depends="required">
<arg0 key="名前" resource="false">
</field>
<field property="mail" depends="required,minlength">
<arg0 key="メールアドレス" resource="false">
<arg1 key="${var:minlength}" resource="false" />
<var>
<var-name>minlength</var-name>
<var-value>5</var-value>
</var>
</field>
package jp.sample.form;
public class HelloForm {
private String name;
private String mail;
// Getter/Setter
・・・
}
Spring
package jp.sample.form;
public class HelloForm {
@NotEmpty // depends="required"のとき
private String name;
@NotEmpty // depends="required"のとき
@Length(min=5) // depends="minlength"のとき minはvar-valueの値
private String mail;
// Getter/Setter
・・・
}
主なValidationの変換表
Struts | Spring |
---|---|
depends="required" |
@NotNull または@NotEmpty
|
depends="minlength" またはdepends="maxlength"
|
@Length |
depends="invalid" |
@Pattern |
depends="intRange" |
@Max または@Min または@Range
|
depends="creditcard" |
@CreditCardNumber |
depends="email" |
@Email |
メッセージ・リソースファイルもSpringに合わせて少し変更。
# エラーメッセージ
NotEmpty={0}を入力してください。 # {0}はフィールド名
Length={0}は{2}文字以上で入力してください。# {2}はminの値(ここではmaxは省略)
# フィールドの日本語名
name=名前
mail=メールアドレス
コントローラーでValidationするように修正します。
@Controller
public class HelloAction {
@RequestMapping("/hello")
public String hello(@Valid @ModelAttribute HelloForm form,
BindingResult result, Model model) {
if (result.hasErrors()) {
// エラー時の処理
}
・・・
return "hello";
}
JSPにエラーを表示させる部分を追加します。
<form:errors path="*" element="div" />
path="*"
はすべてのエラーを表示します。
path="フィールド名"
にするとそのフィールドのエラーだけを表示できます。
と、ここまでは簡単だったのですが、ちょっと落とし穴がありました。
Strutsの場合は、validation.xmlに書いてある順にエラーを表示してくれていましたが、
Springはフィールド、エラーの種類の順番がランダムでした。
と表示されるときもあるのです。
これはイマイチだったのでエラーをソートすることにしました。
ソートをいれるとコントローラーはこのような感じです。
@Controller
public class HelloAction {
@RequestMapping("/hello")
public String hello(@Valid @ModelAttribute HelloForm form,
BindingResult result, Model model) {
if (result.hasErrors()) {
// ソート処理
List<FieldError> errors = sortErrors(result);
// エラーメッセージを格納
model.addAttribute("errors", errors);
}
・・・
return "hello";
}
private List<FieldError> sortErrors(BindingResult result) {
// エラー表示するフィールドの順番
final String FIELD_ORDER[] = {"name", "mail"};
// エラー表示するエラー種類の順番
final String ERROR_TYPE_ORDER[] = {"NotEmpty", "Length"};
List<FieldError> sortedErrors = new LinkedList<FieldError>();
for (String field : FIELD_ORDER) {
// フィールド別にエラーを取得
List<FieldError> fieldErrors = result.getFieldErrors(field); // ①
int errorSize = fieldErrors.size();
if (errorSize == 0) {
continue;
}
// エラーが1つの場合
if (errorSize == 1) {
sortedErrors.addAll(fieldErrors);
continue;
}
// エラーが複数の場合、エラー種類別にソート
for (String errorType : ERROR_TYPE_ORDER) {
for (FieldError fieldError: fieldErrors) {
// エラー種類を比較
if (errorType != null &&
errorType.equals(fieldError.getCode())) { // ②
sortedErrors.add(fieldError);
break;
}
}
}
}
return sortedErrors;
}
}
①result.getFieldError(フィールド名);
で対象のフィールドのエラーが取得できること
②fieldError.getCode();
でエラーの種類が取得できることがポイントです。
JSPのエラー表示部分も変更します。
<c:forEach items="${errors}" var="error">
<spring:message message="${error}" /><br>
</c:forEach>
#おまけ
個人的にハマったところのメモです。
-
Sending a list of objects from view to controller : limited to 256 objects
⇒Controllerに送信できるリストのサイズはデフォルトで256。 -
Binding a map of lists in Spring MVC
⇒JSPでネストしているデータを扱う時はlist[${index}].field
のようにする。
#最後に
Springは巨大なフレームワークなので、どこから取りかかれば良いか、最初は悩むと思います。
知識ゼロからここまでくるのに結構苦労しましたが、新しいことを勉強するのは楽しいです。
この記事が少しでも何かのお役に立てれば幸いです♥