05.Spring MVC 3 の実際のサンプル

概要

ここでは、Spring MVCの実際のサンプルを見てみます。
 
日本語のWEB上で紹介されているサンプルは、学習の導入用のものが多く、Hello World的な参照画面で終わっています。
ここではもう少し掘り下げたものを見てみたいと思います。
 

【環境など】
こちらを参照ください。

【ソース一式をダウンロードできるようにしました】
この画面の一番下にダウンロードリンクがあります。
もしくは、こちらを押してください。
mavenになっているので、インポートの仕方が分からない方はこちらを見てみてください。

【より実践的なサンプル】
このページでは骨組みのようなソースで、実際に使うには足りないことがたくさんあります。
より実践的なサンプルは以下を参照ください。
 

 
 
 

目標

  まず、以下のサンプルの目標(ゴール)を示します。

  【動作について】
    入力画面 ⇒ 確認画面 ⇒ 完了画面、という画面を作成していきます。

    画面とControllerの連携のパターンとしては、「複数の画面に1つのControllerを結びつけるパターン」を使用します。
    (パターンについてはこちらの記事を参照)
 
  【URLと処理メソッドのマッピングの実現方法】
    URLと処理メソッドのマッピングの方法は、いくつか考えられます。
    ここでは、Controllerクラスにアノテーションでマッピングを記述する方法をとります。
 
    実際には、例えば「http://localhost:8080/sample/user/edit.html」というURLの場合、
    親のパス「/user」までを設定ファイルで管理して、「edit」のマッピングだけをControllerに記述する設計もやりたいはずです。
    親のパスまでControllerのプログラマーに管理させてしまうと、全体的なURLパスの構成を設計者が管理できないからです。
    このような場合のサンプルはまた別の記事で見ていこうと思います。
 
  【リクエストパラメタを受け取るモデルについて】
    以前の記事でも書きましたが、業務モデルをそのまま、リクエストパラメタを受け取りに使用しない方がよいです。
    このサンプルでもFormクラスを作成し、その中で業務モデルを使用する方法をとります。
    このサンプルではクラスを増やすのが面倒でしたのでinnerクラスで実装しましたが、外部にした方が分かりやすいかもしれません。
    このあたりは設計しだいかと思います。
 
  【妥当性チェックの実装方法】
    Springでデフォルトで用意されている妥当性チェックは、Spring Modules、Hibernate Validatorなどです。
    いずれにしても、どのようなValidatorもカスタマイズすれば使用できるようになります。
    ここでは標準的に使用されるHibernate Validatorを使用します。必要なjarファイルも用意してください。
 
  【例外処理について】
    今回は例外処理の設定はしません。
    また別の記事で扱おうかと思っています。
 
 
  コードの後に簡単な説明を入れますので、それを読みながら実行していただければと思います。
 
  一番下にイメージ画像をつけましたので、先にそちらを見ていただいても良いかと思います。
 
 

使用サンプル

<web.xml の記述サンプル>

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">


 <display-name>test</display-name>
 <welcome-file-list>
  <welcome-file>top.jsp</welcome-file>
     <welcome-file>index.jsp</welcome-file>
 </welcome-file-list>
 
 
 <!-- spring用のリスナー-->
 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>
   /WEB-INF/spring/applicationContext-webmvc.xml
  </param-value>
 </context-param>

 
 <!-- エンコード -->
 <filter>
  <filter-name>CharacterEncodingFilter</filter-name>
  <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  <init-param>
   <param-name>encoding</param-name>
   <param-value>utf-8</param-value>
  </init-param>
  <init-param>
   <param-name>forceEncoding</param-name>
   <param-value>true</param-value>
  </init-param>
 </filter>
 <filter-mapping>
  <filter-name>CharacterEncodingFilter</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>
 
 
 <!-- Spring MVCの設定 -->
 <servlet>
  <servlet-name>spring_mvc_sample</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
   <param-name>contextConfigLocation</param-name>
   <param-value></param-value>
  </init-param>
 </servlet>
 <servlet-mapping>
  <servlet-name>spring_mvc_sample</servlet-name>
  <url-pattern>*.html</url-pattern>
 </servlet-mapping> 
 
