StrutsからSpringMVCへの移行まとめ

# はじめに
社内の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](https://spring.io/tools/sts)のプラグインを追加して色々触ってみました。

以下、SpringMVCを使ってHello Worldはできた前提で進めます。

#本番作業
大きく分けてやることは3つです。
1.JSPのタグを変更
2.画面遷移を変更
3.Validationを変更

Springは*設定より規約*なフレームワークです。
主にStrutsのstruts-config.xmlとvalidation.xmlに設定していた画面遷移まわりの内容を移行していきます。

##JSPのタグを変更

タグライブラリの指定
**Struts**
```<%@ taglib prefix="bean" uri="/WEB-INF/struts-bean.tld" %>
<%@ 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>
```
```lang:HelloAction.java
 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**

```lang:HelloAction.java
 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**

```lang:validation.xml
 <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>
```

```lang:HelloForm.java
 package jp.sample.form;

 public class HelloForm {
  private String name;
  private String mail;

  //　Getter/Setter
  ・・・
　}
```

**Spring**

```lang:HelloForm.java
 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に合わせて少し変更。

```lang:MessageResources.properties
 # エラーメッセージ
 NotEmpty={0}を入力してください。 # {0}はフィールド名
 Length={0}は{2}文字以上で入力してください。# {2}はminの値(ここではmaxは省略)

 # フィールドの日本語名
 name=名前
 mail=メールアドレス
```

コントローラーでValidationするように修正します。

```lang:HelloAction.java
 @Controller
 public class HelloAction {
  @RequestMapping("/hello")
   public String hello(@Valid @ModelAttribute HelloForm form, 
　　　　　　　　　　　　BindingResult result, Model model) {
    if (result.hasErrors()) {
     // エラー時の処理
    }
 　 ・・・
    return "hello";
   }
```

JSPにエラーを表示させる部分を追加します。

```lang:hello.jsp
	<form:errors path="*" element="div" />
```
`path="*"`はすべてのエラーを表示します。
`path="フィールド名"`にするとそのフィールドのエラーだけを表示できます。

と、ここまでは簡単だったのですが、ちょっと落とし穴がありました。
Strutsの場合は、validation.xmlに書いてある順にエラーを表示してくれていましたが、
Springはフィールド、エラーの種類の順番がランダムでした。

例えば、
![error1.jpg](https://qiita-image-store.s3.amazonaws.com/0/102543/790e06d7-7b26-c7b5-9240-d852968e0879.jpeg)


と表示されるときもあれば、
![error2.jpg](https://qiita-image-store.s3.amazonaws.com/0/102543/c3febdb6-c0c3-4b3c-1ae8-f14c7f1b5d65.jpeg)


と表示されるときもあるのです。
これはイマイチだったのでエラーをソートすることにしました。

ソートをいれるとコントローラーはこのような感じです。

```lang:HelloAction.java
 @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;
            }
            // エラーが１つの場合
            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のエラー表示部分も変更します。

```lang:hello.jsp
	<c:forEach items="${errors}" var="error">
		<spring:message message="${error}"  /><br>
	</c:forEach>
```

これでエラーの順番が固定されて表示できるようになりました。
![error3.jpg](https://qiita-image-store.s3.amazonaws.com/0/102543/7fd3c4c5-1433-a00d-1b53-c4ec872c5fb1.jpeg)

#おまけ
個人的にハマったところのメモです。

- [Sending a list of objects from view to controller : limited to 256 objects](http://stackoverflow.com/questions/27252974/sending-a-list-of-objects-from-view-to-controller-limited-to-256-objects)
⇒Controllerに送信できるリストのサイズはデフォルトで256。

- [Binding a map of lists in Spring MVC](http://stackoverflow.com/questions/12339013/binding-a-map-of-lists-in-spring-mvc)
⇒JSPでネストしているデータを扱う時は`list[${index}].field`のようにする。

#最後に
Springは巨大なフレームワークなので、どこから取りかかれば良いか、最初は悩むと思います。
知識ゼロからここまでくるのに結構苦労しましたが、新しいことを勉強するのは楽しいです。
この記事が少しでも何かのお役に立てれば幸いです♥