Spring MVC/Boot RequestMappingアノテーション徹底解説

Spring MVC/Boot @RequestMapping アノテーション徹底解説

はじめに

Webアプリケーション開発において、特定のリクエストを処理するコード(ハンドラー)にマッピングすることは、フレームワークの最も基本的な機能の一つです。Spring Framework、特にSpring MVCおよびその進化形であるSpring Bootでは、この重要な役割を主に@RequestMappingアノテーションが担っています。

@RequestMappingアノテーションは非常に強力で柔軟性が高く、単にURLとメソッドを紐づけるだけでなく、HTTPメソッド、リクエストパラメータ、ヘッダー、さらにはリクエストやレスポンスのメディアタイプに至るまで、様々な条件に基づいてリクエストを適切なハンドラーメソッドに振り分けることができます。

この記事では、Spring MVCおよびSpring Bootにおける@RequestMappingアノテーションについて、その基本的な使い方から高度な機能、実践的なヒントまでを徹底的に解説します。約5000語にわたる詳細な説明と豊富なコード例を通して、読者が@RequestMappingアノテーションを完全に理解し、自身のアプリケーション開発で自信を持って使いこなせるようになることを目指します。

この記事を読むことで、あなたは以下のことを習得できます。

  • @RequestMappingの基本的な役割と、クラスレベルおよびメソッドレベルでの適用方法
  • HTTPメソッドに基づいたリクエストマッピングの方法
  • URLパスから動的に値を取り出すパス変数の使い方
  • クエリパラメータやフォームデータを受け取る方法
  • リクエストヘッダーやCookieの値を利用したマッピングやデータ取得
  • リクエストやレスポンスのメディアタイプによる条件付け
  • パラメータやヘッダーの有無・値による詳細なマッピング制御
  • パスパターン(ワイルドカード)を使った柔軟なマッピング
  • SpringBoot環境における@RequestMappingの typical な利用方法
  • 効率的で保守性の高いAPI設計における@RequestMappingの活用法

さあ、Springにおけるリクエストマッピングの中核である@RequestMappingの世界へ深く潜り込んでいきましょう。

@RequestMapping アノテーションの基本

@RequestMappingアノテーションは、Spring MVCまたはSpring WebFluxアプリケーションにおいて、HTTPリクエストをコントローラー内の特定のハンドラーメソッドにマッピングするために使用されます。これは、特定のエンドポイント(URL)へのリクエストが来たときに、どのJavaメソッドがそのリクエストを処理すべきかをSpringフレームワークに指示する役割を果たします。

@RequestMappingは、クラスレベルとメソッドレベルの両方に適用できます。

  • クラスレベル: クラス全体に対するベースパスを指定します。これにより、そのクラス内のすべてのハンドラーメソッドのマッピングパスに、指定したベースパスがプレフィックスとして付加されます。
  • メソッドレベル: 特定のハンドラーメソッドに対するパスやその他のマッピング条件を指定します。クラスレベルに@RequestMappingが指定されている場合、メソッドレベルのパスはクラスレベルのパスからの相対パスとして扱われます。

最も基本的な使い方は、value属性(またはそのエイリアスであるpath属性)でリクエストパスを指定することです。

“`java
package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller // このクラスがSpring MVCのコントローラーであることを示す
@RequestMapping(“/greeting”) // クラスレベル: このクラス内のメソッドはすべて “/greeting” から始まるパスになる
public class GreetingController {

// メソッドレベル: GETリクエストかつパスが "/greeting" (クラスレベル + メソッドレベル) の場合にこのメソッドが呼ばれる
@RequestMapping("/")
@ResponseBody // メソッドの戻り値をHTTPレスポンスボディに直接書き込む
public String sayHello() {
    return "Hello, World!";
}

// メソッドレベル: GETリクエストかつパスが "/greeting/bye" の場合にこのメソッドが呼ばれる
@RequestMapping("/bye")
@ResponseBody
public String sayGoodbye() {
    return "Goodbye!";
}

// 複数のパスを指定することも可能
@RequestMapping({"/hi", "/hello"})
@ResponseBody
public String sayHiOrHello() {
    return "Hi or Hello!";
}

}
“`

上記の例では、
* GET /greeting へのリクエストは sayHello() メソッドによって処理されます。
* GET /greeting/bye へのリクエストは sayGoodbye() メソッドによって処理されます。
* GET /greeting/hi または GET /greeting/hello へのリクエストは sayHiOrHello() メソッドによって処理されます。

value属性とpath属性は全く同じ意味で、どちらを使っても構いません。通常はより短く直感的なvalueがよく使われますが、パスであることを強調したい場合はpathを使う開発者もいます。

また、上記の例ではHTTPメソッドを指定していませんが、@RequestMappingはデフォルトですべてのHTTPメソッド(GET, POST, PUT, DELETEなど)を受け付けます。特定のリクエストメソッドに限定したい場合は、次に説明するmethod属性を使用します。

@ResponseBodyアノテーションは、このメソッドの戻り値がViewの名前ではなく、HTTPレスポンスボディに直接書き込まれるべきであることをSpringに指示します。RESTful APIを開発する際によく使われます。クラスレベルに@RestControllerアノテーションを使用すると、そのクラス内のすべてのメソッドがデフォルトで@ResponseBodyの動作をするようになります。@RestController@Controller@ResponseBodyを組み合わせたメタアノテーションです。

リクエストメソッドによるマッピング

Webアプリケーションにおけるリクエストは、GET, POST, PUT, DELETEなどの様々なHTTPメソッドを持ちます。RESTful APIの設計において、これらのメソッドを適切に使い分けることは非常に重要です。@RequestMappingアノテーションは、method属性を使って特定のリクエストメソッドにマッピングを限定することができます。

method属性には、org.springframework.web.bind.annotation.RequestMethod列挙型の値を指定します。

“`java
package com.example.demo.controller;

import org.springframework.web.bind.annotation.*;

@RestController // @Controller + @ResponseBody
@RequestMapping(“/products”)
public class ProductController {

// GET /products - 全ての製品を取得
@RequestMapping(method = RequestMethod.GET)
public String getAllProducts() {
    return "List of all products";
}

// POST /products - 新しい製品を作成
@RequestMapping(method = RequestMethod.POST)
public String createProduct(@RequestBody String productDetails) {
    System.out.println("Creating product with details: " + productDetails);
    return "Product created";
}

// GET /products/{id} - 特定の製品を取得 (パス変数については後述)
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String getProductById(@PathVariable Long id) {
    return "Product with id: " + id;
}

// PUT /products/{id} - 特定の製品を更新
@RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public String updateProduct(@PathVariable Long id, @RequestBody String productDetails) {
    System.out.println("Updating product " + id + " with details: " + productDetails);
    return "Product updated";
}

// DELETE /products/{id} - 特定の製品を削除
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public String deleteProduct(@PathVariable Long id) {
    System.out.println("Deleting product with id: " + id);
    return "Product deleted";
}

}
“`

この例では、/productsという同じパスに対して、HTTPメソッドに応じて異なるメソッドが呼び出されるように設定しています。GET /productsgetAllProducts() に、POST /productscreateProduct() にマッピングされます。同様に、/products/{id} というパス変数を含むパスに対しても、GET, PUT, DELETE メソッドでそれぞれ別のハンドラーメソッドが対応します。