</web-app>
 
  SpringのDispatcherServletを設定します。
 
 

<画面処理サンプル(Controller): com.sample.controller.UserController.java>

package com.sample.controller;

import java.text.SimpleDateFormat;
import java.util.Date;
import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import com.sample.business.model.User;
import com.sample.business.service.UserService;
 
/**
 * このアノテーションをつけて、component-scanさせるとControllerとして扱われます。
 */

@Controller
@RequestMapping(value="/user/")
public class UserController {
 @Autowired
 private UserService userService;
 
 /**
  * formモデルのバインダーの初期化。リクエストパラメタをモデルに変換するたびに呼ばれる。
  */

 @InitBinder("form")
 public void initBinderForm(WebDataBinder binder) {
  //バインドするときの日付のフォーマット指定。
  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
  dateFormat.setLenient(false);
  binder.registerCustomEditor(Date.class, "user.upDate", new CustomDateEditor(dateFormat, true));
  
  //Userオブジェクトのうち、user.nameパラメタを受け取りたくない場合
  binder.setAllowedFields("user.age", "user.upDate");
 }
 
 
 /**
  * モデルオブジェクトの初期化
  * (サンプルのためセキュリティの考慮がないことに注意!
  *  ログイン時のユーザIDと以下のuserIdが一致するかをチェックし、一致しない場合エラーにすべき。
  */
 @ModelAttribute("form")
 public Form newRequest(
   @RequestParam(required=false, value="user.id") String userId
 ) {
  Form f = new Form();
  //
  User user = null;
  if(userId == null){
   user = new User();
  }else{
   user = this.userService.getUser(userId);
  }
  //
  f.setUser(user);
  return f;
 }
 
 
 
 //リクエスト処理------------------------------------------
 @RequestMapping(value="edit/input", method=RequestMethod.GET)
 public String input(Form form) {
  //既にnewRequestでモデルをDBから取り出し、設定しているので何もする必要がない
  return "user-Edit-Input";
 }
 
 @RequestMapping(value="edit/confirm", method=RequestMethod.POST)
 public String confirm(@Valid Form form, BindingResult result) {
  //@Validを指定したモデルは妥当性チェックが実行される
  if(result.hasErrors()){
   return "user-Edit-Input";
  }
  return "user-Edit-Confirm";
 }
 
