Skip to content

SpringMVC

  • SpringMVC は Servlet と同じような機能を持ち、どちらも Web 層開発技術です。
  • SpringMVC は Java で実装された MVC モデルベースの軽量 Web フレームワークです。
  • Servlet と比べて使いやすく、開発が便利で、柔軟性も高いです。

現在、多くのプロジェクトは SpringBoot フレームワークで開発されています。そのため、この記事では重要な概念を中心に説明し、細かい実装は深く扱いません。

ケース

Step 1:座標を導入する

xml
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

Step 2:SpringMVC Controller クラスを作成する

Servlet と同じような機能を持ちます。

java
@Controller					    //このクラスがBeanであることを宣言する
public class userControler {
    @RequestMapping("/save")    //現在の処理のアクセスパスを設定する
    @ResponseBody			    //現在の処理の戻り値タイプを設定する
    public String save(){
        System.out.println("user save...");
        return "{'info':'springmvc'}";
    }
}

Step 3:SpringMVC 設定クラスを作成する

java
@Configuration 
@ComponentScan("com.protectark.mysqlt")
public class SpringMvcConfig {
}

Step 4:Servlet コンテナ起動設定クラスを定義する

このクラスの中で Spring の設定を読み込みます。

java
public class servletInConfig extends AbstractDispatcherServletInitializer {
    
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext act = new AnnotationConfigWebApplicationContext();
        act.register(SpringMvcConfig.class);
        return act;
    }
    
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }
}

アノテーション説明

アノテーション種類位置役割
@ControllerクラスアノテーションSpringMVC Controller クラス上SpringMVC の核心 Controller Bean を設定する
@RequestMapping(地址)メソッドアノテーションSpringMVC Controller クラス内のメソッド上現在の Controller メソッドのリクエストパスを設定する
@ResponseBodyメソッドアノテーションSpringMVC Controller メソッド定義の上現在の Controller メソッドの戻り値を直接レスポンス内容にする

動作フロー

サーバー起動時の初期化:

  1. サーバーが起動し、ServletContainersInitConfig クラスを実行して Web コンテナを初期化する。
  2. createServletApplicationContext メソッドを実行し、WebApplicationContext オブジェクトを作成する。
  3. SpringMvcConfig を読み込む。
  4. @ComponentScan により対応する Bean を読み込む。
  5. UserController を読み込み、各 @RequestMapping の名前が具体的なメソッドに対応する。
  6. getServletMappings メソッドを実行し、すべてのリクエストが SpringMVC を通るように定義する。

単一リクエストの流れ:

  1. localhost/save へリクエストを送る。
  2. Web コンテナはすべてのリクエストが SpringMVC を通ることを確認し、リクエストを SpringMVC に渡す。
  3. リクエストパス /save を解析する。
  4. /save に対応する save() メソッドを実行する。
  5. save() を実行する。
  6. @ResponseBody があるため、save() の戻り値をレスポンスボディとして返す。

設定クラスの簡略化

Spring は AbstractDispatcherServletInitializer の子クラスとして、AbstractAnnotationConfigDispatcherServletInitializer を提供しています。

java
public class servletInConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{springConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{springMvcConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

リクエストとレスポンス

パス衝突を解決する

チームで複数人が開発する場合、リクエストパスの衝突を避ける必要があります。
通常は、モジュール名をリクエストパスのプレフィックスとして設定します。

方法:

  1. 各メソッドの @RequestMapping パスを変更する。例:/user/save/book/save
  2. クラスのリクエストパスを設定する。例:クラスに /user、メソッドに /save
アノテーション種類位置役割
@RequestMapping(value)メソッド / クラスアノテーションSpringMVC Controller クラスまたはメソッド上現在の Controller メソッドのリクエストパスを設定する。クラス上に設定すると、全メソッド共通のプレフィックスになる
java
@Controller
@RequestMapping("/user")
public class userControler {
    @RequestMapping("/save")
    @ResponseBody
    public String save(){
        return "{'info':'springmvc'}";
    }
}

中文文字化けを解決する

送信された中国語が文字化けする場合、Servlet コンテナ起動設定クラス servletInConfig で親クラスのメソッドをオーバーライドし、Web コンテナに文字エンコーディングフィルターを追加します。

java
@Override
protected Filter[] getServletFilters() {
    CharacterEncodingFilter filter = new CharacterEncodingFilter();
    filter.setEncoding("UTF-8");
    return new Filter[]{filter};
}

リクエストパラメータと仮引数名が違う場合

たとえば、メソッドの仮引数名が usernameage で、実際のリクエストパラメータが nameage の場合、usernamenull になります。

この場合は @RequestParam("name") を使い、パラメータ名を指定します。

java
@Controller
public class userControler {
    @RequestMapping("/save")
    @ResponseBody
    public String save(@RequestParam("name") String username,
                       @RequestParam("age") int age){
        System.out.println(username + age);
        return "{'info':'springmvc'}";
    }
}

パラメータをエンティティクラスで受け取る

必要なパラメータが多い場合、通常はエンティティクラスを作り、それで受け取ります。

java
@Controller
public class userControler {
    @RequestMapping("/save")
    @ResponseBody
    public String save(User user){
        return "{'info':'springmvc'}";
    }
}
java
public class User{
    private String username;
    private int age;
    //その他の属性
    //getter、setter、toStringを実装する
}

リクエストの属性名がエンティティクラス内の属性名と完全に一致する場合、自動的に user に入ります。

JSON データを渡す

JSON データはリクエストヘッダーのパラメータではなく、リクエストボディにあります。
そのため、受け取るときは、リクエストボディのデータであることを示すアノテーションが必要です。

まず JSON データ変換用の依存を導入します。

xml
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.0</version>
</dependency>

次に、SpringMVC に JSON 変換を有効にすることを伝えます。
SpringMvcConfig@EnableWebMvc を追加します。

java
@Configuration 
@ComponentScan("com.protectark.mysqlt")
@EnableWebMvc
public class SpringMvcConfig {
}

JSON はリクエストボディにあるため、Controller メソッドの仮引数では @RequestParam ではなく @RequestBody を使います。

java
@RequestMapping("/inputuser")
@ResponseBody
public String init(@RequestBody User user){
    System.out.println("input user ==>"+user);
    return "{'info':'springMVC'}";
}
アノテーション種類位置役割
@EnableWebMvc設定クラスアノテーションSpringMVC 設定クラス上SpringMVC の複数の補助機能を有効にする
@RequestBody仮引数アノテーションController メソッド仮引数前リクエストボディ内のデータをリクエスト引数へ渡す。一つのハンドラーメソッドで一回だけ使える

RequestBodyRequestParam の比較

  • 違い

    • @RequestParam は URL パラメータ、フォームパラメータ(application/x-www-form-urlencoded)を受け取る
    • @RequestBody は JSON データ(application/json)を受け取る
  • 使用場面

    • 後期の開発では JSON 形式のデータ送信が中心になるため、@RequestBody がよく使われる
    • JSON 以外の形式なら、@RequestParam でリクエストパラメータを受け取る

@PathVariable は仮引数アノテーションであり、パスパラメータと仮引数の関係をバインドします。

java
@RequestMapping("/users/{id}")
@ResponseBody
public String init(@PathVariable Integer id){
    System.out.println("input id ==>"+id);
    return "{'info':'springMVC'}";
}

日付型パラメータの受け渡し

SpringMVC は String 型のパラメータを直接受け取り、自動で日付型に変換できます。形式を指定したい場合は @DateTimeFormat を使います。

java
@RequestMapping("/inputuser")
@ResponseBody
public String init(@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date date){
    System.out.println("input date ==>"+date);
    return "{'info':'springMVC'}";
}
アノテーション種類位置役割
@DateTimeFormat仮引数アノテーションSpringMVC Controller メソッド仮引数前日付時間型データの形式を設定する

レスポンス

JSON データを返したい場合は、エンティティクラスを返すだけでよいです。

java
@RequestMapping("/select")
@ResponseBody
public User select(){
	User user = new User();
    return user;
}

SpringMVC は自動でエンティティクラスを JSON データに変換します。

REST スタイル

概念

REST(Representational State Transfer)は、表現状態転移という考え方です。