複数のHTTPメソッドに対応させたい場合は、method属性に配列で指定します。

java
@RequestMapping(value = "/items/{id}", method = {RequestMethod.GET, RequestMethod.HEAD})
public String getItem(@PathVariable Long id) {
// HEADリクエストの場合もこのメソッドが呼ばれますが、Springはボディを含めずにレスポンスを返します
return "Item details for id: " + id;
}

ショートカットアノテーション

Spring 4.3以降では、一般的なHTTPメソッドに対応する専用のショートカットアノテーションが導入されました。これらは@RequestMapping(method = RequestMethod.XXX)の代わりに使うことができ、コードの可読性を大きく向上させます。

  • @GetMapping (GET)
  • @PostMapping (POST)
  • @PutMapping (PUT)
  • @DeleteMapping (DELETE)
  • @PatchMapping (PATCH)

これらのアノテーションは@RequestMappingのメタアノテーションであり、内部的には対応するmethod属性が設定された@RequestMappingとして機能します。例えば、@GetMapping@RequestMapping(method = RequestMethod.GET)と同等です。

ショートカットアノテーションの利用を強く推奨します。

上記のProductControllerの例をショートカットアノテーションに書き換えると、より簡潔で分かりやすくなります。

“`java
package com.example.demo.controller;

import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController // @Controller + @ResponseBody
@RequestMapping(“/api/products”) // クラスレベルのベースパス
public class ProductController {

// GET /api/products - 全ての製品を取得
@GetMapping
public List<Product> getAllProducts() {
    // 製品リストを取得するロジック
    return List.of(new Product(1L, "Laptop"), new Product(2L, "Mouse"));
}

// POST /api/products - 新しい製品を作成
@PostMapping
public Product createProduct(@RequestBody Product product) {
    // 製品を作成するロジック
    System.out.println("Creating product: " + product);
    return product; // 作成された製品を返す(ここでは受け取ったものをそのまま返す例)
}

// GET /api/products/{id} - 特定の製品を取得
@GetMapping("/{id}") // パス変数 {id} を指定
public Product getProductById(@PathVariable Long id) {
    // 指定されたIDの製品を取得するロジック
    System.out.println("Fetching product with id: " + id);
    return new Product(id, "Sample Product"); // 仮の製品を返す
}

// PUT /api/products/{id} - 特定の製品を更新
@PutMapping("/{id}")
public Product updateProduct(@PathVariable Long id, @RequestBody Product productDetails) {
    // 指定されたIDの製品を更新するロジック
    System.out.println("Updating product " + id + " with details: " + productDetails);
    return new Product(id, productDetails.getName() + " (Updated)"); // 更新された製品を返す
}

// DELETE /api/products/{id} - 特定の製品を削除
@DeleteMapping("/{id}")
public String deleteProduct(@PathVariable Long id) {
    // 指定されたIDの製品を削除するロジック
    System.out.println("Deleting product with id: " + id);
    return "Product with id " + id + " deleted successfully";
}

}

// 簡単なProductクラスの定義 (ここではネストして定義)
class Product {
private Long id;
private String name;

public Product(Long id, String name) {
    this.id = id;
    this.name = name;
}

// Getter, Setter, toString など (省略)
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override
public String toString() { return "Product{id=" + id + ", name='" + name + "'}"; }

}
“`

このように、ショートカットアノテーションを使うことで、どのHTTPメソッドに対応するメソッドなのかが一目で分かり、コードがより読みやすくなります。特別な理由がない限り、ショートカットアノテーションを使用することが推奨されます。

パス変数 (@PathVariable)

RESTfulなURI設計では、リソースの識別子をURLパスの一部に含めることがよくあります。例えば、/users/123123 はユーザーIDを表すといった場合です。Spring MVCでは、@PathVariableアノテーションを使用して、URLパスのテンプレート変数から値を取得し、ハンドラーメソッドのパラメータとして受け取ることができます。

パス変数は、@RequestMappingvalueまたはpath属性で {変数名} の形式で定義します。そして、ハンドラーメソッドのパラメータに@PathVariableアノテーションを付け、その後にパス変数と同じ名前のパラメータを定義します。

“`java
package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(“/users”)
public class UserController {

// GET /users/{userId} という形式のリクエストを処理
@GetMapping("/{userId}")
public String getUserProfile(@PathVariable Long userId) {
    // URLパスの {userId} の値が Long 型の userId パラメータに自動的にバインドされる
    return "User profile for ID: " + userId;
}

// 複数のパス変数も可能
// GET /users/{userId}/orders/{orderId} という形式のリクエストを処理
@GetMapping("/{userId}/orders/{orderId}")
public String getUserOrder(@PathVariable("userId") Long userIdentifier, // パラメータ名とパス変数名が異なる場合は明示的に指定
                           @PathVariable Long orderId) { // パラメータ名とパス変数名が同じ場合は省略可能
    return "Order " + orderId + " for user " + userIdentifier;
}

}
“`

パス変数名の指定

@PathVariableアノテーションは、デフォルトではメソッドパラメータ名をパス変数名として使用しようとします。上記のgetUserProfileメソッドのように、メソッドパラメータ名(userId)とパス変数名({userId})が同じであれば、@PathVariableに名前を指定する必要はありません。

しかし、getUserOrderメソッドの例のように、メソッドパラメータ名(userIdentifier)とパス変数名({userId})が異なる場合は、@PathVariable("パス変数名")のように明示的にパス変数名を指定する必要があります。コードの可読性を高めるために、パラメータ名とパス変数名を一致させるか、明示的に名前を指定することが推奨されます。

型変換

Springは、パス変数から取得した文字列をハンドラーメソッドのパラメータの型(プリミティブ型、ラッパー型、Stringなど)に自動的に変換しようとします。上記の例では、{userId}{orderId}の値は文字列として取得されますが、SpringによってLong型に変換されます。変換に失敗した場合(例えば、数値としてパースできない文字列が渡された場合)、Springはエラーを発生させ、クライアントには通常400 Bad Requestなどのステータスコードが返されます。

カスタム型への変換が必要な場合は、SpringのConverterやFormatter SPIを利用して独自の変換ロジックを定義することも可能です。

パス変数の必須・オプション

デフォルトでは、@PathVariableは必須です。つまり、パス変数に対応するパスセグメントがリクエストURLに存在しない場合、マッピングに失敗します。

@PathVariableにはrequired属性がありますが、これは通常falseに設定することはできません。なぜなら、パス変数はURLパスの一部であり、存在しないとパス自体がマッチしなくなるためです。

補足: @PathVariablerequired=falseは技術的には存在しますが、特定のパスパターン(例: /users/{id} vs /users)で同じメソッドをマッピングしたい場合にのみ意味を持ちます。しかし、これは混乱を招きやすいため、通常は異なるパスパターンには異なるメソッドを定義するか、@RequestParamなど他の手段を検討すべきです。

正規表現によるパス変数

パス変数には正規表現を適用して、パスセグメントとして許容するパターンをより厳密に制御することができます。これは、ファイル名のようにドット (.) を含むパスセグメントを扱う際などに特に役立ちます。

