kagamihogeの日記

kagamihogeの日記です。

Web on Reactive Stack(version 5.0.3.RELEASE) 2. WebClientをテキトーに訳した

https://docs.spring.io/spring/docs/5.0.3.RELEASE/spring-framework-reference/web-reactive.html#webflux-client

2. WebClient

spring-webfluxモジュールには、Reactive Streamsのバックプレッシャを用いるHTTPリクエスト用のノンブロッキング・リアクティブなクライアントが含まれます。

このクライアントはHTTP codecsおよびサーバのfunctional web frameworkの基礎部分は共通です。

WebClientはHTTPクライアントライブラリを下側に置く高レベルのAPIです。デフォルトではReactor Nettyを使いますが、別のClientHttpConnectorを使用してプラグイン可能です。WebClient APIは出力にFluxMonoを返し、入力にReactive StreamsのPublisherを受けます。(Reactive Libraries参照)

RestTemplateと比較すると、WebClientはより関数型でJava 8のラムダを取り入れたfluent APIです。同期・非同期の両方を備え、ストリーミングを含むので、ノンブロッキングI/Oで有用です。

2.1. Retrieve

retrieve()メソッドで簡単にレスポンスボディを取得してデコードできます。

    WebClient client = WebClient.create("http://example.org");

    Mono<Person> result = client.get()
            .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
            .retrieve()
            .bodyToMono(Person.class);

また、レスポンスからデコードするオブジェクトのストリームを得られます。

    Flux<Quote> result = client.get()
            .uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
            .retrieve()
            .bodyToFlux(Quote.class);

デフォルトでは、4xxか5xxのステータスコードのレスポンスはWebClientResponseExceptionのエラーになりますが、これはカスタマイズ可能です。

    Mono<Person> result = client.get()
            .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
            .retrieve()
            .onStatus(HttpStatus::is4xxServerError, response -> ...)
            .onStatus(HttpStatus::is5xxServerError, response -> ...)
            .bodyToMono(Person.class);

2.2. Exchange

exchange()ではより細かい制御ができます。以下の例はretrieve()と同等ですが、ClientResponseにアクセスしています。

    Mono<Person> result = client.get()
            .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
            .exchange()
            .flatMap(response -> response.bodyToMono(Person.class));

この段階で、完全なResponseEntityを生成することもできます。

    Mono<ResponseEntity<Person>> result = client.get()
            .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
            .exchange()
            .flatMap(response -> response.toEntity(Person.class));

retrieve()と異なり、exchange()では、4xxと5xxレスポンスにおける自動的なエラー通知はありません。自前でステータスコードをチェックして続行するかを決めるコードを書く必要があります。

exchange()を使う場合、ClientResponseのtoEntityかbodyメソッドのいずれかを必ず使用してください。これは、リソースを開放してHTTPコネクションプーリングに潜在的な問題を抱えるのを回避するためです。もしレスポンスの中身が無い場合はbodyToMono(Void.class)を使います。ただし、レスポンスの中身がある場合、コネクションは閉じてプールには戻らない点に注意してください。

2.3. Request body

リクエストボディはObjectからエンコードします。

    Mono<Person> personMono = ... ;

    Mono<Void> result = client.post()
            .uri("/persons/{id}", id)
            .contentType(MediaType.APPLICATION_JSON)
            .body(personMono, Person.class)
            .retrieve()
            .bodyToMono(Void.class);

エンコードされたオブジェクトのストリームも使えます。

    Flux<Person> personFlux = ... ;

    Mono<Void> result = client.post()
            .uri("/persons/{id}", id)
            .contentType(MediaType.APPLICATION_STREAM_JSON)
            .body(personFlux, Person.class)
            .retrieve()
            .bodyToMono(Void.class);

値そのままを使う場合、syncBodyというショートカットのメソッドを使います。

    Person person = ... ;

    Mono<Void> result = client.post()
            .uri("/persons/{id}", id)
            .contentType(MediaType.APPLICATION_JSON)
            .syncBody(person)
            .retrieve()
            .bodyToMono(Void.class);

2.3.1. Form data

フォームデータの送信にはボディにMultiValueMap<String, String>を使用します。なお、MultiValueMap<String, String>によって自動的に"application/x-www-form-urlencoded"をセットします。

    MultiValueMap<String, String> formData = ... ;

    Mono<Void> result = client.post()
            .uri("/path", id)
            .syncBody(formData)
            .retrieve()
            .bodyToMono(Void.class);

BodyInsertersでインラインにフォームデータを記述できます。

    import static org.springframework.web.reactive.function.BodyInserters.*;

    Mono<Void> result = client.post()
            .uri("/path", id)
            .body(fromFormData("k1", "v1").with("k2", "v2"))
            .retrieve()
            .bodyToMono(Void.class);

2.3.2. Multipart data

マルチパートデータの送信にはMultiValueMap<String, ?>を使用し、その値には、パートのボディを表すObjectか、パートのボディとヘッダーを表すHttpEntity、のどちらかを使います。パートを作るにはMultipartBodyBuilderを使います。

    MultipartBodyBuilder builder = new MultipartBodyBuilder();
    builder.part("fieldPart", "fieldValue");
    builder.part("filePart", new FileSystemResource("...logo.png"));
    builder.part("jsonPart", new Person("Jason"));

    MultiValueMap<String, HttpEntity<?>> parts = builder.build();

    Mono<Void> result = client.post()
            .uri("/path", id)
            .syncBody(parts)
            .retrieve()
            .bodyToMono(Void.class);

なお、各パートのコンテンツタイプは書き込むファイルの拡張子かObjectの型に基づいて自動設定します。また、各パートごとにコンテンツタイプを明示的に指定することも可能です。

BodyInsertersでインラインにマルチパートデータを記述するやり方もあります。

    import static org.springframework.web.reactive.function.BodyInserters.*;

    Mono<Void> result = client.post()
            .uri("/path", id)
            .body(fromMultipartData("fieldPart", "value").with("filePart", resource))
            .retrieve()
            .bodyToMono(Void.class);

2.4. Builder options

WebClientの生成は簡単で、staticファクトリメソッドcreate()と、すべてのリクエストのベースとなるURLを与えるcreate(String)で行います。WebClient.builder()にはより細かいオプションがあります。

基底のHTTPクライアントをカスタマイズするには以下のようにします。

    SslContext sslContext = ...

    ClientHttpConnector connector = new ReactorClientHttpConnector(
            builder -> builder.sslContext(sslContext));

    WebClient webClient = WebClient.builder()
            .clientConnector(connector)
            .build();

HTTPメッセージのエンコード・デコードで使われるHTTP codecsをカスタマイズするには以下のようにします。

    ExchangeStrategies strategies = ExchangeStrategies.builder()
            .codecs(configurer -> {
                // ...
            })
            .build();

    WebClient webClient = WebClient.builder()
            .exchangeStrategies(strategies)
            .build();

ビルダーはFiltersを追加するのに使用します。

URIの構築やデフォルトヘッダー(とクッキー)などその他のオプションについてはIDEWebClient.Builderを確認してみてください。

WebClientの作成後は、新しくWebClientを生成するための新しいbuilderを常に取得可能です。元のインスタンスをベースにしますが、現在のインスタンスには何も影響を与えません。

    WebClient modifiedClient = client.mutate()
            // user builder methods...
            .build();

2.5. Filters

WebClient supports interception style request filtering:

    WebClient client = WebClient.builder()
            .filter((request, next) -> {
                ClientRequest filtered = ClientRequest.from(request)
                        .header("foo", "bar")
                        .build();
                return next.exchange(filtered);
            })
            .build();

ExchangeFilterFunctionsはベーシック認証用のフィルターです。

    // static import of ExchangeFilterFunctions.basicAuthentication

    WebClient client = WebClient.builder()
            .filter(basicAuthentication("user", "pwd"))
            .build();

また、オリジナルのインスタンスに影響を与えることなくWebClientインスタンスを変更できます*1

*1:You can also mutate an existing WebClient instance without affecting the original:が原文。mutateを「変更」と訳すのはだいぶニュアンスが失われているとは思うが、あんま良い日本語浮かばず。

Web on Reactive Stack(version 5.0.3.RELEASE) 1. Spring WebFluxをテキトーに訳した(1.5 - 1.9)

1.5. Functional Endpoints

Spring WebFluxには、軽量な関数型プログラミングモデルがあり、関数ではルーティング・リクエスト処理とイミュータブルな設計を行います。アノテーションベースとは別のプログラミングモデルですが、基礎部分で動作するReactive Spring Webは同一です。

1.5.1. HandlerFunction