 @RequestMapping(value="edit/finish", method=RequestMethod.POST)
 public String finish(@Valid Form form, BindingResult result) throws Exception {
  if(result.hasErrors()){
   return "user-Edit-Input";
  }
  
  //データ更新
  this.userService.updateUser(form.user);
  return "user-Edit-Finish";
 }
 
 
 //---------------------------------------------
 //フォーム(HTML用のパラメタを受け取れるように作っておいた方がよいと思います)
 public static class Form{
  @Valid
  private User user;
  public User getUser() {
   return user;
  }
  public void setUser(User user) {
   this.user = user;
  }
 }
}
 
  詳細は後の記事で触れます。ここでは、簡単な説明にとどめます。
 
 
  【@Controllerについて】
   このアノテーションをつけたクラスをControllerとして扱います。
   ただし、Spring設定ファイルにこのアノテーションを有効にする記述(mvc:annotation-driven、context:component-scan)を
   しなければなりません。
 
  【@InitBinderについて】
   Binderの初期化をすることができます。
   上記では、upDateプロパティに日付のフォーマットを指定しています。
   この日付の設定はJSPでも適用されるので、HTML上でも、"2012/04/07"のように出力されることになります。
   また、setAllowedFields()メソッドで、設定を許可するプロパティを指定できます。逆に許可しないプロパティを設定することもできます。
 
  【@ModelAttributeについて】
   このアノテーションは、メソッドにつけることも、引数につけることもできます。
   メソッドに付けた場合は、前の記事で見たモデル前処理になります。
   引数に付けた場合は、Modelオブジェクトから指定の名称のオブジェクトを取得して、引数に渡すようになります。
   また引数の場合、このアノテーションは省略でき、上記のinput()、comfirm()、finish()メソッドでは省略されています。
   省略時の名称は、クラス名の先頭を小文字にしたものになります。(例:@ModelAttribute("form"))
 
  【@RequestMappingについて】
   このアノテーションでURLとのマッピングをしますが、クラスにつけることも、メソッドにつけることもできます。
   クラスにつけると、URLの親のパスにマッチし、さらにメソッドに付けたパスが、親からの相対パスになります。
   ですので、例えばinput()メソッドは、「user/edit/input.html」のパスのリクエストについて呼び出されることになります。
 
  【画面処理メソッドの引数について】
   StrutsなどのWEBフレームワークでは、引数が決まっています。しかしSpring MVCでは自由に引数を決められます。

   例えば、HttpServletRequestを指定すればそれを渡してくれます。引数の順番もルールは特にありません。
   例外として、BindingResultだけは@Validをつけたモデル引数の後に記述しなければなりません。
     参考: 処理メソッドの引数と返り値
 
  【@Validについて】
   これをつけた引数は、妥当性チェックを行います。
   以前の記事で見ましたように、Controllerのメソッドでは引数を渡すときに、initBinder()メソッドを呼び出してBinderを初期化します。
   そのBinderでバインドしたオブジェクトを渡します。このときに、@Validをつけた場合だけ妥当性チェックを行います。
     参考: 10.妥当性チェックについて
 
  【Formクラスについて】
   フィールドに@Validがついています。
   処理メソッドconfirm()に@Validがついているのに何でさらに@Validをつけるの?と疑問に思った方もいらっしゃるかもしれません。
   引数につけた@Validはその引数に対して行うので、Form内につけたValidation用のアノテーションをチェックするだけです。
   自動で内部のオブジェクトをネストしながらチェックはしません。
   ですので、userフィールドのValidationを働かせるために@Validをつけました。
 
 
 

<モデル: com.sample.business.model.User>

package com.sample.business.model;

import java.util.Date;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.Range;
 
 
public class User {
 @NotNull
 @Size(min=3,max=3)
 private String id;
 
 @NotNull
 @Size(min=1)
 private String name;
 
 @NotNull
 @Range(min=10, max=99)
 private int age;
 
 private Date upDate; //更新日
   
 public String getId() {
  return id;
 }
 public void setId(String id) {
  this.id = id;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public int getAge() {
  return age;
 }
 public void setAge(int age) {
  this.age = age;
 }
 public Date getUpDate() {
  return upDate;
 }
 public void setUpDate(Date upDate) {
  this.upDate = upDate;
 }
}
 
  【NotNullなどのアノテーションについて】
   すでに知っていおられる方もいらっしゃるかも知れませんが、妥当性チェックのためのものです。
   NotNullは、値がnullのときにエラーにします。
 
 

<ビジネスロジック: com.sample.business.service.UserService>

package com.sample.business.service;

import com.sample.business.model.User;
import com.sample.dao.UserDao;

//本当はインターフェースからimplemetsすべきですが、ここでは簡略のためそのままクラスを作っています。
public class UserService {
    private UserDao userDao;
    public UserDao getUserDao() {
        return userDao;
    }
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    public User getUser(String id){
        return this.userDao.getUser(id);
    }
   