パス変数の定義の後に :正規表現 の形式で正規表現を指定します。

“`java
package com.example.demo.controller;

import org.springframework.core.io.Resource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

@RestController
@RequestMapping(“/files”)
public class FileController {

// GET /files/my_document.pdf などのリクエストを処理
// パス変数名 {fileName} に続く :.+ は、1文字以上の任意の文字にマッチする正規表現
// これにより、ファイル名にドットが含まれていてもパス変数として正しくキャプチャされる
@GetMapping("/{fileName:.+}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) throws IOException {
    System.out.println("Attempting to download file: " + fileName);

    // ここではクラスパスからファイルを取得する例
    Resource resource = new ClassPathResource("static/" + fileName);

    if (resource.exists() && resource.isReadable()) {
        MediaType contentType = MediaType.TEXT_PLAIN; // デフォルトのContent-Type

        // ファイル名に応じて適切なContent-Typeを設定するロジック (ここでは省略)
        if (fileName.endsWith(".pdf")) {
            contentType = MediaType.APPLICATION_PDF;
        } else if (fileName.endsWith(".png")) {
             contentType = MediaType.IMAGE_PNG;
        }
        // ... 他のファイルタイプ ...


        return ResponseEntity.ok()
                .contentType(contentType)
                .body(resource);
    } else {
        // ファイルが見つからない場合は404を返す
        return ResponseEntity.notFound().build();
    }
}

}
“`

通常のパス変数 {fileName} のままでは、Springのデフォルトのパスマッチング設定によっては、パスセグメント内のドットより後の部分が無視されてしまうことがあります(例: /files/my_document.pdf{fileName} としてmy_documentとマッチするなど)。正規表現を使用することで、この挙動を制御し、my_document.pdf全体をfileNameとして取得できるようになります。

正規表現は強力ですが、複雑になりすぎると可読性を損なう可能性があるため、必要な範囲で使用することが望ましいです。

リクエストパラメータ (@RequestParam)

HTTPリクエストでは、クエリパラメータ (?key1=value1&key2=value2...) やフォームデータ (application/x-www-form-urlencodedmultipart/form-data) を通じて、追加のデータを送信することがよくあります。Spring MVCでは、@RequestParamアノテーションを使用して、これらのリクエストパラメータの値を取得し、ハンドラーメソッドのパラメータとして受け取ることができます。

@RequestParamは、ハンドラーメソッドのパラメータに付与します。デフォルトでは、メソッドパラメータ名と同じ名前のリクエストパラメータを探します。

“`java
package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping(“/search”)
public class SearchController {

// GET /search?query=spring を処理
@GetMapping
public String performSearch(@RequestParam String query) { // パラメータ名とリクエストパラメータ名が同じ
    return "Searching for: " + query;
}

// GET /search/products?name=laptop&category=electronics&page=1&size=10 を処理
@GetMapping("/products")
public String searchProducts(@RequestParam String name,
                             @RequestParam("category") String productCategory, // パラメータ名とリクエストパラメータ名が異なる場合
                             @RequestParam int page, // プリミティブ型への変換
                             @RequestParam Integer size) { // ラッパー型への変換
    return String.format("Searching products: name=%s, category=%s, page=%d, size=%d",
                         name, productCategory, page, size);
}

}
“`

リクエストパラメータ名の指定

@RequestParamアノテーションも、value属性(またはname属性)を使って、対応するリクエストパラメータ名を明示的に指定できます。上記のsearchProductsメソッドの例のように、@RequestParam("category") String productCategoryは、リクエストパラメータ名categoryの値をString型のproductCategoryパラメータにバインドします。メソッドパラメータ名とリクエストパラメータ名が同じであれば、名前の指定は省略可能です (@RequestParam String name)。

型変換

@RequestParamで受け取る値も、Springによって自動的にハンドラーメソッドパラメータの型に変換されます。文字列から数値型 (int, Integer, long, Longなど)、真偽値 (boolean, Boolean) など、標準的な型変換は自動で行われます。変換に失敗した場合、通常は400 Bad Requestエラーが返されます。

必須・オプション設定 (required)

デフォルトでは、@RequestParamは必須です。つまり、対応するリクエストパラメータがURLまたはフォームデータに含まれていない場合、Springはエラーを発生させます。

パラメータをオプションにしたい場合は、required属性をfalseに設定します。

java
// GET /items?id=123 または GET /items の両方を処理したい場合
@GetMapping("/items")
public String getItem(@RequestParam(required = false) Long id) {
if (id != null) {
return "Getting item with ID: " + id;
} else {
return "Getting all items";
}
}

オプションのパラメータを受け取る場合、パラメータの型をラッパー型(Integer, Long, Booleanなど)またはOptional<>にすることをお勧めします。プリミティブ型(int, long, booleanなど)はnullを表現できないため、オプションのパラメータには適していません。required = falseでプリミティブ型を指定した場合、Springはパラメータが欠落している場合にその型のデフォルト値(例: intなら0booleanならfalse)をバインドしようとしますが、これは意図しない挙動を引き起こす可能性があります。

Optionalクラス(Java 8以降)を使うと、パラメータが存在するかどうかをより安全かつ明示的に扱えます。

java
// GET /items?id=123 または GET /items の両方を処理したい場合
@GetMapping("/items_optional")
public String getItemOptional(@RequestParam Optional<Long> id) {
if (id.isPresent()) {
return "Getting item with ID: " + id.get();
} else {
return "Getting all items (optional id)";
}
}

デフォルト値 (defaultValue)

リクエストパラメータが提供されなかった場合にデフォルト値を設定したい場合は、defaultValue属性を使用します。defaultValue属性を指定すると、そのパラメータは自動的にオプション (required = false相当) になります。

defaultValue属性の値は文字列として指定し、Springがそれをパラメータの型に変換します。

java
// GET /list?page=2&size=50 または GET /list (page=0, size=20を使用) を処理
@GetMapping("/list")
public String getList(@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return String.format("Fetching list: page=%d, size=%d", page, size);
}

この例では、リクエストパラメータpageが指定されなければ0が、sizeが指定されなければ20が使用されます。

複数の値を持つパラメータ (ListArray)

同じ名前のリクエストパラメータが複数存在する場合があります。例えば、GET /products?id=101&id=102&id=103 のように、複数の製品IDを渡したい場合です。Spring MVCでは、ハンドラーメソッドのパラメータをListや配列として宣言することで、これらの複数の値を簡単に受け取ることができます。

“`java
// GET /products?id=101&id=102&id=103 を処理
@GetMapping(“/products/by-ids”)
public String getProductsByIds(@RequestParam List id) {
return “Fetching products with IDs: ” + id; // 例: “Fetching products with IDs: [101, 102, 103]”
}

// 配列でも同様に受け取れる
@GetMapping(“/products/by-ids-array”)
public String getProductsByIdsArray(@RequestParam Long[] id) {
return “Fetching products with IDs: ” + List.of(id);
}
“`

Springは、同じ名前のパラメータ値を収集し、指定されたListまたは配列の型に変換してバインドします。

Mapでのパラメータ取得

