Spring Boot は、Web アプリケーションの開発に最適です。組み込み Tomcat、Jetty、Undertow、Netty を使用して、自己完結型の HTTP サーバーを作成できます。ほとんどの Web アプリケーションは、spring-boot-starter-web モジュールを使用して、すばやく起動して実行します。spring-boot-starter-webflux モジュールを使用して、リアクティブ Web アプリケーションを構築することもできます。

Spring Boot Web アプリケーションをまだ開発していない場合は、入門セクションの "Hello World!" の例に従うことができます。

1. サーブレット Web アプリケーション

サーブレットベースの Web アプリケーションを構築する場合は、Spring MVC または Jersey 用の Spring Boot の自動構成を利用できます。

1.1. 「Spring Web MVC フレームワーク」

Spring Web MVC フレームワーク ( "Spring MVC" と呼ばれることが多い) は、リッチな「モデル、ビュー、コントローラー」Web フレームワークです。Spring MVC を使用すると、受信 HTTP リクエストを処理するための特別な @Controller または @RestController Bean を作成できます。コントローラーのメソッドは、@RequestMapping アノテーションを使用して HTTP にマップされます。

次のコードは、JSON データを提供する典型的な @RestController を示しています。

Java
import java.util.List;

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 User getUser(@PathVariable Long userId) {
        return this.userRepository.findById(userId).get();
    }

    @GetMapping("/{userId}/customers")
    public List<Customer> getUserCustomers(@PathVariable Long userId) {
        return this.userRepository.findById(userId).map(this.customerRepository::findByUser).get();
    }

    @DeleteMapping("/{userId}")
    public void deleteUser(@PathVariable Long userId) {
        this.userRepository.deleteById(userId);
    }

}
Kotlin
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")
class MyRestController(private val userRepository: UserRepository, private val customerRepository: CustomerRepository) {

    @GetMapping("/{userId}")
    fun getUser(@PathVariable userId: Long): User {
        return userRepository.findById(userId).get()
    }

    @GetMapping("/{userId}/customers")
    fun getUserCustomers(@PathVariable userId: Long): List<Customer> {
        return userRepository.findById(userId).map(customerRepository::findByUser).get()
    }

    @DeleteMapping("/{userId}")
    fun deleteUser(@PathVariable userId: Long) {
        userRepository.deleteById(userId)
    }

}

次の例に示すように、関数型バリアントである "WebMvc.fn" は、ルーティング構成をリクエストの実際の処理から分離します。

Java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.function.RequestPredicate;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;

import static org.springframework.web.servlet.function.RequestPredicates.accept;
import static org.springframework.web.servlet.function.RouterFunctions.route;

@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {

    private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);

    @Bean
    public RouterFunction<ServerResponse> routerFunction(MyUserHandler userHandler) {
        return route()
                .GET("/{user}", ACCEPT_JSON, userHandler::getUser)
                .GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
                .DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
                .build();
    }

}
Kotlin
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.servlet.function.RequestPredicates.accept
import org.springframework.web.servlet.function.RouterFunction
import org.springframework.web.servlet.function.RouterFunctions
import org.springframework.web.servlet.function.ServerResponse

@Configuration(proxyBeanMethods = false)
class MyRoutingConfiguration {

    @Bean
    fun routerFunction(userHandler: MyUserHandler): RouterFunction<ServerResponse> {
        return RouterFunctions.route()
            .GET("/{user}", ACCEPT_JSON, userHandler::getUser)
            .GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
            .DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
            .build()
    }

    companion object {
        private val ACCEPT_JSON = accept(MediaType.APPLICATION_JSON)
    }

}
Java
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

@Component
public class MyUserHandler {

    public ServerResponse getUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

    public ServerResponse getUserCustomers(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

    public ServerResponse deleteUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

}
Kotlin
import org.springframework.stereotype.Component
import org.springframework.web.servlet.function.ServerRequest
import org.springframework.web.servlet.function.ServerResponse

@Component
class MyUserHandler {

    fun getUser(request: ServerRequest?): ServerResponse {
        return ServerResponse.ok().build()
    }

    fun getUserCustomers(request: ServerRequest?): ServerResponse {
        return ServerResponse.ok().build()
    }

    fun deleteUser(request: ServerRequest?): ServerResponse {
        return ServerResponse.ok().build()
    }

}

Spring MVC はコア Spring Framework の一部であり、詳細情報はリファレンスドキュメントで入手できます。spring.io/guides で入手可能な Spring MVC をカバーするガイドもいくつかあります。

RouterFunction Bean をいくつでも定義して、ルーターの定義をモジュール化できます。優先順位を適用する必要がある場合は、Bean をオーダーできます。

1.1.1. Spring MVC 自動構成

Spring Boot は、ほとんどのアプリケーションで適切に動作する Spring MVC の自動構成を提供します。これは @EnableWebMvc の必要性を置き換えるものであり、2 つを一緒に使用することはできません。Spring MVC のデフォルトに加えて、自動構成では次の機能が提供されます。

これらの Spring Boot MVC のカスタマイズを保持し、さらに MVC のカスタマイズ(インターセプター、フォーマッター、View Controller、およびその他の機能)を作成する場合は、@EnableWebMvc なしで型 WebMvcConfigurer の独自の @Configuration クラスを追加できます。

RequestMappingHandlerMappingRequestMappingHandlerAdapter、または ExceptionHandlerExceptionResolver のカスタムインスタンスを提供し、Spring Boot MVC カスタマイズを保持したい場合は、WebMvcRegistrations 型の Bean を宣言し、それを使用してこれらのコンポーネントのカスタムインスタンスを提供できます。カスタムインスタンスは、Spring MVC によるさらなる初期化と構成の対象になります。後続の処理に参加し、必要に応じてオーバーライドするには、WebMvcConfigurer を使用する必要があります。

自動構成を使用せず、Spring MVC を完全に制御したい場合は、@EnableWebMvc というアノテーションを付けた独自の @Configuration を追加します。あるいは、@EnableWebMvc の Javadoc の説明に従って、独自の @Configuration アノテーション付き DelegatingWebMvcConfiguration を追加します。

1.1.2. Spring MVC 変換サービス

Spring MVC は、application.properties または application.yaml ファイルから値を変換するために使用されるものとは異なる ConversionService を使用します。これは、PeriodDurationDataSize コンバーターが使用できず、@DurationUnit および @DataSizeUnit アノテーションが無視されることを意味します。

Spring MVC が使用する ConversionService をカスタマイズする場合は、WebMvcConfigurer Bean に addFormatters メソッドを提供できます。このメソッドから、好きなコンバーターを登録したり、ApplicationConversionService で利用可能な静的メソッドに委譲したりできます。

変換は、spring.mvc.format.* 構成プロパティを使用してカスタマイズすることもできます。構成されていない場合は、次のデフォルトが使用されます。

プロパティ DateTimeFormatter

spring.mvc.format.date

ofLocalizedDate(FormatStyle.SHORT)

spring.mvc.format.time

ofLocalizedTime(FormatStyle.SHORT)

spring.mvc.format.date-time

ofLocalizedDateTime(FormatStyle.SHORT)

1.1.3. HttpMessageConverters

Spring MVC は、HttpMessageConverter インターフェースを使用して HTTP リクエストとレスポンスを変換します。適切なデフォルトは、すぐに含まれています。例: オブジェクトは、JSON (Jackson ライブラリを使用) または XML (使用可能な場合は Jackson XML 拡張を使用するか、Jackson XML 拡張が使用できない場合は JAXB を使用) に自動的に変換できます。デフォルトでは、文字列は UTF-8 でエンコードされます。

コンテキストに存在する HttpMessageConverter Bean は、コンバーターのリストに追加されます。同じ方法でデフォルトのコンバーターをオーバーライドすることもできます。

コンバーターを追加またはカスタマイズする必要がある場合は、次のように、Spring Boot の HttpMessageConverters クラスを使用できます。

Java
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;

@Configuration(proxyBeanMethods = false)
public class MyHttpMessageConvertersConfiguration {

    @Bean
    public HttpMessageConverters customConverters() {
        HttpMessageConverter<?> additional = new AdditionalHttpMessageConverter();
        HttpMessageConverter<?> another = new AnotherHttpMessageConverter();
        return new HttpMessageConverters(additional, another);
    }

}
Kotlin
import org.springframework.boot.autoconfigure.http.HttpMessageConverters
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.converter.HttpMessageConverter

@Configuration(proxyBeanMethods = false)
class MyHttpMessageConvertersConfiguration {

