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のプラグインを追加して色々触ってみました。

以下、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

struts-config.xml
 <action path="/hello" type="jp.sample.action.HelloAction"
   name="HelloForm" scope="request" parameter="start">
   <forward name="success" path="/hello.jsp" />
  </action>
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

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

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>
HelloForm.java
 package jp.sample.form;

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

  // Getter/Setter
  ・・・
 }

Spring

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に合わせて少し変更。

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

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

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

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にエラーを表示させる部分を追加します。

hello.jsp
    <form:errors path="*" element="div" />

path="*"はすべてのエラーを表示します。
path="フィールド名"にするとそのフィールドのエラーだけを表示できます。

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

例えば、

と表示されるときもあれば、

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

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

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;
            }
            // エラーが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のエラー表示部分も変更します。

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

これでエラーの順番が固定されて表示できるようになりました。

おまけ

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

最後に

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