HTTPリクエストはHandlerFunctionで処理し、これは基本的にはServerRequestを受け取ってMono<ServerResponse>を返す関数です。アノテーションベースを知っているユーザ向けに言えば、ハンドラファクションは@RequestMappingメソッドに相当します。

ServerRequestServerResponseはイミュータブルなインタフェースで、基底のHTTPメッセージにJDK-8らしいやり方でアクセスを提供します。アクセスはReactive Streamsのノンブロッキングバックプレッシャで行います。リクエストはReactorのFluxもしくはMonoでボディを公開し、レスポンスはボディとしてReactive StreamsのPublisherをアクセプトします。これの合理性についてはReactive Librariesで解説します。

ServerRequestはHTTPリクエストの各種要素、メソッド・URI・クエリパラメータ・ヘッダー(ServerRequest.Headers経由)、へのアクセスを提供します。ボディへのアクセスはbodyメソッドで行います。たとえば、以下はリクエストボディをMono<String>に抽出します。

Mono<String> string = request.bodyToMono(String.class);

また、以下はボディをFluxに抽出する方法で、Personはボディのコンテンツからデシリアライズするクラスです(つまりボディがJSON, JAXB(XMLの場合)を含む場合PersonクラスはJacksonが処理する)。

Flux<Person> people = request.bodyToFlux(Person.class);