    @Bean
    fun customConverters(): HttpMessageConverters {
        val additional: HttpMessageConverter<*> = AdditionalHttpMessageConverter()
        val another: HttpMessageConverter<*> = AnotherHttpMessageConverter()
        return HttpMessageConverters(additional, another)
    }

}

さらに制御するには、HttpMessageConverters をサブクラス化し、その postProcessConverters および / または postProcessPartConverters メソッドをオーバーライドすることもできます。これは、Spring MVC がデフォルトで構成するコンバーターの一部を並べ替えたり削除したりする場合に役立ちます。

1.1.4. MessageCodesResolver

Spring MVC には、バインディングエラーからエラーメッセージをレンダリングするためのエラーコードを生成するための戦略があります: MessageCodesResolverspring.mvc.message-codes-resolver-format プロパティ PREFIX_ERROR_CODE または POSTFIX_ERROR_CODE を設定すると、Spring Boot によって作成されます ( DefaultMessageCodesResolver.Format (Javadoc) の列挙を参照)。

1.1.5. 静的コンテンツ

デフォルトでは、Spring Boot は、クラスパス内の /static (または /public または /resources または /META-INF/resources) というディレクトリから、または ServletContext のルートから静的コンテンツを提供します。Spring MVC の ResourceHttpRequestHandler を使用しているため、独自の WebMvcConfigurer を追加して addResourceHandlers メソッドをオーバーライドすることで、その動作を変更できます。

スタンドアロン Web アプリケーションでは、コンテナーのデフォルトサーブレットは有効になっていません。server.servlet.register-default-servlet プロパティを使用して有効にすることができます。

デフォルトのサーブレットはフォールバックとして機能し、Spring が処理しないと決定した場合に ServletContext のルートからコンテンツを提供します。Spring は常に DispatcherServlet を介してリクエストを処理できるため、ほとんどの場合、これは発生しません (デフォルトの MVC 構成を変更しない限り)。

デフォルトでは、リソースは /** にマップされますが、spring.mvc.static-path-pattern プロパティでそれを調整できます。たとえば、すべてのリソースを /resources/** に再配置するには、次のようにします。

Properties
spring.mvc.static-path-pattern=/resources/**
Yaml
spring:
  mvc:
    static-path-pattern: "/resources/**"

spring.web.resources.static-locations プロパティを使用して静的リソースの場所をカスタマイズすることもできます(デフォルト値をディレクトリの場所のリストに置き換えます)。ルートサーブレットコンテキストパス "/" も、場所として自動的に追加されます。

前述の「標準」の静的リソースの場所に加えて、Webjars コンテンツ (英語) には特別なケースが作成されます。デフォルトでは、パスが /webjars/** のリソースは、Webjars 形式でパッケージ化されている場合、jar ファイルから提供されます。パスは spring.mvc.webjars-path-pattern プロパティでカスタマイズできます。

アプリケーションが jar としてパッケージ化されている場合は、src/main/webapp ディレクトリを使用しないでください。このディレクトリは一般的な標準ですが、war パッケージでのみ機能し、jar を生成する場合、ほとんどのビルドツールでは暗黙のうちに無視されます。

Spring Boot は、Spring MVC が提供する高度なリソース処理機能もサポートし、静的リソースのキャッシュ無効化や Webjar のバージョンに依存しない URL の使用などのユースケースを可能にします。

Webjar にバージョンに依存しない URL を使用するには、webjars-locator-core 依存関係を追加します。次に、Webjar を宣言します。例として jQuery を使用して、"/webjars/jquery/jquery.min.js" を追加すると、"/webjars/jquery/x.y.z/jquery.min.js" が生成されます(x.y.z は Webjar バージョンです)。

JBoss を使用する場合、webjars-locator-core ではなく webjars-locator-jboss-vfs 依存関係を宣言する必要があります。それ以外の場合、すべての Webjar は 404 として解決されます。

キャッシュ無効化を使用するには、次の構成ですべての静的リソースのキャッシュ無効化ソリューションを構成し、URL に <link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/> などのコンテンツハッシュを効果的に追加します。

Properties
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
Yaml
spring:
  web:
    resources:
      chain:
        strategy:
          content:
            enabled: true
            paths: "/**"
リソースへのリンクは、Thymeleaf および FreeMarker 用に自動構成された ResourceUrlEncodingFilter のおかげで、実行時にテンプレートで書き換えられます。JSP を使用する場合は、このフィルターを手動で宣言する必要があります。現在、他のテンプレートエンジンは自動的にサポートされていませんが、カスタムテンプレートマクロ / ヘルパーおよび ResourceUrlProvider (Javadoc) を使用することができます。

JavaScript モジュールローダーなどを使用してリソースを動的にロードする場合、ファイルの名前を変更することはできません。そのため、他の戦略もサポートされており、組み合わせることができます。"fixed" 戦略では、次の例に示すように、ファイル名を変更せずに URL に静的バージョン文字列を追加します。

Properties
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
spring.web.resources.chain.strategy.fixed.enabled=true
spring.web.resources.chain.strategy.fixed.paths=/js/lib/
spring.web.resources.chain.strategy.fixed.version=v12
Yaml
spring:
  web:
    resources:
      chain:
        strategy:
          content:
            enabled: true
            paths: "/**"
          fixed:
            enabled: true
            paths: "/js/lib/"
            version: "v12"

この構成では、"/js/lib/" にある JavaScript モジュールは固定バージョン管理戦略("/v12/js/lib/mymodule.js")を使用しますが、他のリソースはコンテンツコンテンツ(<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>)を引き続き使用します。

サポートされるオプションの詳細については、WebProperties.Resources [GitHub] (英語) を参照してください。

この機能は、専用のブログ投稿 (英語) および Spring Framework のリファレンスドキュメントで詳細に説明されています。

1.1.6. ウェルカムページ

Spring Boot は、静的なウェルカムページとテンプレート化されたウェルカムページの両方をサポートしています。最初に、構成された静的コンテンツの場所で index.html ファイルを探します。見つからない場合は、index テンプレートを探します。どちらかが見つかった場合、アプリケーションのウェルカムページとして自動的に使用されます。

これは、アプリケーションによって定義された実際のインデックスルートのフォールバックとしてのみ機能します。順序は HandlerMapping Bean の順序によって定義され、デフォルトでは次のようになります。

RouterFunctionMapping

RouterFunction Bean で宣言されたエンドポイント

RequestMappingHandlerMapping

@Controller Bean で宣言されたエンドポイント

WelcomePageHandlerMapping

ようこそページのサポート

1.1.7. カスタムファビコン

他の静的リソースと同様に、Spring Boot は構成された静的コンテンツの場所で favicon.ico をチェックします。そのようなファイルが存在する場合、アプリケーションのファビコンとして自動的に使用されます。

1.1.8. パスマッチングとコンテンツネゴシエーション

Spring MVC は、リクエストパスを調べて、アプリケーションで定義されたマッピング (たとえば、コントローラーメソッドの @GetMapping アノテーション) と照合することにより、受信 HTTP リクエストをハンドラーにマッピングできます。

Spring Boot は、デフォルトでサフィックスパターンマッチングを無効にすることを選択します。これは、"GET /projects/spring-boot.json" のようなリクエストが @GetMapping("/projects/spring-boot") マッピングにマッチングされないことを意味します。これは、Spring MVC アプリケーションのベストプラクティスと見なされています。この機能は、これまで、適切な "Accept" リクエストヘッダーを送信しなかった HTTP クライアントで主に役立ちました。正しいコンテンツ型をクライアントに送信する必要がありました。今日では、コンテントネゴシエーションの信頼性ははるかに高くなっています。

適切な "Accept" リクエストヘッダーを一貫して送信しない HTTP クライアントを処理する方法は他にもあります。サフィックスマッチングを使用する代わりに、クエリパラメーターを使用して、"GET /projects/spring-boot?format=json" などのリクエストが @GetMapping("/projects/spring-boot") にマップされるようにすることができます。

Properties
spring.mvc.contentnegotiation.favor-parameter=true
Yaml
spring:
  mvc:
    contentnegotiation:
      favor-parameter: true

または、別のパラメーター名を使用する場合:

Properties
spring.mvc.contentnegotiation.favor-parameter=true
spring.mvc.contentnegotiation.parameter-name=myparam
Yaml
spring:
  mvc:
    contentnegotiation:
      favor-parameter: true
      parameter-name: "myparam"

ほとんどの標準メディア型はすぐにサポートされますが、新しいメディア型を定義することもできます。

Properties
spring.mvc.contentnegotiation.media-types.markdown=text/markdown
Yaml
spring:
  mvc:
    contentnegotiation:
      media-types:
        markdown: "text/markdown"

Spring Framework 5.3 の時点で、Spring MVC は、コントローラーへのリクエストパスを照合するための 2 つの戦略をサポートしています。デフォルトでは、Spring Boot は PathPatternParser 戦略を使用します。PathPatternParser は最適化された実装 (英語) ですが、AntPathMatcher 戦略と比較していくつかの制限があります。PathPatternParser は、一部のパスパターンバリアントの使用を制限します。また、パスプレフィックス (spring.mvc.servlet.path) を使用して DispatcherServlet を構成することとも互換性がありません。

次の例に示すように、戦略は spring.mvc.pathmatch.matching-strategy 構成プロパティを使用して構成できます。

Properties
spring.mvc.pathmatch.matching-strategy=ant-path-matcher
Yaml
spring:
  mvc:
    pathmatch:
      matching-strategy: "ant-path-matcher"

デフォルトでは、リクエストのハンドラーが見つからない場合、Spring MVC は 404 未検出 エラーレスポンスを送信します。代わりに NoHandlerFoundException をスローするには、configprop:spring.mvc.throw-exception-if-no-handler-found を true に設定します。デフォルトでは、静的コンテンツの提供は /** にマップされるため、すべてのリクエストにハンドラーが提供されることに注意してください。NoHandlerFoundException をスローするには、spring.mvc.static-path-pattern を /resources/** などのより具体的な値に設定するか、spring.web.resources.add-mappings を false に設定して、静的コンテンツの提供を完全に無効にする必要があります。

1.1.9. ConfigurableWebBindingInitializer

Spring MVC は WebBindingInitializer を使用して、特定のリクエストに対して WebDataBinder を初期化します。独自の ConfigurableWebBindingInitializer@Bean を作成すると、Spring Boot はそれを使用するように Spring MVC を自動的に構成します。

1.1.10. テンプレートエンジン

REST Web サービスだけでなく、Spring MVC を使用して動的 HTML コンテンツを提供することもできます。Spring MVC は、Thymeleaf、FreeMarker、JSP など、さまざまなテンプレートテクノロジをサポートしています。また、他の多くのテンプレートエンジンには、独自の Spring MVC 統合が含まれています。

Spring Boot には、次のテンプレートエンジンの自動構成サポートが含まれています。

可能であれば、JSP を避ける必要があります。組み込みサーブレットコンテナーで使用する場合、いくつかの既知の制限があります。

これらのテンプレートエンジンのいずれかを既定の構成で使用すると、src/main/resources/templates からテンプレートが自動的に選択されます。

アプリケーションの実行メソッドに応じて、IDE はクラスパスの順序を変える場合があります。IDE でメインメソッドからアプリケーションを実行すると、Maven または Gradle を使用して、またはパッケージ化された jar からアプリケーションを実行する場合とは異なる順序になります。これにより、Spring Boot が予期されたテンプレートを見つけられない可能性があります。この問題が発生した場合は、IDE でクラスパスを並べ替えて、モジュールのクラスとリソースを最初に配置できます。

1.1.11. エラー処理

デフォルトでは、Spring Boot はすべてのエラーを適切な方法で処理する /error マッピングを提供し、サーブレットコンテナーに「グローバル」エラーページとして登録されます。マシンクライアントの場合、エラー、HTTP ステータス、例外メッセージの詳細を含む JSON レスポンスを生成します。ブラウザークライアントの場合、同じデータを HTML 形式でレンダリングする「ホワイトラベル」エラービューがあります(カスタマイズするには、error に解決される View を追加します)。

デフォルトのエラー処理動作をカスタマイズする場合に設定できる server.error プロパティがいくつかあります。付録の “サーバープロパティ” セクションを参照してください。

デフォルトの動作を完全に置き換えるには、ErrorController を実装してその型の Bean 定義を登録するか、型 ErrorAttributes の Bean を追加して既存のメカニズムを使用しますが、内容を置き換えます。

BasicErrorController は、カスタム ErrorController の基本クラスとして使用できます。これは、新しいコンテンツ型のハンドラーを追加する場合に特に便利です(デフォルトでは、text/html を具体的に処理し、他のすべてにフォールバックを提供します)。これを行うには、BasicErrorController を継承し、produces 属性を持つ @RequestMapping を使用して public メソッドを追加し、新しい型の Bean を作成します。

Spring Framework 6.0 の時点で、RFC 7807 問題の詳細がサポートされています。Spring MVC は、次のような application/problem+json メディア型でカスタムエラーメッセージを生成できます。

{
  "type": "https://example.org/problems/unknown-project",
  "title": "Unknown project",
  "status": 404,
  "detail": "No project found for id 'spring-unknown'",
  "instance": "/projects/spring-unknown"
}

このサポートは、spring.mvc.problemdetails.enabled を true に設定することで有効にできます。

次の例に示すように、@ControllerAdvice アノテーションが付けられたクラスを定義して、特定のコントローラーまたは例外型、あるいはその両方を返すように JSON ドキュメントをカスタマイズすることもできます。

Java
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice(basePackageClasses = SomeController.class)
public class MyControllerAdvice extends ResponseEntityExceptionHandler {

    @ResponseBody
    @ExceptionHandler(MyException.class)
    public ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
        HttpStatus status = getStatus(request);
        return new ResponseEntity<>(new MyErrorBody(status.value(), ex.getMessage()), status);
    }

    private HttpStatus getStatus(HttpServletRequest request) {
        Integer code = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        HttpStatus status = HttpStatus.resolve(code);
        return (status != null) ? status : HttpStatus.INTERNAL_SERVER_ERROR;
    }

}
Kotlin
import jakarta.servlet.RequestDispatcher
import jakarta.servlet.http.HttpServletRequest
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler

@ControllerAdvice(basePackageClasses = [SomeController::class])
class MyControllerAdvice : ResponseEntityExceptionHandler() {

    @ResponseBody
    @ExceptionHandler(MyException::class)
    fun handleControllerException(request: HttpServletRequest, ex: Throwable): ResponseEntity<*> {
        val status = getStatus(request)
        return ResponseEntity(MyErrorBody(status.value(), ex.message), status)
    }

    private fun getStatus(request: HttpServletRequest): HttpStatus {
        val code = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE) as Int
        val status = HttpStatus.resolve(code)
        return status ?: HttpStatus.INTERNAL_SERVER_ERROR
    }

}

上記の例では、MyException が SomeController と同じパッケージで定義されたコントローラーによってスローされた場合、ErrorAttributes 表現の代わりに MyErrorBody POJO の JSON 表現が使用されます。

場合によっては、コントローラーレベルで処理されたエラーが Web 観察やメトリクスインフラストラクチャによって記録されないことがあります。アプリケーションは、処理された例外を観測コンテキストに設定することにより、そのような例外が観測とともに確実に記録されるようにすることができます。

カスタムエラーページ

特定のステータスコードのカスタム HTML エラーページを表示する場合は、/error ディレクトリにファイルを追加できます。エラーページは、静的 HTML(つまり、任意の静的リソースディレクトリに追加される)にすることも、テンプレートを使用して作成することもできます。ファイルの名前は、正確なステータスコードまたはシリーズマスクである必要があります。

例: 404 を静的 HTML ファイルにマップするには、ディレクトリ構造は次のようになります。

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- public/
             +- error/
             |   +- 404.html
             +- <other public assets>

FreeMarker テンプレートを使用してすべての 5xx エラーをマップするには、ディレクトリ構造は次のようになります。

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- templates/
             +- error/
             |   +- 5xx.ftlh
             +- <other templates>

より複雑なマッピングの場合、次の例に示すように、ErrorViewResolver インターフェースを実装する Bean を追加することもできます。

Java
import java.util.Map;

import jakarta.servlet.http.HttpServletRequest;

import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.ModelAndView;

public class MyErrorViewResolver implements ErrorViewResolver {

    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        // Use the request or status to optionally return a ModelAndView
        if (status == HttpStatus.INSUFFICIENT_STORAGE) {
            // We could add custom model values here
            new ModelAndView("myview");
        }
        return null;
    }

}
Kotlin
import jakarta.servlet.http.HttpServletRequest
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver
import org.springframework.http.HttpStatus
import org.springframework.web.servlet.ModelAndView

class MyErrorViewResolver : ErrorViewResolver {

    override fun resolveErrorView(request: HttpServletRequest, status: HttpStatus,
            model: Map<String, Any>): ModelAndView? {
        // Use the request or status to optionally return a ModelAndView
        if (status == HttpStatus.INSUFFICIENT_STORAGE) {
            // We could add custom model values here
            return ModelAndView("myview")
        }
        return null
    }

}

@ExceptionHandler メソッド@ControllerAdvice などの通常の Spring MVC 機能も使用できます。その後、ErrorController は未処理の例外を取得します。

Spring MVC 以外のエラーページのマッピング

Spring MVC を使用しないアプリケーションの場合、ErrorPageRegistrar インターフェースを使用して ErrorPages を直接登録できます。この抽象化は、基盤となる埋め込みサーブレットコンテナーと直接連携し、Spring MVC DispatcherServlet がなくても機能します。

Java
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;

@Configuration(proxyBeanMethods = false)
public class MyErrorPagesConfiguration {

    @Bean
    public ErrorPageRegistrar errorPageRegistrar() {
        return this::registerErrorPages;
    }

    private void registerErrorPages(ErrorPageRegistry registry) {
        registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
    }

}
Kotlin
import org.springframework.boot.web.server.ErrorPage
import org.springframework.boot.web.server.ErrorPageRegistrar
import org.springframework.boot.web.server.ErrorPageRegistry
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpStatus

@Configuration(proxyBeanMethods = false)
class MyErrorPagesConfiguration {

    @Bean
    fun errorPageRegistrar(): ErrorPageRegistrar {
        return ErrorPageRegistrar { registry: ErrorPageRegistry -> registerErrorPages(registry) }
    }

    private fun registerErrorPages(registry: ErrorPageRegistry) {
        registry.addErrorPages(ErrorPage(HttpStatus.BAD_REQUEST, "/400"))
    }

}
Filter によって処理されるパスで ErrorPage を登録する場合(Jersey や Wicket などの一部の非 SpringWeb フレームワークで一般的)、次に示すように、Filter を ERROR ディスパッチャーとして明示的に登録する必要があります。次の例:
Java
import java.util.EnumSet;

import jakarta.servlet.DispatcherType;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyFilterConfiguration {

    @Bean
    public FilterRegistrationBean<MyFilter> myFilter() {
        FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>(new MyFilter());
        // ...
        registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
        return registration;
    }

}
Kotlin
import jakarta.servlet.DispatcherType
import org.springframework.boot.web.servlet.FilterRegistrationBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.EnumSet

@Configuration(proxyBeanMethods = false)
class MyFilterConfiguration {

    @Bean
    fun myFilter(): FilterRegistrationBean<MyFilter> {
        val registration = FilterRegistrationBean(MyFilter())
        // ...
        registration.setDispatcherTypes(EnumSet.allOf(DispatcherType::class.java))
        return registration
    }

}

デフォルトの FilterRegistrationBean には ERROR ディスパッチャー型が含まれていないことに注意してください。

WAR デプロイ でのエラー処理

サーブレットコンテナーにデプロイされると、Spring Boot はエラーページフィルターを使用して、エラーステータスのあるリクエストを適切なエラーページに転送します。サーブレット仕様ではエラーページを登録するための API が提供されていないため、これが必要です。war ファイルをデプロイするコンテナーとアプリケーションが使用するテクノロジーによっては、追加の構成が必要になる場合があります。

エラーページフィルターは、レスポンスがまだコミットされていない場合にのみ、リクエストを正しいエラーページに転送できます。デフォルトでは、WebSphere アプリケーションサーバー 8.0 以降は、サーブレットのサービスメソッドが正常に完了すると、レスポンスをコミットします。com.ibm.ws.webcontainer.invokeFlushAfterService を false に設定して、この動作を無効にする必要があります。

1.1.12. CORS サポート

クロスオリジンリソース共有 [Mozilla] (CORS)は、ほとんどのブラウザー (英語) で実装されている W3C 仕様 (英語) であり、IFRAME や JSONP などの安全性の低いアプローチを使用する代わりに、どのようなクロスドメインリクエストを認可するかを柔軟に指定できます。

バージョン 4.2 以降、Spring MVC は CORS をサポートします。Spring Boot アプリケーションで @CrossOrigin (Javadoc) アノテーションを使用してコントローラーメソッド CORS 構成を使用する場合、特定の構成は必要ありません。グローバル CORS 設定は、次の例に示すように、カスタマイズされた addCorsMappings(CorsRegistry) メソッドで WebMvcConfigurer Bean を登録することで定義できます。

Java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {

            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**");
            }

        };
    }

}
Kotlin
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer

@Configuration(proxyBeanMethods = false)
class MyCorsConfiguration {

    @Bean
    fun corsConfigurer(): WebMvcConfigurer {
        return object : WebMvcConfigurer {
            override fun addCorsMappings(registry: CorsRegistry) {
                registry.addMapping("/api/**")
            }
        }
    }

}

1.2. JAX-RS および Jersey

REST エンドポイントに JAX-RS プログラミングモデルを使用する場合は、Spring MVC の代わりに使用可能な実装の 1 つを使用できます。Jersey (英語) Apache CXF (英語) は、すぐに使用できます。CXF では、アプリケーションコンテキストで Servlet または Filter を @Bean として登録する必要があります。Jersey にはネイティブ Spring サポートがいくつかあるため、スターターとともに Spring Boot での自動構成サポートも提供します。

Jersey を開始するには、spring-boot-starter-jersey を依存関係として含めてから、次の例に示すように、すべてのエンドポイントを登録する型 ResourceConfig の @Bean が 1 つ必要です。

import org.glassfish.jersey.server.ResourceConfig;

import org.springframework.stereotype.Component;

@Component
public class MyJerseyConfig extends ResourceConfig {

    public MyJerseyConfig() {
        register(MyEndpoint.class);
    }

}
Jersey の実行可能アーカイブのスキャンのサポートはかなり制限されています。例: 実行可能な war ファイルを実行している場合、完全に実行可能な jar ファイルまたは WEB-INF/classes  で見つかったパッケージ内のエンドポイントをスキャンできません。この制限を回避するには、packages メソッドを使用せず、前の例に示すように、register メソッドを使用してエンドポイントを個別に登録する必要があります。

より高度なカスタマイズのために、ResourceConfigCustomizer を実装する Bean を任意の数だけ登録することもできます。

登録されたすべてのエンドポイントは、次の例に示すように、HTTP リソースアノテーション(@GET など)を含む @Components である必要があります。

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.springframework.stereotype.Component;

@Component
@Path("/hello")
public class MyEndpoint {

    @GET
    public String message() {
        return "Hello";
    }

}

Endpoint は Spring @Component であるため、そのライフサイクルは Spring によって管理され、@Autowired アノテーションを使用して依存関係を注入し、@Value アノテーションを使用して外部構成を注入できます。デフォルトでは、Jersey サーブレットが登録され、/* にマップされます。@ApplicationPath を ResourceConfig に追加することにより、マッピングを変更できます。

デフォルトでは、Jersey は、jerseyServletRegistration という名前の ServletRegistrationBean 型の @Bean のサーブレットとして設定されます。デフォルトでは、サーブレットは遅延初期化されますが、spring.jersey.servlet.load-on-startup を設定することでその動作をカスタマイズできます。同じ名前で独自の Bean を作成することにより、その Bean を無効化またはオーバーライドできます。spring.jersey.type=filter を設定することにより、サーブレットの代わりにフィルターを使用することもできます(この場合、置換またはオーバーライドする @Bean は jerseyFilterRegistration です)。フィルターには @Order があり、spring.jersey.filter.order で設定できます。Jersey をフィルターとして使用する場合、Jersey によってインターセプトされないリクエストを処理するサーブレットが存在する必要があります。アプリケーションにそのようなサーブレットが含まれていない場合は、server.servlet.register-default-servlet を true に設定して、デフォルトのサーブレットを有効にすることをお勧めします。spring.jersey.init.* を使用してプロパティのマップを指定することにより、サーブレットとフィルターの両方の登録に init パラメーターを指定できます。

1.3. 組み込みサーブレットコンテナーのサポート

サーブレットアプリケーションの場合、Spring Boot には、組み込み Tomcat [Apache] (英語) Jetty (英語) Undertow [GitHub] (英語) サーバーのサポートが含まれています。ほとんどの開発者は、適切な「スターター」を使用して、完全に構成されたインスタンスを取得します。デフォルトでは、組み込みサーバーはポート 8080 で HTTP リクエストをリッスンします。

1.3.1. サーブレット、フィルター、リスナー

組み込みサーブレットコンテナーを使用する場合、Spring Bean を使用するか、サーブレットコンポーネントをスキャンすることにより、サーブレット、フィルター、すべてのリスナー(HttpSessionListener など)をサーブレット仕様から登録できます。

サーブレット、フィルター、リスナーを Spring Bean として登録する

Spring Bean である ServletFilter、サーブレット *Listener インスタンスはすべて、組み込みコンテナーに登録されます。これは、構成中に application.properties から値を参照する場合に特に便利です。

デフォルトでは、コンテキストに含まれるサーブレットが 1 つのみの場合、/ にマッピングされます。複数のサーブレット Bean の場合、Bean 名がパスプレフィックスとして使用されます。フィルターは /* にマップします。

規則ベースのマッピングに十分な柔軟性がない場合は、ServletRegistrationBeanFilterRegistrationBeanServletListenerRegistrationBean クラスを使用して完全に制御できます。

通常、フィルター Bean は順序付けされていないままにしておくのが安全です。特定の順序が必要な場合は、Filter に @Order のアノテーションを付けるか、Ordered を実装するようにする必要があります。Bean メソッドに @Order アノテーションを付けて、Filter の順序を構成することはできません。Filter クラスを変更して @Order を追加したり、Ordered を実装したりできない場合は、Filter の FilterRegistrationBean を定義し、setOrder(int) メソッドを使用して登録 Bean の順序を設定する必要があります。Ordered.HIGHEST_PRECEDENCE でリクエスト本文を読み取るフィルターを構成することは避けてください。これは、アプリケーションの文字エンコード構成に反する可能性があるためです。サーブレットフィルターがリクエストをラップする場合は、OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER 以下の順序で設定する必要があります。

アプリケーション内のすべての Filter の順序を確認するには、web  ロギンググループlogging.level.web=debug)のデバッグレベルロギングを有効にします。登録されたフィルターの詳細(順序や URL パターンなど)は、起動時にログに記録されます。
Filter Bean はアプリケーションのライフサイクルの非常に早い段階で初期化されるため、登録するときは注意してください。他の Bean と相互作用する Filter を登録する必要がある場合は、代わりに DelegatingFilterProxyRegistrationBean (Javadoc) の使用を検討してください。

1.3.2. サーブレットコンテキストの初期化

組み込みサーブレットコンテナーは、jakarta.servlet.ServletContainerInitializer インターフェースまたは Spring の org.springframework.web.WebApplicationInitializer インターフェースを直接実行しません。これは、war 内で実行するように設計されたサードパーティライブラリが Spring Boot アプリケーションを破壊するリスクを減らすことを目的とした意図的な設計上の決定です。

Spring Boot アプリケーションでサーブレットコンテキストの初期化を実行する必要がある場合は、org.springframework.boot.web.servlet.ServletContextInitializer インターフェースを実装する Bean を登録する必要があります。単一の onStartup メソッドは ServletContext へのアクセスを提供し、必要に応じて、既存の WebApplicationInitializer へのアダプターとして簡単に使用できます。

サーブレット、フィルター、リスナーのスキャン

組み込みコンテナーを使用する場合、@ServletComponentScan を使用すると、@WebServlet@WebFilter@WebListener アノテーションが付けられたクラスの自動登録を有効にできます。

@ServletComponentScan は、コンテナーの組み込み検出メカニズムが代わりに使用されるスタンドアロンコンテナーでは効果がありません。

1.3.3. ServletWebServerApplicationContext

内部的には、Spring Boot は組み込みサーブレットコンテナーのサポートに異なる型の ApplicationContext を使用します。ServletWebServerApplicationContext は、単一の ServletWebServerFactory Bean を検索することによってそれ自体をブートストラップする特殊な型の WebApplicationContext です。通常、TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactory は自動構成されています。

通常、これらの実装クラスを意識する必要はありません。ほとんどのアプリケーションは自動構成され、適切な ApplicationContext および ServletWebServerFactory がユーザーに代わって作成されます。

組み込みコンテナーのセットアップでは、ServletContext は、アプリケーションコンテキストの初期化中に発生するサーバーの起動の一部として設定されます。このため、ApplicationContext の Bean は、ServletContext で確実に初期化できません。これを回避する 1 つの方法は、Bean の依存関係として ApplicationContext を挿入し、必要な場合にのみ ServletContext にアクセスすることです。もう 1 つの方法は、サーバーの起動後にコールバックを使用することです。これは、次のように ApplicationStartedEvent をリッスンする ApplicationListener を使用して実行できます。

import jakarta.servlet.ServletContext;

import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.web.context.WebApplicationContext;

public class MyDemoBean implements ApplicationListener<ApplicationStartedEvent> {

    private ServletContext servletContext;

    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        this.servletContext = ((WebApplicationContext) applicationContext).getServletContext();
    }

}

1.3.4. 埋め込みサーブレットコンテナーのカスタマイズ

一般的なサーブレットコンテナー設定は、Spring Environment プロパティを使用して構成できます。通常、application.properties または application.yaml ファイルでプロパティを定義します。

一般的なサーバー設定は次のとおりです。

  • ネットワーク設定: 受信 HTTP リクエストのリッスンポート (server.port)、バインド先のインターフェースアドレス (server.address) など。

  • セッション設定: セッションが永続的か(server.servlet.session.persistent)、セッションタイムアウト(server.servlet.session.timeout)、セッションデータの場所(server.servlet.session.store-dir)、セッション Cookie 構成(server.servlet.session.cookie.*)。

  • エラー管理: エラーページ(server.error.path)などの場所。

  • SSL

  • HTTP 圧縮

Spring Boot は、可能な限り共通の設定を公開しようとしますが、常に可能とは限りません。これらの場合、専用の名前空間はサーバー固有のカスタマイズを提供します(server.tomcat および server.undertow を参照)。たとえば、アクセスログは、組み込みサーブレットコンテナーの特定の機能を使用して設定できます。

完全なリストについては、ServerProperties [GitHub] (英語) クラスを参照してください。
SameSite クッキー

SameSite cookie 属性は、クロスサイトリクエストで Cookie を送信するかどうか、および送信する方法を制御するために Web ブラウザーで使用できます。この属性は、属性が欠落しているときに使用されるデフォルト値を変更し始めた最新の Web ブラウザーに特に関係があります。

セッション Cookie の SameSite 属性を変更する場合は、server.servlet.session.cookie.same-site プロパティを使用できます。このプロパティは、自動構成された Tomcat、Jetty、Undertow サーバーでサポートされています。また、Spring Session サーブレットベースの SessionRepository Bean を構成するためにも使用されます。

例: セッション Cookie に None の SameSite 属性を持たせたい場合は、application.properties または application.yaml ファイルに以下を追加できます。

Properties
server.servlet.session.cookie.same-site=none
Yaml
server:
  servlet:
    session:
      cookie:
        same-site: "none"

HttpServletResponse に追加された他の Cookie の SameSite 属性を変更する場合は、CookieSameSiteSupplier を使用できます。CookieSameSiteSupplier には Cookie が渡され、SameSite 値または null を返す場合があります。

特定の Cookie をすばやく照合するために使用できる便利なファクトリおよびフィルターメソッドがいくつかあります。例: 次の Bean を追加すると、正規表現 myapp.* と一致する名前のすべての Cookie に Lax の SameSite が自動的に適用されます。

Java
import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MySameSiteConfiguration {

    @Bean
    public CookieSameSiteSupplier applicationCookieSameSiteSupplier() {
        return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*");
    }

}
Kotlin
import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MySameSiteConfiguration {

    @Bean
    fun applicationCookieSameSiteSupplier(): CookieSameSiteSupplier {
        return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*")
    }

}
文字エンコード

リクエストおよびレスポンス処理のための組み込みサーブレットコンテナーの文字エンコード動作は、server.servlet.encoding.* 構成プロパティを使用して構成できます。

リクエストの Accept-Language ヘッダーがリクエストのロケールを示す場合、サーブレットコンテナーによって自動的に文字セットにマッピングされます。各コンテナーにはデフォルトのロケールと文字セットのマッピングが用意されており、それらがアプリケーションのニーズを満たしていることを確認する必要があります。そうでない場合は、次の例に示すように、server.servlet.encoding.mapping 構成プロパティを使用してマッピングをカスタマイズします。

Properties
server.servlet.encoding.mapping.ko=UTF-8
Yaml
server:
  servlet:
    encoding:
      mapping:
        ko: "UTF-8"

前述の例では、ko (韓国語) ロケールが UTF-8 にマップされています。これは、従来の war デプロイ の web.xml ファイル内の <locale-encoding-mapping-list> エントリに相当します。

プログラムによるカスタマイズ

組み込みサーブレットコンテナーをプログラムで設定する必要がある場合は、WebServerFactoryCustomizer インターフェースを実装する Spring Bean を登録できます。WebServerFactoryCustomizer は、多数のカスタマイズ setter メソッドを含む ConfigurableServletWebServerFactory へのアクセスを提供します。次の例は、プログラムでポートを設定する方法を示しています。

Java
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;

@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

    @Override
    public void customize(ConfigurableServletWebServerFactory server) {
        server.setPort(9000);
    }

}
Kotlin
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory
import org.springframework.stereotype.Component

@Component
class MyWebServerFactoryCustomizer : WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

    override fun customize(server: ConfigurableServletWebServerFactory) {
        server.setPort(9000)
    }

}

TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactory は ConfigurableServletWebServerFactory の専用バリアントであり、Tomcat、Jetty、Undertow 用にそれぞれ追加のカスタマイズ setter メソッドがあります。次の例は、Tomcat 固有の構成オプションへのアクセスを提供する TomcatServletWebServerFactory をカスタマイズする方法を示しています。

Java
import java.time.Duration;

import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class MyTomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

    @Override
    public void customize(TomcatServletWebServerFactory server) {
        server.addConnectorCustomizers((connector) -> connector.setAsyncTimeout(Duration.ofSeconds(20).toMillis()));
    }

}
Kotlin
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.stereotype.Component
import java.time.Duration

@Component
class MyTomcatWebServerFactoryCustomizer : WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

    override fun customize(server: TomcatServletWebServerFactory) {
        server.addConnectorCustomizers({ connector -> connector.asyncTimeout = Duration.ofSeconds(20).toMillis() })
    }

}
ConfigurableServletWebServerFactory を直接カスタマイズする

ServletWebServerFactory から拡張する必要があるより高度なユースケースの場合は、そのような型の Bean を自分で公開できます。

Setter は、多くの構成オプション用に提供されています。よりエキゾチックな何かをする必要がある場合、いくつかの protected メソッド「フック」も提供されます。詳細については、ソースコードのドキュメント (Javadoc) を参照してください。

自動構成されたカスタマイザーは引き続きカスタムファクトリに適用されるため、そのオプションは慎重に使用してください。

1.3.5. JSP の制限

組み込みサーブレットコンテナーを使用する(実行可能アーカイブとしてパッケージ化されている)Spring Boot アプリケーションを実行する場合、JSP サポートにはいくつかの制限があります。

  • Jetty と Tomcat では、war パッケージを使用すれば動作するはずです。実行可能な war は、java -jar で起動すると動作し、任意の標準コンテナーに配備することもできます。実行可能 jar を使用する場合、JSP はサポートされません。

  • Undertow は JSP をサポートしていません。

  • カスタム error.jsp ページを作成しても、エラー処理のデフォルトビューは上書きされません。代わりにカスタムエラーページを使用する必要があります。

2. リアクティブ Web アプリケーション

Spring Boot は、Spring Webflux の自動構成を提供することにより、リアクティブ Web アプリケーションの開発を簡素化します。

2.1. 「Spring WebFlux フレームワーク」

Spring WebFlux は、Spring Framework 5.0 で導入された新しいリアクティブ Web フレームワークです。Spring MVC とは異なり、サーブレット API を必要とせず、完全に非同期でノンブロッキングであり、Reactor プロジェクト (英語) を通じて Reactive Streams (英語) 仕様を実装します。

Spring WebFlux には、関数型とアノテーション型の 2 つのフレーバーがあります。次の例に示すように、アノテーションベースのものは Spring MVC モデルに非常に近いものです。

Java
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);
    }

}
Kotlin
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
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();
    }

}
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.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
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) {
        ...
    }

}
Kotlin
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> {
        return ServerResponse.ok().build()
    }

    fun getUserCustomers(request: ServerRequest?): Mono<ServerResponse> {
        return ServerResponse.ok().build()
    }

    fun deleteUser(request: ServerRequest?): Mono<ServerResponse> {
        return ServerResponse.ok().build()
    }

}

"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) に設定することで、引き続き選択を強制できます。

2.1.1. Spring WebFlux 自動構成

Spring Boot は、ほとんどのアプリケーションで適切に機能する Spring WebFlux の自動構成を提供します。

自動構成により、Spring のデフォルトに加えて次の機能が追加されます。

  • HttpMessageReader および HttpMessageWriter インスタンスのコーデックの構成(このドキュメントで後述)。

  • WebJars のサポートを含む、静的リソースの提供のサポート(このドキュメントで後述)。

Spring Boot WebFlux 機能を保持し、さらに WebFlux の構成を追加する場合、@EnableWebFlux なしで型 WebFluxConfigurer の独自の @Configuration クラスを追加できます。

Spring WebFlux を完全に制御したい場合は、@EnableWebFlux アノテーションが付けられた独自の @Configuration を追加できます。

2.1.2. Spring WebFlux 変換サービス

Spring WebFlux で使用される ConversionService をカスタマイズする場合は、addFormatters メソッドを使用して WebFluxConfigurer Bean を提供できます。

変換は、spring.webflux.format.* 構成プロパティを使用してカスタマイズすることもできます。構成されていない場合は、次のデフォルトが使用されます。

プロパティ DateTimeFormatter

spring.webflux.format.date

ofLocalizedDate(FormatStyle.SHORT)

spring.webflux.format.time

ofLocalizedTime(FormatStyle.SHORT)

spring.webflux.format.date-time

ofLocalizedDateTime(FormatStyle.SHORT)

2.1.3. HttpMessageReaders および HttpMessageWriters を使用した HTTP コーデック

Spring WebFlux は、HttpMessageReader および HttpMessageWriter インターフェースを使用して、HTTP リクエストおよびレスポンスを変換します。これらは、クラスパスで使用可能なライブラリを調べることにより、実用的なデフォルトを持つように CodecConfigurer で構成されます。

Spring Boot は、コーデックの専用構成プロパティ spring.codec.* を提供します。また、CodecCustomizer インスタンスを使用して、さらにカスタマイズを適用します。例: spring.jackson.* 設定キーは Jackson コーデックに適用されます。

コーデックを追加またはカスタマイズする必要がある場合は、次の例に示すように、カスタム CodecCustomizer コンポーネントを作成できます。

Java
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());
            // ...
        };
    }

}
Kotlin
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())
        }
    }

}

2.1.4. 静的コンテンツ

デフォルトでは、Spring Boot は、クラスパス内の /static (または /public または /resources または /META-INF/resources)と呼ばれるディレクトリから静的コンテンツを提供します。Spring WebFlux の ResourceWebHandler を使用するため、独自の WebFluxConfigurer を追加して addResourceHandlers メソッドをオーバーライドすることにより、その動作を変更できます。

デフォルトでは、リソースは /** にマップされますが、spring.webflux.static-path-pattern プロパティを設定することでそれを調整できます。たとえば、すべてのリソースを /resources/** に再配置するには、次のようにします。

Properties
spring.webflux.static-path-pattern=/resources/**
Yaml
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 ディレクトリを使用しません。

2.1.5. ウェルカムページ

Spring Boot は、静的なウェルカムページとテンプレート化されたウェルカムページの両方をサポートしています。最初に、構成された静的コンテンツの場所で index.html ファイルを探します。見つからない場合は、index テンプレートを探します。どちらかが見つかった場合、アプリケーションのウェルカムページとして自動的に使用されます。

これは、アプリケーションによって定義された実際のインデックスルートのフォールバックとしてのみ機能します。順序は HandlerMapping Bean の順序によって定義され、デフォルトでは次のようになります。

RouterFunctionMapping

RouterFunction Bean で宣言されたエンドポイント

RequestMappingHandlerMapping

@Controller Bean で宣言されたエンドポイント

RouterFunctionMapping for the Welcome Page

The welcome page support

2.1.6. 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.

2.1.7. 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 7807 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
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();
    }

}
Kotlin
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>

2.1.8. 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

WebFilterChainProxy (Spring Security)

-100

HttpExchangesWebFilter

Ordered.LOWEST_PRECEDENCE - 10

2.2. 埋め込み型リアクティブサーバーのサポート

Spring Boot には、Reactor Netty、Tomcat、Jetty、Undertow の組み込みリアクティブ Web サーバーのサポートが含まれています。ほとんどの開発者は、適切な「スターター」を使用して、完全に構成されたインスタンスを取得します。デフォルトでは、組み込みサーバーはポート 8080 で HTTP リクエストをリッスンします。

2.2.1. リアクティブサーバーのカスタマイズ

一般的なリアクティブ Web サーバー設定は、Spring Environment プロパティを使用して構成できます。通常は、application.properties または application.yaml ファイルでプロパティを定義します。

一般的なサーバー設定は次のとおりです。

  • ネットワーク設定: 受信 HTTP リクエストのリッスンポート (server.port)、バインド先のインターフェースアドレス (server.address) など。

  • エラー管理: エラーページ(server.error.path)などの場所。

  • SSL

  • HTTP 圧縮

Spring Boot は可能な限り共通設定を公開しようとしますが、常に可能であるとは限りません。このような場合には、server.netty.* などの専用の名前空間がサーバー固有のカスタマイズを提供します。

完全なリストについては、ServerProperties [GitHub] (英語) クラスを参照してください。
プログラムによるカスタマイズ

リアクティブ Web サーバーをプログラムで構成する必要がある場合は、WebServerFactoryCustomizer インターフェースを実装する Spring Bean を登録できます。WebServerFactoryCustomizer は、多数のカスタマイズ setter メソッドを含む ConfigurableReactiveWebServerFactory へのアクセスを提供します。次の例は、プログラムによるポートの設定を示しています。

Java
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);
    }

}
Kotlin
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)
    }

}

JettyReactiveWebServerFactoryNettyReactiveWebServerFactoryTomcatReactiveWebServerFactoryUndertowReactiveWebServerFactory は、Jetty、Reactor Netty、Tomcat、Undertow 用の追加のカスタマイズ setter メソッドを備えた ConfigurableReactiveWebServerFactory の専用バリアントです。次の例は、Reactor Netty 固有の構成オプションへのアクセスを提供する NettyReactiveWebServerFactory をカスタマイズする方法を示しています。

Java
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)));
    }

}
Kotlin
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 メソッド「フック」も提供されます。詳細については、ソースコードのドキュメント (Javadoc) を参照してください。

自動構成されたカスタマイザーは引き続きカスタムファクトリに適用されるため、そのオプションは慎重に使用してください。

2.3. リアクティブサーバーリソースの構成

Reactor Netty または Jetty サーバーを自動構成する場合、Spring Boot は、サーバーインスタンスに HTTP リソースを提供する特定の Bean ReactorResourceFactory または JettyResourceFactory を作成します。

デフォルトでは、これらのリソースは、最適なパフォーマンスのために、Reactor Netty および Jetty クライアントとも共有されます。

  • 同じテクノロジーがサーバーとクライアントに使用されます

  • クライアントインスタンスは、Spring Boot によって自動構成された WebClient.Builder Bean を使用して構築されます

開発者は、カスタム ReactorResourceFactory または JettyResourceFactory Bean を提供することにより、Jetty および Reactor Netty のリソース構成をオーバーライドできます。これは、クライアントとサーバーの両方に適用されます。

WebClient ランタイムセクションでクライアント側のリソース構成について詳しく知ることができます。

3. グレースフルシャットダウン

正常なシャットダウンは、4 つの組み込み Web サーバー (Jetty、Reactor、Netty、Tomcat、Undertow) すべてと、リアクティブ Web アプリケーションおよびサーブレットベースの Web アプリケーションの両方でサポートされています。これは、アプリケーションコンテキストを閉じる際に発生し、SmartLifecycle Bean を停止する最も早い段階で実行されます。この停止処理では、タイムアウトが使用されます。タイムアウトによって猶予期間が与えられ、その間は既存のリクエストは完了できますが、新しいリクエストは許可されません。

新しいリクエストが許可されない具体的な方法は、使用されている Web サーバーによって異なります。実装によっては、ネットワーク層でリクエストの受け入れを停止したり、特定の HTTP ステータスコードまたは HTTP ヘッダーを含むレスポンスを返すことがあります。永続的な接続を使用すると、リクエストの受け入れを停止する方法が変わることもあります。

Web サーバーで使用される特定のメソッドの詳細については、TomcatWebServer (Javadoc) NettyWebServer (Javadoc) JettyWebServer (Javadoc) または UndertowWebServer (Javadoc) の shutDownGracefully javadoc を参照してください。

Jetty、Reactor、Netty、Tomcat は、ネットワーク層で新しいリクエストの受け入れを停止します。Undertow は新しい接続を受け入れますが、すぐにサービス利用不可 (503) レスポンスでレスポンスします。

Tomcat による正常なシャットダウンには、Tomcat 9.0.33 以降が必要です。

グレースフルシャットダウンを有効にするには、次の例に示すように、server.shutdown プロパティを構成します。

Properties
server.shutdown=graceful
Yaml
server:
  shutdown: "graceful"

タイムアウト期間を構成するには、次の例に示すように、spring.lifecycle.timeout-per-shutdown-phase プロパティを構成します。

Properties
spring.lifecycle.timeout-per-shutdown-phase=20s
Yaml
spring:
  lifecycle:
    timeout-per-shutdown-phase: "20s"
IDE が適切な SIGTERM 信号を送信しない場合、IDE でグレースフルシャットダウンを使用すると正しく機能しない可能性があります。詳細については、IDE のドキュメントを参照してください。

4. Spring Security

Spring Security がクラスパスにある場合、Web アプリケーションはデフォルトで保護されます。Spring Boot は、Spring Security のコンテンツネゴシエーション戦略に基づいて、httpBasic と formLogin のどちらを使用するかを決定します。メソッドレベルのセキュリティを Web アプリケーションに追加するために、必要な設定で @EnableGlobalMethodSecurity を追加することもできます。追加情報は Spring Security リファレンスガイドにあります。

デフォルトの UserDetailsService にはシングルユーザーがいます。次の例に示すように、ユーザー名は user であり、パスワードはランダムであり、アプリケーションの起動時に WARN レベルで出力されます。

Using generated security password: 78fa095d-3f4c-48b1-ad50-e24c31d5cf35

This generated password is for development use only. Your security configuration must be updated before running your application in production.
ロギング構成を微調整する場合は、org.springframework.boot.autoconfigure.security カテゴリが WARN -level メッセージを記録するように設定されていることを確認してください。それ以外の場合、デフォルトのパスワードは出力されません。

spring.security.user.name および spring.security.user.password を提供することにより、ユーザー名とパスワードを変更できます。

Web アプリケーションでデフォルトで取得する基本機能は次のとおりです。

  • UserDetailsService (または WebFlux アプリケーションの場合は ReactiveUserDetailsService)メモリ内ストアを持つ Bean と、生成されたパスワードを持つ単一のユーザー(ユーザーのプロパティについては SecurityProperties.User (Javadoc) を参照)。

  • アプリケーション全体(アクチュエーターがクラスパス上にある場合はアクチュエーターエンドポイントを含む)に対するフォームベースのログインまたは HTTP Basic セキュリティ(リクエストの Accept ヘッダーに依存)。

  • 認証イベントを公開するための DefaultAuthenticationEventPublisher

Bean を追加することにより、異なる AuthenticationEventPublisher を提供できます。

4.1. MVC セキュリティ

デフォルトのセキュリティ構成は SecurityAutoConfiguration および UserDetailsServiceAutoConfiguration に実装されています。SecurityAutoConfiguration は Web セキュリティ用に SpringBootWebSecurityConfiguration をインポートし、UserDetailsServiceAutoConfiguration は非 Web アプリケーションにも関連する認証を構成します。

デフォルトの Web アプリケーションのセキュリティ構成を完全にオフにするか、OAuth2 クライアントやリソースサーバーなどの複数の Spring Security コンポーネントを組み合わせるには、SecurityFilterChain 型の Bean を追加します (追加しても、UserDetailsService 構成やアクチュエーターのセキュリティは無効になりません)。UserDetailsService 構成もオフにするには、型 UserDetailsServiceAuthenticationProvider、または AuthenticationManager の Bean を追加します。

UserDetailsService の自動構成により、クラスパス上にある次の Spring Security モジュールもバックオフされます。

  • spring-security-oauth2-client

  • spring-security-oauth2-resource-server

  • spring-security-saml2-service-provider

これらの依存関係の 1 つ以上に加えて UserDetailsService を使用するには、独自の InMemoryUserDetailsManager Bean を定義します。

アクセスルールは、カスタム SecurityFilterChain Bean を追加することでオーバーライドできます。Spring Boot は、アクチュエーターエンドポイントと静的リソースのアクセスルールをオーバーライドするために使用できる便利なメソッドを提供します。EndpointRequest を使用して、management.endpoints.web.base-path プロパティに基づく RequestMatcher を作成できます。PathRequest を使用して、一般的に使用される場所にリソース用の RequestMatcher を作成できます。

4.2. WebFlux セキュリティ

Spring MVC アプリケーションと同様に、spring-boot-starter-security 依存関係を追加することで、WebFlux アプリケーションを保護できます。デフォルトのセキュリティ構成は ReactiveSecurityAutoConfiguration および UserDetailsServiceAutoConfiguration に実装されています。ReactiveSecurityAutoConfiguration は Web セキュリティ用に WebFluxSecurityConfiguration をインポートし、UserDetailsServiceAutoConfiguration は非 Web アプリケーションにも関連する認証を構成します。

デフォルトの Web アプリケーションのセキュリティ構成を完全にオフにするには、WebFilterChainProxy 型の Bean を追加します (追加しても、UserDetailsService 構成やアクチュエーターのセキュリティは無効になりません)。UserDetailsService 構成もオフにするには、型 ReactiveUserDetailsService または ReactiveAuthenticationManager の Bean を追加します。

次の Spring Security モジュールのいずれかがクラスパス上にある場合も、自動構成はバックオフされます。

  • spring-security-oauth2-client

  • spring-security-oauth2-resource-server

これらの依存関係の 1 つ以上に加えて ReactiveUserDetailsService を使用するには、独自の MapReactiveUserDetailsService Bean を定義します。

カスタム SecurityWebFilterChain Bean を追加することにより、アクセスルールと、OAuth 2 クライアントやリソースサーバーなどの複数の Spring Security コンポーネントの使用を構成できます。Spring Boot は、アクチュエーターエンドポイントおよび静的リソースのアクセスルールをオーバーライドするために使用できる便利なメソッドを提供します。EndpointRequest を使用して、management.endpoints.web.base-path プロパティに基づく ServerWebExchangeMatcher を作成できます。

PathRequest を使用して、一般的に使用される場所のリソース用の ServerWebExchangeMatcher を作成できます。

例: 次のようなものを追加して、セキュリティ構成をカスタマイズできます。

Java
import org.springframework.boot.autoconfigure.security.reactive.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration(proxyBeanMethods = false)
public class MyWebFluxSecurityConfiguration {

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http.authorizeExchange((exchange) -> {
            exchange.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
            exchange.pathMatchers("/foo", "/bar").authenticated();
        });
        http.formLogin(withDefaults());
        return http.build();
    }

}
Kotlin
import org.springframework.boot.autoconfigure.security.reactive.PathRequest
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.Customizer.withDefaults
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.web.server.SecurityWebFilterChain

@Configuration(proxyBeanMethods = false)
class MyWebFluxSecurityConfiguration {

    @Bean
    fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        http.authorizeExchange { spec ->
            spec.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
            spec.pathMatchers("/foo", "/bar").authenticated()
        }
        http.formLogin(withDefaults())
        return http.build()
    }

}

4.3. OAuth2

OAuth2 (英語) は、Spring でサポートされている広く使用されている認可フレームワークです。

4.3.1. クライアント

クラスパスに spring-security-oauth2-client がある場合は、自動構成を利用して OAuth2/OpenIDConnect クライアントをセットアップできます。この構成では、OAuth2ClientProperties のプロパティを利用します。同じプロパティがサーブレットアプリケーションとリアクティブアプリケーションの両方に適用できます。

次の例に示すように、spring.security.oauth2.client プレフィックスに複数の OAuth2 クライアントとプロバイダーを登録できます。

Properties
spring.security.oauth2.client.registration.my-login-client.client-id=abcd
spring.security.oauth2.client.registration.my-login-client.client-secret=password
spring.security.oauth2.client.registration.my-login-client.client-name=Client for OpenID Connect
spring.security.oauth2.client.registration.my-login-client.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-login-client.scope=openid,profile,email,phone,address
spring.security.oauth2.client.registration.my-login-client.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.my-login-client.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-login-client.authorization-grant-type=authorization_code

spring.security.oauth2.client.registration.my-client-1.client-id=abcd
spring.security.oauth2.client.registration.my-client-1.client-secret=password
spring.security.oauth2.client.registration.my-client-1.client-name=Client for user scope
spring.security.oauth2.client.registration.my-client-1.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-1.scope=user
spring.security.oauth2.client.registration.my-client-1.redirect-uri={baseUrl}/authorized/user
spring.security.oauth2.client.registration.my-client-1.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-client-1.authorization-grant-type=authorization_code

spring.security.oauth2.client.registration.my-client-2.client-id=abcd
spring.security.oauth2.client.registration.my-client-2.client-secret=password
spring.security.oauth2.client.registration.my-client-2.client-name=Client for email scope
spring.security.oauth2.client.registration.my-client-2.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-2.scope=email
spring.security.oauth2.client.registration.my-client-2.redirect-uri={baseUrl}/authorized/email
spring.security.oauth2.client.registration.my-client-2.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-client-2.authorization-grant-type=authorization_code

spring.security.oauth2.client.provider.my-oauth-provider.authorization-uri=https://my-auth-server.com/oauth2/authorize
spring.security.oauth2.client.provider.my-oauth-provider.token-uri=https://my-auth-server.com/oauth2/token
spring.security.oauth2.client.provider.my-oauth-provider.user-info-uri=https://my-auth-server.com/userinfo
spring.security.oauth2.client.provider.my-oauth-provider.user-info-authentication-method=header
spring.security.oauth2.client.provider.my-oauth-provider.jwk-set-uri=https://my-auth-server.com/oauth2/jwks
spring.security.oauth2.client.provider.my-oauth-provider.user-name-attribute=name
Yaml
spring:
  security:
    oauth2:
      client:
        registration:
          my-login-client:
            client-id: "abcd"
            client-secret: "password"
            client-name: "Client for OpenID Connect"
            provider: "my-oauth-provider"
            scope: "openid,profile,email,phone,address"
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            client-authentication-method: "client_secret_basic"
            authorization-grant-type: "authorization_code"

          my-client-1:
            client-id: "abcd"
            client-secret: "password"
            client-name: "Client for user scope"
            provider: "my-oauth-provider"
            scope: "user"
            redirect-uri: "{baseUrl}/authorized/user"
            client-authentication-method: "client_secret_basic"
            authorization-grant-type: "authorization_code"

          my-client-2:
            client-id: "abcd"
            client-secret: "password"
            client-name: "Client for email scope"
            provider: "my-oauth-provider"
            scope: "email"
            redirect-uri: "{baseUrl}/authorized/email"
            client-authentication-method: "client_secret_basic"
            authorization-grant-type: "authorization_code"

        provider:
          my-oauth-provider:
            authorization-uri: "https://my-auth-server.com/oauth2/authorize"
            token-uri: "https://my-auth-server.com/oauth2/token"
            user-info-uri: "https://my-auth-server.com/userinfo"
            user-info-authentication-method: "header"
            jwk-set-uri: "https://my-auth-server.com/oauth2/jwks"
            user-name-attribute: "name"

OpenID Connect ディスカバリ (英語) をサポートする OpenID Connect プロバイダーの場合、構成をさらに簡素化できます。プロバイダーは、発行者識別子としてアサートする URI である issuer-uri を使用して構成する必要があります。例: 提供された issuer-uri が "https://example.com" の場合、「OpenID プロバイダー構成リクエスト」が "https://example.com/.well-known/openid-configuration" に対して行われます。結果は "OpenID Provider Configuration Response" であると予想されます。次の例は、OpenID Connect プロバイダーを issuer-uri で構成する方法を示しています。

Properties
spring.security.oauth2.client.provider.oidc-provider.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/
Yaml
spring:
  security:
    oauth2:
      client:
        provider:
          oidc-provider:
            issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/"

デフォルトでは、Spring Security の OAuth2LoginAuthenticationFilter は /login/oauth2/code/* に一致する URL のみを処理します。redirect-uri をカスタマイズして別のパターンを使用する場合は、そのカスタムパターンを処理するための構成を提供する必要があります。例: サーブレットアプリケーションの場合、次のような独自の SecurityFilterChain を追加できます。

Java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class MyOAuthClientConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((requests) -> requests
                .anyRequest().authenticated()
            )
            .oauth2Login((login) -> login
                .redirectionEndpoint((endpoint) -> endpoint
                    .baseUri("/login/oauth2/callback/*")
                )
            );
        return http.build();
    }

}
Kotlin
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.web.SecurityFilterChain

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
open class MyOAuthClientConfiguration {

    @Bean
    open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            authorizeHttpRequests {
                authorize(anyRequest, authenticated)
            }
            oauth2Login {
                redirectionEndpoint {
                    baseUri = "/login/oauth2/callback/*"
                }
            }
        }
        return http.build()
    }

}
Spring Boot は、クライアント登録の管理のために Spring Security によって使用される InMemoryOAuth2AuthorizedClientService を自動構成します。InMemoryOAuth2AuthorizedClientService の機能には制限があるため、開発環境でのみ使用することをお勧めします。本番環境では、JdbcOAuth2AuthorizedClientService を使用するか、OAuth2AuthorizedClientService の独自の実装を作成することを検討してください。
Common Provider の OAuth2 クライアント登録

Google、Github、Facebook、Okta などの一般的な OAuth2 プロバイダーと OpenID プロバイダーについては、プロバイダーのデフォルトのセット (それぞれ googlegithubfacebookokta) が提供されています。

これらのプロバイダーをカスタマイズする必要がない場合は、provider 属性をデフォルトを推測する必要がある属性に設定できます。また、クライアント登録のキーがデフォルトでサポートされているプロバイダーと一致する場合、Spring Boot も同様に推測します。

つまり、次の例の 2 つの構成では Google プロバイダーを使用します。

Properties
spring.security.oauth2.client.registration.my-client.client-id=abcd
spring.security.oauth2.client.registration.my-client.client-secret=password
spring.security.oauth2.client.registration.my-client.provider=google
spring.security.oauth2.client.registration.google.client-id=abcd
spring.security.oauth2.client.registration.google.client-secret=password
Yaml
spring:
  security:
    oauth2:
      client:
        registration:
          my-client:
            client-id: "abcd"
            client-secret: "password"
            provider: "google"
          google:
            client-id: "abcd"
            client-secret: "password"

4.3.2. リソースサーバー

クラスパスに spring-security-oauth2-resource-server がある場合、Spring Boot は OAuth2 リソースサーバーをセットアップできます。JWT 構成の場合、次の例に示すように、JWK セット URI または OIDC 発行者 URI を指定する必要があります。

Properties
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://example.com/oauth2/default/v1/keys
Yaml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: "https://example.com/oauth2/default/v1/keys"
Properties
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/
Yaml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/"
認可サーバーが JWK Set URI をサポートしていない場合、JWT の署名の検証に使用される公開鍵でリソースサーバーを構成できます。これは、spring.security.oauth2.resourceserver.jwt.public-key-location プロパティを使用して実行できます。値は、PEM エンコードされた x509 形式の公開キーを含むファイルを指す必要があります。

spring.security.oauth2.resourceserver.jwt.audiences プロパティを使用して、JWT の aud クレームの予期される値を指定できます。例: JWT に値 my-audience の aud クレームを含めるよう要求するには:

Properties
spring.security.oauth2.resourceserver.jwt.audiences[0]=my-audience
Yaml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          audiences:
            - "my-audience"

同じプロパティがサーブレットアプリケーションとリアクティブアプリケーションの両方に適用されます。あるいは、サーブレットアプリケーション用に独自の JwtDecoder Bean を定義したり、リアクティブアプリケーション用に ReactiveJwtDecoder を定義したりできます。

JWT の代わりに Opaque トークンが使用されている場合は、次のプロパティを構成して、イントロスペクションを通じてトークンを検証できます。

Properties
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://example.com/check-token
spring.security.oauth2.resourceserver.opaquetoken.client-id=my-client-id
spring.security.oauth2.resourceserver.opaquetoken.client-secret=my-client-secret
Yaml
spring:
  security:
    oauth2:
      resourceserver:
        opaquetoken:
          introspection-uri: "https://example.com/check-token"
          client-id: "my-client-id"
          client-secret: "my-client-secret"

繰り返しますが、同じプロパティがサーブレットアプリケーションとリアクティブアプリケーションの両方に適用できます。あるいは、サーブレットアプリケーション用に独自の OpaqueTokenIntrospector Bean を定義したり、リアクティブアプリケーション用に ReactiveOpaqueTokenIntrospector を定義したりできます。

4.3.3. 認可サーバー

クラスパスに spring-security-oauth2-authorization-server がある場合は、いくつかの自動構成を利用して、サーブレットベースの OAuth2 認可サーバーをセットアップできます。

次の例に示すように、spring.security.oauth2.authorizationserver.client プレフィックスで複数の OAuth2 クライアントを登録できます。

Properties
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-id=abcd
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-secret={noop}secret1
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-authentication-methods[0]=client_secret_basic
spring.security.oauth2.authorizationserver.client.my-client-1.registration.authorization-grant-types[0]=authorization_code
spring.security.oauth2.authorizationserver.client.my-client-1.registration.authorization-grant-types[1]=refresh_token
spring.security.oauth2.authorizationserver.client.my-client-1.registration.redirect-uris[0]=https://my-client-1.com/login/oauth2/code/abcd
spring.security.oauth2.authorizationserver.client.my-client-1.registration.redirect-uris[1]=https://my-client-1.com/authorized
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[0]=openid
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[1]=profile
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[2]=email
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[3]=phone
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[4]=address
spring.security.oauth2.authorizationserver.client.my-client-1.require-authorization-consent=true
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-id=efgh
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-secret={noop}secret2
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-authentication-methods[0]=client_secret_jwt
spring.security.oauth2.authorizationserver.client.my-client-2.registration.authorization-grant-types[0]=client_credentials
spring.security.oauth2.authorizationserver.client.my-client-2.registration.scopes[0]=user.read
spring.security.oauth2.authorizationserver.client.my-client-2.registration.scopes[1]=user.write
spring.security.oauth2.authorizationserver.client.my-client-2.jwk-set-uri=https://my-client-2.com/jwks
spring.security.oauth2.authorizationserver.client.my-client-2.token-endpoint-authentication-signing-algorithm=RS256
Yaml
spring:
  security:
    oauth2:
      authorizationserver:
        client:
          my-client-1:
            registration:
              client-id: "abcd"
              client-secret: "{noop}secret1"
              client-authentication-methods:
                - "client_secret_basic"
              authorization-grant-types:
                - "authorization_code"
                - "refresh_token"
              redirect-uris:
                - "https://my-client-1.com/login/oauth2/code/abcd"
                - "https://my-client-1.com/authorized"
              scopes:
                - "openid"
                - "profile"
                - "email"
                - "phone"
                - "address"
            require-authorization-consent: true
          my-client-2:
            registration:
              client-id: "efgh"
              client-secret: "{noop}secret2"
              client-authentication-methods:
                - "client_secret_jwt"
              authorization-grant-types:
                - "client_credentials"
              scopes:
                - "user.read"
                - "user.write"
            jwk-set-uri: "https://my-client-2.com/jwks"
            token-endpoint-authentication-signing-algorithm: "RS256"
client-secret プロパティは、構成された PasswordEncoder と一致する形式である必要があります。PasswordEncoder のデフォルトのインスタンスは PasswordEncoderFactories.createDelegatingPasswordEncoder() によって作成されます。

Spring Boot が Spring Authorization Server に提供する自動構成は、すぐに開始できるように設計されています。ほとんどのアプリケーションはカスタマイズを必要とし、自動構成をオーバーライドするためにいくつかの Bean を定義する必要があります。

次のコンポーネントを Bean として定義して、Spring Authorization Server に固有の自動構成をオーバーライドできます。

  • RegisteredClientRepository

  • AuthorizationServerSettings

  • SecurityFilterChain

  • com.nimbusds.jose.jwk.source.JWKSource<com.nimbusds.jose.proc.SecurityContext>

  • JwtDecoder

Spring Boot は、登録されたクライアントの管理のために Spring Authorization Server によって使用される InMemoryRegisteredClientRepository を自動構成します。InMemoryRegisteredClientRepository の機能は限られているため、開発環境でのみ使用することをお勧めします。本番環境では、JdbcRegisteredClientRepository を使用するか、独自の RegisteredClientRepository 実装を作成することを検討してください。

追加情報は、Spring Authorization Server リファレンスガイド入門の章にあります。

4.4. SAML 2.0

4.4.1. 証明書利用者

クラスパスに spring-security-saml2-service-provider がある場合は、自動構成を利用して SAML 2.0 依存パーティをセットアップできます。この構成では、Saml2RelyingPartyProperties のプロパティを使用します。

証明書利用者登録は、ID プロバイダー IDP とサービスプロバイダー SP の間のペア構成を表します。次の例に示すように、spring.security.saml2.relyingparty プレフィックスに複数の証明書利用者を登録できます。

Properties
spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.url=https://myapp/logout/saml2/slo
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.response-url=https://remoteidp2.slo.url
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.binding=POST
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.verification.credentials[0].certificate-location=path-to-verification-cert
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.entity-id=remote-idp-entity-id1
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.sso-url=https://remoteidp1.sso.url

spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.verification.credentials[0].certificate-location=path-to-other-verification-cert
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.entity-id=remote-idp-entity-id2
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.sso-url=https://remoteidp2.sso.url
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.url=https://remoteidp2.slo.url
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.response-url=https://myapp/logout/saml2/slo
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.binding=POST
Yaml
spring:
  security:
    saml2:
      relyingparty:
        registration:
          my-relying-party1:
            signing:
              credentials:
              - private-key-location: "path-to-private-key"
                certificate-location: "path-to-certificate"
            decryption:
              credentials:
              - private-key-location: "path-to-private-key"
                certificate-location: "path-to-certificate"
            singlelogout:
               url: "https://myapp/logout/saml2/slo"
               response-url: "https://remoteidp2.slo.url"
               binding: "POST"
            assertingparty:
              verification:
                credentials:
                - certificate-location: "path-to-verification-cert"
              entity-id: "remote-idp-entity-id1"
              sso-url: "https://remoteidp1.sso.url"

          my-relying-party2:
            signing:
              credentials:
              - private-key-location: "path-to-private-key"
                certificate-location: "path-to-certificate"
            decryption:
              credentials:
              - private-key-location: "path-to-private-key"
                certificate-location: "path-to-certificate"
            assertingparty:
              verification:
                credentials:
                - certificate-location: "path-to-other-verification-cert"
              entity-id: "remote-idp-entity-id2"
              sso-url: "https://remoteidp2.sso.url"
              singlelogout:
                url: "https://remoteidp2.slo.url"
                response-url: "https://myapp/logout/saml2/slo"
                binding: "POST"

SAML2 ログアウトの場合、デフォルトでは、Spring Security の Saml2LogoutRequestFilter および Saml2LogoutResponseFilter は /logout/saml2/slo に一致する URL のみを処理します。AP が開始したログアウトリクエストの送信先となる url または AP がログアウトレスポンスを送信する response-url をカスタマイズする場合は、別のパターンを使用して、そのカスタムパターンを処理するための構成を提供する必要があります。例: サーブレットアプリケーションの場合、次のような独自の SecurityFilterChain を追加できます。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration(proxyBeanMethods = false)
public class MySamlRelyingPartyConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
        http.saml2Login(withDefaults());
        http.saml2Logout((saml2) -> saml2.logoutRequest((request) -> request.logoutUrl("/SLOService.saml2"))
            .logoutResponse((response) -> response.logoutUrl("/SLOService.saml2")));
        return http.build();
    }

}

5. Spring Session

Spring Boot は、さまざまなデータストアに Spring Session 自動構成を提供します。サーブレット Web アプリケーションを構築する場合、次のストアを自動構成できます。

  • Redis

  • JDBC

  • Hazelcast

  • MongoDB

サーブレットの自動構成により、@Enable*HttpSession を使用する必要がなくなります。

クラスパスに単一の Spring Session モジュールが存在する場合、Spring Boot はそのストア実装を自動的に使用します。複数の実装がある場合、Spring Boot は特定の実装を選択するために次の順序を使用します。

  1. Redis

  2. JDBC

  3. Hazelcast

  4. MongoDB

  5. Redis、JDBC、Hazelcast、MongoDB のいずれも使用できない場合、SessionRepository は構成されません。

リアクティブ Web アプリケーションを構築する場合、次のストアを自動構成できます。

  • Redis

  • MongoDB

リアクティブ自動構成は、@Enable*WebSession を使用する必要性を置き換えます。

サーブレット構成と同様に、複数の実装がある場合、Spring Boot は特定の実装を選択するために次の順序を使用します。

  1. Redis

  2. MongoDB

  3. Redis も MongoDB も利用できない場合、ReactiveSessionRepository は構成されません。

各ストアには特定の追加設定があります。たとえば、次の例に示すように、JDBC ストアのテーブルの名前をカスタマイズできます。

Properties
spring.session.jdbc.table-name=SESSIONS
Yaml
spring:
  session:
    jdbc:
      table-name: "SESSIONS"

セッションのタイムアウトを設定するには、spring.session.timeout プロパティを使用できます。そのプロパティがサーブレット Web アプリケーションで設定されていない場合、自動構成は server.servlet.session.timeout の値にフォールバックします。

@Enable*HttpSession (サーブレット)または @Enable*WebSession (リアクティブ)を使用して、Spring Session の構成を制御できます。これにより、自動構成がバックオフします。Spring Session は、前述の構成プロパティではなく、アノテーションの属性を使用して構成できます。

6. Spring for GraphQL

GraphQL アプリケーションを構築する場合は、Spring Boot の Spring for GraphQL の自動構成を利用できます。Spring for GraphQL プロジェクトは GraphQL Java [GitHub] (英語) に基づいています。少なくとも spring-boot-starter-graphql スターターが必要です。GraphQL はトランスポートに依存しないため、GraphQL API を Web 上で公開するには、アプリケーションに 1 つ以上の追加のスターターが必要です。

スターター トランスポート 実装

spring-boot-starter-web

HTTP

Spring MVC

spring-boot-starter-websocket

WebSocket

サーブレットアプリ用の WebSocket

spring-boot-starter-webflux

HTTP、WebSocket

Spring WebFlux

spring-boot-starter-rsocket

TCP、WebSocket

Spring WebFlux on Reactor Netty

6.1. GraphQL スキーマ

Spring GraphQL アプリケーションには、起動時に定義済みのスキーマが必要です。デフォルトでは、src/main/resources/graphql/** に ".graphqls" または ".gqls" スキーマファイルを書き込むことができ、Spring Boot はそれらを自動的に取得します。spring.graphql.schema.locations を使用して場所をカスタマイズし、spring.graphql.schema.file-extensions を使用してファイル拡張子をカスタマイズできます。

すべてのアプリケーションモジュール内のスキーマファイルとその場所の依存関係を Spring Boot で検出する場合は、spring.graphql.schema.locations を "classpath*:graphql/**/" に設定できます (classpath*: プレフィックスに注意してください)。

次のセクションでは、このサンプル GraphQL スキーマを検討し、2 つの型と 2 つのクエリを定義します。

type Query {
    greeting(name: String! = "Spring"): String!
    project(slug: ID!): Project
}

""" A Project in the Spring portfolio """
type Project {
    """ Unique string id used in URLs """
    slug: ID!
    """ Project name """
    name: String!
    """ URL of the git repository """
    repositoryUrl: String!
    """ Current support status """
    status: ProjectStatus!
}

enum ProjectStatus {
    """ Actively supported by the Spring team """
    ACTIVE
    """ Supported by the community """
    COMMUNITY
    """ Prototype, not officially supported yet  """
    INCUBATING
    """ Project being retired, in maintenance mode """
    ATTIC
    """ End-Of-Lifed """
    EOL
}
デフォルトでは、GraphiQL などのツールに必要なため、スキーマでフィールドイントロスペクションが許可 (英語) されます。スキーマに関する情報を公開したくない場合は、spring.graphql.schema.introspection.enabled を false に設定することでイントロスペクションを無効にできます。

6.2. GraphQL RuntimeWiring

GraphQL Java RuntimeWiring.Builder を使用して、カスタムスカラー型、ディレクティブ、型リゾルバー、DataFetcher などを登録できます。Spring 構成で RuntimeWiringConfigurer Bean を宣言して、RuntimeWiring.Builder にアクセスできます。Spring Boot はそのような Bean を検出し、GraphQlSource ビルダーに追加します。

ただし、通常、アプリケーションは DataFetcher を直接実装せず、代わりにアノテーション付きコントローラーを作成します。Spring Boot は、アノテーション付きのハンドラーメソッドを持つ @Controller クラスを自動的に検出し、DataFetcher として登録します。@Controller クラスを使用したグリーティングクエリの実装例を次に示します。

Java
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;

@Controller
public class GreetingController {