    public void updateUser(User user){
        this.userDao.updateUser(user);
    }
}
 
 
 
 
 

<Dao: com.sample.dao.UserDao>

package com.sample.dao;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import com.sample.business.model.User;
 
//本当はインターフェースからimplemetsすべきですが、ここでは簡略のためそのままクラスを作っています。
public class UserDao {
 public User getUser(String id){
   //ここではハードコードしていますが、本当はDBから値を取得します。
   User user = new User();
   try {
      user.setId(id);
      user.setName("未華子");
      user.setAge(21);
      SimpleDateFormat f =new SimpleDateFormat("yyyy/MM/dd");
      user.setUpDate(f.parse("2012/04/01"));
   } catch (ParseException e) {}
      return user;
   }
    
   public void updateUser(User user){
     //DBに値を更新する処理を書きます。ここでは省略。
   }
}

 

<メッセージリソース(画面用): /src/messages-spring_ja.properties> 

#Spring typemismatch エラー
typeMismatch.int=数値を入力してください
typeMismatch.java.math.BigDecimal=数値を入力してください
typeMismatch.java.util.Date=日付を入力してください。

#モデルのプロパティ名
model.user.name=ユーザ名
model.user.age=年齢
model.user.upDate=更新日


  型変換エラーメッセージ用と、画面表示用のモデルのプロパティ名を設定します。
  この記事では、妥当性チェックのエラーメッセージとファイルを分けました。一緒にしても良いと思います。
 
 
 



<メッセージリソース(Hibernate Validatorエラーメッセージ用): /src/ValidationMessages_ja.properties>

javax.validation.constraints.AssertFalse.message=不正な値が入力されました。
javax.validation.constraints.AssertTrue.message=不正な値が入力されました。
javax.validation.constraints.DecimalMax.message={value}より同じか小さい値を入力してください。
javax.validation.constraints.DecimalMin.message={value}より同じか大きい値を入力してください。
javax.validation.constraints.Digits.message=整数{integer}桁以内、小数{fraction}桁以内で入力してください。
javax.validation.constraints.Future.message=未来の日付を入力してください。
javax.validation.constraints.Max.message={value}より同じか小さい値を入力してください。
javax.validation.constraints.Min.message={value}より同じか大きい値を入力してください。
javax.validation.constraints.NotNull.message=値が未入力です。
javax.validation.constraints.Null.message=値は未入力でなければいけません。
javax.validation.constraints.Past.message=過去の日付を入力してください。
javax.validation.constraints.Pattern.message="{regexp}"にマッチしていません。
javax.validation.constraints.Size.message=サイズは{min}から{max}の間の値を入力してください。
org.hibernate.validator.constraints.Email.message=E-mail形式で入力してください。
org.hibernate.validator.constraints.Length.message=文字の長さは{min}から{max}の間で入力してください。
org.hibernate.validator.constraints.NotEmpty.message=値が未入力です。
org.hibernate.validator.constraints.Range.message={min}から{max}の間の値を入力してください

 
  こちらのWEBの内容から引用させていただきました。
 
  ちなみにメッセージソースは通常Spring設定ファイルで読み込ませる必要があります。
  しかし、Hibernate Validatorではメッセージソースのファイル名を「ValidationMessages」にしておくと、自動で読み込みます。
  ここでは自動読み込みを使用しており、以下のSpring設定ファイルでも記述していません。
  もし、違うファイル名にしたい場合はSpring設定ファイルで「ResourceBundleMessageSource」などの読み込みクラスを設定し、
  「LocalValidatorFactoryBean」のvalidationMessageSourceプロパティーに設定する必要があるようです。

 

<Spring設定ファイル: WebContent/WEB-INF/spring/applicationContext-webmvc.xml>

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop ="http://www.springframework.org/schema/aop"
xmlns:tx  ="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">


 <context:component-scan base-package="com.sample.controller" />
 <mvc:annotation-driven />

 <bean id="validator"
      class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
     
 <!-- View -->
 <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
  <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
  <property name="prefix" value="/WEB-INF/jsp/"/>
  <property name="suffix" value=".jsp"/>
  <property name="order" value="2"/>
 </bean>
 