上述のbodyToMonobodyToFluxジェネリックServerRequest.body(BodyExtractor)メソッドを使用する便利なメソッドです。BodyExtractorは関数型のストラテジインタフェースで、自前の抽出ロジックを書くものですが、一般的なBodyExtractorインスタンスは```BodyExtractors````ユーティリティクラスにあります。よって、上記サンプルは以下のようにも書けます。

Mono<String> string = request.body(BodyExtractors.toMono(String.class);
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class);

同様に、ServerResponseはHTTPレスポンスへのアクセスを提供します。これはイミュータブルなので、ビルダーでServerResponseを生成します。ビルダーで、レスポンスステータス・レスポンスヘッダーの追加・ボディの指定、が出来ます。たとえば、レスポンスを、200 OKステータス・JSONコンテンツタイプ・ボディ指定、で作るには以下のようにします。

Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);

また、以下は、201 CREATEDステータス・"Location"ヘッダー・空のボディ、のレスポンスを作ります。

URI location = ...
ServerResponse.created(location).build();

HandlerFunctionをこれらを使用して作れます。たとえば、以下はごく単純な"Hello World"なラムダのハンドラーで、200 ステータス・ボディにString、のレスポンスを返します。

HandlerFunction<ServerResponse> helloWorld =
  request -> ServerResponse.ok().body(fromObject("Hello World"));

上述のように、ラムダでハンドラ関数を簡単に書けますが、場合によっては可読性を損なうので、複数の関数がある場合はメンテナンス性が悪くなります。よって、単一のハンドラかコントローラに、関連するハンドラ関数をグループ化することを推奨します。たとえば、以下はreactiveのPersonリポジトリを公開するクラスです。

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;

public class PersonHandler {

    private final PersonRepository repository;

    public PersonHandler(PersonRepository repository) {
        this.repository = repository;
    }

    public Mono<ServerResponse> listPeople(ServerRequest request) { // 1.
        Flux<Person> people = repository.allPeople();
        return ServerResponse.ok().contentType(APPLICATION_JSON).body(people, Person.class);
    }

    public Mono<ServerResponse> createPerson(ServerRequest request) { // 2.
        Mono<Person> person = request.bodyToMono(Person.class);
        return ServerResponse.ok().build(repository.savePerson(person));
    }

    public Mono<ServerResponse> getPerson(ServerRequest request) { // 3.
        int personId = Integer.valueOf(request.pathVariable("id"));
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();
        Mono<Person> personMono = repository.getPerson(personId);
        return personMono
                .flatMap(person -> ServerResponse.ok().contentType(APPLICATION_JSON).body(fromObject(person)))
                .switchIfEmpty(notFound);
    }
}
  1. listPeopleJSONリポジトリのすべてのPersionオブジェクトを返します。
  2. createPersonはリクエストボディ内のPersionを新規作成します。PersonRepository.savePerson(Person)Mono<Void>を返す点に注意してください。personがリクエストから読み込まれて保存されると、空のMonoは完了シグナルを出します。完了シグナル受信時、つまりPerson保存完了時、にレスポンスを送信するにはbuild(Publisher<Void>)を使うことになります。
  3. getPersonはパス変数にIDを指定して単一のpersonを返すハンドラー関数です。リポジトリからPersonを取得し、もし存在すればJSONレスポンスを生成します。無ければ、switchIfEmpty(Mono<T>)で404 Not Foundレスポンスを返しています。

1.5.2. RouterFunction

リクエストはRouterFunctionがハンドラー関数にルーティングします。RouterFunctionの関数はServerRequestを受け取ってMono<HandlerFunction>を返します。リクエストがある特定のルーティングにマッチするとハンドラ関数が返され、マッチしない場合は空のMonoが返されます。アノテーションベースを知っているユーザ向けに言えば、RouterFunction@RequestMappingアノテーションに相当します。

基本的には、ルータ―関数を自身で書くことはありませんが、リクエスト述語とハンドラ関数でルータ関数を生成するにはRouterFunctions.route(RequestPredicate, HandlerFunction)を使います。述語が適用可能な場合、そのリクエストはハンドラー関数にルーティングが行われ、そうでない場合ルーティングは実行されず、404 Not Foundレスポンスが返されます。自前のRequestPredicateを書くことは可能ですが、そうする必要は無く、良くある一般的な述語はRequestPredicatesユーティリティクラスにあり、例えば、パスペース・HTTPメソッド・コンテンツタイプなどでのマッチングです。routeを使用する、"Hello World"なハンドラ―関数へのルーティングの記述は以下のようになります。

RouterFunction<ServerResponse> helloWorldRoute =
    RouterFunctions.route(RequestPredicates.path("/hello-world"),
    request -> Response.ok().body(fromObject("Hello World")));

二つのルータ関数を一つの複合ルータ関数にまとめることが出来ます。最初のルーティングの述語にマッチしない場合、二つ目が評価されます。複合ルータ関数はそれぞれ順に評価されるので、狭い述語を先に置き、より汎用の述語を後に置きます。RouterFunction.and(RouterFunction)RouterFunction.andRoute(RequestPredicate, HandlerFunction)で二つのルータ関数を複合化します。後者はRouterFunction.and()RouterFunctions.route()を組み合わせて一度にやるものです。

上述したPersonHandlerを例にとると、個々のハンドラへのルータ関数は以下のように定義します。ハンドラ関数の参照はメソッド参照を使用しています。

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;

PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);

RouterFunction<ServerResponse> personRoute =
    route(GET("/person/{id}").and(accept(APPLICATION_JSON)), handler::getPerson)
        .andRoute(GET("/person").and(accept(APPLICATION_JSON)), handler::listPeople)
        .andRoute(POST("/person").and(contentType(APPLICATION_JSON)), handler::createPerson);

ルータ関数では、RequestPredicate.and(RequestPredicate)RequestPredicate.or(RequestPredicate)で、リクエスト述語の組み合わせを指定しています。これらの動作は見た目ままで、andは与えられた述語が両方ともマッチした場合で、orどちらかです。RequestPredicatesの述語の多くが複合になっています。たとえば、RequestPredicates.GET(String)RequestPredicates.method(HttpMethod)RequestPredicates.path(String)の複合です。

1.5.3. Running a server

HTTPサーバでのルータ関数の動かし方について。最もシンプルなやり方はルータ関数を以下のいずれかの手段でHttpHandlerに変換します。

  • RouterFunctions.toHttpHandler(RouterFunction)
  • RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)

戻り値のHttpHandlerは各種サーバ固有のアダプタで使用可能で、以下のHttpHandlerに示す通りです。

別の方法として、WebFlux Configを使用するDispatcherHandlerベースのセットアップで動かす方法があります。WebFlux Configはリクエスト処理に必要となるコンポーネントを宣言するのにSpring configurationを使用します。WebFlux Java configは以下のファンクショナルエンドポイント用の基盤コンポーネントを宣言します。

  • RouterFunctionMapping - Spring configurationで一つ以上のRouterFunction<?>を検出すると、それらをRouterFunction.andOtherで複合化し、出来たRouterFunctionにリクエストをルーティングする。
  • HandlerFunctionAdapter - リクエストにマッピングされているHandlerFunctionDispatcherHandlerが呼び出すためのシンプルなアダプタ。
  • ServerResponseResultHandler - HandlerFunctionの呼び出し結果をServerResponsewriteToを呼び出すことで処理する。

上述のコンポーネントDispatcherHandlerのリクエスト処理ライフサイクルにファンクショナルエンドポイントをフィットさせるためのもので、同時に、アノテーションのコントローラがもし存在すればこれと並存させるためのものでもあります。また、Spring Boot WebFlux starterがファンクショナルエンドポイントを有効化する方法でもあります。

以下はWebFlux Java configの例です。(動かし方はDispatcherHandler参照)

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Bean
    public RouterFunction<?> routerFunctionA() {
        // ...
    }

    @Bean
    public RouterFunction<?> routerFunctionB() {
        // ...
    }

    // ...

    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        // configure message conversion...
    }

    @Override
    default void addCorsMappings(CorsRegistry registry) {
        // configure CORS...
    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        // configure view resolution for HTML rendering...
    }
}

1.5.4. HandlerFilterFunction

ルータ関数でマッピングしたルーティングはRouterFunction.filter(HandlerFilterFunction)でフィルタをかけられます。HandlerFilterFunctionServerRequestHandlerFunctionを受け取りServerResponseを返す関数です。このハンドラ関数の引数はチェーンの次の要素で、基本的にはルーティング先のHandlerFunctionですが、複数のフィルタが適用される場合は別のFilterFunctionになる場合があります。アノテーションベースの類似機能は@ControllerAdvice and/or ServletFilterで実現できます。いま、ルーティングにセキュリティフィルタを追加するとして、特定のパスが許可されるかどうかを判定するSecurityManagerが既にあるとします。

import static org.springframework.http.HttpStatus.UNAUTHORIZED;

SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = ...

RouterFunction<ServerResponse> filteredRoute =
    route.filter((request, next) -> {
        if (securityManager.allowAccessTo(request.path())) {
            return next.handle(request);
        }
        else {
            return ServerResponse.status(UNAUTHORIZED).build();
        }
  });

上記の例にあるようにnext.handle(ServerRequest)は必ずしも呼ぶ必要はありません。上記ではアクセスを許可する場合にだけハンドラ関数を呼び出しています。

ファンクショナルエンドポイントでのCORSはCorsWebFilterで行っています。

1.6. CORS

Spring MVCと同等

1.6.1. Introduction

Spring MVCと同等

セキュリティ上の理由によりブラウザはオリジンサーバ外のリソースへのAJAX呼び出しを禁止しています。あるタブで銀行アカウントを取得すると別タブのevil.comでも取得出来てしまいます。最初のタブで取得したクレデンシャルを使用するAJAXリクエストを、二つ目のタブのevil.comのスクリプトで利用可能にさせるできではありません。

Cross-Origin Resource Sharing (CORS)はたいていのブラウザが実装しているW3C specificationで、IFRAMEやJSOPなどのセキュリティが低く強力というわけでもない回避策よりも、許可するクロスドメインリクエストの種類を指定可能になります。

1.6.2. Processing

Spring MVCと同等

CORS仕様ではpreflight, simple, actual requestsを区別しています。CORSの動作を知るにはこの記事や、他にもいろいろありますが、詳細は仕様を参照してください。

Spring WebFluxのHandlerMappingはCORSのビルトイン機能です。リクエストからハンドラのマッピング正常終了後、HandlerMappingはリクエスト・ハンドラ・以降のアクションにおけるCORS configurationをチェックします。Preflight requestsは直接処理されますが、simpleとactual CORSリクエストは intercepted, validated, and have required CORS response headers set.

クロスオリジンリクエスト(Originヘッダが存在してリクエストのホストが異なる)の有効化には、明示的なCORS configurations宣言が必要です。CORS configurationが見つからない場合、preflight requestsを拒否します。simpleおよびactual CORSリクエストのレスポンスにCORSヘッダーを追加しないと、以降のブラウザはそれらを拒否します。

HandlerMappingCorsConfigurationマッピングベースのURLパターンで個々に設定できます。たいていの場合はアプリケーションはそうしたマッピングを宣言するのにWebFlux Java configを使います。この場合、すべてのHadlerMapppingに渡される単一のグローバルマッピングになります。

HandlerMappingのグローバルなCORS configurationはより細かい、ハンドラレベルのCORS configurationと組み合わせることが出来ます。たとえば、アノテーションのコントローラはクラスかメソッドレベルの@CrossOriginを使えます(その他のハンドラではCorsConfigurationSourceを実装する)。

グローバルとローカルのconfigurationの組み合わせの際のルールは基本的にはadditiveです。allowCredentialsmaxAgeなど単一値のみ取る属性では、ローカルがグローバルをオーバーライドします。詳細はCorsConfiguration#combine(CorsConfiguration)を参照。

各機能の詳細やより細かいカスタマイズについては以下を参照してください。

  • CorsConfiguration
  • CorsProcessor, DefaultCorsProcessor
  • AbstractHandlerMapping

1.6.3. @CrossOrigin

Spring MVCと同等

@CrossOriginによりコントローラーのメソッドでクロスオリジンリクエストを有効化します。

@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin
    @GetMapping("/{id}")
    public Mono<Account> retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public Mono<Void> remove(@PathVariable Long id) {
        // ...
    }
}

デフォルトの@CrossOriginは以下を許可します。

  • すべてのオリジン。
  • すべてのヘッダー。
  • コントローラーメソッドにマッピングされているすべてのHTTPメソッド
  • allowedCredentialsはデフォルトでは無効化で、これはクッキーとCSRFトークンなど重要なユーザ固有情報を公開するtrust levelを確立するためです。よって必要な場合にだけ使用すべきです。
  • maxAge30分。

クラスレベルの@CrossOriginはすべてのメソッドに引き継がれます。

@CrossOrigin(origins = "http://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

    @GetMapping("/{id}")
    public Mono<Account> retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public Mono<Void> remove(@PathVariable Long id) {
        // ...
    }
}

クラスとメソッド両方でCrossOriginを使用可能です。

@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin("http://domain2.com")
    @GetMapping("/{id}")
    public Mono<Account> retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public Mono<Void> remove(@PathVariable Long id) {
        // ...
    }
}

1.6.4. Global Config

Spring MVCと同等

コントローラのメソッドレベルに加えて、グローバルのCORS configurationを定義したい場合もあります。HandlerMappingにURLベースのCorsConfigurationマッピングを設定できます。ただし、たいていのアプリケーションではWebFlux Java configを使うことになると思われます。

デフォルトのグローバルconfigurationは以下を有効化します。

  • すべてのオリジン。
  • すべてのヘッダー。
  • GET, HEAD, POSTのメソッド。
  • allowedCredentialsはデフォルトでは無効化で、これはクッキーとCSRFトークンなど重要なユーザ固有情報を公開するtrust levelを確立するためです。よって必要な場合にだけ使用すべきです。
  • maxAge30分。

WebFlux Java configでCORSを有効化するにはCorsRegistryコールバックを使います。

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {

        registry.addMapping("/api/**")
            .allowedOrigins("http://domain2.com")
            .allowedMethods("PUT", "DELETE")
            .allowedHeaders("header1", "header2", "header3")
            .exposedHeaders("header1", "header2")
            .allowCredentials(true).maxAge(3600);

        // Add more mappings...
    }
}

1.6.5. CORS WebFilter

Spring MVCと同等

ビルトインのCorsWebFilterでCORSを使用可能で、ファンクショナルエンドポイントにはこちらがよりフィットします。

フィルタの設定にはCorsWebFilter beanを宣言してそのコンストラクタにCorsConfigurationSourceを渡します。

@Bean
CorsWebFilter corsFilter() {

    CorsConfiguration config = new CorsConfiguration();

    // Possibly...
    // config.applyPermitDefaultValues()

    config.setAllowCredentials(true);
    config.addAllowedOrigin("http://domain1.com");
    config.addAllowedHeader("");
    config.addAllowedMethod("");

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);

    return new CorsWebFilter(source);
}

1.7. Web Security

Spring MVCと同等

Spring Securityは悪意のある攻撃からwebアプリケーションを保護する機能を提供します。詳細はSpring Security reference documentationを参照してください。

1.8. WebFlux Config

Spring MVCと同等

WebFlux Java configはコントローラやファンクショナルエンドポイントでのリクエスト処理に必要となるコンポーネントを宣言しており、カスタマイズのAPIを提供します。Java configが生成するbeanを理解する必要は必ずしもありませんが、必要になった場合は、WebFluxConfigurationSupportを見たりSpecial bean typesを参照してください。

configuration APIでは利用できない、より高度なカスタマイズについては、Advanced config modeでフルコントロールを得られます。

1.8.1. Enable WebFlux config

Spring MVCと同等

Java configで@EnableWebFluxを使用します。

@Configuration
@EnableWebFlux
public class WebConfig {
}

上記により多数のSpring WebFlux infrastructure beansを登録します。また、クラスパス上で利用可能な(JSONXML用などの)依存性に適したbeanも登録します。

1.8.2. WebFlux config API

Spring MVCと同等

Java configでWebFluxConfigurerインタフェースを実装します。

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    // Implement configuration methods...

}

1.8.3. Conversion, formatting

Spring MVCと同等

デフォルトではNumberDate用のフォーマッターがあり、これらでは@NumberFormat@DateTimeFormatが使えます。また、Joda Timeがクラスパス上にある場合、Joda Timeも使えるようになります。

カスタムのフォーマッターとコンバータを登録するには以下のようにします。

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // ...
    }

}

FormatterRegistrarsを使うにはFormatterRegistrar SPIFormattingConversionServiceFactoryBeanを参照してください。

1.8.4. Validation

Spring MVCと同等

デフォルトでは、Bean ValidationHibernate Validatorなど、がクラスパス上にあればグローバルのValidatorLocalValidatorFactoryBeanを登録し、@Controllerメソッド引数の@ValidValidatedで使われます。

Java configで、グローバルのValidatorインスタンスをカスタマイズできます。

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public Validator getValidator(); {
        // ...
    }

}

なお、ローカルにValidatorを登録することもできます。

@Controller
public class MyController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addValidators(new FooValidator());
    }

}

どこかでLocalValidatorFactoryBeanをインジェクションする必要が生じる場合、beanを生成して@Primaryを付与し、MVC configで宣言しているものとのコンフリクトを回避してください。

1.8.5. Content type resolvers

Spring MVCと同等

Spring WebFluxでは@Controllerが要求するメディアタイプの決定方法を設定可能です。デフォルトでは"Accept"ヘッダーのみチェックしますが、クエリ―パラメータベースに変更することも可能です。

コンテンツタイプのリゾルバをカスタマイズするには以下のようにします。

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
        // ...
    }
}

1.8.6. HTTP message codecs

Spring MVCと同等

リクエストとレスポンスの読み書きをカスタマイズできます。

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        // ...
    }
}

ServerCodecConfigurerがデフォルトのリーダーとライターを提供します。これを使用して、リーダー・ライターを追加したり、デフォルトをカスタマイズしたり、あるいは、デフォルトを完全に置き換えたりします。

Jackson JSONXMLでは、JacksonのデフォルトプロパティをカスタマイズするJackson2ObjectMapperBuilderの使用を検討してください。

  1. DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES をdisabledにする。
  2. MapperFeature.DEFAULT_VIEW_INCLUSIONをdisabledにする。

また、以下がクラスパス上にある場合、自動的に登録します。

  1. jackson-datatype-jdk7: java.nio.file.PathなどJava 7サポート。
  2. jackson-datatype-joda: Joda-Timeサポート。
  3. jackson-datatype-jsr310: Java 8 Date & Time APIサポート
  4. jackson-datatype-jdk8: OptionalなどJava 8サポート

1.8.7. View resolvers

Spring MVCと同等

viewのリゾルバを設定するには以下のようにします。

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        // ...
    }
}

なお、FreeMarkerではそのライブラリ用の設定が必要です。

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    // ...

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("classpath:/templates");
        return configurer;
    }

}

1.8.8. Static resources

Spring MVCと同等

Resourceのリストで静的リソースを記述します。

以下の例では、"/resources"で始まるリクエストの場合、クラスパス上の静的リソース"/static"を参照する相対パスになります。リソースは、HTTPリクエスト削減およびブラウザキャッシュを確保するため、1年の有効期限になります。Last-Modifiedヘッダを評価し、もし存在すれば304ステータスコードを返します。

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
            .addResourceLocations("/public", "classpath:/static/")
            .setCachePeriod(31556926);
    }

}

リソースハンドラはResourceResolverResourceTransformerのチェーンが出来ます。which can be used to create a toolchain for working with optimized resources.

VersionResourceResolverは、コンテンツから算出するMD5・固定アプリケーションバージョンなど、によるバージョン付きリソースURLを使えます。ContentVersionStrategyMD5ハッシュ)はモジュールローダーを使うJavaScriptリソースなどで有用な選択肢となります。

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public/")
                .resourceChain(true)
                .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
    }

}

ResourceUrlProviderでURLリライトを行い、たとえばバージョンを追加するのに、リゾルバとtransformersのチェーンを適用できます。WebFlux configはResourceUrlProviderを提供するのにでこれを他で使います。

Spring MVCと異なり、今のところのWebFluxでは透過的な静的リソースリライトを行う方法が無く、これはリゾルバとtransformersのノンブロッキングチェーンを利用可能なビュー技術が存在しないためです(Amazon S3上のリソースなど)。ローカルのリソースを提供する場合のみ、直接ResourceUrlProviderを使います(例えばカスタムタグを経由するなどして)。この場合ブロックは0秒です。

WebJarsWebJarsResourceResolverで使用可能で、クラスパス上に"org.webjars:webjars-locator"がある場合自動的に登録します。このリゾルバはjarのバージョンを含むようにURLをリライトするので、バージョンの無いURLにマッチングします。たとえば、"/jquery/jquery.min.js"から"/jquery/1.2.0/jquery.min.js"など。

1.8.9. Path Matching

Spring MVCと同等

Spring WebFluxはパスパターンのパース済み表現、```PathPattern、を使用します。また、リクエストパスはRequestPathです。これらにより、リクエストパスのデコードやセミコロン削除をするかどうかを指定する必要を省きます。PathPatternはデコード済みのパスセグメントにアクセスして安全にマッチングが出来ます。*1