全てのリクエストパラメータをキー・バリューペアのMapとして取得したい場合は、@RequestParam Map<String, String> paramsのように宣言できます。

java
@GetMapping("/params")
public String getAllParams(@RequestParam Map<String, String> params) {
StringBuilder sb = new StringBuilder("Received parameters:\n");
params.forEach((key, value) -> sb.append(key).append("=").append(value).append("\n"));
return sb.toString();
}

この場合、リクエストパラメータのキーと値はすべて文字列としてMapに格納されます。個別の型変換が必要な場合は、@RequestParamで個別にパラメータを受け取る方が便利です。

リクエストヘッダー (@RequestHeader)

HTTPリクエストには、User-AgentAcceptContent-Typeなどのヘッダーが含まれます。これらのヘッダー情報は、クライアントの種類、許容するメディアタイプ、リクエストボディの形式などを知るために重要です。Spring MVCでは、@RequestHeaderアノテーションを使用して、特定のリクエストヘッダーの値を取得し、ハンドラーメソッドのパラメータとして受け取ることができます。

@RequestHeaderアノテーションは、ハンドラーメソッドのパラメータに付与します。デフォルトでは、メソッドパラメータ名と同じ名前(ヘッダー名は大文字・小文字を区別しないため、一般的にはケバブケースをキャメルケースに変換した名前)のヘッダーを探します。より確実には、ヘッダー名を明示的に指定します。

“`java
package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(“/headers”)
public class HeaderController {

// GET /headers - User-Agentヘッダーを取得
@GetMapping
public String getUserAgent(@RequestHeader("User-Agent") String userAgent) { // ヘッダー名を明示的に指定
    return "User Agent: " + userAgent;
}

// GET /headers/accept - Acceptヘッダーを取得 (パラメータ名と同じヘッダー名を使用)
@GetMapping("/accept")
public String getAcceptHeader(@RequestHeader String accept) { // "accept" ヘッダーを探す
    return "Accept Header: " + accept;
}

// 複数のヘッダーを取得
@GetMapping("/multi")
public String getMultipleHeaders(@RequestHeader("Host") String host,
                                 @RequestHeader("Connection") String connection) {
    return String.format("Host: %s, Connection: %s", host, connection);
}

}
“`

ヘッダー名の指定

@RequestHeaderも、value属性(またはname属性)で対応するヘッダー名を明示的に指定できます。HTTPヘッダー名は通常、User-Agentのように単語をハイフンでつなぐケバブケースですが、Javaのパラメータ名としてはキャメルケース(例: userAgent)が一般的です。そのため、@RequestHeader("User-Agent") String userAgentのように明示的にヘッダー名を指定することが一般的です。パラメータ名がヘッダー名をキャメルケースに変換したものと一致する場合は、@RequestHeader String userAgentのように省略してもSpringは正しく解決しようとしますが、明示的に指定する方が安全で分かりやすいでしょう。

必須・オプション設定 (required) と デフォルト値 (defaultValue)

@RequestHeaderrequired属性とdefaultValue属性を持ちます。デフォルトではrequired = true(必須)です。

ヘッダーが存在しない場合にエラーとせず、オプションとして扱いたい場合はrequired = falseを設定します。

java
// Refererヘッダーはオプション
@GetMapping("/referer")
public String getReferer(@RequestHeader(value = "Referer", required = false) String referer) {
if (referer != null) {
return "Referer: " + referer;
} else {
return "No Referer header";
}
}

パラメータが提供されなかった場合にデフォルト値を設定したい場合は、defaultValue属性を使用します。defaultValueを指定すると、パラメータは自動的にオプション (required = false相当) になります。

java
// Custom-Headerが指定されなければデフォルト値を使用
@GetMapping("/custom-header")
public String getCustomHeader(@RequestHeader(value = "Custom-Header", defaultValue = "DefaultValue") String customHeader) {
return "Custom Header: " + customHeader;
}

Mapでのヘッダー取得

全てのリクエストヘッダーをキー・バリューペアのMapとして取得したい場合は、@RequestHeader Map<String, String> headersのように宣言できます。キーはヘッダー名(通常はオリジナルの形式)、値はヘッダー値の文字列になります。

java
@GetMapping("/all-headers")
public String getAllHeaders(@RequestHeader Map<String, String> headers) {
StringBuilder sb = new StringBuilder("Received headers:\n");
headers.forEach((key, value) -> sb.append(key).append(": ").append(value).append("\n"));
return sb.toString();
}

Cookie (@CookieValue)

Webアプリケーションでは、セッション管理やユーザーのカスタマイズのためにCookieがよく利用されます。Spring MVCでは、@CookieValueアノテーションを使用して、特定のリクエストCookieの値を取得し、ハンドラーメソッドのパラメータとして受け取ることができます。

@CookieValueアノテーションは、ハンドラーメソッドのパラメータに付与します。@RequestParam@RequestHeaderと同様に、デフォルトではメソッドパラメータ名と同じ名前のCookieを探しますが、Cookie名を明示的に指定するのが一般的です。

“`java
package com.example.demo.controller;

import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(“/cookies”)
public class CookieController {

// GET /cookies - MyCookieという名前のCookieの値を取得
@GetMapping
public String getMyCookie(@CookieValue("MyCookie") String myCookieValue) { // Cookie名を明示的に指定
    return "Value of MyCookie: " + myCookieValue;
}

// オプションのCookie - JSESSIONIDは存在しない場合もある
@GetMapping("/session")
public String getSessionId(@CookieValue(value = "JSESSIONID", required = false) String sessionId) {
    if (sessionId != null) {
        return "JSESSIONID: " + sessionId;
    } else {
        return "JSESSIONID not found";
    }
}

// デフォルト値を持つCookie
@GetMapping("/user-pref")
public String getUserPreference(@CookieValue(value = "UserPreference", defaultValue = "default") String userPreference) {
    return "User Preference: " + userPreference;
}

}
“`

Cookie名の指定

@CookieValueも、value属性(またはname属性)で対応するCookie名を明示的に指定します。例えば、@CookieValue("MyCookie") String myCookieValueは、MyCookieという名前のCookieの値をString型のmyCookieValueパラメータにバインドします。

型変換

Cookieの値は常に文字列として取得されますが、Springはこれをパラメータの型(プリミティブ型、ラッパー型、Stringなど)に自動的に変換しようとします。

必須・オプション設定 (required) と デフォルト値 (defaultValue)

@CookieValuerequired属性とdefaultValue属性を持ち、これらは@RequestParam@RequestHeaderと同様に機能します。

デフォルトではrequired = true(必須)です。Cookieが存在しない場合にエラーとせず、オプションとして扱いたい場合はrequired = falseを設定します。オプションの場合、パラメータの型はラッパー型またはOptionalにする必要があります。

パラメータが提供されなかった場合にデフォルト値を設定したい場合は、defaultValue属性を使用します。defaultValueを指定すると、パラメータは自動的にオプション (required = false相当) になります。

コンシューマブル/プロデューサブルメディアタイプ (consumes, produces)

