spring
springframework
spring-mvc

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

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

特定のクラスを指定して受け取る

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

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

WebRequest

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

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

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

HttpMethod

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

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

ServletRequest

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

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

ServletResponse

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

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

MultipartRequest

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

@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を直接操作する場合に使用します。

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

Principal

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

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

Locale

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

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

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

リクエストヘッダーのAccept-Languageで表示する言語を切り替える場合、次のような実装をする必要があります。

@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です。

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

Reader

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

Writer

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

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