Spring WebFluxはサフィックスパターンマッチングをサポートせず、パスマッチングに関するカスタマイズのオプションは二つだけ存在します。trailing slashesとマッチングするかどうか(デフォルトtrue)、case-sensitiveとマッチングするかどうか(デフォルトfalse)。

これらオプションをカスタマイズするには以下のようにします。

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        // ...
    }

}

1.8.10. Advanced config mode

Spring MVCと同等

@EnableWebFluxDelegatingWebFluxConfigurationをインポートします。これは、(1)WebFluxアプリケーション用のデフォルトのSpring configuration、(2)configurationをカスタマイズするためのWebFluxConfigurerへのデリゲートの検出、を行います。

advanced modeでは、@EnableWebFluxを削除し、WebFluxConfigurerを実装する代わりにDelegatingWebFluxConfigurationを直接拡張します。

@Configuration
public class WebConfig extends DelegatingWebFluxConfiguration {

    // ...

}

WebConfigの既存メソッドはそのままで、ベースクラスのbean宣言をオーバーライドします。また、クラスパス上のその他のWebMvcConfigurerも使えます。

1.9. HTTP/2

Spring MVCと同等

Servlet 4コンテナはHTTP/2サポートが要求され、Spring Framework 5はServlet API 4と互換性があります。プログラミングモデルの観点からは特にこれといってアプリケーションが何かする必要はありません。サーバ設定に関する考慮事項があります。HTTP/2 wiki pageを参照してください。

現行のSpring WebFluxはNettyのHTTP/2をサポートしません。また、クライアントへのpushing resources programmaticallyもサポートしません。

*1:Spring WebFlux uses parsed representation of path patterns i.e. PathPattern, and also the incoming request path???i.e. RequestPath, which eliminates the need to indicate whether to decode the request path, or remove semicolon content, since PathPattern can now access decoded path segment values and match safely.が原文。カンマで区切られた文がズラッとならぶと訳するのがきつい…

Web on Reactive Stack(version 5.0.3.RELEASE) 1. Spring WebFluxをテキトーに訳した(1.4)

1.4. Annotated Controllers

Spring MVCと同等

Spring WebFluxはアノテーションベースのプログラミングモデルを提供しており、@Controller@RestControllerコンポーネントは、リクエスマッピング・リクエスト入力・例外ハンドリングなど、を表現するのにアノテーションを用います。コントローラには適当なメソッドを作りますが、ベースクラスの拡張や特定インタフェースの実装は不要です。

以下が例です。

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String handle() {
        return "Hello WebFlux";
    }
}

この例ではメソッドはレスポンスボディに書き込むStringを返しています。

1.4.1. @Controller

Spring MVCと同等

一般的なSpringのbeanでコントローラーを定義します。ステレオタイプ@Controllerは自動検出を行うものです。これはクラスパスの@Componentクラスを検出するSpringの汎用サポート機能で、該当のbean定義を自動登録します。また、コントローラのステレオタイプとしても振る舞うので、これはそのクラスがwebコンポーネントの役割を担うことを指します。

@Controllerの自動検出を有効化するには、Java configurationにコンポーネントスキャンを追加します。

@Configuration
@ComponentScan("org.example.web")
public class WebConfig {

    // ...
}

@RestController@Controller@ResponseBodyから成る複合アノテーションです。これはコントローラのすべてのメソッドは型レベルの@ResponseBodyアノテーションを引き継ぎ、それらのメソッドはレスポンスボディに書き込む、という意味になります(対してmodel-and-viewレンダリングもある)。

1.4.2. Request Mapping

Spring MVCと同等

@RequestMappingはリクエストをコントローラのメソッドにマッピングするのに使います。URL・HTTPのメソッド・リクエストパラメータ・ヘッダ・メディアタイプなどでマッチする様々な属性があります。共通のマッピングを表現するにはクラスレベルで使用し、特定のエンドポイントマッピングに落とし込むにはメソッドレベルで使用します。

また、@RequestMappingには特定のHTTPメソッド用のショートカットがあります。

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

これらは複合アノテーションで、アノテーション自身に@RequestMappingが付けられています。これらは通常メソッドレベルで使います。クラスレベルには@RequestMappingが共通のマッピングを表すのに便利です。

@RestController
@RequestMapping("/persons")
class PersonController {

    @GetMapping("/{id}")
    public Person getPerson(@PathVariable Long id) {
        // ...
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void add(@RequestBody Person person) {
        // ...
    }
}

URI Patterns

Spring MVCと同等

リクエストのマッピングにはワイルドカードパターンを使います。

  • ?は1文字とマッチ
  • *はパスセグメント内で0文字以上とマッチ
  • **は0個以上のパスセグメントにマッチ

またURL変数とその値にアクセスする@PathVariableを宣言できます。

@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
    // ...
}

URL変数はクラスとメソッドレベルで使えます。

@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {

    @GetMapping("/pets/{petId}")
    public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
        // ...
    }
}

URI変数は自動的に適当な型に変換され、出来ない場合はTypeMismatchExceptionをスローします。単純な型のint, long, Dateはデフォルトでサポートしており、その他のデータ型は自前で登録します。型変換Binder Methodsを参照してください。

URI変数には明示的な名前、例えば@PathVariable("customId")、を付けられますが、名前と同じ場合には省略可能です。省略するには、デバッグ情報を付けるか、Java 8では-parametersフラグを付けてコンパイルします。

URI変数宣言{*varName}は、ゼロ個以上のパスセグメントにマッチします。例えば、/resources/{*path}/resources/のすべてのファイルにマッチし、"path"変数は完全な相対パスになります*1

URI変数宣言{varName:regex}正規表現付きのもので、例えばURL /spring-web-3.0.5 .jar"の場合、以下のメソッドはname, version, extentionを抽出します。

@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String ext) {
    // ...
}

URIパスパターンにはプレースホルダ${...}を入れることも可能で、これは開始時にPropertyPlaceHolderConfigurerを介して、ローカル・システム・環境などのプロパティソースで解決されます。使用例としては、何らかの外部設定に基づくベースURLをパラメータ化するなどです。

Spring WebFluxはURIパスマッチングにPathPatternPathPatternParserを使用します。これらはどちらもspring-webのもので、実行時に大量のURIパスパターンマッチングを行うwebアプリケーションのHTTP URLパスに使うために作られたものです。

Spring WebFluxはサフィックスパターンマッチをサポートせず、対照的に、Spring MVC/person/person.*にマッチするなどのマッピングをします。

URLベースのコンテンツネゴシエーションがもし必要であれば、クエリパラメータを推奨します。クエリパラメータは、単純かつ明示的で、URIパスベースで脆弱性を作る危険性を減らせます。

Pattern Comparison

Spring MVCと同等

URLに複数のパターンがマッチする場合、最もマッチするものを決定する必要があります。

PathPattern.SPECIFICITY_COMPARATOR

すべてのパターンについて、URI変数とワイルドカードの個数に基づくスコア算出を行い、URI変数のスコアはワイルドカードより低いです。トータルスコアがより低いパターンが選ばれます。もし二つのパターンが同一スコアの場合、最も長い方が選ばれます。

**, {*varName}などcatch-allのパターンはスコア計算から除外されて常にソート順が末尾になります。もし二つのパターンともcatch-allの場合、最も長い方が選ばれます。

Consumable Media Types

Spring MVCと同等

リクエストのContent-Typeを用いてリクエスマッピングを絞り込むことが出来ます。

@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
    // ...
}

consumes属性は否定表現もサポートしていおり、!text/plainは"text/plain"以外のコンテンツタイプという意味になります。

クラスレベルで共通のconsumes属性を宣言できます。他のリクエスマッピングの属性とは異なり、これをクラスレベルで使う場合、メソッドレベルのconsumes属性はクラスレベルの宣言の拡張ではなくオーバライドします。

MediaTypeAPPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUEなど一般的なメディアタイプの定数があります。

Producible Media Types

Spring MVCと同等

Acceptリクエストヘッダーでリクエスマッピングや、コントローラーのメソッドがプロデュースするコンテンツタイプのリストを、絞り込めます。

@GetMapping(path = "/pets/{petId}", produces = "application/json;charset=UTF-8")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}

メディアタイプには文字列を指定します。否定表現、例えば!text/plainを使用可能で、これは"text/plain"以外の意味になります。

クラスレベルで共通のproduces属性を宣言できます。他のリクエスマッピングの属性とは異なり、これをクラスレベルで使う場合、メソッドレベルのproduces属性はクラスレベルの宣言の拡張ではなくオーバライドします。

MediaTypeAPPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUEなど一般的なメディアタイプの定数があります。

Parameters and Headers

Spring MVCと同等

クエリパラメータの条件によりリクエスマッピングを絞り込めます。クエリパラメータが存在するかどうか("myParam")、存在しないかどうか("!myParam")、特定の値かどうか("myParam=myValue")、が使えます。

@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String petId) {
    // ...
}

リクエストヘッダ―でも同じような条件が使えます。

@GetMapping(path = "/pets", headers = "myHeader=myValue")
public void findPet(@PathVariable String petId) {
    // ...
}

HTTP HEAD, OPTIONS

Spring MVCと同等

@GetMapping - と@RequestMapping(method=HttpMethod.GET)はリクエスマッピングでは透過的にHTTP HEADをサポートします*2。コントローラーのメソッドには特に手を入れる必要はありません。HttpHandlerのサーバアダプタが適用されるレスポンスラッパは、"Content-Length"ヘッダーに書き込みバイト数をセットし、レスポンスには何も書き込みません。

デフォルトではHTTP OPTIONSは、マッチするURLパターンのすべての@RequestMapping methodsに"Allow"レスポンスヘッダを指定することで処理されるようになります。

HTTPメソッド宣言の無い@RequestMappingでは、"Allow"ヘッダーは"GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS"がセットされます。コントローラのメソッドは、特定のHTTPメソッド用のアノテーション@GetMapping, @PostMappingなど、使用するHTTPメソッドを常に宣言するようにしてください。

@RequestMappingのメソッドは明示的にHTTP HEADとHTTP OPTIONSにマッピングされますが、一般的な使用法ではありません。

1.4.3. Handler methods

Spring MVCと同等

@RequestMappingハンドラーメソッドのシグネチャは柔軟に決められ、コントローラメソッドの引数と戻り値にはいくつかの選択肢があります。

Method arguments

Spring MVCと同等

以下のテーブルはコントローラーメソッド引数で使用可能な一覧です。

Reactive型(Reactor, RxJava, その他)をブロッキングI/Oを必要とする引数、例えばリクエストボディの読み込み、で使用できます。この点はdescription列に記しています。ブロッキングを必要としない引数ではReactive型は使用できません。

JDK 1.8のjava.util.Optinalrequired属性、たとえば@RequestParam, @RequestHeader、を持つアノテーションのメソッド引数と組み合わせて使用できます。また、required=falseと同等になります。

