リアクティブ Web アプリケーション
Spring Boot は、Spring Webflux の自動構成を提供することにより、リアクティブ Web アプリケーションの開発を簡素化します。
「Spring WebFlux フレームワーク」
Spring WebFlux は、Spring Framework 5.0 で導入された新しいリアクティブ Web フレームワークです。Spring MVC とは異なり、サーブレット API を必要とせず、完全に非同期でノンブロッキングであり、Reactor プロジェクト (英語) を通じて Reactive Streams (英語) 仕様を実装します。
Spring WebFlux には、関数型とアノテーション型の 2 つのフレーバーがあります。次の例に示すように、アノテーションベースのものは Spring MVC モデルに非常に近いものです。
Java
Kotlin
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.web.bind.annotation.DeleteMapping;
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("/users")
public class MyRestController {
private final UserRepository userRepository;
private final CustomerRepository customerRepository;
public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
this.userRepository = userRepository;
this.customerRepository = customerRepository;
}
@GetMapping("/{userId}")
public Mono<User> getUser(@PathVariable Long userId) {
return this.userRepository.findById(userId);
}
@GetMapping("/{userId}/customers")
public Flux<Customer> getUserCustomers(@PathVariable Long userId) {
return this.userRepository.findById(userId).flatMapMany(this.customerRepository::findByUser);
}
@DeleteMapping("/{userId}")
public Mono<Void> deleteUser(@PathVariable Long userId) {
return this.userRepository.deleteById(userId);
}
}
import org.springframework.web.bind.annotation.DeleteMapping
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
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
@RestController
@RequestMapping("/users")
class MyRestController(private val userRepository: UserRepository, private val customerRepository: CustomerRepository) {
@GetMapping("/{userId}")
fun getUser(@PathVariable userId: Long): Mono<User?> {
return userRepository.findById(userId)
}
@GetMapping("/{userId}/customers")
fun getUserCustomers(@PathVariable userId: Long): Flux<Customer> {
return userRepository.findById(userId).flatMapMany { user: User? ->
customerRepository.findByUser(user)
}
}
@DeleteMapping("/{userId}")
fun deleteUser(@PathVariable userId: Long): Mono<Void> {
return userRepository.deleteById(userId)
}
}
WebFlux は Spring Framework の一部であり、詳細な情報はリファレンスドキュメントで入手できます。
次の例に示すように、関数型バリアントである "WebFlux.fn" は、ルーティング構成をリクエストの実際の処理から分離します。
Java
Kotlin
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicate;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {
private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);
@Bean
public RouterFunction<ServerResponse> monoRouterFunction(MyUserHandler userHandler) {
return route()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build();
}
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.reactive.function.server.RequestPredicates.DELETE
import org.springframework.web.reactive.function.server.RequestPredicates.GET
import org.springframework.web.reactive.function.server.RequestPredicates.accept
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.RouterFunctions
import org.springframework.web.reactive.function.server.ServerResponse
@Configuration(proxyBeanMethods = false)
class MyRoutingConfiguration {
@Bean
fun monoRouterFunction(userHandler: MyUserHandler): RouterFunction<ServerResponse> {
return RouterFunctions.route(
GET("/{user}").and(ACCEPT_JSON), userHandler::getUser).andRoute(
GET("/{user}/customers").and(ACCEPT_JSON), userHandler::getUserCustomers).andRoute(
DELETE("/{user}").and(ACCEPT_JSON), userHandler::deleteUser)
}
companion object {
private val ACCEPT_JSON = accept(MediaType.APPLICATION_JSON)
}
}
Java
Kotlin
import reactor.core.publisher.Mono;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
@Component
public class MyUserHandler {
public Mono<ServerResponse> getUser(ServerRequest request) {
...
}
public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
...
}
public Mono<ServerResponse> deleteUser(ServerRequest request) {
...
}
}
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono
@Component
class MyUserHandler {
fun getUser(request: ServerRequest?): Mono<ServerResponse> {
...
}
fun getUserCustomers(request: ServerRequest?): Mono<ServerResponse> {
...
}
fun deleteUser(request: ServerRequest?): Mono<ServerResponse> {
...
}
}
"WebFlux.fn" は Spring Framework の一部であり、詳細情報はそのリファレンスドキュメントに記載されています。
RouterFunction Bean をいくつでも定義して、ルーターの定義をモジュール化できます。優先順位を適用する必要がある場合は、Bean をオーダーできます。 |
開始するには、spring-boot-starter-webflux
モジュールをアプリケーションに追加します。
アプリケーションに spring-boot-starter-web モジュールと spring-boot-starter-webflux モジュールの両方を追加すると、WebFlux ではなく Spring Boot が Spring MVC を自動構成します。この動作が選択されたのは、多くの Spring 開発者が spring-boot-starter-webflux を Spring MVC アプリケーションに追加してリアクティブ WebClient を使用するためです。選択したアプリケーションの種類を SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE) に設定することで、引き続き選択を強制できます。 |
Spring WebFlux 自動構成
Spring Boot は、ほとんどのアプリケーションで適切に機能する Spring WebFlux の自動構成を提供します。
自動構成により、Spring のデフォルトに加えて次の機能が追加されます。
HttpMessageReader
およびHttpMessageWriter
インスタンスのコーデックの構成(このドキュメントで後述)。WebJars のサポートを含む静的リソースの提供のサポート ( このドキュメントの後半で説明)。
Spring Boot WebFlux 機能を保持し、さらに WebFlux の構成を追加する場合、@EnableWebFlux
なしで型 WebFluxConfigurer
の独自の @Configuration
クラスを追加できます。
自動構成された HttpHandler
にさらにカスタマイズを追加する場合は、型 WebHttpHandlerBuilderCustomizer
の Bean を定義し、使用して WebHttpHandlerBuilder
を変更できます。
Spring WebFlux を完全に制御したい場合は、@EnableWebFlux
アノテーションが付けられた独自の @Configuration
を追加できます。
Spring WebFlux 変換サービス
Spring WebFlux で使用される ConversionService
をカスタマイズする場合は、addFormatters
メソッドを使用して WebFluxConfigurer
Bean を提供できます。
変換は、spring.webflux.format.*
構成プロパティを使用してカスタマイズすることもできます。構成されていない場合は、次のデフォルトが使用されます。
プロパティ | DateTimeFormatter | フォーマット |
---|---|---|
|
|
|
|
| java.time の |
|
| java.time の |
HttpMessageReaders および HttpMessageWriters を使用した HTTP コーデック
Spring WebFlux は、HttpMessageReader
および HttpMessageWriter
インターフェースを使用して、HTTP リクエストおよびレスポンスを変換します。これらは、クラスパスで使用可能なライブラリを調べることにより、実用的なデフォルトを持つように CodecConfigurer
で構成されます。
Spring Boot は、コーデックの専用構成プロパティ spring.codec.*
を提供します。また、CodecCustomizer
インスタンスを使用して、さらにカスタマイズを適用します。例: spring.jackson.*
設定キーは Jackson コーデックに適用されます。
コーデックを追加またはカスタマイズする必要がある場合は、次の例に示すように、カスタム CodecCustomizer
コンポーネントを作成できます。
Java
Kotlin
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.ServerSentEventHttpMessageReader;
@Configuration(proxyBeanMethods = false)
public class MyCodecsConfiguration {
@Bean
public CodecCustomizer myCodecCustomizer() {
return (configurer) -> {
configurer.registerDefaults(false);
configurer.customCodecs().register(new ServerSentEventHttpMessageReader());
// ...
};
}
}
import org.springframework.boot.web.codec.CodecCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.http.codec.CodecConfigurer
import org.springframework.http.codec.ServerSentEventHttpMessageReader
class MyCodecsConfiguration {
@Bean
fun myCodecCustomizer(): CodecCustomizer {
return CodecCustomizer { configurer: CodecConfigurer ->
configurer.registerDefaults(false)
configurer.customCodecs().register(ServerSentEventHttpMessageReader())
}
}
}
Boot のカスタム JSON シリアライザーとデシリアライザーを活用することもできます。
静的コンテンツ
デフォルトでは、Spring Boot は、クラスパス内の /static
(または /public
または /resources
または /META-INF/resources
)と呼ばれるディレクトリから静的コンテンツを提供します。Spring WebFlux の ResourceWebHandler
を使用するため、独自の WebFluxConfigurer
を追加して addResourceHandlers
メソッドをオーバーライドすることにより、その動作を変更できます。
デフォルトでは、リソースは /**
にマップされますが、spring.webflux.static-path-pattern
プロパティを設定することでそれを調整できます。たとえば、すべてのリソースを /resources/**
に再配置するには、次のようにします。
プロパティ
YAML
spring.webflux.static-path-pattern=/resources/**
spring:
webflux:
static-path-pattern: "/resources/**"
spring.web.resources.static-locations
を使用して、静的リソースの場所をカスタマイズすることもできます。これにより、デフォルト値がディレクトリの場所のリストに置き換えられます。そうすると、デフォルトのウェルカムページ検出がカスタムの場所に切り替わります。起動時にいずれかの場所に index.html
がある場合、アプリケーションのホームページです。
前述の「標準」の静的リソースの場所に加えて、Webjars コンテンツ (英語) には特別なケースが作成されます。デフォルトでは、パスが /webjars/**
のリソースは、Webjars 形式でパッケージ化されている場合、jar ファイルから提供されます。パスは spring.webflux.webjars-path-pattern
プロパティでカスタマイズできます。
Spring WebFlux アプリケーションはサーブレット API に厳密に依存していないため、war ファイルとしてデプロイすることはできず、src/main/webapp ディレクトリを使用しません。 |
ウェルカムページ
Spring Boot は、静的なウェルカムページとテンプレート化されたウェルカムページの両方をサポートしています。最初に、構成された静的コンテンツの場所で index.html
ファイルを探します。見つからない場合は、index
テンプレートを探します。どちらかが見つかった場合、アプリケーションのウェルカムページとして自動的に使用されます。
これは、アプリケーションによって定義された実際のインデックスルートのフォールバックとしてのみ機能します。順序は HandlerMapping
Bean の順序によって定義され、デフォルトでは次のようになります。
|
|
|
|
|
The welcome page support |
Template Engines
As well as REST web services, you can also use Spring WebFlux to serve dynamic HTML content. Spring WebFlux supports a variety of templating technologies, including Thymeleaf, FreeMarker, and Mustache.
Spring Boot includes auto-configuration support for the following templating engines:
When you use one of these templating engines with the default configuration, your templates are picked up automatically from src/main/resources/templates
.
Error Handling
Spring Boot provides a WebExceptionHandler
that handles all errors in a sensible way.
Its position in the processing order is immediately before the handlers provided by WebFlux, which are considered last.
For machine clients, it produces a JSON response with details of the error, the HTTP status, and the exception message.
For browser clients, there is a “whitelabel” error handler that renders the same data in HTML format.
You can also provide your own HTML templates to display errors (see the next section).
Before customizing error handling in Spring Boot directly, you can leverage the RFC 9457 Problem Details support in Spring WebFlux.
Spring WebFlux can produce custom error messages with the application/problem+json
media type, like:
{
"type": "https://example.org/problems/unknown-project",
"title": "Unknown project",
"status": 404,
"detail": "No project found for id 'spring-unknown'",
"instance": "/projects/spring-unknown"
}
This support can be enabled by setting spring.webflux.problemdetails.enabled
to true
.
The first step to customizing this feature often involves using the existing mechanism but replacing or augmenting the error contents.
For that, you can add a bean of type ErrorAttributes
.
To change the error handling behavior, you can implement ErrorWebExceptionHandler
and register a bean definition of that type.
Because an ErrorWebExceptionHandler
is quite low-level, Spring Boot also provides a convenient AbstractErrorWebExceptionHandler
to let you handle errors in a WebFlux functional way, as shown in the following example:
-
Java
-
Kotlin
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.function.server.ServerResponse.BodyBuilder;
@Component
public class MyErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, WebProperties webProperties,
ApplicationContext applicationContext, ServerCodecConfigurer serverCodecConfigurer) {
super(errorAttributes, webProperties.getResources(), applicationContext);
setMessageReaders(serverCodecConfigurer.getReaders());
setMessageWriters(serverCodecConfigurer.getWriters());
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml);
}
private boolean acceptsXml(ServerRequest request) {
return request.headers().accept().contains(MediaType.APPLICATION_XML);
}
public Mono<ServerResponse> handleErrorAsXml(ServerRequest request) {
BodyBuilder builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR);
// ... additional builder calls
return builder.build();
}
}
import org.springframework.boot.autoconfigure.web.WebProperties
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler
import org.springframework.boot.web.reactive.error.ErrorAttributes
import org.springframework.context.ApplicationContext
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.codec.ServerCodecConfigurer
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.RouterFunctions
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono
@Component
class MyErrorWebExceptionHandler(
errorAttributes: ErrorAttributes, webProperties: WebProperties,
applicationContext: ApplicationContext, serverCodecConfigurer: ServerCodecConfigurer
) : AbstractErrorWebExceptionHandler(errorAttributes, webProperties.resources, applicationContext) {
init {
setMessageReaders(serverCodecConfigurer.readers)
setMessageWriters(serverCodecConfigurer.writers)
}
override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml)
}
private fun acceptsXml(request: ServerRequest): Boolean {
return request.headers().accept().contains(MediaType.APPLICATION_XML)
}
fun handleErrorAsXml(request: ServerRequest): Mono<ServerResponse> {
val builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
// ... additional builder calls
return builder.build()
}
}
For a more complete picture, you can also subclass DefaultErrorWebExceptionHandler
directly and override specific methods.
In some cases, errors handled at the controller level are not recorded by web observations or the metrics infrastructure. Applications can ensure that such exceptions are recorded with the observations by setting the handled exception on the observation context.
Custom Error Pages
If you want to display a custom HTML error page for a given status code, you can add views that resolve from error/*
, for example by adding files to a /error
directory.
Error pages can either be static HTML (that is, added under any of the static resource directories) or built with templates.
The name of the file should be the exact status code, a status code series mask, or error
for a default if nothing else matches.
Note that the path to the default error view is error/error
, whereas with Spring MVC the default error view is error
.
For example, to map 404
to a static HTML file, your directory structure would be as follows:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>
To map all 5xx
errors by using a Mustache template, your directory structure would be as follows:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.mustache
+- <other templates>
Web Filters
Spring WebFlux provides a WebFilter
interface that can be implemented to filter HTTP request-response exchanges.
WebFilter
beans found in the application context will be automatically used to filter each exchange.
Where the order of the filters is important they can implement Ordered
or be annotated with @Order
.
Spring Boot auto-configuration may configure web filters for you.
When it does so, the orders shown in the following table will be used:
Web Filter | Order |
---|---|
|
|
|
|
埋め込み型リアクティブサーバーのサポート
Spring Boot には、次の組み込みリアクティブ Web サーバーのサポートが含まれています: Reactor、Netty、Tomcat、Jetty、Undertow。ほとんどの開発者は、適切なスターターを使用して、完全に構成されたインスタンスを取得します。デフォルトでは、組み込みサーバーはポート 8080 で HTTP リクエストをリッスンします。
リアクティブサーバーのカスタマイズ
一般的なリアクティブ Web サーバー設定は、Spring Environment
プロパティを使用して構成できます。通常は、application.properties
または application.yaml
ファイルでプロパティを定義します。
一般的なサーバー設定は次のとおりです。
Spring Boot は可能な限り共通設定を公開しようとしますが、常に可能であるとは限りません。このような場合には、server.netty.*
などの専用の名前空間がサーバー固有のカスタマイズを提供します。
完全なリストについては、ServerProperties (Javadoc) クラスを参照してください。 |
プログラムによるカスタマイズ
リアクティブ Web サーバーをプログラムで構成する必要がある場合は、WebServerFactoryCustomizer
インターフェースを実装する Spring Bean を登録できます。WebServerFactoryCustomizer
は、多数のカスタマイズ setter メソッドを含む ConfigurableReactiveWebServerFactory
へのアクセスを提供します。次の例は、プログラムによるポートの設定を示しています。
Java
Kotlin
import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> {
@Override
public void customize(ConfigurableReactiveWebServerFactory server) {
server.setPort(9000);
}
}
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory
import org.springframework.stereotype.Component
@Component
class MyWebServerFactoryCustomizer : WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> {
override fun customize(server: ConfigurableReactiveWebServerFactory) {
server.setPort(9000)
}
}
JettyReactiveWebServerFactory
、NettyReactiveWebServerFactory
、TomcatReactiveWebServerFactory
、UndertowReactiveWebServerFactory
は、Jetty、Reactor Netty、Tomcat、Undertow 用の追加のカスタマイズ setter メソッドを備えた ConfigurableReactiveWebServerFactory
の専用バリアントです。次の例は、Reactor Netty 固有の構成オプションへのアクセスを提供する NettyReactiveWebServerFactory
をカスタマイズする方法を示しています。
Java
Kotlin
import java.time.Duration;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
@Component
public class MyNettyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
@Override
public void customize(NettyReactiveWebServerFactory factory) {
factory.addServerCustomizers((server) -> server.idleTimeout(Duration.ofSeconds(20)));
}
}
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.stereotype.Component
import java.time.Duration
@Component
class MyNettyWebServerFactoryCustomizer : WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
override fun customize(factory: NettyReactiveWebServerFactory) {
factory.addServerCustomizers({ server -> server.idleTimeout(Duration.ofSeconds(20)) })
}
}
ConfigurableReactiveWebServerFactory を直接カスタマイズする
ReactiveWebServerFactory
から拡張する必要があるより高度なユースケースの場合は、そのような型の Bean を自分で公開できます。
Setter は、多くの構成オプション用に提供されています。さらに特殊な操作が必要な場合に備えて、protected メソッド「フック」もいくつか提供されています。詳細については、ConfigurableReactiveWebServerFactory
(Javadoc) API ドキュメントを参照してください。
自動構成されたカスタマイザーは引き続きカスタムファクトリに適用されるため、そのオプションは慎重に使用してください。 |
リアクティブサーバーリソースの構成
Reactor Netty または Jetty サーバーを自動構成する場合、Spring Boot は、サーバーインスタンスに HTTP リソースを提供する特定の Bean ReactorResourceFactory
または JettyResourceFactory
を作成します。
デフォルトでは、これらのリソースは、最適なパフォーマンスのために、Reactor Netty および Jetty クライアントとも共有されます。
同じテクノロジーがサーバーとクライアントに使用されます
クライアントインスタンスは、Spring Boot によって自動構成された
WebClient.Builder
Bean を使用して構築されます
開発者は、カスタム ReactorResourceFactory
または JettyResourceFactory
Bean を提供することにより、Jetty および Reactor Netty のリソース構成をオーバーライドできます。これは、クライアントとサーバーの両方に適用されます。
クライアント側のリソース構成の詳細については、WebClient ランタイムセクションを参照してください。