  • 従来のリソース表現
    • http://localhost/user/getById?id=1
    • http://localhost/user/saveUser
  • REST スタイルのリソース表現
    • http://localhost/user/1
    • http://localhost/user

メリット:記述が簡単になり、リソースへのアクセス方式を隠せます。URL だけでは、そのリソースに対してどの操作をするか分かりません。

REST スタイルでは、リソースへアクセスするとき、HTTP メソッドで操作を区別します。

  • http://localhost/users:すべてのユーザー情報を検索、GET
  • http://localhost/users/1:指定ユーザー情報を検索、GET
  • http://localhost/users:ユーザー情報を追加、POST
  • http://localhost/users:ユーザー情報を変更、PUT
  • http://localhost/users/1:ユーザー情報を削除、DELETE

この方式は一種の約束であり、REST スタイルと呼ばれます。この方式でリソースへアクセスすることを RESTful と呼びます。
モジュール名は通常、複数形を使います。例:usersbooksaccounts

RESTful クイック開発

REST スタイルでは JSON 通信がよく使われます。そのため、各メソッドに @ResponseBody が必要になります。これを Controller クラス上に置くこともできます。さらに、@Controller@ResponseBody を合わせた @RestController が提供されています。

リクエスト方式も主に四種類なので、@RequestMapping から次のアノテーションが派生しています。

  • @PostMapping
  • @GetMapping
  • @PutMapping
  • @DeleteMapping
java
@RestController
@RequestMapping("/users")
public class userControler {
	@GetMapping()
     public String save(@Requestbody User user){ 
         return "{'info':'springmvc'}";
    }
    @DeleteMapping()
    public String save(@Requestbody User user){
        return "{'info':'springmvc'}";
    }
}

統一結果クラス

統一結果クラスには次のメリットがあります。

一、保守性の向上

  • 戻り値を集中管理でき、統一的な変更と拡張がしやすい。
  • エラー処理ロジックを明確に定義でき、保守とデバッグがしやすい。

二、安定性と信頼性の向上

  • 例外情報を標準化し、フロントエンドの解析エラーを避ける。
  • グローバル例外処理により、システムクラッシュを防ぐ。

三、チーム協作効率の向上

  • フロントエンドとバックエンドに明確な規約を提供し、コミュニケーションコストを減らす。
  • 再利用性が高く、コードの重複を減らす。

四、監視と統計がしやすい

  • ログ形式を統一し、分析と監視がしやすい。
  • データ統計分析に役立ち、問題やボトルネックを発見しやすい。

通常、統一結果クラスは codemessagedata の三部分で構成されます。

java
@Data
public class Result<T> implements Serializable {
    private Integer code;
    private String msg;
    private T data;

    public static <T> Result<T> success() {
        Result<T> result = new Result<T>();
        result.code = 1;
        return result;
    }

    public static <T> Result<T> success(T object) {
        Result<T> result = new Result<T>();
        result.data = object;
        result.code = 1;
        return result;
    }

    public static <T> Result<T> error(String msg) {
        Result result = new Result();
        result.msg = msg;
        result.code = 0;
        return result;
    }
}

code は通常、事前に定義したステータスコードを一つのクラスにまとめて管理します。

java
public class Code {
    public static final Integer SAVE_OK = 200;
    /* など */
}

例外ハンドラー

基本概念

開発中、例外は避けられません。例外が発生しやすい場所と原因は次の通りです。

  • フレームワーク内部の例外:使い方が不適切なため
  • データ層の例外:外部サーバー障害などのため
  • 業務層の例外:業務ロジックの記述ミスのため
  • 表示層の例外:データ収集、検証などのルール違反のため
  • ツールクラスの例外:実装が厳密でなく、堅牢性が足りないため

すべての層で例外が発生する可能性があるため、すべて上位へ投げ、表示層で統一処理します。

Spring が提供する例外ハンドラーは、プロジェクト内の例外を集中処理できます。

java
@RestControllerAdvice
public class ProjectExceptionAdvice {
    
    @ExceptionHandler(Exception.class)
    public Result doException(Exception ex){
        return new Result(666,null);
    }
}

注意:

  1. 例外ハンドラーは SpringMVC 技術に属するため、SpringMvcConfig が認識できるパスに置く必要があります。通常は controller パッケージ下に置きます。
  2. 統一結果クラスを返す必要があるため、例外ハンドラーも結果クラスを返すようにします。

プロジェクト内の例外処理

プロジェクト例外分類:

  • 業務例外(BusinessException)
    • 規約通りのユーザー操作によって発生する例外
    • 規約違反のユーザー操作によって発生する例外
  • システム例外(SystemException)
    • 実行中に予測できるが避けられない例外
  • その他の例外(Exception)
    • 開発者が予測していなかった例外

処理方針:

  • 業務例外:対応するメッセージをユーザーへ返し、正しい操作を促す
  • システム例外:固定メッセージをユーザーへ返し、運用担当者へ通知し、ログを記録する
  • その他の例外:固定メッセージをユーザーへ返し、開発者へ通知し、ログを記録する

例外を定義する

分類に基づき、システム例外と業務例外を定義できます。
exception パッケージを作成し、カスタム例外を保存します。

java
public class SystemException extends RuntimeException{
    private Integer code;

    public SystemException(Integer code) {
        this.code = code;
    }

    public SystemException(String message, Integer code) {
        super(message);
        this.code = code;
    }

    public SystemException(String message, Throwable cause, Integer code) {
        super(message, cause);
        this.code = code;
    }
}

同じように BusinessException も作成します。

カスタム例外を投げる

業務層で例外が発生しそうな場所を包み、カスタム例外へ変換して投げます。

java
public User getById(Integer id){
    User user = userMapper.getById(id);
    if (user == null) {
        throw new SystemException(Code.SYSTEM_NULL,"结果为空");
    }
    return user;
}

カスタム例外を処理する

java
@RestControllerAdvice
public class ProjectExceptionAdvice {
    
    @ExceptionHandler(SystemException.class)
    public Result doSystemException(SystemException ex){
        /* ログ記録 */
        /* 運用担当者へ通知 */
        return new Result(ex.getCode,ex.getMessage);
    }

    @ExceptionHandler(Exception.class)
    public Result doException(Exception ex){
        return new Result(666,null);
    }
}

インターセプター

インターセプター(Interceptor)は、メソッド呼び出しを動的に遮断する仕組みです。SpringMVC では、Controller メソッドの実行を動的に遮断できます。

役割:

  • 指定メソッドの呼び出し前後に、事前に設定したコードを実行する
  • 元のメソッドの実行を止める

主な用途:

  • 権限とアクセス制御:ユーザー認証と権限確認を行い、特定リソースへのアクセスを制限する
  • ログと性能監視:リクエスト情報を記録し、性能ボトルネックを分析する
  • データ処理:リクエストデータの前処理とレスポンスデータの後処理を行う
  • 重複送信防止と検証:フォームの重複送信を防ぎ、フォームデータを検証する
  • システム連携:システム間のやり取りを制御し、共通業務ロジックを実行する

フィルターとの違い

  • 所属が違う:Filter は Servlet 技術、Interceptor は SpringMVC 技術
  • 遮断対象が違う:Filter はすべてのアクセスを強化する。Interceptor は SpringMVC へのアクセスだけを強化する
  • 設計目的が違う:Interceptor は業務ロジック前後の処理に重点がある。Filter はリクエストとレスポンスの一般的なフィルタリングに重点がある
  • 実行順序が違う:Interceptor は Spring MVC 内の登録順またはアノテーション順で実行される。Filter は Web コンテナの設定順で doFilter を実行する

使用方法

まず interceptor パッケージを作成します。このパッケージは controller パッケージ下に置いてもよいです。インターセプターは表示層を強化するためです。

インターセプタークラスを作成し、HandlerInterceptor インターフェースを実装します。

java
@Component
public class Interceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

SpringMVC 設定クラスを作成します。

java
@Configuration
public class SpringMvcConfig extends WebMvcConfigurationSupport {

    @Autowired
    private Interceptor interceptor;

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor).addPathPatterns("/users");
    }
}

複数インターセプターを設定する

Interceptor をもとに Interceptor2 を作成し、SpringMvcConfig を変更します。

java
@Configuration
public class SpringMvcConfig extends WebMvcConfigurationSupport {

    @Autowired
    private Interceptor interceptor;

    @Autowired
    private Interceptor2 interceptor2;

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor).addPathPatterns("/users");
        registry.addInterceptor(interceptor2).addPathPatterns("/users");
    }
}

Released under the MIT License.