HTTPリクエストやレスポンスには、そのボディの内容がどのような形式(メディアタイプ、またはMIMEタイプ)であるかを示す情報が含まれます。リクエストではContent-Typeヘッダーで、レスポンスではAcceptヘッダー(クライアントが受け付け可能な形式)およびContent-Typeヘッダー(サーバーが実際に返信する形式)で指定されます。

@RequestMappingアノテーションは、consumes属性とproduces属性を使用して、リクエストとレスポンスのメディアタイプに基づいてマッピングを限定することができます。これは、同じURLパスでも、異なる形式のデータを扱う場合に、異なるハンドラーメソッドを呼び出したい場合に非常に役立ちます(例: 同じ/usersでも、application/jsonを受け付けるPOSTとapplication/xmlを受け付けるPOSTを分ける)。

consumes 属性

consumes属性は、ハンドラーメソッドが処理できるリクエストボディのメディアタイプを指定します。SpringはリクエストのContent-Typeヘッダーを見て、この属性にマッチするメソッドを探します。

値にはメディアタイプを表す文字列(例: "application/json", "text/plain", "application/xml")またはorg.springframework.http.MediaTypeクラスの定数(例: MediaType.APPLICATION_JSON_VALUE)を指定します。複数のメディアタイプを指定することも可能です。

“`java
package com.example.demo.controller;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(“/data”)
public class DataController {

// POST /data で、Content-Typeが application/json のリクエストを処理
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public String handleJsonRequest(@RequestBody String jsonBody) {
    System.out.println("Received JSON body: " + jsonBody);
    return "Processed JSON";
}

// POST /data で、Content-Typeが application/xml のリクエストを処理
@PostMapping(consumes = MediaType.APPLICATION_XML_VALUE)
public String handleXmlRequest(@RequestBody String xmlBody) {
    System.out.println("Received XML body: " + xmlBody);
    return "Processed XML";
}

// 複数のContent-Typeを受け付ける場合
@PostMapping(value = "/text", consumes = {MediaType.TEXT_PLAIN_VALUE, MediaType.TEXT_HTML_VALUE})
public String handleTextOrHtml(@RequestBody String textBody) {
    System.out.println("Received text or HTML body: " + textBody);
    return "Processed Text/HTML";
}

}
“`

上記の例では、POST /dataに対して、Content-Type: application/jsonのリクエストはhandleJsonRequest()に、Content-Type: application/xmlのリクエストはhandleXmlRequest()にマッピングされます。

produces 属性

produces属性は、ハンドラーメソッドが生成できるレスポンスボディのメディアタイプを指定します。SpringはリクエストのAcceptヘッダーを見て、この属性にマッチするメソッドを探します。また、レスポンスのContent-Typeヘッダーをこの属性の値に設定します。

値にはメディアタイプを表す文字列またはMediaTypeクラスの定数を指定します。複数のメディアタイプを指定することも可能です。その場合、クライアントのAcceptヘッダーで指定された優先順位に基づいて、最も適切なメディアタイプが選択されます(Content Negotiation)。

“`java
package com.example.demo.controller;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(“/content”)
public class ContentController {

// GET /content で、Acceptヘッダーに応じて異なる形式のレスポンスを生成
// Accept: application/json の場合はこちら
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public String getJsonContent() {
    System.out.println("Producing JSON response");
    return "{\"message\": \"Hello from JSON!\"}";
}

// Accept: application/xml の場合はこちら
@GetMapping(produces = MediaType.APPLICATION_XML_VALUE)
public String getXmlContent() {
    System.out.println("Producing XML response");
    return "<message>Hello from XML!</message>";
}

// デフォルトとしてJSONを返す (最も一般的なケース)
// produces属性が指定されていない、または複数のproduces属性を持つメソッドがある場合、
// SpringはAcceptヘッダーに基づいて最適なメソッドを選択しようとします。
// 通常、produces属性は具体的なメディアタイプを指定し、そのメディアタイプをサポートする
// HttpMessageConverterが利用可能である必要があります。
@GetMapping("/default")
public Object getDefaultContent() {
     // HttpMessageConverterによってJSONやXMLなどに変換されるオブジェクトを返す
     return new SimpleObject("Default Message");
}

}

class SimpleObject {
private String message;
public SimpleObject(String message) { this.message = message; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}
“`

上記の例では、GET /contentに対して、Accept: application/jsonヘッダーを持つリクエストはgetJsonContent()に、Accept: application/xmlヘッダーを持つリクエストはgetXmlContent()にマッピングされます。クライアントがAccept: application/json, application/xmlのように両方を受け付けることを示している場合、SpringはAcceptヘッダーのQファクター(優先度)や、アプリケーションで設定されているHttpMessageConverterの順序などを考慮して最適な形式を選択し、対応するメソッドを呼び出します。

@RestControllerを使用する場合、戻り値は自動的に適切なHttpMessageConverterによって指定されたproducesのメディアタイプに変換されてレスポンスボディに書き込まれます。JSONやXMLの変換には通常、JacksonやJAXBなどのライブラリが必要です。SpringBootではこれらのライブラリは自動的に設定されます。

consumes属性とproduces属性は、クラスレベルとメソッドレベルの両方で指定可能です。メソッドレベルの指定は、クラスレベルの指定をオーバーライドします。

リクエストパラメータ/ヘッダーの条件 (params, headers)

@RequestMappingは、リクエストパラメータやリクエストヘッダーの存在、あるいはその値に基づいてマッピングを限定することもできます。これは、同じパスに対しても、特定の条件を満たすリクエストだけを処理したい場合に便利です。例えば、APIのバージョンをヘッダーやクエリパラメータで指定する場合などに利用できます。

params 属性

params属性は、特定のリクエストパラメータが存在するか、あるいは特定の値を持つかによってマッピングを限定します。値には以下の形式の文字列を指定します。

  • paramName: リクエストパラメータparamNameが存在する場合にマッチ
  • !paramName: リクエストパラメータparamNameが存在しない場合にマッチ
  • paramName=paramValue: リクエストパラメータparamNameが存在し、かつその値がparamValueに等しい場合にマッチ
  • paramName!=paramValue: リクエストパラメータparamNameが存在し、かつその値がparamValueに等しくない場合にマッチ

“`java
package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(“/items”)
public class ItemController {

// GET /items?version=1 のみを処理
@GetMapping(params = "version=1")
public String getItemsV1(@RequestParam String version) {
    return "Getting items for API version 1";
}

// GET /items?version=2 のみを処理
@GetMapping(params = "version=2")
public String getItemsV2(@RequestParam String version) {
    return "Getting items for API version 2";
}

// GET /items (versionパラメータがない場合) を処理
@GetMapping(params = "!version")
public String getItemsDefault() {
    return "Getting items (default version)";
}

// GET /items?status=active&type=physical のみを処理
@GetMapping(params = {"status=active", "type=physical"})
public String getActivePhysicalItems(@RequestParam String status, @RequestParam String type) {
    return String.format("Getting active physical items (status: %s, type: %s)", status, type);
}

}
“`

この例では、/itemsという同じパスに対するGETリクエストでも、versionクエリパラメータの値によって異なるメソッドが呼び出されます。params属性は文字列の配列を受け取るため、複数の条件をAND条件として指定できます。

headers 属性