    @QueryMapping
    public String greeting(@Argument String name) {
        return "Hello, " + name + "!";
    }

}
Kotlin
import org.springframework.graphql.data.method.annotation.Argument
import org.springframework.graphql.data.method.annotation.QueryMapping
import org.springframework.stereotype.Controller

@Controller
class GreetingController {

    @QueryMapping
    fun greeting(@Argument name: String): String {
        return "Hello, $name!"
    }

}

6.3. Querydsl および QueryByExample リポジトリのサポート

Spring Data は、Querydsl リポジトリと QueryByExample リポジトリの両方をサポートします。Spring GraphQL は、Querydsl および QueryByExample リポジトリを DataFetcher として構成できます。

@GraphQlRepository でアノテーションが付けられ、次のいずれかを継承する Spring Data リポジトリ:

  • QuerydslPredicateExecutor

  • ReactiveQuerydslPredicateExecutor

  • QueryByExampleExecutor

  • ReactiveQueryByExampleExecutor

Spring Boot によって検出され、トップレベルのクエリに一致する DataFetcher の候補と見なされます。

6.4. トランスポート

6.4.1. HTTP および WebSocket

GraphQL HTTP エンドポイントは、デフォルトで HTTP POST /graphql にあります。パスは spring.graphql.path でカスタマイズできます。

Spring MVC と Spring の両方の HTTP エンドポイント WebFlux は、0 の @Order を持つ RouterFunction Bean によって提供されます。独自の RouterFunction Bean を定義する場合は、適切な @Order アノテーションを追加して、それらが正しくソートされるようにすることができます。

GraphQL WebSocket エンドポイントはデフォルトでオフになっています。有効にするには:

  • サーブレットアプリケーションの場合は、WebSocket スターター spring-boot-starter-websocket を追加します

  • WebFlux アプリケーションの場合、追加の依存関係は必要ありません

  • どちらの場合も、spring.graphql.websocket.path アプリケーションプロパティを設定する必要があります

Spring GraphQL は Web インターセプトモデルを提供します。これは、HTTP リクエストヘッダーから情報を取得して GraphQL コンテキストに設定したり、同じコンテキストから情報を取得してレスポンスヘッダーに書き込んだりする場合に非常に便利です。Spring Boot を使用すると、WebInterceptor Bean を宣言して、Web トランスポートに登録することができます。

Spring MVC および Spring WebFlux は、CORS(クロスオリジンリソースシェアリング)リクエストをサポートします。CORS は、さまざまなドメインを使用するブラウザーからアクセスされる GraphQL アプリケーションの Web 構成の重要な部分です。

Spring Boot は、spring.graphql.cors.* 名前空間で多くの構成プロパティをサポートします。設定例を次に示します。

Properties
spring.graphql.cors.allowed-origins=https://example.org
spring.graphql.cors.allowed-methods=GET,POST
spring.graphql.cors.max-age=1800s
Yaml
spring:
  graphql:
    cors:
      allowed-origins: "https://example.org"
      allowed-methods: GET,POST
      max-age: 1800s

6.4.2. RSocket

RSocket は、WebSocket または TCP に加えて、トランスポートとしてもサポートされています。RSocket サーバーが構成されているが完了すると、spring.graphql.rsocket.mapping を使用して特定のルートに GraphQL ハンドラーを構成できます。例: そのマッピングを "graphql" として構成すると、RSocketGraphQlClient でリクエストを送信するときにルートとして使用できるようになります。

Spring Boot は、コンポーネントに挿入できる RSocketGraphQlClient.Builder<?> Bean を自動構成します。

Java
@Component
public class RSocketGraphQlClientExample {