 <!-- Declare the Interceptor -->
 <mvc:interceptors>
  <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" p:paramName="locale" />
 </mvc:interceptors>
  
 <!-- Declare the Resolver -->
 <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver" >
  <property name="defaultLocale" value="ja" />
 </bean>
 
 <!-- メッセージ -->
 <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"
  p:basenames="messages-spring" />
 
 <!-- サービスとDaoの登録 -->
 <bean id="userService" class="com.sample.business.service.UserService">
  <property name="userDao" ref="userDao"/>
 </bean>
 <bean id="userDao" class="com.sample.dao.UserDao"/>
</beans>
 
  【mvc:などのタグ】
   後の記事で詳しく見ていきます。
   ここでは重要な、context:component-scan、mvc:annotation-drivenだけ見てください。
   この2つを記述することで、アノテーションベースのMVCを実現できます。
 
 
 

<JSPファイル(トップページ): WebContent/top.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>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>トップ</title>
</head>
<body>
テスト<br>
<a href="user/edit/input.html?user.id=001">Springテストへ</a><br>

</body>
</html>
 
 

<JSPファイル(入力画面): WebContent/WEB-INF/jsp/user-Edit-Input.jsp>

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ page session="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>

<html>
<body>

<form:form action="confirm.html" method="POST" modelAttribute="form">
<form:hidden path="user.id"/>
<table border="1">
 <tr>
  <th><spring:message code="model.user.name" /><br></th>
  <td><spring:bind path="user.name">${status.value}</spring:bind><br></td>
 </tr>
 <tr>
  <th><spring:message code="model.user.age"/><br></th>
  <td><form:input path="user.age"/><br><form:errors path="user.age" cssStyle="color:red"/></td>
 </tr>
 <tr>
  <th><spring:message code="model.user.upDate"/><br></th>
  <td><form:input path="user.upDate" maxlength="10"/><br><form:errors path="user.upDate" cssStyle="color:red"/></td>
 </tr>
</table>
<br>
 <input type="submit" value="確認" />
</form:form>

</body>
</html>

 
  【JSPタグについて】
   spring:、form:などのJSPタグについては別の記事で詳しく見ていきます。
   ここでは、Modelクラスに設定されたオブジェクトを結ぶものと思っていただければ十分です。

 

<JSPファイル(入力画面): WebContent/WEB-INF/jsp/user-Edit-Confirm.jsp>

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ page session="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>

<html>
<body>
<form:form action="finish.html" method="POST" modelAttribute="form">
<form:hidden path="user.id"/>
<table border="1">
 <tr>
  <th><spring:message code="model.user.name" /><br></th>
  <td><spring:bind path="user.name">${status.value}</spring:bind></td>
 </tr>
 <tr>
  <th><spring:message code="model.user.age"/><br></th>
  <td><spring:bind path="user.age">${status.value}</spring:bind>
  <form:hidden path="user.age"/> </td>
 </tr>
 <tr>
  <th><spring:message code="model.user.upDate"/><br></th>
  <td><spring:bind path="user.upDate">${status.value}</spring:bind>
  <form:hidden path="user.upDate" /></td>
 </tr>
</table>

<input type="submit" value="完了" />
</form:form>
</body>
</html>

 

 

<JSPファイル(入力画面): WebContent/WEB-INF/jsp/user-Edit-Finish.jsp>

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>

<html>
<body>
完了しました。
</body>
</html>
 
 
 
 
 
 

まとめ

画面イメージ

 
 
  サンプルの画面のイメージは上記のとおりです。
  型変換エラーも妥当性チェックエラーもちゃんと実行されています。
 
  サンプルのコード量が多いのでコピーが大変かもしれません。すみません。
  うまく動作してくれると嬉しいです。
 
 
 
 
 

サブページ リスト

 
 
 
 

 Created Date: 2012/04/07