Spring MVC コントローラの引数 その2

Spring MVCのコントローラメソッドは、画面で入力されたパラメータ以外に、様々な情報を引数として受け取ることができます。

# 特定のクラスを指定して受け取る
メソッドの引数に特定のクラスを指定することで受け取ることができる情報がいくつかあります。

+ <font color="DarkGray">org.springframework.web.context.request.</font>[WebRequest](#webrequest)
+ <font color="DarkGray">org.springframework.http.</font>[HttpMethod](#httpmethod)
+ <font color="DarkGray">javax.servlet.</font>[ServletRequest](#servletrequest)
+ <font color="DarkGray">javax.servlet.</font>[ServletResponse](#servletresponse)
+ <font color="DarkGray">org.springframework.web.multipart.</font>[MultipartRequest](#multipartrequest)
+ <font color="DarkGray">javax.servlet.http.</font>[HttpSession](#httpsession)
+ <font color="DarkGray">java.security.</font>[Principal](#principal)
+ <font color="DarkGray">java.util.</font>[Locale](#locale)
+ <font color="DarkGray">java.util.</font>[TimeZone](#timezone)
+ <font color="DarkGray">java.time.</font>[ZoneId](#zoneid)
+ <font color="DarkGray">java.io.</font>[InputStream](#inputstream)
+ <font color="DarkGray">java.io.</font>[OutputStream](#outputstream)
+ <font color="DarkGray">java.io.</font>[Reader](#reader)
+ <font color="DarkGray">java.io.</font>[Writer](#writer)

> Note
これらの引数にオブジェクトをバインドする処理は、org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolverとorg.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolverに実装されています。

## WebRequest
WebRequestは一般的なWebのリクエストのメタデータにアクセスするための汎用的なインターフェースを提供します。
このWebRequestから、リクエストパラメータや、リクエストヘッダなどを取得できますが、ほとんどのメタデータは専用のアクセス方法が用意されているので、WebRequestを使用するケースは限定されるでしょう。

例えば、リクエストヘッダのキー名の一覧を取得する場合は次のようになります。

```java
@GetMapping("hello")
public String hello(WebRequest webRequest) {
    webRequest.getHeaderNames().forEachRemaining(System.out::println);
```

## HttpMethod
Httpリクエストのメソッドの種類を表す列挙です。
例えば、GETとPOSTの両方のリクエストを受け付けるコントローラーで、実際のリクエストのメソッドを判定する場合などに使用できます。

```java
@RequestMapping("hello")
public String hello(HttpMethod httpMethod) {
    if (httpMethod == HttpMethod.GET) {
            ...
    }
```

## ServletRequest
Java EEのServlet APIで規定されているServletRequestです。
リクエストの生の情報を取得する場合に使用します。

```java
@GetMapping("hello")
public String hello(ServletRequest servletRequest) {
    String protocol = servletRequest.getProtocol();
```

## ServletResponse
Java EEのServlet APIで規定されているServletResponseです。
Servletがクライアントに返すレスポンスを操作する場合に使用します。

```java
@GetMapping("hello")
public String hello(ServletResponse servletResponse) {
    servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.name());
```

## MultipartRequest
Multipartリクエストによりアップロードされたファイルを受け取る場合に使用します。
ファイルのアップロードはMultipartFileオブジェクトで受け取ることができますが、複数のアップロードファイルをまとめて処理する場合や、JavaScriptなどで動的にファイルアップロードの入力項目が動的に増減する場合はMultipartRequestオブジェクトのほうが操作しやすいです。

```java
@PostMapping("hello")
public String hello(MultipartRequest multipartRequest) {
    multipartRequest.getFileNames().forEachRemaining(param -> {
        System.out.println(multipartRequest.getFile(param).getOriginalFilename());
    });
```

## HttpSession
Java EEのServlet APIで規定されているHttpSessionです。
HttpSessionを直接操作する場合に使用します。

```java
@GetMapping("hello")
public String hello(HttpSession httpSession) {
    Collections.list(httpSession.getAttributeNames()).forEach(System.out::println);
```

## Principal
認証情報などを受け取る場合に使用します。

```java
@GetMapping("hello")
public String hello(Principal principal) {
    String userId = principal.getName();
```

## Locale
ロケール情報を受け取ります。ロケール情報から言語を取得することができるので、国際化アプリケーションで表示する言語を自動的に選択する場合に使用できます。

```java
@GetMapping("hello")
public String hello(Locale locale) {
    ResourceBundle resources = ResourceBundle.getBundle("messages", locale);
```

このLocaleオブジェクトはリクエストヘッダーのAccept-Languageをもとに設定されます。
リクエストヘッダーにAccept-Languageが存在しない場合、アプリケーションで既定しているデフォルトの言語を選択したい、というケースでは、このLocaleオブジェクトは使用できません。
なぜなら、リクエストヘッダーにAccept-Languageが存在しない場合でも、Localeオブジェクトは言語が設定された状態になり、アプリケーションから見ると、Localeオブジェクトには常に何らかのロケールが設定されているからです。
これは、ServletRequestインターフェースのgetLocale()メソッドの仕様に原因があります。
getLocale()メソッドの動作は「リクエストがAccept-Languageヘッダーを提供しない場合、このメソッドはサーバーのデフォルトロケールを返します。」と規定されているからです。

リクエストヘッダーのAccept-Languageでロケールを制御する場合、次のような実装をする必要があります。

```java
@GetMapping("hello")
public String hello(Locale locale,
        @RequestHeader(name = "Accept-Language", required = false) String acceptLanguage) {
    // リクエストヘッダーにAccept-Languageが存在しない場合
    if (acceptLanguage == null) {
        // アプリケーションのデフォルトLocaleを設定
        locale = Locale.JAPANESE;
    }
    ResourceBundle resources = ResourceBundle.getBundle("messages", locale);
```

## TimeZone
画面で動的に言語を切り替えるアプリケーションで、切り替えにLocaleResolverを使用した場合、TimeZoneを取得することができます。
LocaleResolverを使用していない場合、サーバーのデフォルトTimeZoneになります。

## ZoneId
Localeと同様、画面で動的に言語を切り替えるアプリケーションで、切り替えにLocaleResolverを使用した場合、ZoneIdを取得することができます。
LocaleResolverを使用していない場合、サーバーのデフォルトZoneIdになります。

## InputStream
リクエストボディーをInputStreamで受け取ります。
しかし、リクエストボディーはSpringによって読み取られているので、InputStreamは常に空になります。

## OutputStream
レスポンスの内容を出力するためのOutputStreamです。

```java
@PostMapping("hello")
@ResponseBody
public void hello(OutputStream out) throws IOException {
    out.write("Hello".getBytes(StandardCharsets.UTF_8));
```

## Reader
リクエストボディーをReaderで受け取ります。
しかし、リクエストボディーはSpringによって読み取られているので、InputStream同様、Readerは常に空になります。

## Writer
レスポンスの内容を出力するためのWriterです。

```java
@PostMapping("hello")
@ResponseBody
public void hello(Writer writer) throws IOException {
    writer.write("Hello");
```