Controller method argument Description
ServerWebExchange ServerWebExchangeのフルアクセス。このクラスは、HTTPリクエスト・レスポンス・リクエストとセッション属性・checkNotModifiedメソッド、などのコンテナ
ServerHttpRequest, ServerHttpResponse HTTPリクエストやレスポンスへのアクセス用
WebSession セッションへのアクセス用。属性が追加されない限り、新規セッションの開始を強制しない。reactive型サポート対象。
java.security.Principal 現在の認証済みユーザ。基本的には既知のPrincipal実装になる。reactive型サポート対象。
org.springframework.http.HttpMethod リクエストのHTTPメソッド
java.util.Locale 使用可能な中で最も特定的なLocaleResolverで決定される現在のリクエストのロケール
Java 6+: java.util.TimeZone
Java 8+: java.time.ZoneId
LocaleContextResolverが決定する現在のリクエストに関連付けられたタイムゾーン
@PathVariable URIテンプレート変数へのアクセス用。URI Patterns参照。
@MatrixVariable URIパスセグメント内のname-valueペアへのアクセス用。Matrix variables
@RequestParam Servletリクエストパラメータへのアクセス用。パラメータ値はメソッド引数の型に変換される。@RequestParamを参照。
属性の設定には@RequestParam以外の方法もあります。この表の"Any other argument"も参照。
@RequestHeader リクエストヘッダ―へのアクセス用。ヘッダー値はメソッド引数の型に変換される。@RequestHeader
@CookieValue クッキーへのアクセス用。クッキーの値はメソッド引数の型に変換される。@CookieValue
@RequestBody HTTPリクエストボディへのアクセス用。ボディコンテンツはHttpMessageReaderを使用するメソッド引数の型に変換される。reactive型サポート対象。
HttpEntity<B> リクエストヘッダ―とボディへのアクセス用。ボディはHttpMessageReaderに変換される。reactive型サポート対象。
@RequestPart "multipart/form-data"リクエストのpart部分へのアクセス用。reactive型サポート対象。MultipartMultipart Readerを参照。
java.util.Map,
org.springframework.ui.Model,
org.springframework.ui.ModelMap
HTMLコントローラーおよびビューのテンプレートで使うモデルへのアクセス用。
@ModelAttribute モデル(無ければインスタンス化される)の属性へのアクセス用。データバインディングとvalidationが適用される。@ModelAttribute, Model Methods, Binder Methodsを参照。
属性の設定には@ModelAttribute以外の方法もあります。この表の"Any other argument"も参照。
Errors, BindingResult command object(@ModelAttribute引数のこと)のvalidationとデータバインディングのエラー、もしくは、@RequestBodyもしくは@RequestPartのvalidationエラー。ErrorsBindingResultはvalidation対象メソッド引数の直後に宣言する必要がある。
SessionStatus +
クラスレベル@SessionAttributes
あるフォーム処理の完了時に、クラスレベルの@SessionAttributesで宣言したセッション属性のクリーンアップを指示する。@SessionAttributesを参照。
UriComponentsBuilder 相対URLを現在のリクエストのホスト・ポート・スキーマ・コンテキストパスを考慮したものにする。サーブレットマッピングリテラルパートはForwardedX-Forwarded-*ヘッダーに入る。
@SessionAttribute 任意のセッション属性へのアクセス用。クラスレベルの@SessionAttributes宣言でセッションに持たせるモデル属性以外のセッション属性にもアクセスできます。
@RequestAttribute リクエスト属性へのアクセス用。@RequestAttributeを参照。
その他 メソッド引数が上記いずれにもマッチしない場合、デフォルトでは、BeanUtils#isSimplePropertyに該当する単純な型の場合は@RequestParamに解決される。そうでない場合は@ModelAttributeになる。
Return values

Spring MVCと同等

以下の表はコントローラのメソッドで使用可能な戻り値です。すべての戻り値でReactive型、Reactor, RxJava, その他、が使用可能です。

Controller method return value Description
@ResponseBody HttpMessageWriterエンコードしてレスポンスに書き込まれる。@ResponseBody参照。
HttpEntity<B>, ResponseEntity<B> HttpMessageWriterエンコードするボディとHTTPヘッダーを含む完全なレスポンス。ResponseEntity参照。
HttpHeaders ボディ無しでヘッダーを持つレスポンスを返す。
String ViewResolverで解決するビュー名で、command objectsと@ModelAttributeのメソッドが決定する暗黙モデルで使用される*3。ハンドラメソッドではModel引数を宣言してモデルを使用します(上記参照)。
View 暗黙モデルと一緒にレンダリングに使うViewインスタンス。ハンドラメソッドではModel引数を宣言してモデルを使用します(上記参照)。
java.util.Map, org.springframework.ui.Model 暗黙モデルに追加される属性。ビュー名はリクエストパスに基づいて暗黙的に決定される。
@ModelAttribute モデルに追加される属性。ビュー名はリクエストパスに基づいて暗黙的に決定される。
@ModelAttributeの使用は任意です。この表の「それ以外の戻り値」を参照。
Rendering モデルとビューをレンダリングするシナリオ用のAPI
void voidのメソッドは、おそらく非同期(Mono<Void>など)で、ServerHttpResponseServerWebExchange引数・@ResponseStatusのいずれかの場合、戻り値型(もしくはnull)はレスポンスを完全に処理したと見なします。コントローラーがpositive ETagかlastModifiedタイムスタンプチェックを行う場合も同様に扱われます。上記いずれにも当てはまらない場合、voidはRESTコントローラーでは"no response body"を意味し、HTMLコントローラーではデフォルトのビュー名になります。
Flux<ServerSentEvent>,
Observable<ServerSentEvent>,
その他のReactive型
server-sentイベントの送出。データを書き込む必要がある場合のみ、SeverSentEventラッパーは省略可能です。(ただし、text/event-streamは、リクエストするか、属性のプロデュース経由でマッピングに宣言する必要があります。*4
それ以外の戻り値 上記いずれにも該当しない場合、デフォルトでは、Stringvoidの場合ビュー名として扱われます(voidはデフォルトビュー名が適用)。また、BeanUtils#isSimplePropertyに該当する単純な型でない場合はモデルに追加される属性になります。それ以外はunresolvedのままです。

Type Conversion

Spring MVCと同等

コントローラメソッドの引数でSpringの入力リクエストを表現するアノテーション、例えば@RequestParam, @RequestHeader, @PathVariable, @MatrixVariable, @CookieValue、は引数をString以外で宣言する場合は型変換を基本的には必要とします。

このような場合は型変換は設定されているコンバーターを自動的に適用します。デフォルトでは、単純な型、int, long, Date、などです。型変換はWebDataBinderでカスタマイズするか、FormattingConversionServiceFormattersを登録します。Spring Field Formatting参照。

Matrix variables

Spring MVCと同等

RFC 3986ではパスセグメント内のname-valueペアについての提案です。Spring WebFluxではTim Berners-Leeの過去ポストに基づきこれを"matrix variables"と呼称していますが、URIパスパラメータ(URI path parameters)とも呼称します。

Matrix variablesはパスセグメント内に配置可能で、各変数はセミコロンで区切り、複数の値はカンマで区切ります。例:"/cars;color=red,green;year=2012"。複数の値は変数名を繰り返すことでも指定可能です。例:"color=red;color=green;color=blue"

Spring MVCとは異なり、WebFluxではURLのmatrix variablesの有無はリクエスマッピングに影響を与えません。言い換えると、可変要素をマスクするためのURI変数を用意する必要はありません。コントローラーメソッドでmatrix variablesにアクセスする場合、matrix variablesを含むパスセグメントにURI変数を追加する必要があります。以下がその例です。

// GET /pets/42;q=11;r=22

@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {

    // petId == 42
    // q == 11
}

すべてのパスセグメントがmatrix variablesを含む場合、matrix variableを含みうるパス変数のあいまいさを無くす必要があります。

// GET /owners/42;q=11/pets/21;q=22

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable(name="q", pathVar="ownerId") int q1,
        @MatrixVariable(name="q", pathVar="petId") int q2) {

    // q1 == 11
    // q2 == 22
}

matrix variableにはデフォルト値を指定可能です。

// GET /pets/42

@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {

    // q == 1
}

すべてのmatrix variablesを取得するにはMultiValueMapを使います。


// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable MultiValueMap<String, String> matrixVars,
        @MatrixVariable(pathVar="petId"") MultiValueMap<String, String> petMatrixVars) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 22, "s" : 23]
}

@RequestParam

Spring MVCと同等

コントローラでクエリパラメータをメソッド引数にバインドするには@RequestParamを使います。以下が使用例です。

@Controller
@RequestMapping("/pets")
public class EditPetForm {

    // ...