headers属性は、特定のリクエストヘッダーが存在するか、あるいは特定の値を持つかによってマッピングを限定します。値にはparams属性と同様の形式の文字列を指定します。ヘッダー名は大文字・小文字を区別しないマッチングが行われます。

  • headerName: リクエストヘッダーheaderNameが存在する場合にマッチ
  • !headerName: リクエストヘッダーheaderNameが存在しない場合にマッチ
  • headerName=headerValue: リクエストヘッダーheaderNameが存在し、かつその値がheaderValueに等しい場合にマッチ
  • headerName!=headerValue: リクエストヘッダーheaderNameが存在し、かつその値がheaderValueに等しくない場合にマッチ

“`java
package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(“/api”)
public class ApiController {

// GET /api で、X-API-Version: 1 ヘッダーを持つリクエストのみを処理
@GetMapping(headers = "X-API-Version=1")
public String getApiV1(@RequestHeader("X-API-Version") String apiVersion) {
    return "API Version 1 Response";
}

// GET /api で、X-API-Version: 2 ヘッダーを持つリクエストのみを処理
@GetMapping(headers = "X-API-Version=2")
public String getApiV2(@RequestHeader("X-API-Version") String apiVersion) {
    return "API Version 2 Response";
}

// GET /api (X-API-Versionヘッダーがない場合) を処理
 // Note: headers = "!X-API-Version" のマッチングは、上記の特定バージョンにマッチしなかった場合にのみ検討される
 // Springのマッピング順序に注意が必要
@GetMapping(headers = "!X-API-Version")
public String getApiDefault() {
    return "Default API Response";
}

// GET /api で、User-Agentヘッダーが特定の値を持つリクエストのみを処理
@GetMapping(headers = "User-Agent=MyApp")
public String getForMyApp(@RequestHeader("User-Agent") String userAgent) {
    return "Response for MyApp client";
}

}
“`

headers属性も文字列の配列を受け取り、複数の条件をAND条件として指定できます。

params属性とheaders属性は強力ですが、多用しすぎるとマッピングルールが複雑になり、どのメソッドが呼ばれるかを把握しにくくなる可能性があります。特にAPIバージョン管理においては、URIによるバージョン指定(例: /v1/users, /v2/users)の方がRESTfulの原則に近く、クライアント側も理解しやすいため推奨されることが多いです。ただし、ヘッダーによるバージョン指定など、特定のユースケースではこれらの属性が役立ちます。

パターンマッチング

@RequestMappingvalue(またはpath)属性では、静的なパスだけでなく、ワイルドカードを使ったパターンマッチングも利用できます。SpringはデフォルトでAntスタイルのパスパターンをサポートしており、柔軟なURLマッピングを可能にします。