    private final RSocketGraphQlClient graphQlClient;

    public RSocketGraphQlClientExample(RSocketGraphQlClient.Builder<?> builder) {
        this.graphQlClient = builder.tcp("example.spring.io", 8181).route("graphql").build();
    }
Kotlin
@Component
class RSocketGraphQlClientExample(private val builder: RSocketGraphQlClient.Builder<*>) {

そして、リクエストを送信します。

Java
Mono<Book> book = this.graphQlClient.document("{ bookById(id: \"book-1\"){ id name pageCount author } }")
    .retrieve("bookById")
    .toEntity(Book.class);
Kotlin
val book = graphQlClient.document(
    """
    {
        bookById(id: "book-1"){
            id
            name
            pageCount
            author
        }
    }               
    """
)
    .retrieve("bookById").toEntity(Book::class.java)

6.5. 例外処理

Spring GraphQL を使用すると、アプリケーションは、順次呼び出される 1 つ以上の Spring DataFetcherExceptionResolver コンポーネントを登録できます。例外は、graphql.GraphQLError オブジェクトのリストに解決する必要があります。Spring GraphQL 例外処理ドキュメントを参照してください。Spring Boot は DataFetcherExceptionResolver Bean を自動的に検出し、GraphQlSource.Builder に登録します。

6.6. GraphiQL とスキーマプリンター

Spring GraphQL は、GraphQLAPI を使用または開発する際に開発者を支援するためのインフラストラクチャーを提供します。

Spring GraphQL には、デフォルトで "/graphiql" で公開されるデフォルトの GraphiQL [GitHub] (英語) ページが付属しています。このページはデフォルトで無効になっており、spring.graphql.graphiql.enabled プロパティでオンにできます。このようなページを公開する多くのアプリケーションは、カスタムビルドを好みます。デフォルトの実装は開発中に非常に役立ちます。これが、開発中に spring-boot-devtools で自動的に公開される理由です。

spring.graphql.schema.printer.enabled プロパティが有効になっている場合は、/graphql/schema で GraphQL スキーマをテキスト形式で公開することもできます。

7. Spring HATEOAS

ハイパーメディアを利用する RESTful API を開発する場合、Spring Boot は、ほとんどのアプリケーションで適切に機能する Spring HATEOAS の自動構成を提供します。自動構成は、@EnableHypermediaSupport を使用する必要性を置き換え、ハイパーメディアベースのアプリケーションの構築を容易にするために多数の Bean を登録します。これには、LinkDiscoverers (クライアント側サポート用)や、レスポンスを目的の表現に正しくマーシャリングするように構成された ObjectMapper が含まれます。ObjectMapper は、さまざまな spring.jackson.* プロパティを設定するか、存在する場合は Jackson2ObjectMapperBuilder Bean によってカスタマイズされます。

@EnableHypermediaSupport を使用して、Spring HATEOAS の構成を制御できます。これを行うと、前述の ObjectMapper のカスタマイズが無効になることに注意してください。

spring-boot-starter-hateoas は Spring MVC に固有であり、Spring WebFlux と組み合わせないでください。Spring HATEOAS を Spring WebFlux と一緒に使用するために、spring-boot-starter-webflux とともに org.springframework.hateoas:spring-hateoas への直接依存関係を追加できます。

デフォルトでは、application/json を受け入れるリクエストは application/hal+json レスポンスを受け取ります。この動作を無効にするには、spring.hateoas.use-hal-as-default-json-media-type を false に設定し、HypermediaMappingInformation または HalConfiguration を定義して、アプリケーションとそのクライアントのニーズを満たすように Spring HATEOAS を構成します。

8. 次のステップ

これで、Spring Boot を使用して Web アプリケーションを開発する方法を十分に理解できたはずです。次のいくつかのセクションでは、Spring Boot がさまざまなデータテクノロジーメッセージングシステム、およびその他の IO 機能とどのように統合されるかについて説明します。アプリケーションのニーズに基づいて、これらのいずれかを選択できます。