    @GetMapping
    public String setupForm(@RequestParam("petId") int petId, Model model) {
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

}

Servlet APIの"request paramater"の概念は、クエリパラメータ・フォームデータ・マルチパートをを一つにまとめていますが、これと異なりWebFluxでは、ServerWebExchangeを介してそれぞれにアクセスします。@RequestParamはクエリパラメータにだけバインドしますが、command objectにクエリパラメータ・フォームデータ・マルチパートをデータバインディングできます。

@RequestParaのメソッド引数のデフォルトは必須ですが、@RequestParamrequiredfalseにするかjava.util.Optionalでラップできます。

ターゲットメソッドの引数型がStringでは無い場合、型変換を自動的に適用します。mvc-ann-typeconversion

@RequestParamMap<String, String>MultiValueMap<String, String>で宣言する場合、そのマップにはすべてのクエリパラメータが入ります。

属性を設定するのに@RequestParamは必ずしも必須ではない点に注意してください。デフォルトでは、引数は、BeanUtils#isSimplePropertyが単純な値型と判定し、かつ、その他の引数リゾルバで解決しない場合、@RequestParamを付与したと見なします。

@RequestHeader

Spring MVCと同等

コントローラーでリクエストヘッダをメソッド引数にバインドするには@RequestHeaderを使います。

以下のヘッダーがあると仮定します。

Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding         gzip,deflate
Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive              300

以下によりAccept-EncodingKeep-Aliveの値を取得します。

@GetMapping("/demo")
public void handle(
        @RequestHeader("Accept-Encoding") String encoding,
        @RequestHeader("Keep-Alive") long keepAlive) {
    //...
}

ターゲットメソッドの引数型がStringでは無い場合、型変換を自動的に適用します。mvc-ann-typeconversion

@RequestHeaderMap<String, String>MultiValueMap<String, String>HttpHeadersで宣言する場合、そのマップにはすべてのヘッダー値が入ります。

カンマ区切り文字列をStringもしくは型変換システムが利用可能な型のarray/collectionへの変換をビルトインで利用可能です。例えば、@RequestHeader("Accept")のメソッド引数はString以外にString[]List<String>も可能です。

@CookieValue

Spring MVCと同等

コントローラーでHTTPクッキーの値をメソッド引数にバインドするには@CookieValueを使います。

以下のクッキーがあるとします。

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

以下はクッキーの値を取得するコード例です。

@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
    //...
}

ターゲットメソッドの引数型がStringでは無い場合、型変換を自動的に適用します。mvc-ann-typeconversion

@ModelAttribute

Spring MVCと同等

メソッド引数でモデルの属性にアクセスには@ModelAttributeを使います。存在しない場合はインスタンス化します。モデル属性はフォームフィールドの名前と一致するクエリパラメータの値を同じに出来ます。これはデータバインディングと呼ばれ、クエリパラメータとフォームフィールド間のパースと変換の手間を省くものです。

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }

上記Petインスタンスは以下のように解決が行われます。

  • Model Methodsで追加済みの場合、そのモデルから取得。
  • @SessionAttributesのHTTPセッションから取得。
  • デフォルトコンストラクタの呼び出しから取得。
  • クエリパラメータかフォームフィールドにマッチする引数で"primary constructor"を呼び出して取得。引数名はJavaBeansの@ConstructorPropertiesバイトコード内にある実行時保持型のパラメータ名(runtime-retained parameter names )で決定される。

モデル属性のインスタンスを取得後に、データバインディングを適用します。WebExchangeDataBinderクラスは、クエリパラメータ名とフォームフィールドを、ターゲットオブジェクトのフィールド名とマッチングします。マッチするフィールドは適宜型変換後に値が入ります。データバインディング(とvalidation)の詳細はValidationを参照。データバインディングのカスタマイズの詳細はBinder Methodsを参照。

データバインディングはエラーになる可能性があります。デフォルトではWebExchangeBindExceptionになりますが、コントローラーメソッドでそうしたエラーを処理するには@ModelAttributeのすぐ横にBindingResult引数を追加します。

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}

Validationを行うにはデータバインディング後にjavax.validation.ValidかSpringの@Validatedを付与します(詳細はBean validationSpring validationを参照)。

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}

Spring WebFluxは、Spring MVCと異なり、モデルにreactive型(Mono<Account>io.reactivex.Single<Account>など)を使えます。@ModelAttributeにはreactive型ラッパーを使用しても良いし、しなくても良いです。必要に応じて実際の値に解決されます*5。なお、ここでBindingResultを使うには、上で見たように、reactive型ラッパを使わず、BindingResultの前に@ModelAttributeを宣言する必要があります。もしくは、reactive型を通してエラーを処理します。

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public Mono<String> processSubmit(@Valid @ModelAttribute("pet") Mono<Pet> petMono) {
    return petMono
        .flatMap(pet -> {
            // ...
        })
        .onErrorResume(ex -> {
            // ...
        });
}

属性を設定するのに@ModelAttributeは必ずしも必須ではない点に注意してください。

デフォルトでは、引数は、BeanUtils#isSimplePropertyが単純な値型では無いと判定し、かつ、その他の引数リゾルバで解決しない場合、@ModelAttributeを付与したと見なします。

@SessionAttributes

Spring MVCと同等

@SessionAttributesWebSessionにモデル属性を格納するのに使用します。ある特定のコントローラーで使うセッション属性を宣言する型レベルアノテーションです。基本的には、モデル属性の名前かモデル属性の型を列挙し、後続のリクエストでアクセスするためにセッションへ透過的な保存が行われます。

@Controller
@SessionAttributes("pet")
public class EditPetForm {
    // ...
}

初回リクエストで"pet"という名前のモデル属性をモデルに追加し、自動的に昇格してWebSessionに保存が行われます。これを削除するには別のコントローラメソッドの引数でSessionStatusを使用します。

@Controller
@SessionAttributes("pet")
public class EditPetForm {

    // ...

    @PostMapping("/pets/{id}")
    public String handle(Pet pet, BindingResult errors, SessionStatus status) {
        if (errors.hasErrors) {
            // ...
        }
            status.setComplete();
            // ...
        }
    }
}

@SessionAttribute

Spring MVCと同等

グローバル管理、つまり対象コントローラの外(フィルタなど)、で既に存在するセッション属性にアクセスする場合、存在してもしていなくても、メソッド引数で@SessionAttributeを使います。

@GetMapping("/")
public String handle(@SessionAttribute User user) {
    // ...
}

セッション属性の追加や削除を行う場合はコントローラメソッドにWebSessionをインジェクションします。

コントローラーの処理フローの一部をセッションのモデル属性に一時保存する場合、@SessionAttributesで説明するSessionAttributesを使用してください。

@RequestAttribute

Spring MVCと同等

@SessionAttribute同様に、@RequestAttributeで既に存在する、WebFilterなどが作成する、リクエスト属性にアクセスします。

@GetMapping("/")
public String handle(@RequestAttribute Client client) {
    // ...
}

Multipart

Spring MVCと同等

Multipart Readerの解説の通り、ServerWebExchangeでマルチパートにアクセスします。コントローラーでファイルアップロードのフォームを処理するベストな方法はcommand objectのデータバインディングを使います。

class MyForm {

    private String name;

    private MultipartFile file;

    // ...

}

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(MyForm form, BindingResult errors) {
        // ...
    }

}

RESTfulサービスの非ブラウザクライアントからもマルチパートリクエストをサブミットできます。

POST /someUrl
Content-Type: multipart/mixed

--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit

{
    "name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file-data"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...

JSONをデシリアライズする@RequestPartで"meta-data"にアクセスできます。この処理は設定したHTTP Message Codecsを使用します。

@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
        @RequestPart("file-data") FilePart file) {
    // ...
}

ストリーミングでマルチパートをシーケンシャルにアクセスするには、@RequestBodyFlux<Part>を使います。

@PostMapping("/")
public String handle(@RequestBody Flux<Part> parts) {
    // ...
}

@RequestPartは、Standard Bean Validationを適用するための、javax.validation.ValidかSpringの@Validatedを一緒に使えます。デフォルトではvalidationエラーはWebExchangeBindExceptionとなり400 (BAD_REQUEST)レスポンスになります。もしくは、ErrorsBindingResult引数によりコントローラ内でvalidationエラーを処理できます。

@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
        BindingResult result) {
    // ...
}

@RequestBody

Spring MVCと同等

リクエストボディを読み込みHttpMessageReader経由でObjectにデシリアライズするには@RequestBodyを使います。以下は@RequestBodyの例です。

@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
    // ...
}

Spring MVCと異なり、WebFluxでは、@RequestBodyメソッド引数はreactive型と完全なノンブロッキング読込およびストリーミングを行えます。

@PostMapping("/accounts")
public void handle(@RequestBody Mono<Account> account) {
    // ...
}

メッセージリーダのカスタマイズや設定変更はWebFlux ConfigHTTP message codecsを使います。

@RequestBodyは、Standard Bean Validationを適用するための、javax.validation.ValidかSpringの@Validatedを一緒に使えます。デフォルトではvalidationエラーはWebExchangeBindExceptionとなり400 (BAD_REQUEST)レスポンスになります。もしくは、ErrorsBindingResult引数によりコントローラ内でvalidationエラーを処理できます。

@PostMapping("/accounts")
public void handle(@Valid @RequestBody Account account, BindingResult result) {
    // ...
}

HttpEntity

Spring MVCと同等

HttpEntityは基本的には@RequestBodyの使い方と同じですが、こちらはリクエストヘッダ―とボディを公開するコンテナオブジェクトがベースになっています。

@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
    // ...
}

@ResponseBody

Spring MVCと同等

@ResponseBodyHttpMessageWriterシリアライズしたレスポンスボディを返すメソッドで使います。

@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
    // ...
}

@ResponseBodyはすべてのコントローラーメソッドに引き継ぐためにクラスレベルでも使えます。@RestControllerでは@Controllerおよび@ResponseBodyのメタアノテーションを意味します。

@ResponseBodyはreactive型をサポートし、ReactorないしRxJavaの型を返してこれのクラスがプロデュースする非同期な値をレスポンスにレンダリングできます。JSONレンダリングの詳細はJackson JSONを参照。

@ResponseBodyメソッドはJSON serialization viewsと組み合わせることが出来ます。mvc-ann-jacksonを参照。

メッセージ書き込みもしくは設定変更にはWebFlux ConfigHTTP message codecsを使います。

ResponseEntity

Spring MVCと同等

ResponseEntityは基本的には@ResponseBodyの使い方と同じですが、こちらはリクエストヘッダ―とボディを公開するコンテナオブジェクトがベースになっています。

@PostMapping("/something")
public ResponseEntity<String> handle() {
    // ...
    URI location = ...
    return new ResponseEntity.created(location).build();
}

Jackson JSON

Jackson serialization views

Spring MVCと同等

Spring WebFluxにはJackson's Serialization Views*6用の機能が組み込まれています。これはObjectのフィールドのサブセットのみレンダリングします。これを@ResponseBodyもしくはResponseEntityのコントローラーメソッドで使うには、Jacksonの@JsonViewでserialization view classを有効化します。

@RestController
public class UserController {

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView.class)
    public User getUser() {
        return new User("eric", "7!jd#h23");
    }
}

public class User {

    public interface WithoutPasswordView {};
    public interface WithPasswordView extends WithoutPasswordView {};

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @JsonView(WithoutPasswordView.class)
    public String getUsername() {
        return this.username;
    }

    @JsonView(WithPasswordView.class)
    public String getPassword() {
        return this.password;
    }
}

@JsonViewにはview classの配列を指定できますが、一つのコントローラーメソッドには一つのJsonViewのみ指定可能です。複数のviewを有効化する場合はcomposite interfaceを使用してください。

1.4.4. Model Methods

Spring MVCと同等

@ModelAttribute@RequestMappingメソッド引数で、モデルのObjectの生成とアクセスおよびリクエストへのバインディング、を行うために使います。

@ModelAttributeはコントローラーのメソッドレベルでも使えます。

これはリクエストの処理ではなく、リクエストの処理前

コントローラには複数の@ModelAttributeメソッドを作れます。同一コントローラ内の@RequestMappingメソッドより前に、そうしたメソッドをすべて呼び出します。@ModelAttributeメソッドは@ControllerAdviceを介して複数コントローラー間で共有できます。詳細はController Advice参照。

@ModelAttributeメソッドのシグネチャは柔軟に決められます。このメソッドで使用できる引数は@RequestMappingメソッドとほぼ同じですが、@ModelAttribute自身とリクエストボディに関するものは除きます。

以下は@ModelAttributeの例です。

@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
    model.addAttribute(accountRepository.findAccount(number));
    // add more ...
}

属性を一つ追加するだけの場合は、

@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountRepository.findAccount(number);
}

名前を明示的に指定しない場合、デフォルト名はConventionsJavadocの説明にあるとおりObject型に基づいて決定されます。名前指定可能な方のaddAttributeメソッドを使うか、@ModelAttributeのname属性で明示的な名前を割り当てられます。

Spring WebFluxは、Spring MVCと異なり、Mono<Account>io.reactivex.Single<Account>などのreactive型をモデルで使用できます。@RequestMappingの実行時、@ModelAttributeの引数をラッパー無しで宣言すると、こうした非同期モデル属性は実際の値に透過的な解決(とモデル更新)が行われます。

@ModelAttribute
public void addAccount(@RequestParam String number) {
    Mono<Account> accountMono = accountRepository.findAccount(number);
    model.addAttribute("account", accountMono);
}

@PostMapping("/accounts")
public String handle(@ModelAttribute Account account, BindingResult errors) {
    // ...
}

ビューレンダリングの前に、reactive型ラッパーのモデル属性はその実際の値に解決(とモデル更新)が行われます。

@ModelAttribute@RequestMappingのメソッドレベルでも使えます。この場合は@RequestMappingメソッドの戻り値はモデル属性と解釈されます。これはHTMLコントローラのデフォルトの振る舞いであって、基本的には使わないもので、戻り値がStringでなければビュー名とは解釈されません。@ModelAttributeはモデル属性の名前をカスタマイズ可能です。

@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
    // ...
    return account;
}

1.4.5. Binder Methods

Spring MVCと同等

文字列ベースのリクエスト(例:リクエストパラメータ、パス変数、ヘッダー、クッキーなど)を表すメソッド引数の型変換のカスタマイズをするには@Controllerもしくは@ControllerAdviceクラスで@InitBinderのメソッドを作ります。型変換は@ModelAttribute(つまりcommand objects)のリクエストパラメータのデータバインディングにも適用されます。

@InitBinderのメソッドは、コントローラ固有のjava.bean.PropertyEditorかSpringのConverterFormatter、を登録できます。また、WebFlux Java configを使用してグローバルなFormattingConversionServiceConverterFormatterを登録できます。

@InitBinderメソッドは@RequestMappingのメソッドと同じような引数を使用できますが、@ModelAttribute(command object)は使えません。通常、登録を行うためのWebDataBinder引数と一緒に宣言し、voidを返します。

@Controller
public class FormController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    // ...
}

また、FormattingConversionServiceを介してFormatterベースのクラスを登録する場合、コントローラー固有のFormatterを登録できます。

@Controller
public class FormController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
    }

    // ...
}

1.4.6. Controller Advice

Spring MVCと同等

通常、@ExceptionHandler, @InitBinder, @ModelAttributeのメソッドは、これらを宣言する@Controller(かそのクラス階層)内に適用するものです。これらのメソッドをグローバルに、複数のコントローラに、適用したい場合、@ControllerAdviceもしくは@RestControllerAdviceのクラスで宣言します。

@ControllerAdviceには@Componentがついているので、コンポーネントスキャンによってSpringのbeansとして登録されます。また、@RestControllerAdvice@ControllerAdvice@ResponseBodyのメタアノテーションで、@ExceptionHandlerのメソッドはメッセージ変換を介してレスポンスボディにレンダリングされます。

@RequestMapping@ExceptionHandlerメソッド用のinfrastructure classesが@ControllerAdviceのSpring beanを検出すると、実行時にそれらのメソッドを適用します。グローバルの(@ControllerAdviceの)@ExceptionHandlerメソッドはローカルの(@Controllerあとに適用されます。これと対照的に、グローバルの@ModelAttribute@InitBinderメソッドはローカルのそれのまえに適用されます。

デフォルトでは@ControllerAdviceメソッドはすべてのリクエスト、つまりすべてのコントローラー、に適用されますが、アノテーションの属性を通じてコントローラーのサブセットに絞り込みをかけられます。

// @RestControllerを付与するすべてのコントローラー
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}

// 特定パッケージ内のすべてのコントローラー
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}

// 特定のクラスとassignableなすべてのコントローラ
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}

なお、上記セレクタは実行時に評価されるので、過度の使用はパフォーマンスに影響を及ぼす可能性があります。詳細は@ControllerAdviceJavadocを参照。

*1:variable captures the complete relative pathが原文

*2:http://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/spring-method-head-options-support/ 実際にコード書いて試してないんで間違ってるかもしれないが、springのGETは暗黙的にHEADをサポートする、とかなんとか

*3:used together with the implicit model - determined through command objects and @ModelAttribute methods.が原文。よくわからん

*4:however text/event-stream must be requested or declared in the mapping through the produces attributeが原文。わからん…

*5:and it will be resolved accordingly, to the actual value if necessary.が原文。訳に自信がない

*6:リンク切れしてるんで適宜ぐぐる