Antスタイルのパスパターンで使えるワイルドカードは以下の通りです。

  • ?: 1文字にマッチします。
  • *: パスセグメント内(//の間、または/と終端の間)の0文字以上の文字にマッチします。
  • **: 0個以上のパスセグメントにマッチします。これは通常、パターンパスの最後や、独立したパスセグメント(例: /path/**/anotherPath)として使用されます。

“`java
package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(“/patterns”)
public class PatternMatchingController {

// /patterns/a?a にマッチ (例: /patterns/aaa, /patterns/aba)
@GetMapping("/a?a")
public String singleCharWildcard() {
    return "'?' matched";
}

// /patterns/files/* にマッチ (例: /patterns/files/report.pdf, /patterns/files/image.png)
// ただし、/patterns/files/some/report.pdf にはマッチしない
@GetMapping("/files/*")
public String segmentWildcard() {
    return "'*' matched in segment";
}

 // /patterns/any/path/here/... にマッチ (例: /patterns/any/a, /patterns/any/a/b, /patterns/any/a/b/c)
// **は複数のパスセグメントにマッチ
@GetMapping("/any/**")
public String multiSegmentWildcard() {
    return "'**' matched multiple segments";
}

// /patterns/users/{id} にマッチするが、IDは数値のみを許可する場合
// パス変数と組み合わせることも多い
@GetMapping("/users/{id:\\d+}") // IDが1つ以上の数字であることを要求
public String getUserById(@PathVariable Long id) {
    return "Matched user id (numeric): " + id;
}

// ** を使った例: /patterns/docs/2023/report.txt など、任意階層のファイルにマッチ
@GetMapping("/docs/**/{fileName}")
public String getDocFile(@PathVariable String fileName) {
     // 注意: ** とパス変数を組み合わせる場合、Springは最も具体的なマッチングを選択します。
     // fileNameだけではパス全体は取得できません。パス全体を取得するには HttpServletRequest などを利用します。
     return "Matched doc file with name: " + fileName;
}

}
“`

パス変数とパターンマッチング

上記の例のように、パス変数とワイルドカードや正規表現を組み合わせて使うことも可能です。{変数名:正規表現}の形式でパス変数に正規表現を適用すると、そのパスセグメントが正規表現にマッチする場合にのみマッピングが成功します。

マッチングの優先順位

複数の@RequestMappingパターンが1つのリクエストURLにマッチする場合、Springは最も具体的なパターンを持つハンドラーメソッドを優先します。具体性の判断は、静的なパスセグメントの数、ワイルドカードの種類(? < * < **)、パス変数の有無などに基づいて行われます。例えば、/users/123 というリクエストに対して、/users/{id} というパターンは /users/*/users/** よりも具体的であると判断され、優先されます。正規表現を含むパス変数は、含まないパス変数よりも具体的とみなされる場合があります。

** ワイルドカードは最も包括的なパターンであり、通常は最も低い優先順位と見なされます。そのため、より具体的な静的パスや他のワイルドカードパターンが優先されます。

Trailing Slash Matching (末尾スラッシュのマッチング)

Spring MVCでは、歴史的に/users/users/のような末尾にスラッシュがあるかないかのURLを同じものとみなす設定がデフォルトで行われていました。しかし、この動作は厳密なURLマッチングやRESTful原則と乖離する場合があるため、Spring Boot 2.6からはデフォルトで無効になっています。

現在のデフォルト設定(Spring Boot 2.6以降)では、/usersという@GetMapping/users/というリクエストにはマッチしません。もし両方のリクエストを受け付けたい場合は、明示的に両方のパターンを指定する必要があります。

“`java
// Spring Boot 2.6+ のデフォルト設定では、これは /users にのみマッチ
@GetMapping(“/users”)
public String getUsers() {
return “Users list”;
}

// /users と /users/ の両方にマッチさせたい場合
@GetMapping({“/users”, “/users/”})
public String getUsersAndTrailingSlash() {
return “Users list (with/without trailing slash)”;
}
“`

または、アプリケーションの設定でこの挙動を変更することも可能ですが、混乱を避けるためにも、明示的にパスを指定するか、新しいデフォルトの挙動に合わせたURL設計を行うことが推奨されます。

その他の属性

@RequestMappingには、これまで説明した主要な属性の他に、いくつかの属性があります。

  • name: マッピングに名前を付けるための属性です。これは主にデバッグやログ出力、フレームワークの内部処理(例: メソッドのリフレクション情報のキャッシュキーとして使用されるなど)で利用されることがあります。URLを生成するためのリバースルーティングの用途など、名前付きマッピングを特定のフレームワーク機能と連携させる場合にも役立ちますが、アプリケーションコード内で直接的に必須となるケースは少ないかもしれません。

java
@GetMapping(value = "/resource/{id}", name = "getResourceById")
public String getResource(@PathVariable Long id) {
// ...
return "Resource " + id;
}

name属性は任意であり、省略してもマッピングは正常に機能します。

@RequestMappingとSpring Boot

Spring BootはSpring Frameworkをベースにしており、@RequestMappingアノテーションはSpring Bootアプリケーションでも中心的な役割を果たします。SpringBootは開発者が迅速にSpringアプリケーションを構築できるように、多くの設定を自動で行います。@RequestMappingに関連する設定も、これらの自動設定の恩恵を受けています。

Spring Bootアプリケーションでは、@SpringBootApplicationアノテーションが付いたメインクラスを含むパッケージ、およびそのサブパッケージがComponent Scanの対象となります。@Controller@RestControllerアノテーションが付いたクラスはSpring Beanとして管理され、その中の@RequestMapping(またはショートカットアノテーション)が付いたメソッドがリクエストハンドラーとして登録されます。

Spring BootのWebスターター(spring-boot-starter-web)を使用すると、Spring MVCに必要な依存関係(DispatcherServlet、組み込みTomcat/Jetty/Undertow、JSON処理ライブラリなど)と自動設定が提供されます。これにより、開発者はXML設定ファイルなどをほとんど書くことなく、@RequestMappingアノテーションを使ってすぐにWebエンドポイントを定義し始めることができます。

特にRESTful API開発において、@RestController@RequestMapping(またはショートカットアノテーション)の組み合わせは非常に一般的です。@RestControllerを使うことで、クラス内のすべてのメソッドがデフォルトで@ResponseBodyの挙動をするため、JSONやXMLなどのデータを返すAPIエンドポイントを簡単に実装できます。また、Spring Bootの自動設定により、Jacksonなどのライブラリが自動的に設定され、JavaオブジェクトとJSON/XML間の変換が透過的に行われます。

“`java
package com.example.demo; // トップレベルパッケージ

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication // Spring Bootアプリケーションのエントリーポイント
public class DemoApplication {

public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
}

}

// 別ファイル、または同じファイル内 (通常は別ファイル)
package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController // このクラス内のメソッドは @ResponseBody が付与されたとして扱われる
@RequestMapping(“/api/greeters”) // クラスレベルのベースパス
public class GreeterController {

// GET /api/greeters/hello/{name}
@GetMapping("/hello/{name}") // メソッドレベルのパスとパス変数
public Greeting greet(@PathVariable String name) {
    // 戻り値のGreetingオブジェクトは自動的にJSONに変換される (Jacksonがあれば)
    return new Greeting("Hello", name);
}

// GET /api/greeters/goodbye
@GetMapping("/goodbye")
public Greeting goodbye() {
    return new Greeting("Goodbye", "World");
}

}

// 戻り値として使用する簡単なPOJO
class Greeting {
private String greeting;
private String target;

// JacksonなどがJSON <=> Object変換に必要とするデフォルトコンストラクタ (必要に応じて)
public Greeting() {}

public Greeting(String greeting, String target) {
    this.greeting = greeting;
    this.target = target;
}

// GetterとSetter (JSON変換に必要)
public String getGreeting() { return greeting; }
public void setGreeting(String greeting) { this.greeting = greeting; }
public String getTarget() { return target; }
public void setTarget(String target) { this.target = target; }

}
“`

上記のSpring Bootアプリケーション例では、@SpringBootApplicationcom.example.demoパッケージとそのサブパッケージをスキャンし、@RestControllerが付いたGreeterControllerをBeanとして登録します。GreeterController内の@GetMappingが付いたメソッドがリクエストハンドラーとして機能し、それぞれのメソッドの戻り値(Greetingオブジェクト)は、Spring Bootが自動設定したHttpMessageConverter(通常はJackson)によってJSON形式に変換され、HTTPレスポンスボディとして返されます。

このように、Spring Bootは@RequestMappingを使ったWeb開発を非常にシンプルかつ効率的に行えるように、必要なインフラストラクチャを自動的に提供してくれます。

実践的なヒントとベストプラクティス

@RequestMappingは非常に柔軟ですが、適切に使用しないとコードが読みにくくなったり、保守が難しくなったりします。ここでは、@RequestMappingを効果的に使用するための実践的なヒントとベストプラクティスを紹介します。

1. クラスレベルとメソッドレベルの組み合わせを活用する

共通のベースパスを持つエンドポイントをまとめるには、クラスレベルの@RequestMappingが非常に有効です。これにより、各メソッドの@RequestMappingのパス指定を短く保ち、コントローラーの役割を明確にできます。

例: /api/v1/usersに関連するエンドポイントはUserControllerにまとめ、クラスレベルで@RequestMapping("/api/v1/users")を指定する。

2. ショートカットアノテーション (@GetMappingなど) を積極的に使用する

可読性とメンテナンス性を向上させるため、@RequestMapping(method = ...)の代わりに@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMappingを使用することを強く推奨します。これは現代のSpring開発における標準的なプラクティスです。

3. RESTfulなURL設計を心がける

@RequestMappingを使ってエンドポイントを設計する際は、RESTfulの原則に従うことが望ましいです。

  • URIはリソースを表す名詞にする(例: /users, /products)。
  • HTTPメソッドで操作を表す(GET: 取得、POST: 作成、PUT: 全体更新、PATCH: 部分更新、DELETE: 削除)。
  • リソースの階層構造をURIで表現する(例: /users/{userId}/orders/{orderId})。
  • CRUD以外の操作は動詞をURIに含めることも許容される場合がある(例: /products/{productId}/purchase)が、可能な限りリソースとHTTPメソッドで表現することを検討する。

4. APIバージョン管理の検討

APIのバージョン管理は、後方互換性を維持しつつAPIを進化させる上で重要です。@RequestMappingは、バージョン管理のいくつかの方法に対応できます。

  • URIによるバージョン指定: 最も一般的で推奨される方法です。クラスレベルの@RequestMappingでバージョンを付加します。例:
    “`java
    @RestController
    @RequestMapping(“/api/v1/users”) // バージョンをURIに含める
    public class UserControllerV1 { … }

    @RestController
    @RequestMapping(“/api/v2/users”)
    public class UserControllerV2 { … }
    ``
    * **Headerによるバージョン指定**:
    headers属性を利用します。例:headers = “X-API-Version=1”。URIがシンプルになる利点がありますが、クライアントがカスタムヘッダーを扱いやすいか、ドキュメントで明確にする必要があるかなどを考慮する必要があります。
    * **Query Parameterによるバージョン指定**:
    params属性を利用します。例:params = “version=1″`。シンプルですが、URIが冗長になりやすく、RESTful原則からは少し外れる傾向があります。

どの方法を選択するかはプロジェクトの要件によりますが、URIによるバージョン指定が最も分かりやすく、@RequestMappingとも自然に連携できます。

5. パス変数やリクエストパラメータの名前を明確にする

@PathVariable@RequestParamを使用する際は、パラメータ名がその意味を明確に表していることを確認してください。必要であれば、value属性で明示的に指定します。

6. consumes および produces を適切に利用する

JSONやXMLなど、特定のメディアタイプのみを処理/生成するAPIエンドポイントでは、consumesproduces属性を明示的に指定することで、意図しないリクエスト形式を拒否したり、ドキュメントを明確にしたりすることができます。特にRESTful APIではproduces = MediaType.APPLICATION_JSON_VALUEなどを指定することが一般的です。

7. エラーハンドリングとの連携を考慮する

@RequestMappingでマッピングされたハンドラーメソッド内で例外が発生した場合、Springの例外ハンドリングメカニズム(@ExceptionHandler, @ControllerAdviceなど)によって捕捉・処理されます。これらの例外ハンドラーも特定のリクエストパスや例外タイプに紐づけることが可能ですが、通常は@ControllerAdviceを使ったグローバルな例外ハンドリングを設定することで、@RequestMappingを持つすべてのコントローラーメソッドからの例外を一元的に処理できます。

8. ドキュメンテーションツールとの連携

Swagger/OpenAPIなどのAPIドキュメンテーションツールは、@RequestMappingアノテーションの情報(パス、HTTPメソッド、パラメータ、consumes/producesなど)を読み取り、API仕様書を自動生成します。適切な@RequestMappingの使用は、正確なAPIドキュメント生成の基盤となります。

9. パターンマッチングは慎重に使う

**のような広範なワイルドカードは強力ですが、予期しないリクエストにマッチしてしまう可能性があります。より具体的なパス指定を優先し、ワイルドカードは必要な範囲でのみ、意図が明確になるように使用することが推奨されます。

@RequestMappingの代替手段/関連アノテーション

@RequestMappingはSpring MVC/Bootにおける主要なマッピングアノテーションですが、それ単独で使われることは少なく、他のアノテーションと組み合わせて使われるか、または代替となるアノテーションが存在します。

@RestController@ResponseBody

既に何度か触れましたが、@RestController@Controller@ResponseBodyを組み合わせたアノテーションです。@ControllerはModelAndViewを返してViewの名前を解決するビュー指向のコントローラーで使われることが多いのに対し、@RestControllerはメソッドの戻り値をHTTPレスポンスボディに直接書き込むRESTful API向けに設計されています。@RestControllerを使うと、そのクラス内のすべての@RequestMapping(またはショートカットアノテーション)を持つメソッドが自動的に@ResponseBodyの動作をするようになります。RESTful APIを開発する場合は、通常@RestControllerを使用します。

“`java
@RestController // このクラスのメソッドはデフォルトで @ResponseBody の動作
@RequestMapping(“/api/users”)
public class RestUserController {

// このメソッドの戻り値(Userオブジェクト)は自動的にJSONなどに変換されてレスポンスボディに書き込まれる
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) { ... }

}
“`

@RequestBody

@RequestBodyアノテーションは、HTTPリクエストボディの内容をハンドラーメソッドのパラメータとして受け取るために使用されます。リクエストボディは、JSONやXMLなどの形式でデータを送信する際(特にPOSTやPUTリクエスト)に利用されます。Springは、HttpMessageConverterを使用して、リクエストボディを指定されたパラメータの型に変換します。

java
// @RestController環境下
@PostMapping("/products")
public Product createProduct(@RequestBody Product product) { // リクエストボディ(例:JSON)をProductオブジェクトに変換して受け取る
// ...
return product;
}

@RequestMappingconsumes属性と@RequestBodyは密接に関連しています。consumesで指定されたメディアタイプに対応するHttpMessageConverterが、@RequestBodyで指定されたパラメータの型への変換を試みます。

その他のアノテーション

Spring MVC/Bootには、リクエストからデータを取得するための他のアノテーションも多数存在します。

  • @ModelAttribute: クエリパラメータやフォームデータ、あるいはパス変数やセッション属性からオブジェクトにまとめてデータをバインドします。GETリクエストの複雑なフィルタリング条件をオブジェクトで受け取る場合などに便利です。また、ModelAndViewに自動的に属性として追加されるため、Viewにデータを渡す際にも利用されます。
  • @SessionAttribute: HTTPセッションから属性値を取得します。
  • @RequestAttribute: HttpServletRequestのリクエスト属性から値を取得します。
  • @MatrixVariable: マトリックス変数(URLパスの一部に;param=valueの形式で埋め込まれる変数)を取得します。これはRESTfulな設計の一つのスタイルですが、あまり一般的ではありません。

これらのアノテーションは、@RequestMappingでリクエストが特定のハンドラーメソッドにマッピングされた後、そのメソッドのパラメータにリクエストの様々な部分をバインドするために使用されます。つまり、@RequestMappingが「どのメソッドを呼び出すか」を決定し、これらのアノテーションが「呼び出されたメソッドにどのようなデータを渡すか」を決定する役割を担います。

まとめ

この記事では、Spring MVCおよびSpring Bootにおける@RequestMappingアノテーションについて、その基本的な使い方から詳細な機能までを網羅的に解説しました。@RequestMappingは、リクエストパス、HTTPメソッド、リクエストパラメータ、ヘッダー、Cookie、メディアタイプなど、多岐にわたる条件に基づいてHTTPリクエストをハンドラーメソッドにマッピングする、Spring Web開発の中核をなすアノテーションです。

  • クラスレベルとメソッドレベルで適用することで、ベースパスと個別パスを組み合わせて定義できます。
  • method属性または専用のショートカットアノテーション(@GetMappingなど)で、HTTPメソッドを限定したマッピングが可能です。特にショートカットアノテーションは可読性が高く推奨されます。
  • @PathVariableでURLパスの動的な値を、@RequestParamでクエリパラメータやフォームデータを、@RequestHeaderでリクエストヘッダーを、@CookieValueでCookieの値をハンドラーメソッドのパラメータとして簡単に取得できます。これらのアノテーションは型変換、必須/オプション設定、デフォルト値指定の機能を提供します。
  • consumes属性とproduces属性で、リクエスト/レスポンスのメディアタイプに基づいてマッピングを限定できます。これにより、Content Negotiationを実装したり、異なる形式のデータを扱うエンドポイントを区別したりできます。
  • params属性とheaders属性で、リクエストパラメータやヘッダーの存在や値に基づいて、より詳細なマッピング条件を設定できます。
  • Antスタイルのパスパターン(*, **, ?)や正規表現を組み合わせることで、柔軟なURLパターンマッチングが可能です。
  • Spring Boot環境では、@RequestMappingは自動設定されたWebインフラストラクチャとシームレスに連携し、特に@RestControllerと組み合わせることでRESTful APIの開発を効率化します。

@RequestMappingの機能を深く理解し、本記事で紹介した様々な属性や関連アノテーション、そして実践的なヒントを活用することで、あなたはSpring MVC/Bootアプリケーションにおいて、要求仕様に正確に合致し、かつ保守性の高いWebエンドポイントを設計・実装できるようになります。

Spring Web開発の旅は続きます。@RequestMappingはその強力な羅針盤となるでしょう。ぜひ実際にコードを書きながら、その機能と柔軟性を体感してください。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール