ドキュメントのこのパートでは、サーブレット API 上に構築され、サーブレットコンテナーにデプロイされるサーブレットスタック Web アプリケーションのサポートについて説明します。個々の章には、Spring MVCビューテクノロジーCORS サポート、および WebSocket サポートが含まれます。リアクティブスタック Web アプリケーションについては、WebFlux リアクティブスタックを参照してください。

1. Spring Web MVC

Spring Web MVC は、サーブレット API 上に構築されたオリジナルの Web フレームワークであり、最初から Spring Framework に含まれていました。正式名「Spring Web MVC」は、そのソースモジュール(spring-webmvc (GitHub) )の名前に由来しますが、より一般的には「Spring MVC」として知られています。

Spring Web MVC と並行して、Spring Framework 5.0 は、「Spring WebFlux」という名前もそのソースモジュール(spring-webflux (GitHub) )に基づいたリアクティブスタック Web フレームワークを導入しました。このセクションでは、Spring Web MVC について説明します。次のセクションでは、Spring WebFlux について説明します。

ベースライン情報およびサーブレットコンテナーおよび Java EE バージョン範囲との互換性については、Spring Framework Wiki (GitHub) を参照してください。

1.1. DispatcherServlet

Spring MVC は、他の多くの Web フレームワークと同様に、フロントコントローラーパターンを中心に設計されており、中央の Servlet である DispatcherServlet がリクエスト処理の共有アルゴリズムを提供し、実際の作業は構成可能なデリゲートコンポーネントによって実行されます。このモデルは柔軟性があり、多様なワークフローをサポートします。

DispatcherServlet は、Servlet と同様に、Java 構成を使用するか web.xml で、サーブレット仕様に従って宣言およびマッピングする必要があります。次に、DispatcherServlet は Spring 構成を使用して、リクエストのマッピング、ビューの解決、例外処理などに必要なデリゲートコンポーネントを検出します。

次の Java 構成の例では、DispatcherServlet を登録および初期化します。これは、サーブレットコンテナーによって自動検出されます(サーブレット構成を参照)。

Java
public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletCxt) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        ac.refresh();

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}
Kotlin
class MyWebApplicationInitializer : WebApplicationInitializer {

    override fun onStartup(servletCxt: ServletContext) {

        // Load Spring web application configuration
        val ac = AnnotationConfigWebApplicationContext()
        ac.register(AppConfig::class.java)
        ac.refresh()

        // Create and register the DispatcherServlet
        val servlet = DispatcherServlet(ac)
        val registration = servletCxt.addServlet("app", servlet)
        registration.setLoadOnStartup(1)
        registration.addMapping("/app/*")
    }
}
ServletContext API を直接使用することに加えて、AbstractAnnotationConfigDispatcherServletInitializer を継承し、特定のメソッドをオーバーライドすることもできます(コンテキスト階層の例を参照)。

web.xml 構成の次の例は、DispatcherServlet を登録および初期化します。

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/app-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

</web-app>
Spring Boot は、異なる初期化シーケンスに従います。Spring Boot は、サーブレットコンテナーのライフサイクルにフックするのではなく、Spring 構成を使用して、自身と組み込みサーブレットコンテナーをブートストラップします。Filter および Servlet 宣言は、Spring 構成で検出され、サーブレットコンテナーに登録されます。詳細については、Spring Boot のドキュメントを参照してください。

1.1.1. コンテキスト階層

DispatcherServlet は、自身の構成に WebApplicationContext (プレーン ApplicationContext の拡張)を期待しています。WebApplicationContext には、関連付けられている ServletContext および Servlet へのリンクがあります。また、ServletContext にバインドされているため、アプリケーションは RequestContextUtils の静的メソッドを使用して、WebApplicationContext にアクセスする必要がある場合に WebApplicationContext を検索できます。

多くのアプリケーションでは、単一の WebApplicationContext があれば十分で十分です。1 つのルート WebApplicationContext が複数の DispatcherServlet (または他の Servlet)インスタンス間で共有され、それぞれが独自の子 WebApplicationContext 構成を持つコンテキスト階層を持つことも可能です。コンテキスト階層機能の詳細については、ApplicationContext の追加機能を参照してください。

通常、ルート WebApplicationContext には、複数の Servlet インスタンス間で共有する必要があるデータリポジトリやビジネスサービスなどのインフラストラクチャ Bean が含まれています。これらの Bean は事実上継承され、特定の Servlet にローカルな Bean を通常含むサーブレット固有の子 WebApplicationContext でオーバーライド(つまり、再宣言)できます。次の図は、この関連を示しています。

mvc context hierarchy

次の例では、WebApplicationContext 階層を構成します。

Java
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { App1Config.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/app1/*" };
    }
}
Kotlin
class MyWebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {

    override fun getRootConfigClasses(): Array<Class<*>> {
        return arrayOf(RootConfig::class.java)
    }

    override fun getServletConfigClasses(): Array<Class<*>> {
        return arrayOf(App1Config::class.java)
    }

    override fun getServletMappings(): Array<String> {
        return arrayOf("/app1/*")
    }
}
アプリケーションコンテキスト階層が不要な場合、アプリケーションは getServletConfigClasses() から getRootConfigClasses() および null を介してすべての構成を返すことができます。

次の例は、同等の web.xml を示しています。

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app1</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/app1-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app1</servlet-name>
        <url-pattern>/app1/*</url-pattern>
    </servlet-mapping>

</web-app>
アプリケーションコンテキスト階層が不要な場合、アプリケーションは「ルート」コンテキストのみを設定し、contextConfigLocation サーブレットパラメーターを空のままにすることができます。

1.1.2. 特別な Bean タイプ

DispatcherServlet は、リクエストを処理して適切なレスポンスをレンダリングするために特別な Bean に委譲します。「特別な Bean」とは、フレームワーク契約を実装する Spring 管理の Object インスタンスを意味します。通常、これらにはビルトイン契約が付属していますが、プロパティをカスタマイズし、拡張または置換できます。

次の表に、DispatcherServlet によって検出された特別な Bean を示します。

Bean タイプ 説明

HandlerMapping

前処理および後処理用のインターセプターのリストとともにリクエストをハンドラーにマップします。マッピングはいくつかの条件に基づいており、その詳細は HandlerMapping の実装によって異なります。

2 つの主要な HandlerMapping 実装は、RequestMappingHandlerMapping (@RequestMapping アノテーション付きメソッドをサポート)と SimpleUrlHandlerMapping (ハンドラーへの URI パスパターンの明示的な登録を維持)です。

HandlerAdapter

ハンドラーが実際に呼び出される方法に関係なく、DispatcherServlet がリクエストにマップされたハンドラーを呼び出すのに役立ちます。例:アノテーション付きコントローラーを呼び出すには、アノテーションを解決する必要があります。HandlerAdapter の主な目的は、そのような詳細から DispatcherServlet を保護することです。

HandlerExceptionResolver

例外を解決する戦略。例外をハンドラー、HTML エラービュー、またはその他のターゲットにマッピングする可能性があります。例外を参照してください。

ViewResolver

ハンドラーから返された論理 String ベースのビュー名を、レスポンスにレンダリングする実際の View に解決します。ビューリゾルバーおよびビューテクノロジーを参照してください。

LocaleResolver, LocaleContextResolver

国際化されたビューを提供できるように、クライアントが使用している Locale と、場合によってはそれらのタイムゾーンを解決します。ロケールを参照してください。

ThemeResolver

Web アプリケーションで使用できるテーマを解決します。たとえば、パーソナライズされたレイアウトを提供します。テーマを参照してください。

MultipartResolver

いくつかのマルチパート解析ライブラリの助けを借りて、マルチパートリクエスト(ブラウザフォームファイルのアップロードなど)を解析するための抽象化。マルチパートリゾルバーを参照してください。

FlashMapManager

通常はリダイレクトを介して、あるリクエストから別のリクエストに属性を渡すために使用できる「入力」および「出力」 FlashMap を保存および取得します。フラッシュ属性を参照してください。

1.1.3. Web MVC Config

アプリケーションは、リクエストの処理に必要な特別な Bean タイプにリストされているインフラストラクチャ Bean を宣言できます。DispatcherServlet は、特別な Bean ごとに WebApplicationContext をチェックします。一致する Bean タイプがない場合、DispatcherServlet.properties (GitHub) にリストされているデフォルトのタイプにフォールバックします。

ほとんどの場合、MVC 構成が最適な出発点です。Java または XML で必要な Bean を宣言し、それをカスタマイズするための高レベルの構成コールバック API を提供します。

Spring Boot は MVC Java 構成に依存して Spring MVC を構成し、多くの便利なオプションを提供します。

1.1.4. サーブレット構成

Servlet 3.0+ 環境では、サーブレットコンテナーを代替としてプログラムで設定するか、web.xml ファイルと組み合わせて設定するオプションがあります。次の例では、DispatcherServlet を登録します。

Java
import org.springframework.web.WebApplicationInitializer;

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}
Kotlin
import org.springframework.web.WebApplicationInitializer

class MyWebApplicationInitializer : WebApplicationInitializer {

    override fun onStartup(container: ServletContext) {
        val appContext = XmlWebApplicationContext()
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml")

        val registration = container.addServlet("dispatcher", DispatcherServlet(appContext))
        registration.setLoadOnStartup(1)
        registration.addMapping("/")
    }
}

WebApplicationInitializer は、Spring MVC が提供するインターフェースであり、実装が検出され、Servlet 3 コンテナーを初期化するために自動的に使用されるようにします。AbstractDispatcherServletInitializer という名前の WebApplicationInitializer の抽象基本クラス実装により、サーブレットマッピングと DispatcherServlet 構成の場所を指定するメソッドをオーバーライドすることにより、DispatcherServlet の登録がさらに簡単になります。

これは、次の例に示すように、Java ベースの Spring 構成を使用するアプリケーションに推奨されます。

Java
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { MyWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}
Kotlin
class MyWebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {

    override fun getRootConfigClasses(): Array<Class<*>>? {
        return null
    }

    override fun getServletConfigClasses(): Array<Class<*>>? {
        return arrayOf(MyWebConfig::class.java)
    }

    override fun getServletMappings(): Array<String> {
        return arrayOf("/")
    }
}

XML ベースの Spring 構成を使用する場合、次の例に示すように、AbstractDispatcherServletInitializer から直接拡張する必要があります。

Java
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        XmlWebApplicationContext cxt = new XmlWebApplicationContext();
        cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
        return cxt;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}
Kotlin
class MyWebAppInitializer : AbstractDispatcherServletInitializer() {

    override fun createRootApplicationContext(): WebApplicationContext? {
        return null
    }

    override fun createServletApplicationContext(): WebApplicationContext {
        return XmlWebApplicationContext().apply {
            setConfigLocation("/WEB-INF/spring/dispatcher-config.xml")
        }
    }

    override fun getServletMappings(): Array<String> {
        return arrayOf("/")
    }
}

AbstractDispatcherServletInitializer は、次の例に示すように、Filter インスタンスを追加し、DispatcherServlet に自動的にマッピングする便利な方法も提供します。

Java
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] {
            new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
    }
}
Kotlin
class MyWebAppInitializer : AbstractDispatcherServletInitializer() {

    // ...

    override fun getServletFilters(): Array<Filter> {
        return arrayOf(HiddenHttpMethodFilter(), CharacterEncodingFilter())
    }
}

各フィルターは、その具体的なタイプに基づいてデフォルト名で追加され、自動的に DispatcherServlet にマップされます。

AbstractDispatcherServletInitializer の isAsyncSupported 保護メソッドは、DispatcherServlet とそれにマッピングされたすべてのフィルターで非同期サポートを有効にする単一の場所を提供します。デフォルトでは、このフラグは true に設定されています。

最後に、DispatcherServlet 自体をさらにカスタマイズする必要がある場合は、createDispatcherServlet メソッドをオーバーライドできます。

1.1.5. 処理

DispatcherServlet は、リクエストを次のように処理します。

  • WebApplicationContext が検索され、コントローラーおよびプロセス内の他の要素が使用できる属性としてリクエストでバインドされます。デフォルトでは、DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE キーにバインドされています。

  • ロケールリゾルバーはリクエストにバインドされ、プロセス内の要素がリクエストの処理(ビューのレンダリング、データの準備など)に使用するロケールを解決できるようにします。ロケール解決が不要な場合、ロケールリゾルバーは必要ありません。

  • テーマリゾルバーはリクエストにバインドされ、ビューなどの要素が使用するテーマを決定できるようにします。テーマを使用しない場合、無視できます。

  • マルチパートファイルリゾルバーを指定すると、リクエストはマルチパートについてインスペクションされます。マルチパートが見つかった場合、リクエストはプロセス内の他の要素によるさらなる処理のために MultipartHttpServletRequest にラップされます。マルチパート処理の詳細については、マルチパートリゾルバーを参照してください。

  • 適切なハンドラーが検索されます。ハンドラーが見つかった場合、モデルまたはレンダリングを準備するために、ハンドラー(プリプロセッサー、ポストプロセッサー、コントローラー)に関連付けられた実行チェーンが実行されます。あるいは、アノテーション付きコントローラーの場合、ビューを返す代わりに(HandlerAdapter 内で)レスポンスをレンダリングできます。

  • モデルが返されると、ビューがレンダリングされます。モデルが返されない場合(おそらくセキュリティ上の理由で、プリプロセッサまたはポストプロセッサがリクエストをインターセプトしているため)、リクエストは既に満たされている可能性があるため、ビューはレンダリングされません。

WebApplicationContext で宣言された HandlerExceptionResolver Bean は、リクエスト処理中にスローされた例外を解決するために使用されます。これらの例外リゾルバーを使用すると、ロジックをカスタマイズして例外に対処できます。詳細については、例外を参照してください。

Spring DispatcherServlet は、サーブレット API で指定されている last-modification-date の戻り値もサポートしています。特定のリクエストの最終変更日を決定するプロセスは簡単です。DispatcherServlet は適切なハンドラーマッピングを検索し、見つかったハンドラーが LastModified インターフェースを実装しているかどうかをテストします。その場合、LastModified インターフェースの long getLastModified(request) メソッドの値がクライアントに返されます。

web.xml ファイルのサーブレット宣言にサーブレット初期化パラメーター(init-param 要素)を追加することにより、個々の DispatcherServlet インスタンスをカスタマイズできます。次の表に、サポートされているパラメーターを示します。

表 1: DispatcherServlet 初期化パラメーター
パラメーター 説明

contextClass

インスタンス化され、このサーブレットによってローカルに設定される ConfigurableWebApplicationContext を実装するクラス。デフォルトでは、XmlWebApplicationContext が使用されます。

contextConfigLocation

コンテキストの場所を示すために、コンテキストインスタンス(contextClass で指定)に渡される文字列。文字列は、複数のコンテキストをサポートするために、潜在的に複数の文字列で構成されます(区切り文字としてコンマを使用)。Bean が 2 回定義されている複数のコンテキストロケーションの場合、最新のロケーションが優先されます。

namespace

WebApplicationContext の名前空間。デフォルトは [servlet-name]-servlet です。

throwExceptionIfNoHandlerFound

リクエストのハンドラーが見つからなかったときに NoHandlerFoundException をスローするかどうか。次に、例外を HandlerExceptionResolver でキャッチし(たとえば、@ExceptionHandler コントローラーメソッドを使用して)、他の例外として処理できます。

デフォルトでは、これは false に設定されます。その場合、DispatcherServlet は例外を発生させることなくレスポンスステータスを 404(NOT_FOUND)に設定します。

デフォルトのサーブレット処理も設定されている場合、未解決のリクエストは常にデフォルトのサーブレットに転送され、404 は発生しません。

1.1.6. 傍受

すべての HandlerMapping 実装は、特定の機能を特定のリクエストに適用する場合(たとえば、プリンシパルの確認など)に役立つハンドラーインターセプターをサポートしています。インターセプターは、org.springframework.web.servlet パッケージの HandlerInterceptor を、すべての種類の前処理と後処理を行うのに十分な柔軟性を提供する 3 つのメソッドで実装する必要があります。

  • preHandle(..): 実際のハンドラーが実行される前

  • postHandle(..): ハンドラーが実行された後

  • afterCompletion(..): 完全なリクエストが終了した後

preHandle(..) メソッドはブール値を返します。このメソッドを使用して、実行チェーンの処理を中断または続行できます。このメソッドが true を返すと、ハンドラーの実行チェーンが継続します。false を返すと、DispatcherServlet はインターセプター自体がリクエストを処理し(たとえば、適切なビューを表示)、他のインターセプターと実行チェーンの実際のハンドラーの実行を継続しないと想定します。

インターセプターを構成する方法の例については、MVC 構成に関するセクションのインターセプターを参照してください。また、個々の HandlerMapping 実装で setter を使用して直接登録することもできます。

postHandle は、レスポンスが HandlerAdapter 内および postHandle の前に書き込まれコミットされる @ResponseBody および ResponseEntity メソッドではあまり有用ではないことに注意してください。つまり、余分なヘッダーを追加するなど、レスポンスに変更を加えるには遅すぎます。そのようなシナリオでは、ResponseBodyAdvice を実装し、コントローラーのアドバイス Bean として宣言するか、RequestMappingHandlerAdapter で直接構成できます。

1.1.7. 例外

リクエストのマッピング中に例外が発生した場合、またはリクエストハンドラー(@Controller など)からスローされた場合、DispatcherServlet は HandlerExceptionResolver Bean のチェーンに委譲して、例外を解決し、代替処理(通常はエラーレスポンス)を提供します。

次の表に、使用可能な HandlerExceptionResolver 実装を示します。

テーブル 2: HandlerExceptionResolver の実装
HandlerExceptionResolver 説明

SimpleMappingExceptionResolver

例外クラス名とエラービュー名の間のマッピング。ブラウザアプリケーションでエラーページをレンダリングできます。

DefaultHandlerExceptionResolver(Javadoc)

Spring MVC によって発生した例外を解決し、HTTP ステータスコードにマッピングします。代替 ResponseEntityExceptionHandler および REST API の例外も参照してください。

ResponseStatusExceptionResolver

@ResponseStatus アノテーションを使用して例外を解決し、アノテーションの値に基づいて HTTP ステータスコードにマッピングします。

ExceptionHandlerExceptionResolver

@Controller または @ControllerAdvice クラスの @ExceptionHandler メソッドを呼び出すことにより、例外を解決します。@ExceptionHandler メソッドを参照してください。

リゾルバーのチェーン

Spring 構成で複数の HandlerExceptionResolver Bean を宣言し、必要に応じて order プロパティを設定することにより、例外リゾルバーチェーンを形成できます。順序プロパティが高いほど、例外リゾルバーが後から配置されます。

HandlerExceptionResolver の契約は、以下を返すことができることを指定しています。

  • エラービューを指す ModelAndView

  • リゾルバー内で例外が処理された場合、空の ModelAndView

  • null は、後続のリゾルバーが試行するために例外が未解決のままである場合、および例外が最後に残っている場合、サーブレットコンテナーまでバブルアップすることが許可されます。

MVC 構成は、デフォルトの Spring MVC 例外、@ResponseStatus アノテーション付き例外、および @ExceptionHandler メソッドのサポートのために、組み込みリゾルバーを自動的に宣言します。そのリストをカスタマイズするか、置き換えることができます。

コンテナーエラーページ

例外が HandlerExceptionResolver によって未解決のままであり、伝播するために残されている場合、またはレスポンスステータスがエラーステータス(4xx、5xx)に設定されている場合、サーブレットコンテナーは HTML でデフォルトのエラーページをレンダリングできます。コンテナーのデフォルトのエラーページをカスタマイズするために、web.xml でエラーページマッピングを宣言できます。次の例は、その方法を示しています。

<error-page>
    <location>/error</location>
</error-page>

前述の例では、例外がバブルアップするか、レスポンスにエラーステータスが含まれる場合、サーブレットコンテナーはコンテナー内で構成済みの URL(/error など)に対して ERROR ディスパッチを行います。次に、これは DispatcherServlet によって処理され、おそらく @Controller にマッピングされます。これは、次の例に示すように、モデルとともにエラービュー名を返すか、JSON レスポンスをレンダリングするために実装できます。

Java
@RestController
public class ErrorController {

    @RequestMapping(path = "/error")
    public Map<String, Object> handle(HttpServletRequest request) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", request.getAttribute("javax.servlet.error.status_code"));
        map.put("reason", request.getAttribute("javax.servlet.error.message"));
        return map;
    }
}
Kotlin
@RestController
class ErrorController {

    @RequestMapping(path = ["/error"])
    fun handle(request: HttpServletRequest): Map<String, Any> {
        val map = HashMap<String, Any>()
        map["status"] = request.getAttribute("javax.servlet.error.status_code")
        map["reason"] = request.getAttribute("javax.servlet.error.message")
        return map
    }
}
サーブレット API は、Java でエラーページマッピングを作成する方法を提供しません。ただし、WebApplicationInitializer と最小限の web.xml の両方を使用できます。

1.1.8. ビューリゾルバー

Spring MVC は ViewResolver および View インターフェースを定義し、特定のビューテクノロジーに縛られることなくブラウザーでモデルをレンダリングできます。ViewResolver は、ビュー名と実際のビュー間のマッピングを提供します。View は、特定のビューテクノロジーに引き渡す前のデータの準備に対処します。

次の表に、ViewResolver 階層の詳細を示します。

表 3: ViewResolver の実装
ViewResolver 説明

AbstractCachingViewResolver

AbstractCachingViewResolver キャッシュのサブクラスは、それらが解決するインスタンスを表示します。キャッシュにより、特定のビューテクノロジのパフォーマンスが向上します。cache プロパティを false に設定することにより、キャッシュをオフにできます。さらに、実行時に特定のビューをリフレッシュする必要がある場合(たとえば、FreeMarker テンプレートが変更された場合)、removeFromCache(String viewName, Locale loc) メソッドを使用できます。

XmlViewResolver

Spring の XML Bean ファクトリと同じ DTD で XML で記述された構成ファイルを受け入れる ViewResolver の実装。デフォルトの構成ファイルは /WEB-INF/views.xml です。

ResourceBundleViewResolver

バンドルのベース名で指定された ResourceBundle の Bean 定義を使用する ViewResolver の実装。解決するビューごとに、プロパティ [viewname].(class) の値をビュークラスとして使用し、プロパティ [viewname].url の値をビュー URL として使用します。ビューテクノロジーの章で例を見つけることができます。

UrlBasedViewResolver

明示的なマッピング定義なしで、論理ビュー名の URL への直接解決に影響する ViewResolver インターフェースの単純な実装。これは、任意のマッピングを必要とせずに、論理名が簡単にビューリソースの名前と一致する場合に適しています。

InternalResourceViewResolver

InternalResourceView (実質的にはサーブレットと JSP)をサポートする UrlBasedViewResolver の便利なサブクラスと、JstlView や TilesView などのサブクラス。setViewClass(..) を使用して、このリゾルバーによって生成されたすべてのビューのビュークラスを指定できます。詳細については、UrlBasedViewResolver(Javadoc) javadoc を参照してください。

FreeMarkerViewResolver

FreeMarkerView およびそれらのカスタムサブクラスをサポートする UrlBasedViewResolver の便利なサブクラス。

ContentNegotiatingViewResolver

リクエストファイル名または Accept ヘッダーに基づいてビューを解決する ViewResolver インターフェースの実装。コンテンツネゴシエーションを参照してください。

取り扱い

複数のリゾルバー Bean を宣言し、必要に応じて order プロパティを設定して順序を指定することにより、リゾルバーをチェーンで表示できます。順序プロパティが高いほど、後でビューリゾルバーがチェーンに配置されます。

ViewResolver の契約は、ビューが見つからなかったことを示すために null を返すことができることを指定しています。ただし、JSP と InternalResourceViewResolver の場合、JSP が存在するかどうかを判断する唯一の方法は、RequestDispatcher を介してディスパッチを実行することです。常にビューリゾルバーの全体的な順序で最後になるように InternalResourceViewResolver を構成する必要があります。

ビューリゾルバーの構成は、ViewResolver Bean を Spring 構成に追加するのと同じくらい簡単です。MVC 構成は、リゾルバーを表示およびコントローラーロジックなしの HTML テンプレートレンダリングに役立つロジックレスビューコントローラーを追加するための専用の構成 API を提供します。

リダイレクト

ビュー名の特別な redirect: プレフィックスを使用すると、リダイレクトを実行できます。UrlBasedViewResolver (およびそのサブクラス)は、これをリダイレクトが必要な命令として認識します。ビュー名の残りはリダイレクト URL です。

最終的な効果は、コントローラーが RedirectView を返した場合と同じですが、コントローラー自体が論理ビュー名の観点から動作できるようになりました。論理ビュー名(redirect:/myapp/some/resource など)は現在のサーブレットコンテキストに関連してリダイレクトし、redirect:https://myhost.com/some/arbitrary/path などの名前は絶対 URL にリダイレクトします。

コントローラーメソッドに @ResponseStatus のアノテーションが付けられている場合、アノテーション値は RedirectView によって設定されたレスポンスステータスよりも優先されることに注意してください。

転送

最終的に UrlBasedViewResolver およびサブクラスによって解決されるビュー名に特別な forward: プレフィックスを使用することもできます。これにより、RequestDispatcher.forward() を行う InternalResourceView が作成されます。このプレフィックスは InternalResourceViewResolver および InternalResourceView (JSP の場合)では有用ではありませんが、別のビューテクノロジーを使用しているが、リソースの転送をサーブレット / JSP エンジンで処理したい場合に役立ちます。代わりに、複数のビューリゾルバーチェーンを使用することもできます。

コンテンツネゴシエーション

ContentNegotiatingViewResolver(Javadoc) はビュー自体を解決するのではなく、他のビューリゾルバーに委譲し、クライアントがリクエストする表現に似たビューを選択します。表現は、Accept ヘッダーまたは照会パラメーター(たとえば、"/path?format=pdf")から判別できます。

ContentNegotiatingViewResolver は、各 ViewResolvers に関連付けられた View がサポートするメディアタイプ(Content-Type とも呼ばれる)とリクエストメディアタイプを比較することにより、適切な View を選択してリクエストを処理します。互換性のある Content-Type を持つリストの最初の View は、表現をクライアントに返します。ViewResolver チェーンが互換性のあるビューを提供できない場合、DefaultViews プロパティを介して指定されたビューのリストが参照されます。この後者のオプションは、論理ビュー名に関係なく現在のリソースの適切な表現をレンダリングできるシングルトン Views に適しています。Accept ヘッダーにはワイルドカード(たとえば text/*)を含めることができます。その場合、Content-Type が text/xml である View は互換性のある一致です。

構成の詳細については、MVC 構成リゾルバーを表示を参照してください。

1.1.9. ロケール

Spring のアーキテクチャのほとんどの部分は、Spring Web MVC フレームワークがサポートするように、国際化をサポートしています。DispatcherServlet では、クライアントのロケールを使用してメッセージを自動的に解決できます。これは、LocaleResolver オブジェクトを使用して行われます。

リクエストが受信すると、DispatcherServlet はロケールリゾルバーを探し、見つかった場合はそれを使用してロケールを設定しようとします。RequestContext.getLocale() メソッドを使用すると、ロケールリゾルバーによって解決されたロケールを常に取得できます。

自動ロケール解決に加えて、インターセプターをハンドラーマッピングにアタッチして(ハンドラーマッピングインターセプターの詳細については傍受を参照)、特定の状況(たとえば、リクエストのパラメーターに基づいて)でロケールを変更することもできます。

ロケールリゾルバーとインターセプターは org.springframework.web.servlet.i18n パッケージで定義され、通常の方法でアプリケーションコンテキストで構成されます。以下のロケールリゾルバーの選択が Spring に含まれています。

タイムゾーン

クライアントのロケールを取得することに加えて、多くの場合、タイムゾーンを知ることは有用です。LocaleContextResolver インターフェースは LocaleResolver の拡張機能を提供します。これにより、リゾルバーはより豊富な LocaleContext を提供でき、タイムゾーン情報を含めることができます。

利用可能な場合、RequestContext.getTimeZone() メソッドを使用してユーザーの TimeZone を取得できます。タイムゾーン情報は、Spring の ConversionService に登録されている日付 / 時刻 Converter および Formatter オブジェクトによって自動的に使用されます。

ヘッダーリゾルバー

このロケールリゾルバーは、クライアント(Web ブラウザーなど)によって送信されたリクエスト内の accept-language ヘッダーをインスペクションします。通常、このヘッダーフィールドには、クライアントのオペレーティングシステムのロケールが含まれています。このリゾルバーはタイムゾーン情報をサポートしていないことに注意してください。

このロケールリゾルバーは、クライアントに存在する可能性のある Cookie をインスペクションして、Locale または TimeZone が指定されているかどうかを確認します。その場合、指定された詳細を使用します。このロケールリゾルバーのプロパティを使用することにより、Cookie の名前と最大有効期間を指定できます。次の例では、CookieLocaleResolver を定義しています。

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">

    <property name="cookieName" value="clientlanguage"/>

    <!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->
    <property name="cookieMaxAge" value="100000"/>

</bean>

次の表で、プロパティ CookieLocaleResolver について説明します。

セッションリゾルバー

SessionLocaleResolver を使用すると、ユーザーのリクエストに関連付けられている可能性のあるセッションから Locale および TimeZone を取得できます。CookieLocaleResolver とは対照的に、この戦略はローカルに選択されたロケール設定をサーブレットコンテナーの HttpSession に保存します。結果として、これらの設定は各セッションの一時的なものであるため、各セッションが終了すると失われます。

Spring Session プロジェクトなどの外部セッション管理メカニズムと直接的な関連はないことに注意してください。この SessionLocaleResolver は、現在の HttpServletRequest に対して対応する HttpSession 属性を評価および変更します。

ロケールインターセプター

LocaleChangeInterceptor を HandlerMapping 定義の 1 つに追加することにより、ロケールの変更を有効にできます。リクエスト内のパラメーターを検出し、それに応じてロケールを変更し、ディスパッチャーのアプリケーションコンテキストで LocaleResolver の setLocale メソッドを呼び出します。次の例は、siteLanguage という名前のパラメーターを含むすべての *.view リソースへの呼び出しがロケールを変更することを示しています。たとえば、URL https://www.sf.net/home.view?siteLanguage=nl (英語) のリクエストは、サイトの言語をオランダ語に変更します。次の例は、ロケールをインターセプトする方法を示しています。

<bean id="localeChangeInterceptor"
        class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName" value="siteLanguage"/>
</bean>

<bean id="localeResolver"
        class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>

<bean id="urlMapping"
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <ref bean="localeChangeInterceptor"/>
        </list>
    </property>
    <property name="mappings">
        <value>/**/*.view=someController</value>
    </property>
</bean>

1.1.10. テーマ

Spring Web MVC フレームワークテーマを適用して、アプリケーションの全体的なルックアンドフィールを設定し、ユーザーエクスペリエンスを向上させることができます。テーマは、アプリケーションの視覚的なスタイルに影響を与える静的リソース(通常はスタイルシートとイメージ)のコレクションです。

テーマを定義する

Web アプリケーションでテーマを使用するには、org.springframework.ui.context.ThemeSource インターフェースの実装をセットアップする必要があります。WebApplicationContext インターフェースは ThemeSource を継承しますが、その責任を専用の実装に委譲します。デフォルトでは、デリゲートは、クラスパスのルートからプロパティファイルをロードする org.springframework.ui.context.support.ResourceBundleThemeSource 実装です。カスタム ThemeSource 実装を使用するか、ResourceBundleThemeSource のベース名プレフィックスを構成するには、予約名 themeSource でアプリケーションコンテキストに Bean を登録できます。Web アプリケーションコンテキストは、その名前の Bean を自動的に検出して使用します。

ResourceBundleThemeSource を使用する場合、テーマは単純なプロパティファイルで定義されます。次の例に示すように、プロパティファイルにはテーマを構成するリソースがリストされます。

styleSheet=/themes/cool/style.css
background=/themes/cool/img/coolBg.jpg

プロパティのキーは、ビューコードからテーマの要素を参照する名前です。JSP の場合、通常、spring:theme カスタムタグを使用してこれを行います。これは、spring:message タグに非常によく似ています。次の JSP フラグメントは、前の例で定義されたテーマを使用して、ルックアンドフィールをカスタマイズします。

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html>
    <head>
        <link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css"/>
    </head>
    <body style="background=<spring:theme code='background'/>">
        ...
    </body>
</html>

デフォルトでは、ResourceBundleThemeSource は空のベース名プレフィックスを使用します。その結果、プロパティファイルはクラスパスのルートからロードされます。cool.properties テーマ定義をクラスパスのルートのディレクトリに配置します(たとえば、/WEB-INF/classes に)。ResourceBundleThemeSource は標準の Java リソースバンドルロードメカニズムを使用して、テーマの完全な国際化を可能にします。例:オランダ語のテキストを含む特別な背景イメージを参照する /WEB-INF/classes/cool_nl.properties を作成できます。

テーマの解決

前のセクションで説明したように、テーマを定義したら、使用するテーマを決定します。DispatcherServlet は、themeResolver という名前の Bean を探して、使用する ThemeResolver 実装を見つけます。テーマリゾルバーは、LocaleResolver とほぼ同じ方法で機能します。特定のリクエストに使用するテーマを検出し、リクエストのテーマを変更することもできます。次の表は、Spring が提供するテーマリゾルバーについて説明しています。

表 5: ThemeResolver の実装
クラス 説明

FixedThemeResolver

defaultThemeName プロパティを使用して設定された固定テーマを選択します。

SessionThemeResolver

テーマはユーザーの HTTP セッションで維持されます。セッションごとに一度だけ設定する必要がありますが、セッション間で永続化されることはありません。

CookieThemeResolver

選択したテーマは、クライアントの Cookie に保存されます。

Spring は、単純なリクエストパラメーターを使用して、すべてのリクエストでテーマを変更できる ThemeChangeInterceptor も提供します。

1.1.11. マルチパートリゾルバー

org.springframework.web.multipart パッケージの MultipartResolver は、ファイルのアップロードを含むマルチパートリクエストを解析するための戦略です。コモンズ FileUpload (Apache) に基づく実装と Servlet 3.0 マルチパートリクエストの解析に基づく実装があります。

マルチパート処理を有効にするには、multipartResolver という名前で DispatcherServlet Spring 構成で MultipartResolver Bean を宣言する必要があります。DispatcherServlet はそれを検出し、受信リクエストに適用します。content-type が multipart/form-data の POST を受信すると、リゾルバーはコンテンツを解析し、現在の HttpServletRequest を MultipartHttpServletRequest としてラップして、解決されたパーツへのアクセスを提供し、リクエストパラメーターとして公開します。

Apache Commons FileUpload

Apache Commons FileUpload を使用するには、multipartResolver という名前の CommonsMultipartResolver タイプの Bean を構成できます。また、クラスパスへの依存関係として commons-fileupload が必要です。

Servlet 3.0

Servlet 3.0 マルチパート解析は、サーブレットコンテナーの設定を通じて有効にする必要があります。そうするには:

  • Java では、サーブレット登録で MultipartConfigElement を設定します。

  • web.xml で、サーブレット宣言に "<multipart-config>" セクションを追加します。

次の例は、サーブレット登録で MultipartConfigElement を設定する方法を示しています。

Java
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    // ...

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {

        // Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
        registration.setMultipartConfig(new MultipartConfigElement("/tmp"));
    }

}
Kotlin
class AppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {

    // ...

    override fun customizeRegistration(registration: ServletRegistration.Dynamic) {

        // Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
        registration.setMultipartConfig(MultipartConfigElement("/tmp"))
    }

}

Servlet 3.0 構成が配置されたら、タイプ StandardServletMultipartResolver の Bean を名前 multipartResolver で追加できます。

1.1.12. ロギング

Spring MVC のデバッグレベルのロギングは、コンパクトで、最小限で、人に優しいように設計されています。特定の課題をデバッグするときにのみ役立つ他の情報と比較して、何度も何度も役立つ情報の価値の高いビットに焦点を当てています。

TRACE レベルのロギングは、一般に DEBUG と同じ原則に従います(たとえば、消火ホースであってはなりません)が、課題のデバッグに使用できます。さらに、一部のログメッセージには、TRACE と DEBUG で異なる詳細レベルが表示される場合があります。

適切なログは、ログを使用した経験から得られます。記載されているゴールを達成できないものを見つけた場合は、お知らせください。

機密データ

DEBUG および TRACE ロギングは、機密情報を記録する場合があります。これが、リクエストパラメーターとヘッダーがデフォルトでマスクされ、DispatcherServlet の enableLoggingRequestDetails プロパティを介して明示的にそれらのロギングを完全に有効にする必要がある理由です。

次の例は、Java 構成を使用してこれを行う方法を示しています。

Java
public class MyInitializer
        extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return ... ;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return ... ;
    }

    @Override
    protected String[] getServletMappings() {
        return ... ;
    }

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {
        registration.setInitParameter("enableLoggingRequestDetails", "true");
    }

}
Kotlin
class MyInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {

    override fun getRootConfigClasses(): Array<Class<*>>? {
        return ...
    }

    override fun getServletConfigClasses(): Array<Class<*>>? {
        return ...
    }

    override fun getServletMappings(): Array<String> {
        return ...
    }

    override fun customizeRegistration(registration: ServletRegistration.Dynamic) {
        registration.setInitParameter("enableLoggingRequestDetails", "true")
    }
}

1.2. フィルター

spring-web モジュールは、いくつかの有用なフィルターを提供します。

1.2.1. フォームデータ

ブラウザは、HTTP GET または HTTP POST を介してのみフォームデータを送信できますが、ブラウザ以外のクライアントも HTTP PUT、PATCH、および DELETE を使用できます。サーブレット API では、HTTP POST のフォームフィールドアクセスのみをサポートするために ServletRequest.getParameter*() メソッドが必要です。

spring-web モジュールは FormContentFilter を提供し、コンテンツタイプ application/x-www-form-urlencoded の HTTP PUT、PATCH、および DELETE リクエストをインターセプトし、リクエストの本文からフォームデータを読み取り、ServletRequest をラップして、ServletRequest.getParameter*() ファミリーのメソッドでフォームデータを利用できるようにします。

1.2.2. 転送されたヘッダー

リクエストがプロキシ(ロードバランサーなど)を通過すると、ホスト、ポート、スキームが変更される可能性があるため、クライアントの観点から正しいホスト、ポート、スキームを指すリンクを作成することが困難になります。

RFC 7239 (英語) は、プロキシが元のリクエストに関する情報を提供するために使用できる Forwarded HTTP ヘッダーを定義します。X-Forwarded-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-Ssl や X-Forwarded-Prefix など、他の非標準ヘッダーもあります。

ForwardedHeaderFilter は、a) Forwarded ヘッダーに基づいてホスト、ポート、スキームを変更し、b)これらのヘッダーを削除してさらなる影響を排除するために、リクエストを変更するサーブレットフィルターです。フィルターはリクエストをラップすることに依存しているため、RequestContextFilter など、他のフィルターよりも先に並べ替える必要があります。これは、元のリクエストではなく、変更されたリクエストで機能するはずです。

転送されたヘッダーには、意図したとおりにプロキシによってヘッダーが追加されたか、悪意のあるクライアントによってヘッダーが追加されたかをアプリケーションが認識できないため、セキュリティ上の考慮事項があります。これが、外部からの信頼できない Forwarded ヘッダーを削除するように、信頼の境界にあるプロキシを構成する必要がある理由です。ForwardedHeaderFilter を removeOnly=true で構成することもできます。この場合、ヘッダーは削除されますが、使用されません。

非同期リクエストとエラーディスパッチをサポートするには、このフィルターを DispatcherType.ASYNC および DispatcherType.ERROR にマップする必要があります。Spring Framework の AbstractAnnotationConfigDispatcherServletInitializer (サーブレット構成を参照)を使用している場合、すべてのフィルターはすべてのディスパッチタイプに対して自動的に登録されます。ただし、web.xml を介して、または FilterRegistrationBean を介して Spring Boot でフィルターを登録する場合は、DispatcherType.REQUEST に加えて DispatcherType.ASYNC および DispatcherType.ERROR を必ず含めてください。

1.2.3. 浅い ETag

ShallowEtagHeaderFilter フィルターは、レスポンスに書き込まれたコンテンツをキャッシュし、そこから MD5 ハッシュを計算することにより、「浅い」ETag を作成します。次回クライアントが送信するとき、同じことを行いますが、計算された値を If-None-Match リクエストヘッダーと比較し、2 つが等しい場合、304(NOT_MODIFIED)を返します。

この戦略では、リクエストごとに完全なレスポンスを計算する必要があるため、ネットワーク帯域幅は節約されますが、CPU は節約されません。前述のコントローラーレベルでの他の戦略は、計算を回避できます。HTTP キャッシングを参照してください。

このフィルターには、次のような弱い ETag を書き込むようにフィルターを構成する writeWeakETag パラメーターがあります: W/"02a2d595e6ed9a0b24f027f2b63b134d6" (RFC 7232 セクション 2.3 (英語) で定義)。

非同期リクエストをサポートするには、このフィルターを DispatcherType.ASYNC にマッピングして、フィルターが遅延し、最後の非同期ディスパッチの最後まで ETag を正常に生成できるようにする必要があります。Spring Framework の AbstractAnnotationConfigDispatcherServletInitializer (サーブレット構成を参照)を使用している場合、すべてのフィルターはすべてのディスパッチタイプに対して自動的に登録されます。ただし、web.xml 経由で、または FilterRegistrationBean 経由で Spring Boot でフィルターを登録する場合は、必ず DispatcherType.ASYNC を含めてください。

1.2.4. CORS

Spring MVC は、コントローラーのアノテーションを通じて CORS 構成のきめ細かなサポートを提供します。ただし、Spring Security で使用する場合は、Spring Security のチェーンフィルターよりも先に並べ替える必要のある内蔵 CorsFilter を使用することをお勧めします。

詳細については、CORS および CORS フィルターのセクションを参照してください。

1.3. アノテーション付きコントローラー

Spring MVC は、@Controller および @RestController コンポーネントがアノテーションを使用してリクエストマッピング、リクエスト入力、例外処理などを表現するアノテーションベースのプログラミングモデルを提供します。アノテーション付きコントローラーには柔軟なメソッドシグネチャーがあり、基本クラスを継承したり、特定のインターフェースを実装したりする必要はありません。次の例は、アノテーションによって定義されたコントローラーを示しています。

Java
@Controller
public class HelloController {

    @GetMapping("/hello")
    public String handle(Model model) {
        model.addAttribute("message", "Hello World!");
        return "index";
    }
}
Kotlin
import org.springframework.ui.set

@Controller
class HelloController {

    @GetMapping("/hello")
    fun handle(model: Model): String {
        model["message"] = "Hello World!"
        return "index"
    }
}

前の例では、メソッドは Model を受け入れ、ビュー名を String として返しますが、他の多くのオプションが存在し、この章で後ほど説明します。

spring.io のガイドとチュートリアルでは、このセクションで説明するアノテーションベースのプログラミングモデルを使用します。

1.3.1. 宣言

サーブレットの WebApplicationContext で標準 Spring Bean 定義を使用して、コントローラー Bean を定義できます。@Controller ステレオタイプは、クラスパス内の @Component クラスの検出およびそれらの Bean 定義の自動登録の Spring 一般サポートに合わせて、自動検出を可能にします。また、アノテーション付きクラスのステレオタイプとしても機能し、Web コンポーネントとしてのロールを示します。

そのような @Controller Bean の自動検出を有効にするには、次の例に示すように、Java 構成にコンポーネントスキャンを追加できます。

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

    // ...
}
Kotlin
@Configuration
@ComponentScan("org.example.web")
class WebConfig {

    // ...
}

次の例は、前述の例に相当する XML 構成を示しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example.web"/>

    <!-- ... -->

</beans>

@RestController は、それ自体が @Controller および @ResponseBody でメタアノテーションされた合成アノテーションであり、すべてのメソッドが型レベルの @ResponseBody アノテーションを継承するコントローラーを示すため、レスポンステンプレートにビューに直接書き込み、HTML テンプレートでレンダリングします。

AOP プロキシ

場合によっては、実行時にコントローラーを AOP プロキシで装飾する必要があります。1 つの例は、@Transactional アノテーションをコントローラーに直接持つことを選択した場合です。この場合、特にコントローラーについては、クラスベースのプロキシを使用することをお勧めします。これは通常、コントローラーのデフォルトの選択です。ただし、コントローラーが Spring Context コールバックではないインターフェース(InitializingBean*Aware など)を実装する必要がある場合、クラスベースのプロキシを明示的に構成する必要があります。例: <tx:annotation-driven/> では <tx:annotation-driven proxy-target-class="true"/> に変更でき、@EnableTransactionManagement では @EnableTransactionManagement(proxyTargetClass = true) に変更できます。

1.3.2. リクエストマッピング

@RequestMapping アノテーションを使用して、リクエストをコントローラーメソッドにマッピングできます。URL、HTTP メソッド、リクエストパラメーター、ヘッダー、およびメディアタイプで一致するさまざまな属性があります。クラスレベルで使用して共有マッピングを表現したり、メソッドレベルで使用して特定のエンドポイントマッピングに絞り込んだりできます。

@RequestMapping の HTTP メソッド固有のショートカットバリアントもあります。

  • @GetMapping

  • @PostMapping

  • @PutMapping

  • @DeleteMapping

  • @PatchMapping

ほとんどのコントローラーメソッドは、デフォルトですべての HTTP メソッドに一致する @RequestMapping を使用するのではなく、特定の HTTP メソッドにマップする必要があるため、ショートカットはカスタムアノテーションです。同時に、共有マッピングを表現するには、クラスレベルで @RequestMapping が必要です。

次の例には、タイプおよびメソッドレベルのマッピングがあります。

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

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

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void add(@RequestBody Person person) {
        // ...
    }
}
Kotlin
@RestController
@RequestMapping("/persons")
class PersonController {

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

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    fun add(@RequestBody person: Person) {
        // ...
    }
}
URI パターン

次のグローバルパターンとワイルドカードを使用して、リクエストをマップできます。

  • ? は 1 文字に一致する

  • * は、パスセグメント内の 0 個以上の文字に一致する

  • ** は 0 個以上のパスセグメントと一致する

次の例に示すように、URI 変数を宣言し、@PathVariable を使用してそれらの値にアクセスすることもできます。

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

次の例に示すように、クラスおよびメソッドレベルで URI 変数を宣言できます。

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

    @GetMapping("/pets/{petId}")
    public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
        // ...
    }
}
Kotlin
@Controller
@RequestMapping("/owners/{ownerId}")
class OwnerController {

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

URI 変数は適切な型に自動的に変換されるか、TypeMismatchException が発生します。単純型(intlongDate など)はデフォルトでサポートされており、他のデータ型のサポートを登録できます。型変換および DataBinder を参照してください。

URI 変数(@PathVariable("customId") など)に明示的に名前を付けることができますが、名前が同じで、コードがデバッグ情報または Java 8 の -parameters コンパイラフラグを使用してコンパイルされている場合は、詳細を省略できます。

構文 {varName:regex} は、{varName:regex} の構文を持つ正規表現で URI 変数を宣言します。例:URL "/spring-web-3.0.5 .jar" を指定すると、次のメソッドは名前、バージョン、およびファイル拡張子を抽出します。

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

URI パスパターンには、PropertyPlaceHolderConfigurer をローカル、システム、環境、およびその他のプロパティソースに対して使用することにより、起動時に解決される ${…​} プレースホルダーを埋め込むこともできます。これを使用して、たとえば、外部設定に基づいてベース URL をパラメーター化できます。

Spring MVC は、URI パスマッチングのために PathMatcher 契約と spring-core からの AntPathMatcher 実装を使用します。
パターン比較

複数のパターンが URL に一致する場合、比較して最適な一致を見つける必要があります。これは、より具体的なパターンを探す AntPathMatcher.getPatternComparator(String path) を使用して行われます。

パターンの URI 変数のカウント(1 としてカウント)、単一のワイルドカード(1 としてカウント)、および二重のワイルドカード(2 としてカウント)が少ない場合、パターンはそれほど明確ではありません。等しいスコアが与えられると、長いパターンが選択されます。同じスコアと長さを指定すると、ワイルドカードよりも多くの URI 変数を持つパターンが選択されます。

デフォルトのマッピングパターン(/**)はスコアリングから除外され、常に最後にソートされます。また、プレフィックスパターン(/public/** など)は、二重ワイルドカードを持たない他のパターンよりも特定性が低いと見なされます。

詳細については、AntPathMatcher(Javadoc) AntPatternComparator(Javadoc) を参照し、PathMatcher(Javadoc) 実装をカスタマイズできることにも留意してください。構成セクションのパスマッチングを参照してください。

サフィックスマッチ

デフォルトでは、Spring MVC は .* サフィックスパターンマッチングを実行するため、/person にマッピングされたコントローラーも /person.* に暗黙的にマッピングされます。次に、ファイル拡張子を使用して、リクエストに応じて(Accept ヘッダーの代わりに)リクエストされたコンテンツタイプを解釈します(例: /person.pdf/person.xml など)。

ブラウザーが一貫して解釈するのが困難な Accept ヘッダーを送信するために使用した場合、この方法でファイル拡張子を使用する必要がありました。現時点では、もはや必要ではなく、Accept ヘッダーを使用することをお勧めします。

時間の経過とともに、ファイル名拡張子の使用にはさまざまな点で問題があることが判明しています。URI 変数、パスパラメーター、および URI エンコーディングを使用すると、あいまいさが生じる可能性があります。URL ベースの認可とセキュリティに関する推論(詳細については次のセクションを参照)も難しくなります。

ファイル拡張子の使用を完全に無効にするには、次の両方を設定する必要があります。

URL ベースのコンテンツネゴシエーションは引き続き役立ちます(たとえば、ブラウザーで URL を入力する場合)。これを有効にするには、ファイル拡張子に伴う課題のほとんどを回避するために、クエリパラメーターベースの戦略をお勧めします。または、ファイル拡張子を使用する必要がある場合は、ContentNegotiationConfigurer の mediaTypes プロパティを使用して、明示的に登録された拡張子のリストに制限することを検討してください。

サフィックスマッチと RFD

リフレクションファイルダウンロード(RFD)攻撃は、レスポンスに反映されるリクエスト入力(クエリパラメーターや URI 変数など)に依存するという点で XSS に似ています。ただし、JavaScript を HTML に挿入する代わりに、ブラウザを切り替えてダウンロードを実行し、後でダブルクリックしたときにレスポンスを実行可能なスクリプトとして処理することに依存します。

Spring MVC では、@ResponseBody メソッドと ResponseEntity メソッドは、クライアントが URL パス拡張を介してリクエストできる異なるコンテンツタイプをレンダリングできるため、危険にさらされています。サフィックスパターンマッチングを無効にし、コンテンツネゴシエーションにパス拡張機能を使用すると、リスクは低くなりますが、RFD 攻撃を防止するには不十分です。

RFD 攻撃を防ぐために、レスポンス本文をレンダリングする前に、Spring MVC は Content-Disposition:inline;filename=f.txt ヘッダーを追加して、固定された安全なダウンロードファイルを提案します。これは、URL パスに、コンテンツネゴシエーション用にホワイトリストに登録されていないか、明示的に登録されていないファイル拡張子が含まれている場合にのみ行われます。ただし、URL をブラウザーに直接入力すると、副作用が生じる可能性があります。

多くの一般的なパス拡張子はデフォルトでホワイトリストに登録されています。カスタム HttpMessageConverter 実装を備えたアプリケーションは、コンテンツネゴシエーションのファイル拡張子を明示的に登録して、それらの拡張子に Content-Disposition ヘッダーが追加されるのを回避できます。コンテンツタイプを参照してください。

RFD に関連する追加の推奨事項については、CVE-2015-5211 を参照してください。

消費可能なメディアタイプ

次の例に示すように、リクエストの Content-Type に基づいてリクエストマッピングを絞り込むことができます。

Java
@PostMapping(path = "/pets", consumes = "application/json") (1)
public void addPet(@RequestBody Pet pet) {
    // ...
}
1consumes 属性を使用して、コンテンツタイプごとにマッピングを絞り込みます。
Kotlin
@PostMapping("/pets", consumes = ["application/json"]) (1)
fun addPet(@RequestBody pet: Pet) {
    // ...
}
1consumes 属性を使用して、コンテンツタイプごとにマッピングを絞り込みます。

consumes 属性は否定表現もサポートします。たとえば、!text/plain は text/plain 以外のコンテンツタイプを意味します。

クラスレベルで共有 consumes 属性を宣言できます。ただし、他のほとんどのリクエストマッピング属性とは異なり、クラスレベルで使用する場合、メソッドレベルの consumes 属性はクラスレベルの宣言を継承するのではなくオーバーライドします。

MediaType は、APPLICATION_JSON_VALUE や APPLICATION_XML_VALUE などの一般的に使用されるメディアタイプに定数を提供します。
生産可能なメディアタイプ

次の例に示すように、Accept リクエストヘッダーとコントローラーメソッドが生成するコンテンツタイプのリストに基づいて、リクエストマッピングを絞り込むことができます。

Java
@GetMapping(path = "/pets/{petId}", produces = "application/json") (1)
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}
1produces 属性を使用して、コンテンツタイプごとにマッピングを絞り込みます。
Kotlin
@GetMapping("/pets/{petId}", produces = ["application/json"]) (1)
@ResponseBody
fun getPet(@PathVariable petId: String): Pet {
    // ...
}
1produces 属性を使用して、コンテンツタイプごとにマッピングを絞り込みます。

メディアタイプは文字セットを指定できます。否定表現がサポートされています。たとえば、!text/plain は「text/plain」以外のコンテンツタイプを意味します。

クラスレベルで共有 produces 属性を宣言できます。ただし、他のほとんどのリクエストマッピング属性とは異なり、クラスレベルで使用する場合、メソッドレベルの produces 属性はクラスレベルの宣言を継承するのではなくオーバーライドします。

MediaType は、APPLICATION_JSON_VALUE や APPLICATION_XML_VALUE などの一般的に使用されるメディアタイプに定数を提供します。
パラメーター、ヘッダー

リクエストパラメーターの条件に基づいて、リクエストマッピングを絞り込むことができます。リクエストパラメーターの存在(myParam)、パラメーターの不在(!myParam)、または特定の値(myParam=myValue)をテストできます。次の例は、特定の値をテストする方法を示しています。

Java
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") (1)
public void findPet(@PathVariable String petId) {
    // ...
}
1myParam が myValue と等しいかどうかのテスト。
Kotlin
@GetMapping("/pets/{petId}", params = ["myParam=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
    // ...
}
1myParam が myValue と等しいかどうかのテスト。

次の例に示すように、リクエストヘッダー条件で同じものを使用することもできます。

Java
@GetMapping(path = "/pets", headers = "myHeader=myValue") (1)
public void findPet(@PathVariable String petId) {
    // ...
}
1myHeader が myValue と等しいかどうかのテスト。
Kotlin
@GetMapping("/pets", headers = ["myHeader=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
    // ...
}
Content-Type および Accept をヘッダー条件と一致させることができますが、代わりに消費生成を使用することをお勧めします。
HTTP HEAD, OPTIONS

@GetMapping (および @RequestMapping(method=HttpMethod.GET))は、リクエストマッピングのために HTTP HEAD を透過的にサポートします。コントローラーのメソッドを変更する必要はありません。javax.servlet.http.HttpServlet で適用されるレスポンスラッパーは、Content-Length ヘッダーが(実際にレスポンスに書き込まずに)書き込まれたバイト数に設定されるようにします。

@GetMapping (および @RequestMapping(method=HttpMethod.GET))は、暗黙的に HTTP HEAD にマッピングされ、サポートされます。HTTP HEAD リクエストは、ボディを書き込む代わりにバイト数がカウントされ、Content-Length ヘッダーが設定されることを除いて、HTTP GET であるかのように処理されます。

デフォルトでは、HTTP OPTIONS は、Allow レスポンスヘッダーを、一致する URL パターンを持つすべての @RequestMapping メソッドにリストされている HTTP メソッドのリストに設定することによって処理されます。

HTTP メソッド宣言のない @RequestMapping の場合、Allow ヘッダーは GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS に設定されます。コントローラーメソッドは、サポートされている HTTP メソッドを常に宣言する必要があります(たとえば、HTTP メソッド固有のバリアントを使用して: @GetMapping@PostMapping など)。

@RequestMapping メソッドを明示的に HTTP HEAD および HTTP OPTIONS にマップできますが、これは一般的なケースでは必要ありません。

カスタムアノテーション

Spring MVC は、リクエストのマッピングに合成アノテーションの使用をサポートしています。これらは、それ自体が @RequestMapping でメタアノテーションされ、より狭い、より具体的な目的で @RequestMapping 属性のサブセット(またはすべて)を再宣言するように構成されたアノテーションです。

@GetMapping@PostMapping@PutMapping@DeleteMapping および @PatchMapping は、構成されたアノテーションの例です。ほぼ間違いなく、ほとんどのコントローラーメソッドは、デフォルトですべての HTTP メソッドに一致する @RequestMapping を使用するのではなく、特定の HTTP メソッドにマッピングする必要があるためです。合成されたアノテーションの例が必要な場合は、それらがどのように宣言されているかを参照してください。

Spring MVC は、カスタムリクエストマッチングロジックを備えたカスタムリクエストマッピング属性もサポートしています。これは、RequestMappingHandlerMapping のサブクラス化と getCustomMethodCondition メソッドのオーバーライドを必要とする、より高度なオプションです。カスタム属性を確認して、独自の RequestCondition を返すことができます。

明示的な登録

ハンドラーメソッドをプログラムで登録できます。これは、動的な登録や、異なる URL での同じハンドラーの異なるインスタンスなどの高度なケースに使用できます。次の例では、ハンドラーメソッドを登録します。

Java
@Configuration
public class MyConfig {

    @Autowired
    public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) (1)
            throws NoSuchMethodException {

        RequestMappingInfo info = RequestMappingInfo
                .paths("/user/{id}").methods(RequestMethod.GET).build(); (2)

        Method method = UserHandler.class.getMethod("getUser", Long.class); (3)

        mapping.registerMapping(info, handler, method); (4)
    }
}
1 ターゲットハンドラーとコントローラーのハンドラーマッピングを挿入します。
2 リクエストマッピングメタデータを準備します。
3 ハンドラーメソッドを取得します。
4 登録を追加します。
Kotlin
@Configuration
class MyConfig {

    @Autowired
    fun setHandlerMapping(mapping: RequestMappingHandlerMapping, handler: UserHandler) { (1)
        val info = RequestMappingInfo.paths("/user/{id}").methods(RequestMethod.GET).build() (2)
        val method = UserHandler::class.java.getMethod("getUser", Long::class.java) (3)
        mapping.registerMapping(info, handler, method) (4)
    }
}
1 ターゲットハンドラーとコントローラーのハンドラーマッピングを挿入します。
2 リクエストマッピングメタデータを準備します。
3 ハンドラーメソッドを取得します。
4 登録を追加します。

1.3.3. 処理メソッド

@RequestMapping ハンドラーメソッドには柔軟なシグネチャーがあり、サポートされているコントローラーメソッドの引数と戻り値の範囲から選択できます。

メソッド引数

次の表に、サポートされているコントローラーメソッドの引数を示します。リアクティブ型は、引数に対してサポートされていません。

JDK 8 の java.util.Optional は、required 属性(たとえば @RequestParam@RequestHeader など)を持つアノテーションと組み合わせてメソッド引数としてサポートされ、required=false と同等です。

コントローラーメソッドの引数 説明

WebRequestNativeWebRequest

サーブレット API を直接使用せずに、リクエストパラメーターとリクエストおよびセッション属性への汎用アクセス。

javax.servlet.ServletRequestjavax.servlet.ServletResponse

ServletRequestHttpServletRequest や Spring の MultipartRequestMultipartHttpServletRequest など、特定のリクエストまたはレスポンスタイプを選択します。

javax.servlet.http.HttpSession

セッションの存在を強制します。結果として、そのような引数は決して null になりません。セッションアクセスはスレッドセーフではないことに注意してください。複数のリクエストがセッションに同時にアクセスできる場合は、RequestMappingHandlerAdapter インスタンスの synchronizeOnSession フラグを true に設定することを検討してください。

javax.servlet.http.PushBuilder

プログラムによる HTTP/2 リソースプッシュ用の Servlet 4.0 プッシュビルダー API。クライアントがその HTTP/2 機能をサポートしていない場合、サーブレット仕様に従って、注入された PushBuilder インスタンスは null になる可能性があることに注意してください。

java.security.Principal

現在認証されているユーザー  —  既知の場合、特定の Principal 実装クラスである可能性があります。

HttpMethod

リクエストの HTTP メソッド。

java.util.Locale

使用可能な最も具体的な LocaleResolver (実際には、構成された LocaleResolver または LocaleContextResolver)によって決定される現在のリクエストロケール。

java.util.TimeZone + java.time.ZoneId

LocaleContextResolver によって決定される、現在のリクエストに関連付けられたタイムゾーン。

java.io.InputStreamjava.io.Reader

サーブレット API によって公開されている未加工のリクエスト本文へのアクセス用。

java.io.OutputStreamjava.io.Writer

サーブレット API によって公開される生のレスポンス本文へのアクセス用。

@PathVariable

URI テンプレート変数へのアクセス用。URI パターンを参照してください。

@MatrixVariable

URI パスセグメントの名前と値のペアへのアクセス用。行列変数を参照してください。

@RequestParam

マルチパートファイルを含む、サーブレットリクエストパラメーターへのアクセス用。パラメーター値は、宣言されたメソッド引数タイプに変換されます。@RequestParam およびマルチパートを参照してください。

@RequestParam の使用は、単純なパラメーター値ではオプションであることに注意してください。この表の最後にある「その他の引数」を参照してください。

@RequestHeader

リクエストヘッダーへのアクセス用。ヘッダー値は、宣言されたメソッド引数タイプに変換されます。@RequestHeader を参照してください。

@CookieValue

クッキーへのアクセス用。Cookie 値は、宣言されたメソッド引数タイプに変換されます。@CookieValue を参照してください。

@RequestBody

HTTP リクエスト本文へのアクセス用。本文コンテンツは、HttpMessageConverter 実装を使用して、宣言されたメソッド引数タイプに変換されます。@RequestBody を参照してください。

HttpEntity<B>

リクエストヘッダーと本文へのアクセス用。ボディは HttpMessageConverter で変換されます。HttpEntity を参照してください。

@RequestPart

multipart/form-data リクエストでパーツにアクセスするには、パーツのボディを HttpMessageConverter に変換します。マルチパートを参照してください。

java.util.Maporg.springframework.ui.Modelorg.springframework.ui.ModelMap

HTML コントローラーで使用され、ビューレンダリングの一部としてテンプレートに公開されるモデルへのアクセス用。

RedirectAttributes

リダイレクト(クエリ文字列に追加)の場合に使用する属性と、リダイレクト後のリクエストまで一時的に保存されるフラッシュ属性を指定します。リダイレクト属性およびフラッシュ属性を参照してください。

@ModelAttribute

データバインディングと検証が適用されたモデル内の既存の属性(存在しない場合はインスタンス化)へのアクセス用。@ModelAttribute およびモデルおよび DataBinder を参照してください。

@ModelAttribute の使用はオプションであることに注意してください(たとえば、属性を設定するため)。この表の最後にある「その他の引数」を参照してください。

ErrorsBindingResult

コマンドオブジェクト(つまり、@ModelAttribute 引数)の検証およびデータバインディングからのエラー、または @RequestBody または @RequestPart 引数の検証からのエラーへのアクセス。検証されたメソッド引数の直後に Errors または BindingResult 引数を宣言する必要があります。

SessionStatus + クラスレベル @SessionAttributes

フォーム処理の補完をマークするために、クラスレベルの @SessionAttributes アノテーションを介して宣言されたセッション属性のクリーンアップをトリガーします。詳細については、@SessionAttributes を参照してください。

UriComponentsBuilder

現在のリクエストのホスト、ポート、スキーム、コンテキストパス、およびサーブレットマッピングのリテラル部分に関連する URL を準備します。URI リンクを参照してください。

@SessionAttribute

クラスレベルの @SessionAttributes 宣言の結果としてセッションに格納されたモデル属性とは対照的に、任意のセッション属性へのアクセス。詳細については、@SessionAttribute を参照してください。

@RequestAttribute

リクエスト属性へのアクセス用。詳細については、@RequestAttribute を参照してください。

その他の引数

メソッドの引数がこの表の以前の値のいずれにも一致せず、単純型(BeanUtils#isSimpleProperty(Javadoc) によって決定される)である場合、@RequestParam として解決されます。それ以外の場合、@ModelAttribute として解決されます。

戻り値

次の表に、サポートされているコントローラーメソッドの戻り値を示します。リアクティブ型はすべての戻り値でサポートされています。

コントローラーメソッドの戻り値 説明

@ResponseBody

戻り値は HttpMessageConverter 実装を介して変換され、レスポンスに書き込まれます。@ResponseBody を参照してください。

HttpEntity<B>ResponseEntity<B>

完全なレスポンス(HTTP ヘッダーと本文を含む)を指定する戻り値は、HttpMessageConverter 実装を介して変換され、レスポンスに書き込まれます。ResponseEntity を参照してください。

HttpHeaders

ヘッダーを含み、本文を含まないレスポンスを返すため。

String

ViewResolver 実装で解決され、暗黙モデルと一緒に使用されるビュー名 — コマンドオブジェクトと @ModelAttribute メソッドによって決定されます。ハンドラーメソッドは、Model 引数を宣言することにより、プログラムでモデルを強化することもできます(明示的な登録を参照)。

View

暗黙的なモデルと一緒にレンダリングするために使用する View インスタンス — コマンドオブジェクトと @ModelAttribute メソッドによって決定されます。ハンドラーメソッドは、Model 引数を宣言することにより、プログラムでモデルを強化することもできます(明示的な登録を参照)。

java.util.Maporg.springframework.ui.Model

RequestToViewNameTranslator を介して暗黙的に決定されたビュー名を持つ、暗黙モデルに追加される属性。

@ModelAttribute

モデルに追加される属性。ビュー名は RequestToViewNameTranslator によって暗黙的に決定されます。

@ModelAttribute はオプションです。この表の最後にある「その他の戻り値」を参照してください。

ModelAndView オブジェクト

使用するビューとモデルの属性、およびオプションでレスポンスステータス。

void

void 戻り値型(または null 戻り値)を持つメソッドは、ServletResponseOutputStream 引数、または @ResponseStatus アノテーションも持っている場合、レスポンスを完全に処理したと見なされます。コントローラーが ETag または lastModified の正のタイムスタンプチェックを行った場合も同様です(詳細についてはコントローラーを参照してください)。

上記のいずれにも当てはまらない場合、void 戻り型は、REST コントローラーの「レスポンス本文なし」または HTML コントローラーのデフォルトのビュー名選択を示すこともできます。

DeferredResult<V>

前述の戻り値のいずれかを任意のスレッドから非同期的に生成します。たとえば、何らかのイベントまたはコールバックの結果として。非同期リクエストおよび DeferredResult を参照してください。

Callable<V>

Spring MVC 管理スレッドで上記の戻り値のいずれかを非同期に生成します。非同期リクエストおよび Callable を参照してください。

ListenableFuture<V>java.util.concurrent.CompletionStage<V>java.util.concurrent.CompletableFuture<V>

便宜上、DeferredResult の代替(たとえば、基礎となるサービスがそれらのいずれかを返す場合)。

ResponseBodyEmitterSseEmitter

HttpMessageConverter 実装でレスポンスに書き込まれるオブジェクトのストリームを非同期に発行します。ResponseEntity の本体としてもサポートされています。非同期リクエストおよび HTTP ストリーミングを参照してください。

StreamingResponseBody

レスポンス OutputStream に非同期で書き込みます。ResponseEntity の本体としてもサポートされています。非同期リクエストおよび HTTP ストリーミングを参照してください。

リアクティブ型  —  ReactiveAdapterRegistry を介した Reactor、RxJava、またはその他

List に収集された複数値ストリーム(FluxObservable など)を持つ DeferredResult の代替。

ストリーミングシナリオ(例: text/event-streamapplication/json+stream)では、SseEmitter と ResponseBodyEmitter が代わりに使用されます。ServletOutputStream ブロッキング I/O は Spring MVC 管理スレッドで実行され、各書き込みの補完に対してバックプレッシャーが適用されます。

非同期リクエストおよびリアクティブ型を参照してください。

その他の戻り値

この表の以前の値のいずれとも一致せず、String または void である戻り値は、BeanUtils#isSimpleProperty(Javadoc) によって決定される単純型ではない場合、ビュー名として扱われます(RequestToViewNameTranslator によるデフォルトのビュー名の選択が適用されます)。単純型の値は未解決のままです。

型変換

String ベースのリクエスト入力(@RequestParam@RequestHeader@PathVariable@MatrixVariable や @CookieValue など)を表す一部のアノテーション付きコントローラーメソッド引数は、引数が String 以外として宣言されている場合、型変換が必要になる場合があります。

このような場合、構成されたコンバーターに基づいて型変換が自動的に適用されます。デフォルトでは、単純型(intlongDate など)がサポートされています。WebDataBinder (DataBinder を参照)を介して、または Formatters を FormattingConversionService に登録することにより、型変換をカスタマイズできます。Spring フィールドのフォーマットを参照してください。

行列変数

RFC 3986 (英語) は、パスセグメントの名前と値のペアについて説明します。Spring MVC では、Tim Berners-Lee による「古い投稿」 (英語) に基づく「マトリックス変数」と呼ばれますが、URI パスパラメーターとも呼ばれます。

マトリックス変数は任意のパスセグメントに表示でき、各変数はセミコロンで区切られ、複数の値はコンマで区切られます(例: /cars;color=red,green;year=2012)。複数の値は、変数名を繰り返して指定することもできます(例: color=red;color=green;color=blue)。

URL にマトリックス変数が含まれると予想される場合、コントローラーメソッドのリクエストマッピングでは、URI 変数を使用してその変数の内容をマスクし、マトリックス変数の順序と存在に関係なくリクエストを正常に一致させる必要があります。次の例では、マトリックス変数を使用しています。

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

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

    // petId == 42
    // q == 11
}
Kotlin
// GET /pets/42;q=11;r=22

@GetMapping("/pets/{petId}")
fun findPet(@PathVariable petId: String, @MatrixVariable q: Int) {

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

すべてのパスセグメントにマトリックス変数が含まれている可能性があるため、マトリックス変数がどのパス変数にあると予想されるかを明確にする必要がある場合があります。次の例は、その方法を示しています。

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

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

    // q1 == 11
    // q2 == 22
}
Kotlin
// GET /owners/42;q=11/pets/21;q=22

@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(
        @MatrixVariable(name = "q", pathVar = "ownerId") q1: Int,
        @MatrixVariable(name = "q", pathVar = "petId") q2: Int) {

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

次の例に示すように、マトリックス変数をオプションとして定義し、デフォルト値を指定できます。

Java
// GET /pets/42

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

    // q == 1
}
Kotlin
// GET /pets/42

@GetMapping("/pets/{petId}")
fun findPet(@MatrixVariable(required = false, defaultValue = "1") q: Int) {

    // q == 1
}

すべての行列変数を取得するには、次の例に示すように MultiValueMap を使用できます。

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

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

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 22, "s" : 23]
}
Kotlin
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

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

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

マトリックス変数の使用を有効にする必要があることに注意してください。MVC Java 構成では、UrlPathHelper を removeSemicolonContent=false からパスマッチングで設定する必要があります。MVC XML 名前空間では、<mvc:annotation-driven enable-matrix-variables="true"/> を設定できます。

@RequestParam

@RequestParam アノテーションを使用して、サーブレットリクエストパラメーター(つまり、クエリパラメーターまたはフォームデータ)をコントローラーのメソッド引数にバインドできます。

次の例は、その方法を示しています。

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

    // ...

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

    // ...

}
1@RequestParam を使用して petId をバインドします。
Kotlin
import org.springframework.ui.set

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

    // ...

    @GetMapping
    fun setupForm(@RequestParam("petId") petId: Int, model: Model): String { (1)
        val pet = this.clinic.loadPet(petId);
        model["pet"] = pet
        return "petForm"
    }

    // ...

}
1@RequestParam を使用して petId をバインドします。

デフォルトでは、このアノテーションを使用するメソッドパラメーターは必須ですが、@RequestParam アノテーションの required フラグを false に設定するか、java.util.Optional ラッパーで引数を宣言することにより、メソッドパラメーターがオプションであることを指定できます。

ターゲットメソッドのパラメータータイプが String でない場合、タイプ変換が自動的に適用されます。型変換を参照してください。

引数の型を配列またはリストとして宣言すると、同じパラメーター名の複数のパラメーター値を解決できます。

@RequestParam アノテーションが Map<String, String> または MultiValueMap<String, String> として宣言され、パラメーター名がアノテーションで指定されていない場合、マップには、指定された各パラメーター名のリクエストパラメーター値が入力されます。

@RequestParam の使用はオプションであることに注意してください(たとえば、属性を設定するため)。デフォルトでは、単純な値型(BeanUtils#isSimpleProperty(Javadoc) によって決定される)であり、他の引数リゾルバーによって解決されない引数は、あたかも @RequestParam でアノテーションが付けられているかのように扱われます。

@RequestHeader

@RequestHeader アノテーションを使用して、リクエストヘッダーをコントローラーのメソッド引数にバインドできます。

ヘッダー付きの次のリクエストを検討してください。

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

次の例は、Accept-Encoding および Keep-Alive ヘッダーの値を取得します。

Java
@GetMapping("/demo")
public void handle(
        @RequestHeader("Accept-Encoding") String encoding, (1)
        @RequestHeader("Keep-Alive") long keepAlive) { (2)
    //...
}
1Accept-Encoding ヘッダーの値を取得します。
2Keep-Alive ヘッダーの値を取得します。
Kotlin
@GetMapping("/demo")
fun handle(
        @RequestHeader("Accept-Encoding") encoding: String, (1)
        @RequestHeader("Keep-Alive") keepAlive: Long) { (2)
    //...
}
1Accept-Encoding ヘッダーの値を取得します。
2Keep-Alive ヘッダーの値を取得します。

ターゲットメソッドのパラメータータイプが String でない場合、タイプ変換が自動的に適用されます。型変換を参照してください。

@RequestHeader アノテーションが Map<String, String>MultiValueMap<String, String>、または HttpHeaders 引数で使用される場合、マップにはすべてのヘッダー値が入力されます。

コンマ区切りの文字列を、文字列の配列またはコレクション、または型変換システムに既知の他の型に変換するための組み込みサポートが利用可能です。例: @RequestHeader("Accept") アノテーションが付けられたメソッドパラメーターは、タイプ String になりますが、String[] または List<String> にもなります。
@CookieValue

@CookieValue アノテーションを使用して、HTTP Cookie の値をコントローラーのメソッド引数にバインドできます。

次の Cookie を含むリクエストを検討してください。

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

次の例は、Cookie 値を取得する方法を示しています。

Java
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) { (1)
    //...
}
1JSESSIONID Cookie の値を取得します。
Kotlin
@GetMapping("/demo")
fun handle(@CookieValue("JSESSIONID") cookie: String) { (1)
    //...
}
1JSESSIONID Cookie の値を取得します。

ターゲットメソッドのパラメータータイプが String でない場合、タイプ変換が自動的に適用されます。型変換を参照してください。

@ModelAttribute

メソッドの引数で @ModelAttribute アノテーションを使用して、モデルの属性にアクセスしたり、存在しない場合はインスタンス化したりできます。モデル属性は、フィールド名と名前が一致する HTTP サーブレットリクエストパラメーターの値で上書きされます。これはデータバインディングと呼ばれ、個々のクエリパラメーターとフォームフィールドの解析と変換に対処する必要がなくなります。次の例は、その方法を示しています。

Java
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { } (1)
1Pet のインスタンスをバインドします。
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute pet: Pet): String { } (1)
1Pet のインスタンスをバインドします。

上記の Pet インスタンスは次のように解決されます。

  • モデルを使用して既に追加されている場合はモデルから。

  • @SessionAttributes を使用して HTTP セッションから。

  • Converter を介して渡された URI パス変数から(次の例を参照)。

  • デフォルトのコンストラクターの呼び出しから。

  • サーブレットリクエストパラメーターに一致する引数を持つ「プライマリコンストラクター」の呼び出しから。引数名は、JavaBeans @ConstructorProperties またはバイトコード内の実行時保持パラメーター名によって決定されます。

モデルを使用してモデルに属性を設定するのが一般的ですが、他の代替方法は、URI パス変数規則と組み合わせて Converter<String, T> に依存することです。次の例では、モデル属性名 account は URI パス変数 account に一致し、String アカウント番号を登録済みの Converter<String, Account> に渡すことで Account がロードされます。

Java
@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
    // ...
}
Kotlin
@PutMapping("/accounts/{account}")
fun save(@ModelAttribute("account") account: Account): String {
    // ...
}

モデル属性インスタンスが取得された後、データバインディングが適用されます。WebDataBinder クラスは、サーブレットリクエストパラメーター名(クエリパラメーターとフォームフィールド)をターゲット Object のフィールド名に一致させます。必要に応じて、型変換が適用された後、一致するフィールドに値が入力されます。データバインディング(および検証)の詳細については、検証を参照してください。データバインディングのカスタマイズの詳細については、DataBinder を参照してください。

データバインディングはエラーになる可能性があります。デフォルトでは、BindException が発生します。ただし、コントローラーメソッドでこのようなエラーをチェックするには、次の例に示すように、@ModelAttribute のすぐ隣に BindingResult 引数を追加できます。

Java
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { (1)
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}
1@ModelAttribute の隣に BindingResult を追加します。
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute("pet") pet: Pet, result: BindingResult): String { (1)
    if (result.hasErrors()) {
        return "petForm"
    }
    // ...
}
1@ModelAttribute の隣に BindingResult を追加します。

場合によっては、データバインディングなしでモデル属性にアクセスしたい場合があります。このような場合、次の例に示すように、Model をコントローラーに挿入して直接アクセスするか、@ModelAttribute(binding=false) を設定できます。

Java
@ModelAttribute
public AccountForm setUpForm() {
    return new AccountForm();
}

@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
    return accountRepository.findOne(accountId);
}

@PostMapping("update")
public String update(@Valid AccountForm form, BindingResult result,
        @ModelAttribute(binding=false) Account account) { (1)
    // ...
}
1@ModelAttribute(binding=false) の設定。
Kotlin
@ModelAttribute
fun setUpForm(): AccountForm {
    return AccountForm()
}

@ModelAttribute
fun findAccount(@PathVariable accountId: String): Account {
    return accountRepository.findOne(accountId)
}

@PostMapping("update")
fun update(@Valid form: AccountForm, result: BindingResult,
           @ModelAttribute(binding = false) account: Account): String { (1)
    // ...
}
1@ModelAttribute(binding=false) の設定。

javax.validation.Valid アノテーションまたは Spring の @Validated アノテーション(Bean バリデーションおよび Spring 検証)を追加することにより、データバインディング後に検証を自動的に適用できます。次の例は、その方法を示しています。

Java
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { (1)
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}
1Pet インスタンスを検証します。
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@Valid @ModelAttribute("pet") pet: Pet, result: BindingResult): String { (1)
    if (result.hasErrors()) {
        return "petForm"
    }
    // ...
}

@ModelAttribute の使用はオプションです(たとえば、属性を設定するため)。デフォルトでは、(BeanUtils#isSimpleProperty(Javadoc) によって決定される)単純な値型ではなく、他の引数リゾルバーによって解決されない引数は、@ModelAttribute でアノテーションが付けられているかのように扱われます。

@SessionAttributes

@SessionAttributes は、リクエスト間の HTTP サーブレットセッションにモデル属性を格納するために使用されます。これは、特定のコントローラーが使用するセッション属性を宣言する型レベルのアノテーションです。これは通常、後続のアクセスリクエストのためにセッションに透過的に保存されるモデル属性の名前またはモデル属性のタイプをリストします。

次の例では、@SessionAttributes アノテーションを使用しています。

Java
@Controller
@SessionAttributes("pet") (1)
public class EditPetForm {
    // ...
}
1@SessionAttributes アノテーションを使用します。
Kotlin
@Controller
@SessionAttributes("pet") (1)
public class EditPetForm {
    // ...
}
1@SessionAttributes アノテーションを使用します。

最初のリクエストで、名前が pet のモデル属性がモデルに追加されると、自動的に HTTP サーブレットセッションにプロモートされ、保存されます。次の例に示すように、別のコントローラーメソッドが SessionStatus メソッド引数を使用してストレージをクリアするまで、そこに残ります。

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

    // ...

    @PostMapping("/pets/{id}")
    public String handle(Pet pet, BindingResult errors, SessionStatus status) {
        if (errors.hasErrors) {
            // ...
        }
            status.setComplete(); (2)
            // ...
        }
    }
}
1Servlet セッションに Pet 値を保存します。
2 サーブレットセッションから Pet 値をクリアします。
Kotlin
@Controller
@SessionAttributes("pet") (1)
class EditPetForm {

    // ...

    @PostMapping("/pets/{id}")
    fun handle(pet: Pet, errors: BindingResult, status: SessionStatus): String {
        if (errors.hasErrors()) {
            // ...
        }
        status.setComplete() (2)
        // ...
    }
}
1Servlet セッションに Pet 値を保存します。
2 サーブレットセッションから Pet 値をクリアします。
@SessionAttribute

グローバルに(つまり、フィルターによってコントローラーの外部で)管理される既存のセッション属性にアクセスする必要があり、存在する場合と存在しない場合、メソッドパラメーターで @SessionAttribute アノテーションを使用できます。次の例を示します。

Java
@RequestMapping("/")
public String handle(@SessionAttribute User user) { (1)
    // ...
}
1@SessionAttribute アノテーションを使用します。
Kotlin
@RequestMapping("/")
fun handle(@SessionAttribute user: User): String { (1)
    // ...
}

セッション属性の追加または削除が必要なユースケースでは、org.springframework.web.context.request.WebRequest または javax.servlet.http.HttpSession をコントローラーメソッドに挿入することを検討してください。

コントローラーワークフローの一部としてセッションでモデル属性を一時的に保存するには、@SessionAttributes に従って @SessionAttributes の使用を検討してください。

@RequestAttribute

@SessionAttribute と同様に、@RequestAttribute アノテーションを使用して、以前に作成された既存のリクエスト属性にアクセスできます(たとえば、サーブレット Filter または HandlerInterceptor によって)。

Java
@GetMapping("/")
public String handle(@RequestAttribute Client client) { (1)
    // ...
}
1@RequestAttribute アノテーションを使用します。
Kotlin
@GetMapping("/")
fun handle(@RequestAttribute client: Client): String { (1)
    // ...
}
1@RequestAttribute アノテーションを使用します。
リダイレクト属性

デフォルトでは、すべてのモデル属性はリダイレクト URL の URI テンプレート変数として公開されていると見なされます。残りの属性のうち、プリミティブ型またはプリミティブ型のコレクションまたは配列である属性は、クエリパラメーターとして自動的に追加されます。

モデルインスタンスがリダイレクト用に特別に準備されている場合、プリミティブ型の属性をクエリパラメーターとして追加することが望ましい結果になる場合があります。ただし、アノテーション付きコントローラーでは、モデルにレンダリング目的で追加された追加属性(ドロップダウンフィールド値など)を含めることができます。そのような属性が URL に現れる可能性を避けるために、@RequestMapping メソッドは、タイプ RedirectAttributes の引数を宣言し、それを使用して RedirectView で使用可能にする正確な属性を指定できます。メソッドがリダイレクトする場合、RedirectAttributes のコンテンツが使用されます。それ以外の場合、モデルのコンテンツが使用されます。

RequestMappingHandlerAdapter は ignoreDefaultModelOnRedirect と呼ばれるフラグを提供します。これを使用して、コントローラーメソッドがリダイレクトする場合、デフォルトの Model のコンテンツを使用しないことを示すことができます。代わりに、コントローラーメソッドは RedirectAttributes 型の属性を宣言する必要があります。そうしない場合は、RedirectView に属性を渡さないでください。MVC 名前空間と MVC Java 構成の両方は、後方互換性を維持するために、このフラグを false に設定したままにします。ただし、新しいアプリケーションの場合は、true に設定することをお勧めします。

現在のリクエストの URI テンプレート変数は、リダイレクト URL を展開するときに自動的に利用可能になり、Model または RedirectAttributes を介して明示的に追加する必要がないことに注意してください。次の例は、リダイレクトを定義する方法を示しています。

Java
@PostMapping("/files/{path}")
public String upload(...) {
    // ...
    return "redirect:files/{path}";
}
Kotlin
@PostMapping("/files/{path}")
fun upload(...): String {
    // ...
    return "redirect:files/{path}"
}

データをリダイレクトターゲットに渡す別の方法は、フラッシュ属性を使用することです。他のリダイレクト属性とは異なり、フラッシュ属性は HTTP セッションに保存されます(したがって、URL には表示されません)。詳細については、フラッシュ属性を参照してください。

フラッシュ属性

Flash 属性は、あるリクエストが別のリクエストで使用するための属性を保存する方法を提供します。これは、リダイレクト時に最も一般的に必要になります。たとえば、Post-Redirect-Get パターン。Flash 属性は、リダイレクトの前に一時的に保存され(通常はセッションで)、リダイレクト後にリクエストで使用可能になり、すぐに削除されます。

Spring MVC には、フラッシュ属性をサポートする 2 つの主要な抽象化があります。FlashMap はフラッシュ属性を保持するために使用され、FlashMapManager は FlashMap インスタンスを保存、取得、および管理するために使用されます。

Flash 属性のサポートは常に「オン」であり、明示的に有効にする必要はありません。ただし、使用しない場合、HTTP セッションが作成されることはありません。各リクエストには、前のリクエスト(ある場合)から渡された属性を持つ「入力」 FlashMap と、後続のリクエストのために保存する属性を持つ「出力」 FlashMap があります。両方の FlashMap インスタンスは、RequestContextUtils の静的メソッドを介して Spring MVC のどこからでもアクセスできます。

アノテーション付きコントローラーは通常、FlashMap を直接操作する必要はありません。代わりに、@RequestMapping メソッドはタイプ RedirectAttributes の引数を受け入れ、それを使用してリダイレクトシナリオのフラッシュ属性を追加できます。RedirectAttributes を介して追加された Flash 属性は、自動的に「出力」FlashMap に伝播されます。同様に、リダイレクト後、「入力」 FlashMap からの属性は、ターゲット URL を提供するコントローラーの Model に自動的に追加されます。

リクエストをフラッシュ属性に一致させる

フラッシュ属性の概念は他の多くの Web フレームワークに存在し、並行性の課題に時々さらされることが証明されています。これは、定義により、フラッシュ属性は次のリクエストまで保存されるためです。ただし、「次の」リクエストは、意図した受信者ではなく、別の非同期リクエスト(ポーリングまたはリソースリクエストなど)である可能性があります。この場合、フラッシュ属性はすぐに削除されます。

このような課題の可能性を減らすために、RedirectView は、ターゲットリダイレクト URL のパスおよびクエリパラメーターを使用して FlashMap インスタンスを自動的に「スタンプ」します。次に、デフォルトの FlashMapManager は、「入力」 FlashMap をルックアップするときに、その情報を受信リクエストと照合します。

これにより、同時実行性の課題の可能性が完全に排除されるわけではありませんが、リダイレクト URL ですでに利用可能な情報によって大幅に削減されます。主にリダイレクトシナリオにフラッシュ属性を使用することをお勧めします。

マルチパート

MultipartResolver が有効になった後、multipart/form-data を使用した POST リクエストのコンテンツが解析され、通常のリクエストパラメーターとしてアクセス可能になります。次の例では、1 つの通常のフォームフィールドと 1 つのアップロードされたファイルにアクセスします。

Java
@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") MultipartFile file) {

        if (!file.isEmpty()) {
            byte[] bytes = file.getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }
        return "redirect:uploadFailure";
    }
}
Kotlin
@Controller
class FileUploadController {

    @PostMapping("/form")
    fun handleFormUpload(@RequestParam("name") name: String,
                        @RequestParam("file") file: MultipartFile): String {

        if (!file.isEmpty) {
            val bytes = file.bytes
            // store the bytes somewhere
            return "redirect:uploadSuccess"
        }
        return "redirect:uploadFailure"
    }
}

引数タイプを List<MultipartFile> として宣言すると、同じパラメーター名の複数のファイルを解決できます。

@RequestParam アノテーションが Map<String, MultipartFile> または MultiValueMap<String, MultipartFile> として宣言され、アノテーションにパラメーター名が指定されていない場合、指定された各パラメーター名のマルチパートファイルがマップに入力されます。

Servlet 3.0 マルチパート解析では、Spring の MultipartFile の代わりに javax.servlet.http.Part をメソッドの引数またはコレクション値の型として宣言することもできます。

コマンドオブジェクトへのデータバインディングの一部としてマルチパートコンテンツを使用することもできます。例:前の例のフォームフィールドとファイルは、次の例に示すように、フォームオブジェクトのフィールドになります。

Java
class MyForm {

    private String name;

    private MultipartFile file;

    // ...
}

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(MyForm form, BindingResult errors) {
        if (!form.getFile().isEmpty()) {
            byte[] bytes = form.getFile().getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }
        return "redirect:uploadFailure";
    }
}
Kotlin
class MyForm(val name: String, val file: MultipartFile, ...)

@Controller
class FileUploadController {

    @PostMapping("/form")
    fun handleFormUpload(form: MyForm, errors: BindingResult): String {
        if (!form.file.isEmpty) {
            val bytes = form.file.bytes
            // store the bytes somewhere
            return "redirect:uploadSuccess"
        }
        return "redirect:uploadFailure"
    }
}

RESTful サービスシナリオでは、マルチパートリクエストを非ブラウザクライアントから送信することもできます。次の例は、JSON を含むファイルを示しています。

POST /someUrl
Content-Type: multipart/mixed

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

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

@RequestParam を使用して「メタデータ」部分に String としてアクセスできますが、JSON からデシリアライズすることをお勧めします(@RequestBody と同様)。HttpMessageConverter で変換した後、@RequestPart アノテーションを使用してマルチパートにアクセスします。

Java
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
        @RequestPart("file-data") MultipartFile file) {
    // ...
}
Kotlin
@PostMapping("/")
fun handle(@RequestPart("meta-data") metadata: MetaData,
        @RequestPart("file-data") file: MultipartFile): String {
    // ...
}

@RequestPart を javax.validation.Valid と組み合わせて使用したり、Spring の @Validated アノテーションを使用したりできます。どちらも標準 Bean 検証が適用されます。デフォルトでは、検証エラーにより MethodArgumentNotValidException が発生し、これが 400(BAD_REQUEST)レスポンスに変換されます。または、次の例に示すように、Errors または BindingResult 引数を使用して、コントローラー内で検証エラーをローカルで処理できます。

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

@RequestBody アノテーションを使用して、リクエスト本体を読み取り、HttpMessageConverter を介して Object にデシリアライズすることができます。次の例では、@RequestBody 引数を使用しています。

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

MVC 構成メッセージコンバーターオプションを使用して、メッセージ変換を構成またはカスタマイズできます。

@RequestBody を javax.validation.Valid または Spring の @Validated アノテーションと組み合わせて使用できます。どちらも標準 Bean 検証が適用されます。デフォルトでは、検証エラーにより MethodArgumentNotValidException が発生し、これが 400(BAD_REQUEST)レスポンスに変換されます。または、次の例に示すように、Errors または BindingResult 引数を使用して、コントローラー内で検証エラーをローカルで処理できます。

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

HttpEntity は @RequestBody を使用する場合とほぼ同じですが、リクエストヘッダーと本文を公開するコンテナーオブジェクトに基づいています。次のリストに例を示します。

Java
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
    // ...
}
Kotlin
@PostMapping("/accounts")
fun handle(entity: HttpEntity<Account>) {
    // ...
}
@ResponseBody

メソッドで @ResponseBody アノテーションを使用して、HttpMessageConverter を介して戻り値をレスポンス本体に直列化できます。次のリストに例を示します。

Java
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
    // ...
}
Kotlin
@GetMapping("/accounts/{id}")
@ResponseBody
fun handle(): Account {
    // ...
}

@ResponseBody はクラスレベルでもサポートされます。この場合、すべてのコントローラーメソッドによって継承されます。これは @RestController の効果であり、これは @Controller および @ResponseBody でマークされたメタアノテーションにすぎません。

@ResponseBody はリアクティブタイプで使用できます。詳細については、非同期リクエストおよびリアクティブ型を参照してください。

MVC 構成メッセージコンバーターオプションを使用して、メッセージ変換を構成またはカスタマイズできます。

@ResponseBody メソッドを JSON 直列化ビューと組み合わせることができます。詳細については、Jackson JSON を参照してください。

ResponseEntity

ResponseEntity は @ResponseBody に似ていますが、ステータスとヘッダーがあります。例:

Java
@GetMapping("/something")
public ResponseEntity<String> handle() {
    String body = ... ;
    String etag = ... ;
    return ResponseEntity.ok().eTag(etag).build(body);
}
Kotlin
@GetMapping("/something")
fun handle(): ResponseEntity<String> {
    val body = ...
    val etag = ...
    return ResponseEntity.ok().eTag(etag).build(body)
}

Spring MVC は、単一値のリアクティブ型を使用して ResponseEntity を非同期的に生成すること、および / またはボディの単一および複数値のリアクティブ型を使用することをサポートします。

Jackson JSON

Spring は、Jackson JSON ライブラリのサポートを提供します。

JSON ビュー

Spring MVC は Jackson の直列化ビュー (英語) の組み込みサポートを提供し、Object のすべてのフィールドのサブセットのみをレンダリングできます。@ResponseBody または ResponseEntity コントローラーメソッドで使用するには、次の例に示すように、Jackson の @JsonView アノテーションを使用して直列化ビュークラスをアクティブにします。

Java
@RestController
public class UserController {

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

public class User {

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

    private String username;
    private String password;

    public User() {
    }

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

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

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

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView::class)
    fun getUser() = User("eric", "7!jd#h23")
}

class User(
        @JsonView(WithoutPasswordView::class) val username: String,
        @JsonView(WithPasswordView::class) val password: String) {

    interface WithoutPasswordView
    interface WithPasswordView : WithoutPasswordView
}
@JsonView はビュークラスの配列を許可しますが、コントローラーメソッドごとに 1 つしか指定できません。複数のビューをアクティブにする必要がある場合は、複合インターフェースを使用できます。

ビューリゾルバーに依存するコントローラーの場合、次の例に示すように、モデルに直列化ビュークラスを追加できます。

Java
@Controller
public class UserController extends AbstractController {

    @GetMapping("/user")
    public String getUser(Model model) {
        model.addAttribute("user", new User("eric", "7!jd#h23"));
        model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
        return "userView";
    }
}
Kotlin
import org.springframework.ui.set

@Controller
class UserController : AbstractController() {

    @GetMapping("/user")
    fun getUser(model: Model): String {
        model["user"] = User("eric", "7!jd#h23")
        model[JsonView::class.qualifiedName] = User.WithoutPasswordView::class.java
        return "userView"
    }
}

1.3.4. モデル

@ModelAttribute アノテーションを使用できます。

  • モデルから Object を作成またはアクセスし、それを WebDataBinder を介してリクエストにバインドする @RequestMapping メソッドのメソッド引数

  • @RequestMapping メソッド呼び出しの前にモデルを初期化するのに役立つ @Controller または @ControllerAdvice クラスのメソッドレベルのアノテーションとして。

  • 戻り値をマークする @RequestMapping メソッドでは、モデル属性です。

このセクションでは、@ModelAttribute メソッド(前述のリストの 2 番目の項目)について説明します。コントローラーは、@ModelAttribute メソッドをいくつでも持つことができます。このようなメソッドはすべて、同じコントローラー内の @RequestMapping メソッドの前に呼び出されます。@ModelAttribute メソッドは、@ControllerAdvice を介してコントローラー間で共有することもできます。詳細については、コントローラーのアドバイスのセクションを参照してください。

@ModelAttribute メソッドには、柔軟なメソッドシグネチャーがあります。これらは、@ModelAttribute 自体またはリクエストボディに関連するものを除き、@RequestMapping メソッドと同じ引数の多くをサポートします。

次の例は、@ModelAttribute メソッドを示しています。

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

次の例では、属性を 1 つだけ追加します。

Java
@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountRepository.findAccount(number);
}
Kotlin
@ModelAttribute
fun addAccount(@RequestParam number: String): Account {
    return accountRepository.findAccount(number)
}
名前が明示的に指定されていない場合、Conventions(Javadoc) の javadoc に従って、Object タイプに基づいてデフォルト名が選択されます。オーバーロードされた addAttribute メソッドを使用するか、@ModelAttribute の name 属性(戻り値用)を使用して、明示的な名前をいつでも割り当てることができます。

@ModelAttribute を @RequestMapping メソッドのメソッドレベルのアノテーションとして使用することもできます。その場合、@RequestMapping メソッドの戻り値はモデル属性として解釈されます。戻り値がビュー名として解釈される String でない限り、これは HTML コントローラーのデフォルトの動作であるため、通常は必要ありません。次の例に示すように、@ModelAttribute はモデル属性名をカスタマイズすることもできます。

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

1.3.5. DataBinder

@Controller または @ControllerAdvice クラスには、WebDataBinder のインスタンスを初期化する @InitBinder メソッドを含めることができます。これらのメソッドは、次のことができます。

  • リクエストパラメーター(つまり、フォームまたはクエリデータ)をモデルオブジェクトにバインドします。

  • 文字列ベースのリクエスト値(リクエストパラメーター、パス変数、ヘッダー、Cookie など)をコントローラーメソッド引数のターゲットタイプに変換します。

  • HTML フォームをレンダリングするときに、モデルオブジェクト値を String 値としてフォーマットします。

@InitBinder メソッドは、コントローラー固有の java.bean.PropertyEditor または Spring Converter および Formatter コンポーネントを登録できます。さらに、MVC 設定を使用して、Converter および Formatter タイプをグローバルに共有される FormattingConversionService に登録できます。

@InitBinder メソッドは、@ModelAttribute (コマンドオブジェクト)引数を除き、@RequestMapping メソッドと同じ引数の多くをサポートします。通常、それらは WebDataBinder 引数(登録用)および void 戻り値で宣言されます。次のリストに例を示します。

Java
@Controller
public class FormController {

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

    // ...
}
1@InitBinder メソッドの定義。
Kotlin
@Controller
class FormController {

    @InitBinder (1)
    fun initBinder(binder: WebDataBinder) {
        val dateFormat = SimpleDateFormat("yyyy-MM-dd")
        dateFormat.isLenient = false
        binder.registerCustomEditor(Date::class.java, CustomDateEditor(dateFormat, false))
    }

    // ...
}
1@InitBinder メソッドの定義。

あるいは、共有 FormattingConversionService を介して Formatter ベースのセットアップを使用する場合、次の例に示すように、同じアプローチを再利用して、コントローラー固有の Formatter 実装を登録できます。

Java
@Controller
public class FormController {

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

    // ...
}
1 カスタムフォーマッタで @InitBinder メソッドを定義します。
Kotlin
@Controller
class FormController {

    @InitBinder (1)
    protected fun initBinder(binder: WebDataBinder) {
        binder.addCustomFormatter(DateFormatter("yyyy-MM-dd"))
    }

    // ...
}
1 カスタムフォーマッタで @InitBinder メソッドを定義します。

1.3.6. 例外

次の例に示すように、@Controller クラスおよび @ControllerAdvice クラスには、コントローラーメソッドからの例外を処理する @ExceptionHandler メソッドを含めることができます。

Java
@Controller
public class SimpleController {

    // ...

    @ExceptionHandler
    public ResponseEntity<String> handle(IOException ex) {
        // ...
    }
}
Kotlin
@Controller
class SimpleController {

    // ...

    @ExceptionHandler
    fun handle(ex: IOException): ResponseEntity<String> {
        // ...
    }
}

例外は、伝播されているトップレベルの例外(つまり、直接 IOException がスローされている)、またはトップレベルのラッパー例外内の直接の原因(たとえば、IllegalStateException 内にラップされた IOException)に一致します。

例外の種類を一致させるには、前の例で示したように、ターゲットの例外をメソッド引数として宣言することが望ましいです。複数の例外メソッドが一致する場合、通常は原因の例外の一致よりもルートの例外の一致が優先されます。より具体的には、ExceptionDepthComparator は、スローされた例外タイプからの深さに基づいて例外をソートするために使用されます。

または、次の例に示すように、アノテーション宣言により、一致するように例外タイプを絞り込むことができます。

Java
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
    // ...
}
Kotlin
@ExceptionHandler(FileSystemException::class, RemoteException::class)
fun handle(ex: IOException): ResponseEntity<String> {
    // ...
}

次の例に示すように、非常に汎用的な引数シグネチャーを持つ特定の例外タイプのリストを使用することもできます。

Java
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(Exception ex) {
    // ...
}
Kotlin
@ExceptionHandler(FileSystemException::class, RemoteException::class)
fun handle(ex: Exception): ResponseEntity<String> {
    // ...
}

ルートと原因の例外マッチングの違いは驚くべきことです。

前に示した IOException バリアントでは、メソッドは通常、実際の FileSystemException または RemoteException インスタンスを引数として呼び出されます。どちらも IOException から拡張されているためです。ただし、そのような一致する例外がそれ自体 IOException であるラッパー例外内で伝搬される場合、渡される例外インスタンスはそのラッパー例外です。

handle(Exception) バリアントの動作はさらに単純です。これは、折り返しシナリオでは常にラッパー例外で呼び出され、実際には一致する例外が ex.getCause() を介して検出されます。渡された例外は、実際の FileSystemException または RemoteException インスタンスがトップレベルの例外としてスローされた場合のみです。

一般的に、引数シグネチャーをできるだけ具体的に指定することをお勧めします。これにより、ルートと原因の例外タイプ間の不一致の可能性を減らします。マルチマッチングメソッドを個々の @ExceptionHandler メソッドに分割し、それぞれのシグネチャーで単一の特定の例外タイプに一致させることを検討してください。

マルチ @ControllerAdvice 配置では、対応する順序で優先順位付けされた @ControllerAdvice でプライマリルート例外マッピングを宣言することをお勧めします。ルート例外の一致は原因よりも優先されますが、これは特定のコントローラーまたは @ControllerAdvice クラスのメソッド間で定義されます。つまり、優先度の高い @ControllerAdvice Bean の原因一致は、優先度の低い @ControllerAdvice Bean の一致(ルートなど)よりも優先されます。

最後になりましたが、@ExceptionHandler メソッドの実装では、元の形式で例外を再スローすることにより、特定の例外インスタンスの処理を取り消すことを選択できます。これは、ルートレベルの一致、または静的に決定できない特定のコンテキスト内の一致のみに関心があるシナリオで役立ちます。指定された @ExceptionHandler メソッドがそもそも一致していないかのように、再スローされた例外は残りの解像度チェーンを介して伝搬されます。

Spring MVC での @ExceptionHandler メソッドのサポートは、DispatcherServlet レベルの HandlerExceptionResolver メカニズムに基づいています。

メソッド引数

@ExceptionHandler メソッドは、次の引数をサポートしています。

メソッド引数 説明

例外型

発生した例外へのアクセス用。

HandlerMethod

例外を発生させたコントローラーメソッドへのアクセス用。

WebRequestNativeWebRequest

サーブレット API を直接使用せずに、リクエストパラメーターとリクエストおよびセッション属性への汎用アクセス。

javax.servlet.ServletRequestjavax.servlet.ServletResponse

特定のリクエストまたはレスポンスのタイプを選択します(例: ServletRequest または HttpServletRequest または Spring の MultipartRequest または MultipartHttpServletRequest)。

javax.servlet.http.HttpSession

セッションの存在を強制します。結果として、そのような引数は決して null になりません。
セッションアクセスはスレッドセーフではないことに注意してください。複数のリクエストがセッションに同時にアクセスできる場合は、RequestMappingHandlerAdapter インスタンスの synchronizeOnSession フラグを true に設定することを検討してください。

java.security.Principal

現在認証されているユーザー  —  既知の場合、特定の Principal 実装クラスである可能性があります。

HttpMethod

リクエストの HTTP メソッド。

java.util.Locale

現在のリクエストロケール。利用可能な最も具体的な LocaleResolver によって決まります。実際には、構成された LocaleResolver または LocaleContextResolver です。

java.util.TimeZonejava.time.ZoneId

LocaleContextResolver によって決定される、現在のリクエストに関連付けられたタイムゾーン。

java.io.OutputStreamjava.io.Writer

サーブレット API によって公開されている未加工のレスポンス本文へのアクセス用。

java.util.Maporg.springframework.ui.Modelorg.springframework.ui.ModelMap

エラーレスポンスのモデルへのアクセス用。常に空です。

RedirectAttributes

リダイレクト(クエリ文字列に追加される)の場合に使用する属性と、リダイレクト後のリクエストまで一時的に保存されるフラッシュ属性を指定します。リダイレクト属性およびフラッシュ属性を参照してください。

@SessionAttribute

クラスレベルの @SessionAttributes 宣言の結果としてセッションに格納されたモデル属性とは対照的に、任意のセッション属性へのアクセス。詳細については、@SessionAttribute を参照してください。

@RequestAttribute

リクエスト属性へのアクセス用。詳細については、@RequestAttribute を参照してください。

戻り値

@ExceptionHandler メソッドは、次の戻り値をサポートしています。

戻り値 説明

@ResponseBody

戻り値は HttpMessageConverter インスタンスを介して変換され、レスポンスに書き込まれます。@ResponseBody を参照してください。

HttpEntity<B>ResponseEntity<B>

戻り値は、完全なレスポンス(HTTP ヘッダーと本文を含む)が HttpMessageConverter インスタンスを介して変換され、レスポンスに書き込まれることを指定します。ResponseEntity を参照してください。

String

ViewResolver 実装で解決され、暗黙モデルと一緒に使用されるビュー名 — コマンドオブジェクトと @ModelAttribute メソッドによって決定されます。ハンドラーメソッドは、Model 引数(前述)を宣言することにより、プログラムでモデルを強化することもできます。

View

暗黙的なモデルと一緒にレンダリングするために使用する View インスタンス — コマンドオブジェクトと @ModelAttribute メソッドによって決定されます。ハンドラーメソッドは、Model 引数(前述)を宣言することにより、プログラムでモデルを強化することもできます。

java.util.Maporg.springframework.ui.Model

RequestToViewNameTranslator を介して暗黙的に決定されたビュー名を持つ暗黙モデルに追加される属性。

@ModelAttribute

RequestToViewNameTranslator を介して暗黙的に決定されたビュー名でモデルに追加される属性。

@ModelAttribute はオプションです。この表の最後にある「その他の戻り値」を参照してください。

ModelAndView オブジェクト

使用するビューとモデルの属性、およびオプションでレスポンスステータス。

void

void 戻り型(または null 戻り値)を持つメソッドは、ServletResponseOutputStream 引数、または @ResponseStatus アノテーションも持っている場合、レスポンスを完全に処理したと見なされます。コントローラーが ETag または lastModified の正のタイムスタンプチェックを行った場合も同様です(詳細についてはコントローラーを参照してください)。

上記のいずれにも当てはまらない場合、void 戻り型は、REST コントローラーの「レスポンス本文なし」または HTML コントローラーのデフォルトのビュー名選択を示すこともできます。

その他の戻り値

戻り値が上記のいずれにも一致せず、(BeanUtils#isSimpleProperty(Javadoc) によって決定される)単純型ではない場合、デフォルトでは、モデルに追加されるモデル属性として扱われます。単純型の場合、未解決のままです。

REST API の例外

REST サービスの一般的な要件は、レスポンスの本文にエラーの詳細を含めることです。レスポンス本文のエラー詳細の表現はアプリケーション固有であるため、Spring Framework はこれを自動的に行いません。ただし、@RestController は、@ExceptionHandler メソッドと ResponseEntity 戻り値を使用して、レスポンスのステータスと本文を設定できます。そのようなメソッドを @ControllerAdvice クラスで宣言して、グローバルに適用することもできます。

レスポンス本文にエラーの詳細を含むグローバル例外処理を実装するアプリケーションは、ResponseEntityExceptionHandler(Javadoc) の拡張を検討する必要があります。ResponseEntityExceptionHandler(Javadoc) は、Spring MVC が発生する例外の処理を提供し、レスポンス本文をカスタマイズするフックを提供します。これを利用するには、ResponseEntityExceptionHandler のサブクラスを作成し、@ControllerAdvice でアノテーションを付け、必要なメソッドをオーバーライドし、Spring Bean として宣言します。

1.3.7. コントローラーのアドバイス

通常、@ExceptionHandler@InitBinder および @ModelAttribute メソッドは、それらが宣言されている @Controller クラス(またはクラス階層)内で適用されます。そのようなメソッドを(コントローラー間で)よりグローバルに適用したい場合は、@ControllerAdvice または @RestControllerAdvice アノテーションが付けられたクラスで宣言できます。

@ControllerAdvice には @Component のアノテーションが付いています。これは、そのようなクラスをコンポーネントスキャンによって Spring Bean として登録できることを意味します。@RestControllerAdvice は、@ControllerAdvice と @ResponseBody の両方でアノテーションが付けられた合成アノテーションです。これは、本質的に、@ExceptionHandler メソッドがメッセージ変換(ビューリゾルバーまたはテンプレートのレンダリングに対して)によってレスポンス本文にレンダリングされることを意味します。

起動時に、@RequestMapping および @ExceptionHandler メソッドのインフラストラクチャクラスは、@ControllerAdvice アノテーションが付けられた Spring Bean を検出し、実行時にそれらのメソッドを適用します。グローバル @ExceptionHandler メソッド(@ControllerAdvice から)はローカルメソッド(@Controller から)の後に適用さます。対照的に、グローバル @ModelAttribute および @InitBinder メソッドは、ローカルメソッドの前に適用されます。

デフォルトでは、@ControllerAdvice メソッドはすべてのリクエスト(つまり、すべてのコントローラー)に適用されますが、次の例に示すように、アノテーションの属性を使用してコントローラーのサブセットに絞り込むことができます。

Java
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
Kotlin
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = [RestController::class])
class ExampleAdvice1

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
class ExampleAdvice2

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = [ControllerInterface::class, AbstractController::class])
class ExampleAdvice3

前の例のセレクターは実行時に評価され、広範囲に使用するとパフォーマンスに悪影響を与える可能性があります。詳細については、@ControllerAdvice(Javadoc) javadoc を参照してください。

1.4. 関数エンドポイント

Spring Web MVC には、WebMvc.fn が含まれています。WebMvc.fn は、機能を使用してリクエストのルーティングと処理を行い、契約は不変性のために設計されています。これは、アノテーションベースのプログラミングモデルに代わるものですが、それ以外は同じ DispatcherServlet で実行されます。

1.4.1. 概要

WebMvc.fn では、HTTP リクエストは HandlerFunction で処理されます。これは、ServerRequest を取得して ServerResponse を返す関数です。レスポンスオブジェクトとしての両方のリクエストには、HTTP リクエストおよびレスポンスへの JDK 8 フレンドリアクセスを提供する不変の契約があります。HandlerFunction は、アノテーションベースのプログラミングモデルの @RequestMapping メソッドの本体に相当します。

受信リクエストは、RouterFunction を使用してハンドラー関数にルーティングされます。ServerRequest を受け取り、オプションの HandlerFunction (つまり Optional<HandlerFunction>)を返す関数です。ルーター関数が一致すると、ハンドラー関数が返されます。それ以外の場合、空のオプション。RouterFunction は @RequestMapping アノテーションと同等ですが、ルーター関数がデータだけでなく動作も提供するという大きな違いがあります。

RouterFunctions.route() は、次の例に示すように、ルーターの作成を容易にするルータービルダーを提供します。

Java
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
import static org.springframework.web.servlet.function.RouterFunctions.route;

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

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


public class PersonHandler {

    // ...

    public ServerResponse listPeople(ServerRequest request) {
        // ...
    }

    public ServerResponse createPerson(ServerRequest request) {
        // ...
    }

    public ServerResponse getPerson(ServerRequest request) {
        // ...
    }
}
Kotlin
import org.springframework.web.servlet.function.router

val repository: PersonRepository = ...
val handler = PersonHandler(repository)

val route = router { (1)
    accept(APPLICATION_JSON).nest {
        GET("/person/{id}", handler::getPerson)
        GET("/person", handler::listPeople)
    }
    POST("/person", handler::createPerson)
}


class PersonHandler(private val repository: PersonRepository) {

    // ...

    fun listPeople(request: ServerRequest): ServerResponse {
        // ...
    }

    fun createPerson(request: ServerRequest): ServerResponse {
        // ...
    }

    fun getPerson(request: ServerRequest): ServerResponse {
        // ...
    }
}
1 ルーター DSL を使用してルーターを作成します。

RouterFunction を Bean として登録する場合、たとえば @Configuration クラスで公開すると、サーバーの実行に従って、サーブレットによって自動検出されます。

1.4.2. HandlerFunction

ServerRequest および ServerResponse は、ヘッダー、ボディ、メソッド、ステータスコードなど、HTTP リクエストおよびレスポンスへの JDK 8 フレンドリアクセスを提供する不変のインターフェースです。

ServerRequest

ServerRequest は HTTP メソッド、URI、ヘッダー、およびクエリパラメーターへのアクセスを提供し、ボディへのアクセスは body メソッドを介して提供されます。

次の例では、リクエストの本文を String に抽出します。

Java
String string = request.body(String.class);
Kotlin
val string = request.body<String>()

次の例では、ボディを List<Person> に抽出します。Person オブジェクトは、JSON や XML などの直列化された形式からデコードされます。

Java
List<Person> people = request.body(new ParameterizedTypeReference<List<Person>>() {});
Kotlin
val people = request.body<Person>()

次の例は、パラメーターにアクセスする方法を示しています。

Java
MultiValueMap<String, String> params = request.params();
Kotlin
val map = request.params()
ServerResponse

ServerResponse は HTTP レスポンスへのアクセスを提供します。これは不変なので、build メソッドを使用して HTTP レスポンスを作成できます。ビルダーを使用して、レスポンスステータスを設定したり、レスポンスヘッダーを追加したり、本文を提供したりできます。次の例では、JSON コンテンツで 200(OK)レスポンスを作成します。

Java
Person person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
Kotlin
val person: Person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person)

次の例は、Location ヘッダーを持ち、本文がない 201(CREATED)レスポンスを作成する方法を示しています。

Java
URI location = ...
ServerResponse.created(location).build();
Kotlin
val location: URI = ...
ServerResponse.created(location).build()
ハンドラークラス

次の例に示すように、ハンドラー関数をラムダとして記述できます。

Java
HandlerFunction<ServerResponse> helloWorld =
  request -> ServerResponse.ok().body("Hello World");
Kotlin
val helloWorld: (ServerRequest) -> ServerResponse =
  { ServerResponse.ok().body("Hello World") }

これは便利ですが、アプリケーションでは複数の関数が必要であり、複数のインラインラムダが乱雑になる可能性があります。関連するハンドラー関数を、アノテーションベースのアプリケーションの @Controller と同様のロールを持つハンドラークラスにグループ化すると便利です。例:次のクラスは、リアクティブ Person リポジトリを公開します。

Java
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;

public class PersonHandler {

    private final PersonRepository repository;

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

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

    public ServerResponse createPerson(ServerRequest request) throws Exception { (2)
        Person person = request.body(Person.class);
        repository.savePerson(person);
        return ok().build();
    }

    public ServerResponse getPerson(ServerRequest request) { (3)
        int personId = Integer.parseInt(request.pathVariable("id"));
        Person person = repository.getPerson(personId);
        if (person != null) {
            return ok().contentType(APPLICATION_JSON).body(person))
        }
        else {
            return ServerResponse.notFound().build();
        }
    }

}
1listPeople は、リポジトリで見つかったすべての Person オブジェクトを JSON として返すハンドラー関数です。
2createPerson は、リクエスト本文に含まれる新しい Person を保存するハンドラー関数です。
3getPerson は、id パス変数で識別される 1 人の人物を返すハンドラー関数です。Person をリポジトリから取得し、見つかった場合は JSON レスポンスを作成します。見つからない場合、404 未検出レスポンスを返します。
Kotlin
class PersonHandler(private val repository: PersonRepository) {

    fun listPeople(request: ServerRequest): ServerResponse { (1)
        val people: List<Person> = repository.allPeople()
        return ok().contentType(APPLICATION_JSON).body(people);
    }

    fun createPerson(request: ServerRequest): ServerResponse { (2)
        val person = request.body<Person>()
        repository.savePerson(person)
        return ok().build()
    }

    fun getPerson(request: ServerRequest): ServerResponse { (3)
        val personId = request.pathVariable("id").toInt()
        return repository.getPerson(personId)?.let { ok().contentType(APPLICATION_JSON).body(it) }
                ?: ServerResponse.notFound().build()

    }
}
1listPeople は、リポジトリで見つかったすべての Person オブジェクトを JSON として返すハンドラー関数です。
2createPerson は、リクエスト本文に含まれる新しい Person を保存するハンドラー関数です。
3getPerson は、id パス変数で識別される 1 人の人物を返すハンドラー関数です。Person をリポジトリから取得し、見つかった場合は JSON レスポンスを作成します。見つからない場合、404 未検出レスポンスを返します。
検証

関数エンドポイントは、Spring の検証機能を使用して、リクエスト本文に検証を適用できます。例: Person のカスタム Spring バリデーター実装が指定された場合:

Java
public class PersonHandler {

    private final Validator validator = new PersonValidator(); (1)

    // ...

    public ServerResponse createPerson(ServerRequest request) {
        Person person = request.body(Person.class);
        validate(person); (2)
        repository.savePerson(person);
        return ok().build();
    }

    private void validate(Person person) {
        Errors errors = new BeanPropertyBindingResult(person, "person");
        validator.validate(person, errors);
        if (errors.hasErrors()) {
            throw new ServerWebInputException(errors.toString()); (3)
        }
    }
}
1Validator インスタンスを作成します。
2 検証を適用します。
3400 レスポンスに対して例外を発生させます。
Kotlin
class PersonHandler(private val repository: PersonRepository) {

    private val validator = PersonValidator() (1)

    // ...

    fun createPerson(request: ServerRequest): ServerResponse {
        val person = request.body<Person>()
        validate(person) (2)
        repository.savePerson(person)
        return ok().build()
    }

    private fun validate(person: Person) {
        val errors: Errors = BeanPropertyBindingResult(person, "person")
        validator.validate(person, errors)
        if (errors.hasErrors()) {
            throw ServerWebInputException(errors.toString()) (3)
        }
    }
}
1Validator インスタンスを作成します。
2 検証を適用します。
3400 レスポンスに対して例外を発生させます。

ハンドラーは、LocalValidatorFactoryBean に基づいてグローバル Validator インスタンスを作成および注入することにより、標準 Bean 検証 API(JSR-303)を使用することもできます。Spring 検証を参照してください。

1.4.3. RouterFunction

ルーター関数を使用して、リクエストを対応する HandlerFunction にルーティングします。通常、ルーター関数は自分で作成するのではなく、RouterFunctions ユーティリティクラスのメソッドを使用して作成します。RouterFunctions.route() (パラメーターなし)は、ルーター関数を作成するための流れるようなビルダーを提供しますが、RouterFunctions.route(RequestPredicate, HandlerFunction) はルーターを直接作成する方法を提供します。

通常、route() ビルダーを使用することをお勧めします。これは、検出が困難な静的インポートを必要とせずに、一般的なマッピングシナリオに便利なショートカットを提供するためです。たとえば、ルーター関数ビルダーは、GET リクエストのマッピングを作成するメソッド GET(String, HandlerFunction) を提供します。POST 用の POST(String, HandlerFunction)

HTTP メソッドベースのマッピングに加えて、ルートビルダーは、リクエストにマッピングするときに追加の述語を導入する方法を提供します。HTTP メソッドごとに、RequestPredicate をパラメーターとして受け取るオーバーロードされたバリアントがありますが、追加の制約を表現できます。

述部

独自の RequestPredicate を作成できますが、RequestPredicates ユーティリティクラスは、リクエストパス、HTTP メソッド、コンテンツタイプなどに基づいて、一般的に使用される実装を提供します。次の例では、リクエスト述語を使用して、Accept ヘッダーに基づいて制約を作成します。

Java
RouterFunction<ServerResponse> route = RouterFunctions.route()
    .GET("/hello-world", accept(MediaType.TEXT_PLAIN),
        request -> ServerResponse.ok().body("Hello World")).build();
Kotlin
import org.springframework.web.servlet.function.router

val route = router {
    GET("/hello-world", accept(TEXT_PLAIN)) {
        ServerResponse.ok().body("Hello World")
    }
}

以下を使用して、複数のリクエスト述語を一緒に作成できます。

  • RequestPredicate.and(RequestPredicate) —  両方が一致する必要があります。

  • RequestPredicate.or(RequestPredicate) —  どちらも一致できます。

RequestPredicates の述部の多くが構成されています。例: RequestPredicates.GET(String) は RequestPredicates.method(HttpMethod) と RequestPredicates.path(String) から構成されています。上記の例では、ビルダーが RequestPredicates.GET を内部で使用し、それを accept 述語で構成しているため、2 つのリクエスト述語も使用しています。

ルート

ルーター関数は順番に評価されます。最初のルートが一致しない場合、2 番目のルートが評価され、以下同様に評価されます。一般的なルートの前に、より具体的なルートを宣言することは理にかなっています。この動作は、「最も具体的な」コントローラーメソッドが自動的に選択されるアノテーションベースのプログラミングモデルとは異なることに注意してください。

ルーター関数ビルダーを使用する場合、定義されたすべてのルートは、build() から返される 1 つの RouterFunction に構成されます。複数のルーター関数を一緒に構成する他の方法もあります。

  • RouterFunctions.route() ビルダーの add(RouterFunction) 

  • RouterFunction.and(RouterFunction)

  • RouterFunction.andRoute(RequestPredicate, HandlerFunction) —  RouterFunctions.route() をネストした RouterFunction.and() のショートカット。

次の例は、4 つのルートの構成を示しています。

Java
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;

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

RouterFunction<ServerResponse> otherRoute = ...

RouterFunction<ServerResponse> route = route()
    .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) (1)
    .GET("/person", accept(APPLICATION_JSON), handler::listPeople) (2)
    .POST("/person", handler::createPerson) (3)
    .add(otherRoute) (4)
    .build();
1 JSON に一致する Accept ヘッダーを持つ GET /person/{id} は PersonHandler.getPerson にルーティングされます
2 JSON に一致する Accept ヘッダーを持つ GET /person は PersonHandler.listPeople にルーティングされます
3 追加の述部のない POST /person は PersonHandler.createPerson にマップされます
4otherRoute は、他の場所で作成され、構築されたルートに追加されるルーター関数です。
Kotlin
import org.springframework.http.MediaType.APPLICATION_JSON
import org.springframework.web.servlet.function.router

val repository: PersonRepository = ...
val handler = PersonHandler(repository);

val otherRoute = router {  }

val route = router {
    GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) (1)
    GET("/person", accept(APPLICATION_JSON), handler::listPeople) (2)
    POST("/person", handler::createPerson) (3)
}.and(otherRoute) (4)
1 JSON に一致する Accept ヘッダーを持つ GET /person/{id} は PersonHandler.getPerson にルーティングされます
2 JSON に一致する Accept ヘッダーを持つ GET /person は PersonHandler.listPeople にルーティングされます
3 追加の述部のない POST /person は PersonHandler.createPerson にマップされます
4otherRoute は、他の場所で作成され、構築されたルートに追加されるルーター関数です。
ネストされたルート

ルーター関数のグループが共有述語(共有パスなど)を持つことは一般的です。上記の例では、共有述語は、3 つのルートで使用される /person に一致するパス述語になります。アノテーションを使用する場合、/person にマップする型レベルの @RequestMapping アノテーションを使用して、この重複を削除します。WebMvc.fn では、経路述部は、ルーター関数ビルダーの path メソッドを介して共有できます。たとえば、上記の例の最後の数行は、ネストされたルートを使用して次のように改善できます。

Java
RouterFunction<ServerResponse> route = route()
    .path("/person", builder -> builder (1)
        .GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
        .GET("", accept(APPLICATION_JSON), handler::listPeople)
        .POST("/person", handler::createPerson))
    .build();
1path の 2 番目のパラメーターは、ルータービルダーを使用するコンシューマーであることに注意してください。
Kotlin
import org.springframework.web.servlet.function.router

val route = router {
    "/person".nest {
        GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
        GET("", accept(APPLICATION_JSON), handler::listPeople)
        POST("/person", handler::createPerson)
    }
}

パスベースのネストが最も一般的ですが、Builder で nest メソッドを使用して、あらゆる種類の述語にネストできます。上記には、共有 Accept -header 述部の形式での重複がまだ含まれています。nest メソッドと accept を併用すると、さらに改善できます。

Java
RouterFunction<ServerResponse> route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET("", handler::listPeople))
        .POST("/person", handler::createPerson))
    .build();
Kotlin
import org.springframework.web.servlet.function.router

val route = router {
    "/person".nest {
        accept(APPLICATION_JSON).nest {
            GET("/{id}", handler::getPerson)
            GET("", handler::listPeople)
            POST("/person", handler::createPerson)
        }
    }
}

1.4.4. サーバーの実行

通常、MVC 構成を介して DispatcherHandler ベースのセットアップでルーター関数を実行します。MVC 構成は、Spring 構成を使用して、リクエストの処理に必要なコンポーネントを宣言します。MVC Java 構成は、関数エンドポイントをサポートするために次のインフラストラクチャコンポーネントを宣言します。

  • RouterFunctionMapping: Spring 構成内の 1 つ以上の RouterFunction<?> Bean を検出し、RouterFunction.andOther を介して結合し、結果の合成された RouterFunction にリクエストをルーティングします。

  • HandlerFunctionAdapterDispatcherHandler がリクエストにマップされた HandlerFunction を呼び出せるようにする単純なアダプター。

上記のコンポーネントにより、関数エンドポイントは DispatcherServlet リクエスト処理ライフサイクルに適合し、アノテーション付きコントローラー(存在する場合)と並行して(潜在的に)実行されます。また、Spring Boot Web スターターが関数エンドポイントを有効にする方法でもあります。

次の例は、WebFlux Java 構成を示しています。

Java
@Configuration
@EnableMvc
public class WebConfig implements WebMvcConfigurer {

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

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

    // ...

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // configure message conversion...
    }

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

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        // configure view resolution for HTML rendering...
    }
}
Kotlin
@Configuration
@EnableMvc
class WebConfig : WebMvcConfigurer {

    @Bean
    fun routerFunctionA(): RouterFunction<*> {
        // ...
    }

    @Bean
    fun routerFunctionB(): RouterFunction<*> {
        // ...
    }

    // ...

    override fun configureMessageConverters(converters: List<HttpMessageConverter<*>>) {
        // configure message conversion...
    }

    override fun addCorsMappings(registry: CorsRegistry) {
        // configure CORS...
    }

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        // configure view resolution for HTML rendering...
    }
}

1.4.5. ハンドラー関数のフィルタリング

ルーティング関数ビルダーで beforeafter または filter メソッドを使用して、ハンドラー関数をフィルター処理できます。アノテーションを使用すると、@ControllerAdviceServletFilter、またはその両方を使用して同様の機能を実現できます。フィルターは、ビルダーによって構築されたすべてのルートに適用されます。これは、ネストされたルートで定義されたフィルターが「トップレベル」ルートに適用されないことを意味します。たとえば、次の例を考えてみましょう。

Java
RouterFunction<ServerResponse> route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET("", handler::listPeople)
            .before(request -> ServerRequest.from(request) (1)
                .header("X-RequestHeader", "Value")
                .build()))
        .POST("/person", handler::createPerson))
    .after((request, response) -> logResponse(response)) (2)
    .build();
1 カスタムリクエストヘッダーを追加する before フィルターは、2 つの GET ルートにのみ適用されます。
2 レスポンスを記録する after フィルターは、ネストされたものを含むすべてのルートに適用されます。
Kotlin
import org.springframework.web.servlet.function.router

val route = router {
    "/person".nest {
        GET("/{id}", handler::getPerson)
        GET("", handler::listPeople)
        before { (1)
            ServerRequest.from(it)
                    .header("X-RequestHeader", "Value").build()
        }
        POST("/person", handler::createPerson)
        after { _, response -> (2)
            logResponse(response)
        }
    }
}
1 カスタムリクエストヘッダーを追加する before フィルターは、2 つの GET ルートにのみ適用されます。
2 レスポンスを記録する after フィルターは、ネストされたものを含むすべてのルートに適用されます。

ルータービルダーの filter メソッドは HandlerFilterFunction を取ります。これは、ServerRequest と HandlerFunction を取り、ServerResponse を返す関数です。ハンドラー関数パラメーターは、チェーンの次の要素を表します。これは通常、ルーティング先のハンドラーですが、複数が適用される場合は別のフィルターにすることもできます。

特定のパスが許可されているかどうかを判断できる SecurityManager があると仮定して、ルートに簡単なセキュリティフィルターを追加できます。次の例は、その方法を示しています。

Java
SecurityManager securityManager = ...

RouterFunction<ServerResponse> route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET("", handler::listPeople))
        .POST("/person", handler::createPerson))
    .filter((request, next) -> {
        if (securityManager.allowAccessTo(request.path())) {
            return next.handle(request);
        }
        else {
            return ServerResponse.status(UNAUTHORIZED).build();
        }
    })
    .build();
Kotlin
import org.springframework.web.servlet.function.router

val securityManager: SecurityManager = ...

val route = router {
    ("/person" and accept(APPLICATION_JSON)).nest {
        GET("/{id}", handler::getPerson)
        GET("", handler::listPeople)
        POST("/person", handler::createPerson)
        filter { request, next ->
            if (securityManager.allowAccessTo(request.path())) {
                next(request)
            }
            else {
                status(UNAUTHORIZED).build();
            }
        }
    }
}

上記の例は、next.handle(ServerRequest) の呼び出しがオプションであることを示しています。アクセスが許可されている場合、ハンドラー関数のみを実行できます。

ルーター関数ビルダーで filter メソッドを使用する以外に、RouterFunction.filter(HandlerFilterFunction) を介して既存のルーター関数にフィルターを適用することができます。

関数エンドポイントの CORS サポートは、専用の CorsWebFilter を介して提供されます。

1.5. URI リンク

このセクションでは、Spring Framework で URI を操作するために使用できるさまざまなオプションについて説明します。

1.5.1. UriComponents

Spring MVC および Spring WebFlux

UriComponentsBuilder は、次の例に示すように、変数を持つ URI テンプレートから URI を作成できます。

Java
UriComponents uriComponents = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")  (1)
        .queryParam("q", "{q}")  (2)
        .encode() (3)
        .build(); (4)

URI uri = uriComponents.expand("Westin", "123").toUri();  (5)
1URI テンプレートを使用した静的ファクトリメソッド。
2URI コンポーネントを追加または置換します。
3URI テンプレートと URI 変数をエンコードするようリクエストします。
4UriComponents をビルドします。
5 変数を展開し、URI を取得します。
Kotlin
val uriComponents = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")  (1)
        .queryParam("q", "{q}")  (2)
        .encode() (3)
        .build() (4)

val uri = uriComponents.expand("Westin", "123").toUri()  (5)
1URI テンプレートを使用した静的ファクトリメソッド。
2URI コンポーネントを追加または置換します。
3URI テンプレートと URI 変数をエンコードするようリクエストします。
4UriComponents をビルドします。
5 変数を展開し、URI を取得します。

前述の例は、次の例に示すように、1 つのチェーンに統合し、buildAndExpand で短縮できます。

Java
URI uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")
        .queryParam("q", "{q}")
        .encode()
        .buildAndExpand("Westin", "123")
        .toUri();
Kotlin
val uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")
        .queryParam("q", "{q}")
        .encode()
        .buildAndExpand("Westin", "123")
        .toUri()

次の例に示すように、URI に直接移動することで(エンコードを暗示する)、さらに短くすることができます。

Java
URI uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")
        .queryParam("q", "{q}")
        .build("Westin", "123");
Kotlin
val uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")
        .queryParam("q", "{q}")
        .build("Westin", "123")

次の例に示すように、完全な URI テンプレートを使用してさらに短くします。

Java
URI uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}?q={q}")
        .build("Westin", "123");
Kotlin
val uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}?q={q}")
        .build("Westin", "123")

1.5.2. UriBuilder

Spring MVC および Spring WebFlux

UriComponentsBuilder は UriBuilder を実装しています。UriBuilderFactory を使用して、UriBuilder を作成できます。UriBuilderFactory と UriBuilder は、ベース URL、エンコード設定、その他の詳細などの共有構成に基づいて、URI テンプレートから URI を構築するプラグ可能なメカニズムを提供します。

RestTemplate および WebClient を UriBuilderFactory で構成して、URI の準備をカスタマイズできます。DefaultUriBuilderFactory は、UriComponentsBuilder を内部で使用し、共有構成オプションを公開する UriBuilderFactory のデフォルト実装です。

次の例は、RestTemplate を構成する方法を示しています。

Java
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;

String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
Kotlin
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode

val baseUrl = "https://example.org"
val factory = DefaultUriBuilderFactory(baseUrl)
factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES

val restTemplate = RestTemplate()
restTemplate.uriTemplateHandler = factory

次の例では、WebClient を構成します。

Java
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;

String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
Kotlin
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode

val baseUrl = "https://example.org"
val factory = DefaultUriBuilderFactory(baseUrl)
factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES

val client = WebClient.builder().uriBuilderFactory(factory).build()

さらに、DefaultUriBuilderFactory を直接使用することもできます。UriComponentsBuilder の使用に似ていますが、次の例に示すように、静的ファクトリメソッドの代わりに、構成と設定を保持する実際のインスタンスです。

Java
String baseUrl = "https://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);

URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
        .queryParam("q", "{q}")
        .build("Westin", "123");
Kotlin
val baseUrl = "https://example.com"
val uriBuilderFactory = DefaultUriBuilderFactory(baseUrl)

val uri = uriBuilderFactory.uriString("/hotels/{hotel}")
        .queryParam("q", "{q}")
        .build("Westin", "123")

1.5.3. URI エンコーディング

Spring MVC および Spring WebFlux

UriComponentsBuilder は、2 つのレベルでエンコードオプションを公開します。

どちらのオプションも、非 ASCII 文字と不正な文字をエスケープされたオクテットに置き換えます。ただし、最初のオプションは、URI 変数に表示される予約された意味で文字を置き換えます。

「;」を検討してください。これはパスでは有効ですが、意味は予約されています。最初のオプションは「;」を置き換えます。URI 変数には「%3B」が含まれますが、URI テンプレートには含まれません。対照的に、2 番目のオプションはパス内の正当な文字であるため、「;」を置き換えることはありません。

ほとんどの場合、最初のオプションは URI 変数を完全にエンコードされる不透明なデータとして扱うため、期待される結果が得られる可能性がありますが、オプション 2 は URI 変数に意図的に予約文字が含まれている場合にのみ役立ちます。

次の例では、最初のオプションを使用しています。

Java
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
        .queryParam("q", "{q}")
        .encode()
        .buildAndExpand("New York", "foo+bar")
        .toUri();

// Result is "/hotel%20list/New%20York?q=foo%2Bbar"
Kotlin
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
        .queryParam("q", "{q}")
        .encode()
        .buildAndExpand("New York", "foo+bar")
        .toUri()

// Result is "/hotel%20list/New%20York?q=foo%2Bbar"

次の例に示すように、URI に直接移動することで、前述の例を短縮できます(エンコードを意味します)。

Java
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
        .queryParam("q", "{q}")
        .build("New York", "foo+bar")
Kotlin
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
        .queryParam("q", "{q}")
        .build("New York", "foo+bar")

次の例に示すように、完全な URI テンプレートを使用してさらに短縮できます。

Java
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}?q={q}")
        .build("New York", "foo+bar")
Kotlin
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}?q={q}")
        .build("New York", "foo+bar")

WebClient および RestTemplate は、UriBuilderFactory 戦略により内部的に URI テンプレートを展開およびエンコードします。どちらもカスタム戦略で構成できます。次の例に示すように:

Java
String baseUrl = "https://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

// Customize the RestTemplate..
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);

// Customize the WebClient..
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
Kotlin
val baseUrl = "https://example.com"
val factory = DefaultUriBuilderFactory(baseUrl).apply {
    encodingMode = EncodingMode.TEMPLATE_AND_VALUES
}

// Customize the RestTemplate..
val restTemplate = RestTemplate().apply {
    uriTemplateHandler = factory
}

// Customize the WebClient..
val client = WebClient.builder().uriBuilderFactory(factory).build()

DefaultUriBuilderFactory 実装は、UriComponentsBuilder を内部的に使用して URI テンプレートを展開およびエンコードします。ファクトリとして、以下のエンコードモードのいずれかに基づいて、エンコードへのアプローチを構成する単一の場所を提供します。

  • TEMPLATE_AND_VALUES: 前のリストの最初のオプションに対応する UriComponentsBuilder#encode() を使用して、URI テンプレートを事前エンコードし、展開時に URI 変数を厳密にエンコードします。

  • VALUES_ONLY: URI テンプレートをエンコードせず、代わりに、UriUtils#encodeUriUriVariables を使用して URI 変数をテンプレートに展開する前に URI 変数に厳密なエンコードを適用します。

  • URI_COMPONENTS: 前のリストの 2 番目のオプションに対応する UriComponents#encode() を使用して、URI 変数が展開された後に URI コンポーネント値をエンコードします。

  • NONE: エンコードは適用されません。

RestTemplate は、歴史的な理由と後方互換性のために EncodingMode.URI_COMPONENTS に設定されています。WebClient は、DefaultUriBuilderFactory のデフォルト値に依存しています。これは、5.0.x の EncodingMode.URI_COMPONENTS から 5.1 の EncodingMode.TEMPLATE_AND_VALUES に変更されました。

1.5.4. 相対的なサーブレットリクエスト

次の例に示すように、ServletUriComponentsBuilder を使用して、現在のリクエストに関連する URI を作成できます。

Java
HttpServletRequest request = ...

// Re-uses host, scheme, port, path and query string...

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
        .replaceQueryParam("accountId", "{id}").build()
        .expand("123")
        .encode();
Kotlin
val request: HttpServletRequest = ...

// Re-uses host, scheme, port, path and query string...

val ucb = ServletUriComponentsBuilder.fromRequest(request)
        .replaceQueryParam("accountId", "{id}").build()
        .expand("123")
        .encode()

次の例に示すように、コンテキストパスに相対的な URI を作成できます。

Java
// Re-uses host, port and context path...

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request)
        .path("/accounts").build()
Kotlin
// Re-uses host, port and context path...

val ucb = ServletUriComponentsBuilder.fromContextPath(request)
        .path("/accounts").build()

次の例に示すように、サーブレットに関連する URI(/main/* など)を作成できます。

Java
// Re-uses host, port, context path, and Servlet prefix...

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
        .path("/accounts").build()
Kotlin
// Re-uses host, port, context path, and Servlet prefix...

val ucb = ServletUriComponentsBuilder.fromServletMapping(request)
        .path("/accounts").build()
5.1 の時点で、ServletUriComponentsBuilder は Forwarded および X-Forwarded-* ヘッダーからの情報を無視します。これらのヘッダーは、クライアントが発信したアドレスを指定します。ForwardedHeaderFilter を使用して、このようなヘッダーを抽出して使用または破棄することを検討してください。

Spring MVC は、コントローラーメソッドへのリンクを準備するメカニズムを提供します。例:次の MVC コントローラーはリンクの作成を許可します:

Java
@Controller
@RequestMapping("/hotels/{hotel}")
public class BookingController {

    @GetMapping("/bookings/{booking}")
    public ModelAndView getBooking(@PathVariable Long booking) {
        // ...
    }
}
Kotlin
@Controller
@RequestMapping("/hotels/{hotel}")
class BookingController {

    @GetMapping("/bookings/{booking}")
    fun getBooking(@PathVariable booking: Long): ModelAndView {
        // ...
    }
}

次の例に示すように、名前でメソッドを参照することでリンクを準備できます。

Java
UriComponents uriComponents = MvcUriComponentsBuilder
    .fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();
Kotlin
val uriComponents = MvcUriComponentsBuilder
    .fromMethodName(BookingController::class.java, "getBooking", 21).buildAndExpand(42)

val uri = uriComponents.encode().toUri()

上記の例では、実際のメソッド引数値(この場合、長い値: 21)を提供して、パス変数として使用し、URL に挿入します。さらに、値 42 を提供して、型レベルのリクエストマッピングから継承された hotel 変数など、残りの URI 変数を埋めます。メソッドにさらに引数がある場合、URL に不要な引数に null を指定できます。一般に、URL の構築に関連するのは @PathVariable および @RequestParam 引数のみです。

MvcUriComponentsBuilder を使用する追加の方法があります。例:次の例が示すように、名前によるコントローラーメソッドの参照を回避するために、プロキシを介したモックテストに似た手法を使用できます(例では MvcUriComponentsBuilder.on の静的インポートを想定しています)。

Java
UriComponents uriComponents = MvcUriComponentsBuilder
    .fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();
Kotlin
val uriComponents = MvcUriComponentsBuilder
    .fromMethodCall(on(BookingController::class.java).getBooking(21)).buildAndExpand(42)

val uri = uriComponents.encode().toUri()
fromMethodCall を使用したリンク作成に使用できると想定される場合、コントローラーメソッドのシグネチャーは設計上制限されます。適切なパラメーターシグネチャーが必要なこと以外に、戻り値のタイプには技術的な制限がある(つまり、リンクビルダー呼び出しのランタイムプロキシの生成)ため、戻り値のタイプは final であってはなりません。特に、ビュー名の一般的な String 戻り型はここでは機能しません。代わりに、ModelAndView またはプレーンな Object (String 戻り値)を使用する必要があります。

前の例では、MvcUriComponentsBuilder の静的メソッドを使用しています。内部的には、現在のリクエストのスキーム、ホスト、ポート、コンテキストパス、およびサーブレットパスからベース URL を準備するために ServletUriComponentsBuilder に依存しています。これはほとんどの場合にうまく機能します。ただし、場合によっては不十分な場合があります。例:リクエストのコンテキスト外(リンクを準備するバッチプロセスなど)であるか、パスプレフィックスを挿入する必要がある場合(リクエストパスから削除され、再挿入が必要なロケールプレフィックスなど)リンクに)。

そのような場合、ベース URL を使用するために UriComponentsBuilder を受け入れる静的な fromXxx オーバーロードメソッドを使用できます。または、ベース URL を使用して MvcUriComponentsBuilder のインスタンスを作成し、インスタンスベースの withXxx メソッドを使用できます。例:次のリストでは withMethodCall を使用しています。

Java
UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en");
MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base);
builder.withMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();
Kotlin
val base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en")
val builder = MvcUriComponentsBuilder.relativeTo(base)
builder.withMethodCall(on(BookingController::class.java).getBooking(21)).buildAndExpand(42)

val uri = uriComponents.encode().toUri()
5.1 の時点で、MvcUriComponentsBuilder は Forwarded および X-Forwarded-* ヘッダーからの情報を無視します。これらのヘッダーは、クライアントが発信したアドレスを指定します。ForwardedHeaderFilter を使用して、このようなヘッダーを抽出して使用または破棄することを検討してください。

Thymeleaf、FreeMarker、JSP などのビューでは、各リクエストマッピングに暗黙的または明示的に割り当てられた名前を参照することにより、アノテーション付きコントローラーへのリンクを構築できます。

次の例を考えてみましょう。

Java
@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {

    @RequestMapping("/{country}")
    public HttpEntity<PersonAddress> getAddress(@PathVariable String country) { ... }
}
Kotlin
@RequestMapping("/people/{id}/addresses")
class PersonAddressController {

    @RequestMapping("/{country}")
    fun getAddress(@PathVariable country: String): HttpEntity<PersonAddress> { ... }
}

上記のコントローラーがあれば、次のように JSP からリンクを準備できます。

<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
...
<a href="${s:mvcUrl('PAC#getAddress').arg(0,'US').buildAndExpand('123')}">Get Address</a>

上記の例は、Spring タグライブラリ(つまり、META-INF/spring.tld)で宣言された mvcUrl 関数に依存していますが、独自の関数を定義したり、他のテンプレートテクノロジ用に同様の関数を準備したりするのは簡単です。

これがどのように機能するかを次に示します。起動時に、すべての @RequestMapping には HandlerMethodMappingNamingStrategy を介してデフォルト名が割り当てられます。そのデフォルト実装では、クラスの大文字とメソッド名が使用されます(たとえば、ThingController の getThing メソッドは「TC#getThing」になります)。名前の衝突がある場合は、@RequestMapping(name="..") を使用して明示的な名前を割り当てるか、独自の HandlerMethodMappingNamingStrategy を実装できます。

1.6. 非同期リクエスト

Spring MVC は、Servlet 3.0 非同期リクエスト処理と広範囲に統合されています。

  • DeferredResult および Callable はコントローラーメソッドで値を返し、単一の非同期戻り値の基本的なサポートを提供します。

  • コントローラーは、SSE生データを含む複数の値をストリーミングできます。

  • コントローラーは、リアクティブクライアントを使用して、レスポンス処理にリアクティブタイプを返すことができます。

1.6.1. DeferredResult

サーブレットコンテナーで非同期リクエスト処理機能を有効にすると、次の例に示すように、コントローラーメソッドは DeferredResult でサポートされているコントローラーメソッドの戻り値をラップできます。

Java
@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
    DeferredResult<String> deferredResult = new DeferredResult<String>();
    // Save the deferredResult somewhere..
    return deferredResult;
}

// From some other thread...
deferredResult.setResult(result);
Kotlin
@GetMapping("/quotes")
@ResponseBody
fun quotes(): DeferredResult<String> {
    val deferredResult = DeferredResult<String>()
    // Save the deferredResult somewhere..
    return deferredResult
}

// From some other thread...
deferredResult.setResult(result)

コントローラーは、外部イベント(JMS メッセージ)、スケジュールされたタスク、またはその他のイベントへのレスポンスなど、異なるスレッドから非同期的に戻り値を生成できます。

1.6.2. Callable

次の例に示すように、コントローラーは、サポートされている戻り値を java.util.concurrent.Callable でラップできます。

Java
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {

    return new Callable<String>() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };
}
Kotlin
@PostMapping
fun processUpload(file: MultipartFile) = Callable<String> {
    // ...
    "someView"
}

その後、構成された TaskExecutor を介して特定のタスクを実行することにより、戻り値を取得できます。

1.6.3. 処理

サーブレットの非同期リクエスト処理の非常に簡潔な概要を次に示します。

  • request.startAsync() を呼び出すことにより、ServletRequest を非同期モードにすることができます。これを行うことの主な効果は、サーブレット(およびすべてのフィルター)を終了できることですが、レスポンスは開いたままで、後で処理を完了できます。

  • request.startAsync() の呼び出しは AsyncContext を返し、これを使用して非同期処理をさらに制御できます。例: dispatch メソッドを提供します。これは、サーブレット API からの転送に似ていますが、アプリケーションがサーブレットコンテナースレッドでのリクエスト処理を再開できる点が異なります。

  • ServletRequest は、現在の DispatcherType へのアクセスを提供します。これを使用して、初期リクエスト、非同期ディスパッチ、転送、およびその他のディスパッチャータイプの処理を区別できます。

DeferredResult 処理は次のように機能します。

  • コントローラーは DeferredResult を返し、アクセス可能なメモリ内キューまたはリストに保存します。

  • Spring MVC は request.startAsync() を呼び出します。

  • 一方、DispatcherServlet およびすべての構成済みフィルターはリクエスト処理スレッドを終了しますが、レスポンスは開いたままです。

  • アプリケーションはいくつかのスレッドから DeferredResult を設定し、Spring MVC はリクエストをサーブレットコンテナーにディスパッチします。

  • DispatcherServlet が再度呼び出され、非同期に生成された戻り値で処理が再開されます。

Callable 処理は次のように機能します。

  • コントローラーは Callable を返します。

  • Spring MVC は request.startAsync() を呼び出し、Callable を TaskExecutor に送信して、別のスレッドで処理します。

  • 一方、DispatcherServlet およびすべてのフィルターはサーブレットコンテナースレッドを終了しますが、レスポンスは開いたままです。

  • 最終的に、Callable は結果を生成し、Spring MVC はリクエストをサーブレットコンテナーにディスパッチして処理を完了します。

  • DispatcherServlet が再び呼び出され、Callable から非同期的に生成された戻り値で処理が再開されます。

さらに背景とコンテキストについては、Spring MVC 3.2 で非同期リクエスト処理のサポートを紹介したブログ投稿 (英語) を読むこともできます。

例外処理

DeferredResult を使用する場合、setResult または setErrorResult を例外付きで呼び出すかどうかを選択できます。どちらの場合も、Spring MVC はリクエストをサーブレットコンテナーにディスパッチして処理を完了します。その後、コントローラーメソッドが指定された値を返したか、指定された例外を生成したかのように処理されます。その後、例外は通常の例外処理メカニズム(たとえば、@ExceptionHandler メソッドの呼び出し)を通過します。

Callable を使用すると、同様の処理ロジックが発生しますが、主な違いは、結果が Callable から返されるか、例外が発生することです。

傍受

HandlerInterceptor インスタンスはタイプ AsyncHandlerInterceptor で、非同期処理を開始する最初のリクエストで afterConcurrentHandlingStarted コールバックを受け取ることができます(postHandle および afterCompletion の代わりに)。

HandlerInterceptor 実装では、CallableProcessingInterceptor または DeferredResultProcessingInterceptor を登録して、非同期リクエストのライフサイクルとより深く統合することもできます(たとえば、タイムアウトイベントを処理するため)。詳細については、AsyncHandlerInterceptor(Javadoc) を参照してください。

DeferredResult は、onTimeout(Runnable) および onCompletion(Runnable) コールバックを提供します。詳細については、DeferredResult の javadoc を参照してください。Callable は、タイムアウトおよび完了コールバックの追加メソッドを公開する WebAsyncTask の代わりに使用できます。

WebFlux と比較して

Servlet API は元々、Filter-Servlet チェーンを 1 回通過させるために構築されます。Servlet 3.0 に追加された非同期リクエスト処理により、アプリケーションは Filter-Servlet チェーンを終了できますが、レスポンスを開いたままにして、さらに処理できます。Spring MVC 非同期サポートは、そのメカニズムを中心に構築されています。コントローラーが DeferredResult を返すと、フィルターサーブレットチェーンが終了し、サーブレットコンテナースレッドが解放されます。後で、DeferredResult が設定されると、ASYNC ディスパッチが(同じ URL に)行われ、その間にコントローラーが再度マッピングされますが、呼び出しを行うのではなく、DeferredResult 値を使用して(コントローラーが返したように)処理を再開します。

対照的に、Spring WebFlux はサーブレット API 上に構築されておらず、設計上非同期であるため、そのような非同期リクエスト処理機能も必要ありません。非同期処理はすべてのフレームワーク契約に組み込まれており、リクエスト処理のすべての段階で本質的にサポートされています。

プログラミングモデルの観点から見ると、Spring MVC と Spring WebFlux は、コントローラーメソッドの戻り値として非同期とリアクティブ型をサポートしています。Spring MVC は、反応性バックプレッシャーを含むストリーミングもサポートします。ただし、ノンブロッキング I/O に依存し、各書き込みに余分なスレッドを必要としない WebFlux とは異なり、レスポンスへの個々の書き込みはブロッキングのままです(そして、個別のスレッドで実行されます)。

もう 1 つの基本的な違いは、Spring MVC はコントローラーメソッドの引数で非同期またはリアクティブ型(たとえば @RequestBody@RequestPart など)をサポートしていないこと、および非同期およびリアクティブ型をモデル属性として明示的にサポートしていないことです。Spring WebFlux はすべてをサポートしています。

1.6.4. HTTP ストリーミング

単一の非同期戻り値に DeferredResult および Callable を使用できます。複数の非同期値を生成し、レスポンスに書き込む場合はどうなるでしょうか?このセクションでは、その方法について説明します。

オブジェクト

ResponseBodyEmitter 戻り値を使用してオブジェクトのストリームを生成できます。次の例に示すように、各オブジェクトは HttpMessageConverter で直列化され、レスポンスに書き込まれます。

Java
@GetMapping("/events")
public ResponseBodyEmitter handle() {
    ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();
Kotlin
@GetMapping("/events")
fun handle() = ResponseBodyEmitter().apply {
    // Save the emitter somewhere..
}

// In some other thread
emitter.send("Hello once")

// and again later on
emitter.send("Hello again")

// and done at some point
emitter.complete()

ResponseBodyEmitter を ResponseEntity の本文として使用して、レスポンスのステータスとヘッダーをカスタマイズすることもできます。

emitter が IOException をスローした場合(たとえば、リモートクライアントがなくなった場合)、アプリケーションは接続のクリーンアップを担当せず、emitter.complete または emitter.completeWithError を呼び出してはなりません。代わりに、サーブレットコンテナーは自動的に AsyncListener エラー通知を開始し、Spring MVC は completeWithError 呼び出しを行います。次に、この呼び出しはアプリケーションへの最後の ASYNC ディスパッチを 1 回実行し、その間に Spring MVC は構成された例外リゾルバーを呼び出してリクエストを完了します。

SSE

SseEmitter (ResponseBodyEmitter のサブクラス)は、サーバー送信イベント (英語) のサポートを提供します。この場合、サーバーから送信されたイベントは、W3C SSE 仕様に従ってフォーマットされます。コントローラーから SSE ストリームを生成するには、次の例に示すように SseEmitter を返します。

Java
@GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter handle() {
    SseEmitter emitter = new SseEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();
Kotlin
@GetMapping("/events", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
fun handle() = SseEmitter().apply {
    // Save the emitter somewhere..
}

// In some other thread
emitter.send("Hello once")

// and again later on
emitter.send("Hello again")

// and done at some point
emitter.complete()

SSE はブラウザへのストリーミングの主なオプションですが、Internet Explorer はサーバー送信イベントをサポートしていないことに注意してください。Spring の WebSocket メッセージングを、幅広いブラウザを対象とする SockJS フォールバックトランスポート(SSE を含む)とともに使用することを検討してください。

例外処理に関する注意については、前のセクションも参照してください。

生データ

メッセージの変換をバイパスして、レスポンス OutputStream に直接ストリーミングすると便利な場合があります(ファイルのダウンロードなど)。次の例に示すように、StreamingResponseBody 戻り値型を使用してこれを行うことができます。

Java
@GetMapping("/download")
public StreamingResponseBody handle() {
    return new StreamingResponseBody() {
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            // write...
        }
    };
}
Kotlin
@GetMapping("/download")
fun handle() = StreamingResponseBody {
    // write...
}

StreamingResponseBody を ResponseEntity の本文として使用して、レスポンスのステータスとヘッダーをカスタマイズできます。

1.6.5. リアクティブ型

Spring MVC は、コントローラーでのリアクティブクライアントライブラリの使用をサポートしています(WebFlux セクションのリアクティブライブラリも参照してください)。これには、spring-webflux の WebClient および Spring Data リアクティブデータリポジトリなどのその他のものが含まれます。そのようなシナリオでは、コントローラーメソッドからリアクティブ型を返すことができると便利です。

リアクティブな戻り値は次のように処理されます。

  • DeferredResult を使用するのと同様に、単一値の promise が適応されます。例には、Mono (Reactor)または Single (RxJava)が含まれます。

  • ResponseBodyEmitter または SseEmitter を使用するのと同様に、ストリーミングメディアタイプ(application/stream+json または text/event-stream など)を持つ多値ストリームが適応されます。例には、Flux (Reactor)または Observable (RxJava)が含まれます。アプリケーションは Flux<ServerSentEvent> または Observable<ServerSentEvent> を返すこともできます。

  • 他のメディアタイプ(application/json など)を持つ複数値ストリームは、DeferredResult<List<?>> を使用するのと同様に適応されます。

Spring MVC は、spring-core から ReactiveAdapterRegistry(Javadoc) を介して Reactor および RxJava をサポートします。これにより、複数のリアクティブライブラリから適応できます。

レスポンスへのストリーミングでは、リアクティブバックプレッシャーがサポートされていますが、レスポンスへの書き込みは引き続きブロックされ、構成された TaskExecutor を介して別のスレッドで実行され、アップストリームソース(WebClient から返される Flux など)をブロックしません。デフォルトでは、SimpleAsyncTaskExecutor は書き込みのブロックに使用されますが、負荷がかかると適切ではありません。リアクティブ型でストリーミングする予定の場合は、MVC 設定を使用してタスクエグゼキューターを構成する必要があります。

1.6.6. 切断

Servlet API は、リモートクライアントがなくなると通知を行いません。SseEmitter またはリアクティブ型のいずれを介しても、レスポンスにストリーミングしている間、クライアントが切断された場合は書き込みが失敗するため、定期的にデータを送信することが重要です。送信は、空の(コメントのみの)SSE イベント、または相手側がハートビートとして解釈して無視する必要があるその他のデータの形式を取ることができます。

または、組み込みのハートビートメカニズムを備えた Web メッセージングソリューション(WebSocket を介した STOMPSockJS 付き WebSocket など)の使用を検討してください。

1.6.7. 構成

非同期リクエスト処理機能は、サーブレットコンテナーレベルで有効にする必要があります。MVC 構成は、非同期リクエストのいくつかのオプションも公開します。

サーブレットコンテナー

フィルターおよびサーブレット宣言には asyncSupported フラグがあり、true に設定して非同期リクエスト処理を有効にする必要があります。さらに、ASYNCjavax.servlet.DispatchType を処理するためにフィルターマッピングを宣言する必要があります。

Java 構成では、AbstractAnnotationConfigDispatcherServletInitializer を使用してサーブレットコンテナーを初期化すると、これは自動的に行われます。

web.xml 構成では、<async-supported>true</async-supported> を DispatcherServlet および Filter 宣言に追加し、<dispatcher>ASYNC</dispatcher> をフィルターマッピングに追加できます。

Spring MVC

MVC 構成は、非同期リクエスト処理に関連する次のオプションを公開します。

  • Java 構成 : WebMvcConfigurer で configureAsyncSupport コールバックを使用します。

  • XML 名前空間 : <mvc:annotation-driven> の <async-support> 要素を使用します。

以下を構成できます。

  • 非同期リクエストのデフォルトのタイムアウト値。設定されていない場合、基礎となるサーブレットコンテナーに依存します。

  • リアクティブ型を使用したストリーミング時の書き込みのブロック、およびコントローラーメソッドから返された Callable インスタンスの実行に使用する AsyncTaskExecutor。デフォルトでは SimpleAsyncTaskExecutor であるため、リアクティブ型でストリーミングする場合、または Callable を返すコントローラーメソッドがある場合、このプロパティを設定することを強くお勧めします。

  • DeferredResultProcessingInterceptor 実装および CallableProcessingInterceptor 実装。

DeferredResultResponseBodyEmitter、および SseEmitter にデフォルトのタイムアウト値を設定することもできます。Callable の場合、WebAsyncTask を使用してタイムアウト値を提供できます。

1.7. CORS

Spring MVC では、CORS(クロスオリジンリソース共有)を処理できます。このセクションでは、その方法について説明します。

1.7.1. 導入

セキュリティ上の理由から、ブラウザは現在のオリジンの外部のリソースへの AJAX 呼び出しを禁止しています。例:銀行口座を 1 つのタブに、evil.com を別のタブに持つことができます。evil.com のスクリプトは、アカウントからお金を引き出すなど、認証情報を使用して銀行の API に AJAX リクエストを行うことはできません!

Cross-Origin Resource Sharing(CORS)は、ほとんどのブラウザ (英語) で実装されている W3C 仕様 (英語) であり、IFRAME または JSONP に基づく安全性が低く、強力ではない回避策を使用するのではなく、認可される種類のクロスドメインリクエストを指定できます。

1.7.2. 処理

CORS 仕様は、プリフライト、シンプル、および実際のリクエストを区別します。CORS がどのように機能するかを学ぶために、他の多くの人と一緒にこの記事 (英語) を読むか、詳細について仕様を参照してください。

Spring MVC HandlerMapping 実装は、CORS の組み込みサポートを提供します。リクエストをハンドラーに正常にマッピングした後、HandlerMapping 実装は、指定されたリクエストとハンドラーの CORS 構成を確認し、さらにアクションを実行します。プリフライトリクエストは直接処理されますが、シンプルな CORS リクエストと実際の CORS リクエストはインターセプト、検証され、必要な CORS レスポンスヘッダーが設定されています。

クロスオリジンリクエストを有効にするには(つまり、Origin ヘッダーが存在し、リクエストのホストとは異なる)、明示的に宣言された CORS 構成が必要です。一致する CORS 設定が見つからない場合、プリフライトリクエストは拒否されます。CORS ヘッダーは単純および実際の CORS リクエストのレスポンスに追加されないため、ブラウザーはそれらを拒否します。

各 HandlerMapping は、URL パターンベースの CorsConfiguration マッピングで個別に構成(Javadoc) できます。ほとんどの場合、アプリケーションは MVC Java 構成または XML 名前空間を使用してこのようなマッピングを宣言し、その結果、すべての HandlerMappping インスタンスに単一のグローバルマップが渡されます。

HandlerMapping レベルのグローバル CORS 構成と、よりきめ細かいハンドラーレベルの CORS 構成を組み合わせることができます。例:アノテーション付きコントローラーは、クラスまたはメソッドレベルの @CrossOrigin アノテーションを使用できます(他のハンドラーは CorsConfigurationSource を実装できます)。

グローバル設定とローカル設定を結合するためのルールは、一般的に付加的です。たとえば、すべてのグローバルおよびすべてのローカルオリジン。単一の値しか受け入れられない属性(allowCredentials や maxAge など)の場合、ローカルはグローバル値をオーバーライドします。詳細については、CorsConfiguration#combine(CorsConfiguration)(Javadoc) を参照してください。

ソースからさらに学習するか、高度なカスタマイズを行うには、コードビハインドを確認します。

  • CorsConfiguration

  • CorsProcessorDefaultCorsProcessor

  • AbstractHandlerMapping

1.7.3. @CrossOrigin

@CrossOrigin(Javadoc) アノテーションは、次の例に示すように、アノテーション付きコントローラーメソッドでクロスオリジンリクエストを有効にします。

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

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

    @DeleteMapping("/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}
Kotlin
@RestController
@RequestMapping("/account")
class AccountController {

    @CrossOrigin
    @GetMapping("/{id}")
    fun retrieve(@PathVariable id: Long): Account {
        // ...
    }

    @DeleteMapping("/{id}")
    fun remove(@PathVariable id: Long) {
        // ...
    }
}

デフォルトでは、@CrossOrigin は次を許可します。

  • すべての起源。

  • すべてのヘッダー。

  • コントローラーメソッドがマップされるすべての HTTP メソッド。

allowedCredentials はデフォルトでは有効ではありません。これは、ユーザー固有の機密情報(Cookie や CSRF トークンなど)を公開する信頼レベルを確立し、適切な場合にのみ使用する必要があるためです。

maxAge は 30 分に設定されています。

@CrossOrigin はクラスレベルでもサポートされており、次の例に示すように、すべてのメソッドに継承されます。

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

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

    @DeleteMapping("/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}
Kotlin
@CrossOrigin(origins = ["https://domain2.com"], maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {

    @GetMapping("/{id}")
    fun retrieve(@PathVariable id: Long): Account {
        // ...
    }

    @DeleteMapping("/{id}")
    fun remove(@PathVariable id: Long) {
        // ...
    }

次の例に示すように、クラスレベルとメソッドレベルの両方で @CrossOrigin を使用できます。

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

    @CrossOrigin("https://domain2.com")
    @GetMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}
Kotlin
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {

    @CrossOrigin("https://domain2.com")
    @GetMapping("/{id}")
    fun retrieve(@PathVariable id: Long): Account {
        // ...
    }

    @DeleteMapping("/{id}")
    fun remove(@PathVariable id: Long) {
        // ...
    }
}

1.7.4. グローバル構成

細分化されたコントローラーメソッドレベルの構成に加えて、おそらくグローバルな CORS 構成も定義する必要があります。任意の HandlerMapping で URL ベースの CorsConfiguration マッピングを個別に設定できます。ただし、ほとんどのアプリケーションは、MVC Java 構成または MVC XML 名前空間を使用してこれを実行します。

デフォルトでは、グローバル構成により以下が有効になります。

  • すべての起源。

  • すべてのヘッダー。

  • GETHEAD および POST メソッド。

allowedCredentials はデフォルトでは有効ではありません。これは、ユーザー固有の機密情報(Cookie や CSRF トークンなど)を公開する信頼レベルを確立し、適切な場合にのみ使用する必要があるためです。

maxAge は 30 分に設定されています。

Java 構成

MVC Java 構成で CORS を有効にするには、次の例に示すように、CorsRegistry コールバックを使用できます。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {

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

        // Add more mappings...
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun addCorsMappings(registry: CorsRegistry) {

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

        // Add more mappings...
    }
}
XML 構成

XML 名前空間で CORS を有効にするには、次の例に示すように、<mvc:cors> 要素を使用できます。

<mvc:cors>

    <mvc:mapping path="/api/**"
        allowed-origins="https://domain1.com, https://domain2.com"
        allowed-methods="GET, PUT"
        allowed-headers="header1, header2, header3"
        exposed-headers="header1, header2" allow-credentials="true"
        max-age="123" />

    <mvc:mapping path="/resources/**"
        allowed-origins="https://domain1.com" />

</mvc:cors>

1.7.5. CORS フィルター

組み込みの CorsFilter(Javadoc) を介して CORS サポートを適用できます。

CorsFilter を Spring Security で使用しようとする場合、Spring Security には CORS のサポートが組み込まれていることに注意してください。

フィルターを構成するには、次の例に示すように、CorsConfigurationSource をコンストラクターに渡します。

Java
CorsConfiguration config = new CorsConfiguration();

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

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

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

CorsFilter filter = new CorsFilter(source);
Kotlin
val config = CorsConfiguration()

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

config.allowCredentials = true
config.addAllowedOrigin("https://domain1.com")
config.addAllowedHeader("*")
config.addAllowedMethod("*")

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

val filter = CorsFilter(source)

1.8. Web セキュリティ

Spring Security プロジェクトは、悪意のある悪用から Web アプリケーションを保護するためのサポートを提供します。以下を含む Spring Security リファレンスドキュメントを参照してください。

HDIV (英語) は、Spring MVC と統合する別の Web セキュリティフレームワークです。

1.9. HTTP キャッシング

HTTP キャッシングは、Web アプリケーションのパフォーマンスを大幅に改善できます。HTTP キャッシングは、Cache-Control レスポンスヘッダーと、その後の条件付きリクエストヘッダー(Last-Modified や ETag など)を中心に展開します。Cache-Control は、レスポンスをキャッシュおよび再利用する方法について、プライベート(ブラウザなど)およびパブリック(プロキシなど)のキャッシュにアドバイスします。コンテンツが変更されていない場合、ETag ヘッダーを使用して、本文のない 304(NOT_MODIFIED)になる条件付きリクエストを作成します。ETag は、Last-Modified ヘッダーの後継として洗練されたものと見なすことができます。

このセクションでは、Spring Web MVC で使用可能な HTTP キャッシュ関連オプションについて説明します。

1.9.1. CacheControl

CacheControl(Javadoc) は、Cache-Control ヘッダーに関連する設定の構成をサポートし、多くの場所で引数として受け入れられます。

RFC 7234 (英語) は Cache-Control レスポンスヘッダーのすべての可能なディレクティブを記述していますが、CacheControl タイプは、一般的なシナリオに焦点を当てたユースケース指向のアプローチを採用しています。

Java
// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);

// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();

// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();
Kotlin
// Cache for an hour - "Cache-Control: max-age=3600"
val ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS)

// Prevent caching - "Cache-Control: no-store"
val ccNoStore = CacheControl.noStore()

// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
val ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic()

WebContentGenerator は、次のように機能する単純な cachePeriod プロパティ(秒単位で定義)も受け入れます。

  • -1 値は、Cache-Control レスポンスヘッダーを生成しません。

  • 0 値は、'Cache-Control: no-store' ディレクティブを使用してキャッシュを防ぎます。

  • n > 0 値は、'Cache-Control: max-age=n' ディレクティブを使用して、指定されたレスポンスを n 秒間キャッシュします。

1.9.2. コントローラー

コントローラーは、HTTP キャッシングの明示的なサポートを追加できます。リソースの lastModified または ETag 値は、条件付きリクエストヘッダーと比較する前に計算する必要があるため、そうすることをお勧めします。次の例に示すように、コントローラーは ETag ヘッダーと Cache-Control 設定を ResponseEntity に追加できます。

Java
@GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {

    Book book = findBook(id);
    String version = book.getVersion();

    return ResponseEntity
            .ok()
            .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
            .eTag(version) // lastModified is also available
            .body(book);
}
Kotlin
@GetMapping("/book/{id}")
fun showBook(@PathVariable id: Long): ResponseEntity<Book> {

    val book = findBook(id);
    val version = book.getVersion()

    return ResponseEntity
            .ok()
            .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
            .eTag(version) // lastModified is also available
            .body(book)
}

前述の例では、条件付きリクエストヘッダーとの比較でコンテンツが変更されていないことが示された場合、空のボディを含む 304(NOT_MODIFIED)レスポンスが送信されます。それ以外の場合、ETag および Cache-Control ヘッダーがレスポンスに追加されます。

次の例に示すように、コントローラーの条件付きリクエストヘッダーに対してチェックを行うこともできます。

Java
@RequestMapping
public String myHandleMethod(WebRequest webRequest, Model model) {

    long eTag = ... (1)

    if (request.checkNotModified(eTag)) {
        return null; (2)
    }

    model.addAttribute(...); (3)
    return "myViewName";
}
1 アプリケーション固有の計算。
2 レスポンスは 304(NOT_MODIFIED)に設定されています。それ以上の処理はありません。
3 リクエスト処理を続行します。
Kotlin
@RequestMapping
fun myHandleMethod(webRequest: WebRequest, model: Model): String? {

    val eTag: Long = ... (1)

    if (request.checkNotModified(eTag)) {
        return null (2)
    }

    model[...] = ... (3)
    return "myViewName"
}
1 アプリケーション固有の計算。
2 レスポンスは 304(NOT_MODIFIED)に設定されています。それ以上の処理はありません。
3 リクエスト処理を続行します。

eTag 値、lastModified 値、またはその両方に対して条件付きリクエストをチェックするための 3 つのバリアントがあります。条件付き GET および HEAD リクエストの場合、レスポンスを 304(NOT_MODIFIED)に設定できます。条件付き POSTPUT および DELETE の場合、代わりにレスポンスを 412(PRECONDITION_FAILED)に設定して、同時変更を防ぐことができます。

1.9.3. 静的リソース

最適なパフォーマンスを得るには、Cache-Control と条件付きレスポンスヘッダーで静的リソースを提供する必要があります。静的リソースの構成に関するセクションを参照してください。

1.9.4. ETag フィルター

ShallowEtagHeaderFilter を使用して、レスポンスコンテンツから計算される「浅い」 eTag 値を追加し、CPU 時間ではなく帯域幅を節約できます。浅い ETag を参照してください。

1.10. ビューテクノロジー

Spring MVC でのビューテクノロジーの使用はプラガブルです。Thymeleaf、Groovy マークアップテンプレート、JSP、またはその他のテクノロジーを使用するかどうかは、主に構成変更の問題です。この章では、Spring MVC と統合されたビューテクノロジーについて説明します。すでにビューリゾルバーに精通していることを前提としています。

1.10.1. Thymeleaf

Thymeleaf は最新のサーバーサイド Java テンプレートエンジンで、ダブルクリックでブラウザプレビューできる自然な HTML テンプレートに重点を置いています。これは、実行中のサーバーを必要とせずに UI テンプレートの独立した作業 (たとえばデザイナー) に非常に役立ちます。JSP を置き換える場合、Thymeleaf はそうした移行を容易にするための最も広範な機能の 1 つを提供しています。Thymeleaf は積極的に開発・保守されています。詳細については、Thymeleaf (英語) プロジェクトのホームページを参照してください。

Thymeleaf と Spring MVC の統合は、Thymeleaf プロジェクトによって管理されています。構成には、ServletContextTemplateResolverSpringTemplateEngine や ThymeleafViewResolver などのいくつかの Bean 宣言が含まれます。詳細については、Thymeleaf+Spring (英語) を参照してください。

1.10.2. FreeMarker

Apache FreeMarker (英語) は、HTML からメールなどへのあらゆる種類のテキスト出力を生成するためのテンプレートエンジンです。Spring Framework には、FreeMarker テンプレートで Spring MVC を使用するための統合が組み込まれています。

構成を表示

次の例は、FreeMarker をビューテクノロジーとして設定する方法を示しています。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

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

    // Configure FreeMarker...

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("/WEB-INF/freemarker");
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.freeMarker()
    }

    // Configure FreeMarker...

    @Bean
    fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
        setTemplateLoaderPath("/WEB-INF/freemarker")
    }
}

次の例は、XML で同じものを構成する方法を示しています。

<mvc:annotation-driven/>

<mvc:view-resolvers>
    <mvc:freemarker/>
</mvc:view-resolvers>

<!-- Configure FreeMarker... -->
<mvc:freemarker-configurer>
    <mvc:template-loader-path location="/WEB-INF/freemarker"/>
</mvc:freemarker-configurer>

または、次の例に示すように、すべてのプロパティを完全に制御するために FreeMarkerConfigurer Bean を宣言することもできます。

<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
</bean>

テンプレートは、前の例で示した FreeMarkerConfigurer で指定されたディレクトリに保存する必要があります。上記の構成で、コントローラーが welcome のビュー名を返す場合、リゾルバーは /WEB-INF/freemarker/welcome.ftl テンプレートを探します。

FreeMarker の設定

FreeMarkerConfigurer Bean で適切な Bean プロパティを設定することにより、FreeMarker の「設定」と「SharedVariables」を FreeMarker Configuration オブジェクト(Spring によって管理される)に直接渡すことができます。freemarkerSettings プロパティには java.util.Properties オブジェクトが必要で、freemarkerVariables プロパティには java.util.Map が必要です。次の例は、FreeMarkerConfigurer の使用方法を示しています。

<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
    <property name="freemarkerVariables">
        <map>
            <entry key="xml_escape" value-ref="fmXmlEscape"/>
        </map>
    </property>
</bean>

<bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape"/>

Configuration オブジェクトに適用される設定と変数の詳細については、FreeMarker のドキュメントを参照してください。

フォーム処理

Spring は、特に <spring:bind/> 要素を含む JSP で使用するタグライブラリを提供します。この要素により、フォームは主にフォームバッキングオブジェクトの値を表示し、Web またはビジネス層の Validator からの検証の失敗の結果を表示できます。Spring は、FreeMarker と同じ機能をサポートしていますが、フォーム入力要素自体を生成するための追加の便利なマクロがあります。

バインドマクロ

マクロの標準セットは、FreeMarker の spring-webmvc.jar ファイル内で維持されるため、適切に構成されたアプリケーションで常に使用可能です。

Spring テンプレートライブラリで定義されているマクロの一部は内部(プライベート)と見なされますが、マクロ定義にはそのようなスコープは存在せず、呼び出し元のコードとユーザーテンプレートにすべてのマクロが表示されます。次のセクションでは、テンプレート内から直接呼び出す必要があるマクロのみに焦点を当てます。マクロコードを直接表示する場合、ファイルは spring.ftl と呼ばれ、org.springframework.web.servlet.view.freemarker パッケージに含まれています。

簡単なバインド

Spring MVC コントローラーのフォームビューとして機能する FreeMarker テンプレートに基づく HTML フォームでは、次の例のようなコードを使用してフィールド値にバインドし、JSP の同等の方法で各入力フィールドのエラーメッセージを表示できます。次の例は、personForm ビューを示しています。

<!-- FreeMarker macros have to be imported into a namespace.
    We strongly recommend sticking to 'spring'. -->
<#import "/spring.ftl" as spring/>
<html>
    ...
    <form action="" method="POST">
        Name:
        <@spring.bind "personForm.name"/>
        <input type="text"
            name="${spring.status.expression}"
            value="${spring.status.value?html}"/><br />
        <#list spring.status.errorMessages as error> <b>${error}</b> <br /> </#list>
        <br />
        ...
        <input type="submit" value="submit"/>
    </form>
    ...
</html>

<@spring.bind> には「path」引数が必要です。この引数は、コマンドオブジェクトの名前(コントローラー構成で変更しない限り「command」です)の後にピリオドとコマンドオブジェクトのフィールド名が続きます。バインドしたい。command.address.street などのネストされたフィールドを使用することもできます。bind マクロは、web.xml の ServletContext パラメーター defaultHtmlEscape で指定されたデフォルトの HTML エスケープ動作を想定しています。

<@spring.bindEscaped> と呼ばれるマクロの代替形式は、ステータスエラーメッセージまたは値で HTML エスケープを使用するかどうかを明示的に指定する 2 番目の引数を取ります。必要に応じて、true または false に設定できます。追加のフォーム処理マクロにより、HTML エスケープの使用が簡単になります。これらのマクロは可能な限り使用する必要があります。それらについては次のセクションで説明します。

入力マクロ

FreeMarker 用の追加の便利なマクロは、バインディングとフォーム生成(検証エラー表示を含む)の両方を簡素化します。これらのマクロを使用してフォーム入力フィールドを生成する必要はありません。これらを単純な HTML と組み合わせたり、以前に強調した Spring バインドマクロへの直接呼び出しを組み合わせたりすることができます。

次の使用可能なマクロの表は、FreeMarker テンプレート(FTL)定義とそれぞれが取るパラメーターリストを示しています。

表 6: マクロ定義の表
マクロ FTL 定義

message (コードパラメーターに基づいてリソースバンドルから文字列を出力する)

<@spring.message code/>

messageText (コードパラメーターに基づいてリソースバンドルから文字列を出力し、デフォルトパラメーターの値にフォールバックする)

<@spring.messageText code, text/>

url (相対 URL の前にアプリケーションのコンテキストルートを付けます)

<@spring.url relativeUrl/>

formInput (ユーザー入力を収集するための標準入力フィールド)

<@spring.formInput path, attributes, fieldType/>

formHiddenInput (非ユーザー入力を送信するための非表示の入力フィールド)

<@spring.formHiddenInput path, attributes/>

formPasswordInput (パスワードを収集するための標準入力フィールド。このタイプのフィールドには値が入力されないことに注意してください。)

<@spring.formPasswordInput path, attributes/>

formTextarea (長い自由形式のテキスト入力を収集するための大きなテキストフィールド)

<@spring.formTextarea path, attributes/>

formSingleSelect (単一の必要な値を選択できるオプションのドロップダウンボックス)

<@spring.formSingleSelect path, options, attributes/>

formMultiSelect (ユーザーが 0 個以上の値を選択できるオプションのリストボックス)

<@spring.formMultiSelect path, options, attributes/>

formRadioButtons (利用可能な選択肢から単一の選択を可能にするラジオボタンのセット)

<@spring.formRadioButtons path, options separator, attributes/>

formCheckboxes (0 個以上の値を選択できるチェックボックスのセット)

<@spring.formCheckboxes path, options, separator, attributes/>

formCheckbox (単一のチェックボックス)

<@spring.formCheckbox path, attributes/>

showErrors (バウンドフィールドの検証エラーの表示を簡素化)

<@spring.showErrors separator, classOrStyle/>

FreeMarker テンプレートでは、fieldType パラメーターの値として hidden または password を指定して通常の formInput マクロを使用できるため、formHiddenInput および formPasswordInput は実際には必要ありません。

上記のマクロのいずれかのパラメーターには一貫した意味があります。

  • path: バインドするフィールドの名前 (すなわち、「command.name」)

  • options: 入力フィールドで選択できるすべての使用可能な値の Map。マップのキーは、フォームから POST されてコマンドオブジェクトにバインドされた値を表します。キーに対して保存されたマップオブジェクトは、ユーザーにフォーム上に表示されるラベルであり、フォームによってポストバックされる対応する値とは異なる場合があります。通常、このようなマップは、コントローラーによって参照データとして提供されます。必要な動作に応じて、Map 実装を使用できます。厳密にソートされたマップの場合、適切な Comparator で SortedMap (TreeMap など)を使用できます。また、挿入順序で値を返す必要がある任意のマップでは、commons-collections から LinkedHashMap または LinkedMap を使用できます。

  • separator: 複数のオプションが個別の要素(ラジオボタンまたはチェックボックス)として使用可能な場合、リスト内の各オプションを区切るために使用される文字のシーケンス(<br> など)。

  • attributes: HTML タグ自体に含まれる任意のタグまたはテキストの追加文字列。この文字列は、マクロによって文字通りエコーされます。例: textarea フィールドでは、属性('rows = "5" cols = "60"' など)を指定するか、'style = "border:1px solid silver"' などのスタイル情報を渡すことができます。

  • classOrStyleshowErrors マクロの場合、各エラーをラップする span エレメントが使用する CSS クラスの名前。情報が提供されない場合(または値が空の場合)、エラーは <b></b> タグでラップされます。

次のセクションでは、マクロの例を概説します。

入力フィールド

formInput マクロは、path パラメーター(command.name)と追加の attributes パラメーター(次の例では空です)を取ります。このマクロは、他のすべてのフォーム生成マクロとともに、パスパラメーターで暗黙的な Spring バインドを実行します。バインドは新しいバインドが発生するまで有効であるため、showErrors マクロはパスパラメーターを再度渡す必要はありません。バインドが最後に作成されたフィールドで動作します。

showErrors マクロはセパレーターパラメーター(特定のフィールドで複数のエラーを分離するために使用される文字)を受け取り、2 番目のパラメーター(今回はクラス名またはスタイル属性)も受け入れます。FreeMarker は属性パラメーターのデフォルト値を指定できることに注意してください。次の例は、formInput および showWErrors マクロの使用方法を示しています。

<@spring.formInput "command.name"/>
<@spring.showErrors "<br>"/>

次の例は、フォームフラグメントの出力を示しています。名前フィールドを生成し、フィールドに値を指定せずにフォームを送信した後に検証エラーを表示します。検証は、Spring の検証フレームワークを通じて行われます。

生成された HTML は次の例のようになります。

Name:
<input type="text" name="name" value="">
<br>
    <b>required</b>
<br>
<br>

formTextarea マクロは formInput マクロと同じように機能し、同じパラメーターリストを受け入れます。通常、2 番目のパラメーター(attributes)は、textarea のスタイル情報または rows および cols 属性を渡すために使用されます。

選択フィールド

4 つの選択フィールドマクロを使用して、HTML フォームで一般的な UI 値選択入力を生成できます。

  • formSingleSelect

  • formMultiSelect

  • formRadioButtons

  • formCheckboxes

4 つのマクロはそれぞれ、フォームフィールドの値とその値に対応するラベルを含む Map のオプションを受け入れます。値とラベルは同じにすることができます。

次の例は、FTL のラジオボタンです。フォームをバッキングするオブジェクトは、このフィールドに「ロンドン」のデフォルト値を指定しているため、検証は不要です。フォームがレンダリングされると、選択する都市のリスト全体が「cityMap」という名前でモデルの参照データとして提供されます。次のリストに例を示します。

...
Town:
<@spring.formRadioButtons "command.address.town", cityMap, ""/><br><br>

上記のリストは、cityMap の各値に 1 つずつラジオボタンの行をレンダリングし、"" のセパレータを使用しています。追加の属性は提供されません(マクロの最後のパラメーターが欠落しています)。cityMap は、マップ内の各キーと値のペアに同じ String を使用します。マップのキーは、フォームが POST リクエストパラメーターとして実際に送信するものです。マップ値は、ユーザーに表示されるラベルです。前の例では、3 つのよく知られた都市のリストと、フォームバッキングオブジェクトのデフォルト値を指定すると、HTML は次のようになります。

Town:
<input type="radio" name="address.town" value="London">London</input>
<input type="radio" name="address.town" value="Paris" checked="checked">Paris</input>
<input type="radio" name="address.town" value="New York">New York</input>

アプリケーションが(たとえば)内部コードで都市を処理することを期待している場合、次の例に示すように、適切なキーを使用してコードのマップを作成できます。

Java
protected Map<String, ?> referenceData(HttpServletRequest request) throws Exception {
    Map<String, String> cityMap = new LinkedHashMap<>();
    cityMap.put("LDN", "London");
    cityMap.put("PRS", "Paris");
    cityMap.put("NYC", "New York");

    Map<String, Object> model = new HashMap<>();
    model.put("cityMap", cityMap);
    return model;
}
Kotlin
protected fun referenceData(request: HttpServletRequest): Map<String, *> {
    val cityMap = linkedMapOf(
            "LDN" to "London",
            "PRS" to "Paris",
            "NYC" to "New York"
    )
    return hashMapOf("cityMap" to cityMap)
}

コードは、ラジオの値が関連するコードである出力を生成しますが、ユーザーには、次のように、よりユーザーフレンドリーな都市名が表示されます。

Town:
<input type="radio" name="address.town" value="LDN">London</input>
<input type="radio" name="address.town" value="PRS" checked="checked">Paris</input>
<input type="radio" name="address.town" value="NYC">New York</input>
HTML エスケープ

前述のフォームマクロをデフォルトで使用すると、HTML 4.01 に準拠し、Spring のバインドサポートで使用される web.xml ファイルで定義された HTML エスケープのデフォルト値を使用する HTML 要素が生成されます。要素を XHTML 準拠にするか、デフォルトの HTML エスケープ値をオーバーライドするには、テンプレート(またはテンプレートに表示されるモデル)で 2 つの変数を指定できます。テンプレートで指定することの利点は、後でテンプレート処理で異なる値に変更して、フォームの異なるフィールドに異なる動作を提供できることです。

タグの XHTML 準拠に切り替えるには、次の例に示すように、xhtmlCompliant という名前のモデルまたはコンテキスト変数に true の値を指定します。

<#-- for FreeMarker -->
<#assign xhtmlCompliant = true>

このディレクティブを処理した後、Spring マクロによって生成された要素はすべて XHTML 準拠になりました。

同様に、次の例に示すように、フィールドごとに HTML エスケープを指定できます。

<#-- until this point, default HTML escaping is used -->

<#assign htmlEscape = true>
<#-- next field will use HTML escaping -->
<@spring.formInput "command.name"/>

<#assign htmlEscape = false in spring>
<#-- all future fields will be bound with HTML escaping off -->

1.10.3. Groovy マークアップ

Groovy マークアップテンプレートエンジン (英語) は、主に XML に似たマークアップ(XML、XHTML、HTML5 など)の生成を目的としていますが、これを使用してテキストベースのコンテンツを生成できます。Spring Framework には、Groovy マークアップで Spring MVC を使用するための組み込みの統合があります。

Groovy マークアップテンプレートエンジンには Groovy 2.3.1+. が必要です
構成

次の例は、Groovy マークアップテンプレートエンジンを構成する方法を示しています。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

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

    // Configure the Groovy Markup Template Engine...

    @Bean
    public GroovyMarkupConfigurer groovyMarkupConfigurer() {
        GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer();
        configurer.setResourceLoaderPath("/WEB-INF/");
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.groovy()
    }

    // Configure the Groovy Markup Template Engine...

    @Bean
    fun groovyMarkupConfigurer() = GroovyMarkupConfigurer().apply {
        resourceLoaderPath = "/WEB-INF/"
    }
}

次の例は、XML で同じものを構成する方法を示しています。

<mvc:annotation-driven/>

<mvc:view-resolvers>
    <mvc:groovy/>
</mvc:view-resolvers>

<!-- Configure the Groovy Markup Template Engine... -->
<mvc:groovy-configurer resource-loader-path="/WEB-INF/"/>
サンプル

従来のテンプレートエンジンとは異なり、Groovy マークアップは、ビルダー構文を使用する DSL に依存しています。次の例は、HTML ページのサンプルテンプレートを示しています。

yieldUnescaped '<!DOCTYPE html>'
html(lang:'en') {
    head {
        meta('http-equiv':'"Content-Type" content="text/html; charset=utf-8"')
        title('My page')
    }
    body {
        p('This is an example of HTML contents')
    }
}

1.10.4. スクリプトビュー

Spring Framework には、JSR-223 (英語) Java スクリプトエンジン上で実行可能な任意のテンプレートライブラリと Spring MVC を使用するための組み込み統合があります。さまざまなスクリプトエンジンで、次のテンプレートライブラリをテストしました。

スクリプトライブラリ スクリプトエンジン

Handlebars (英語)

Nashorn (英語)

Mustache (英語)

Nashorn (英語)

React (英語)

Nashorn (英語)

EJS (英語)

Nashorn (英語)

ERB (英語)

JRuby (英語)

文字列テンプレート (英語)

Jython (英語)

Kotlin スクリプトテンプレート (GitHub)

Kotlin (英語)

他のスクリプトエンジンを統合するための基本的なルールは、ScriptEngine および Invocable インターフェースを実装する必要があるということです。
要件

クラスパスにスクリプトエンジンが必要です。詳細はスクリプトエンジンによって異なります。

  • Nashorn (英語) JavaScript エンジンには Java 8+ が付属しています。利用可能な最新の更新リリースを使用することを強くお勧めします。

  • JRuby (英語) は、Ruby サポートの依存関係として追加する必要があります。

  • Jython (英語) は、Python サポートの依存関係として追加する必要があります。

  • Kotlin スクリプトをサポートするには、org.jetbrains.kotlin:kotlin-script-util 依存関係と org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory 行を含む META-INF/services/javax.script.ScriptEngineFactory ファイルを追加する必要があります。詳細については、この例 (GitHub) を参照してください。

スクリプトテンプレートライブラリが必要です。Javascript でこれを行う 1 つの方法は、WebJars (英語) を使用することです。

スクリプトテンプレート

ScriptTemplateConfigurer Bean を宣言して、使用するスクリプトエンジン、ロードするスクリプトファイル、テンプレートをレンダリングするために呼び出す関数などを指定できます。次の例では、Mustache テンプレートと Nashorn JavaScript エンジンを使用しています。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

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

    @Bean
    public ScriptTemplateConfigurer configurer() {
        ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
        configurer.setEngineName("nashorn");
        configurer.setScripts("mustache.js");
        configurer.setRenderObject("Mustache");
        configurer.setRenderFunction("render");
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.scriptTemplate()
    }

    @Bean
    fun configurer() = ScriptTemplateConfigurer().apply {
        engineName = "nashorn"
        setScripts("mustache.js")
        renderObject = "Mustache"
        renderFunction = "render"
    }
}

次の例は、XML での同じ配置を示しています。

<mvc:annotation-driven/>

<mvc:view-resolvers>
    <mvc:script-template/>
</mvc:view-resolvers>

<mvc:script-template-configurer engine-name="nashorn" render-object="Mustache" render-function="render">
    <mvc:script location="mustache.js"/>
</mvc:script-template-configurer>

次の例に示すように、コントローラーは Java 構成と XML 構成で同じように見えます。

Java
@Controller
public class SampleController {

    @GetMapping("/sample")
    public String test(Model model) {
        model.addAttribute("title", "Sample title");
        model.addAttribute("body", "Sample body");
        return "template";
    }
}
Kotlin
@Controller
class SampleController {

    @GetMapping("/sample")
    fun test(model: Model): String {
        model["title"] = "Sample title"
        model["body"] = "Sample body"
        return "template"
    }
}

次の例は、Mustache テンプレートを示しています。

<html>
    <head>
        <title>{{title}}</title>
    </head>
    <body>
        <p>{{body}}</p>
    </body>
</html>

レンダリング関数は、次のパラメーターで呼び出されます。

  • String template: テンプレートの内容

  • Map model: ビューモデル

  • RenderingContext renderingContext: アプリケーションコンテキスト、ロケール、テンプレートローダー、および URL へのアクセスを提供する RenderingContext(Javadoc) (5.0 以降)

Mustache.render() はこの署名とネイティブに互換性があるため、直接呼び出すことができます。

テンプレートテクノロジでカスタマイズが必要な場合は、カスタムレンダリング機能を実装するスクリプトを提供できます。例:Handlerbars (英語) は、テンプレートを使用する前にコンパイルする必要があり、サーバー側のスクリプトエンジンでは利用できないブラウザー機能をエミュレートするためにポリフィル (英語) が必要です。

次の例は、その方法を示しています。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

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

    @Bean
    public ScriptTemplateConfigurer configurer() {
        ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
        configurer.setEngineName("nashorn");
        configurer.setScripts("polyfill.js", "handlebars.js", "render.js");
        configurer.setRenderFunction("render");
        configurer.setSharedEngine(false);
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.scriptTemplate()
    }

    @Bean
    fun configurer() = ScriptTemplateConfigurer().apply {
        engineName = "nashorn"
        setScripts("polyfill.js", "handlebars.js", "render.js")
        renderFunction = "render"
        isSharedEngine = false
    }
}
Nashorn で実行されている Handlebars や React など、同時実行用に設計されていないテンプレートライブラリで非スレッドセーフスクリプトエンジンを使用する場合は、sharedEngine プロパティを false に設定する必要があります。その場合、このバグ (英語) のため、Java SE 8 update 60 が必要ですが、通常は最新の Java SE パッチリリースを使用することをお勧めします。

polyfill.js は、次のように、Handlebars が適切に実行するために必要な window オブジェクトのみを定義します。

var window = {};

この基本的な render.js 実装は、テンプレートを使用する前にコンパイルします。本番環境向けの実装では、再利用されたキャッシュ済みテンプレートまたはプリコンパイル済みテンプレートも保存する必要があります。スクリプト側でそれを行うことができます(そして、必要なカスタマイズを処理します。たとえば、テンプレートエンジンの構成を管理します)。次の例は、その方法を示しています。

function render(template, model) {
    var compiledTemplate = Handlebars.compile(template);
    return compiledTemplate(model);
}

その他の構成例については、Spring Framework 単体テスト、Java (GitHub) 、およびリソース (GitHub) を参照してください。

1.10.5. JSP および JSTL

Spring Framework には、Spring MVC を JSP および JSTL とともに使用するための組み込みの統合があります。

リゾルバーを表示

JSP を使用して開発する場合、InternalResourceViewResolver または ResourceBundleViewResolver Bean を宣言できます。

ResourceBundleViewResolver は、プロパティファイルに依存して、クラスと URL にマップされるビュー名を定義します。ResourceBundleViewResolver を使用すると、次の例に示すように、1 つのリゾルバーのみを使用して、異なるタイプのビューを混在させることができます。

<!-- the ResourceBundleViewResolver -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
</bean>

# And a sample properties file is used (views.properties in WEB-INF/classes):
welcome.(class)=org.springframework.web.servlet.view.JstlView
welcome.url=/WEB-INF/jsp/welcome.jsp

productList.(class)=org.springframework.web.servlet.view.JstlView
productList.url=/WEB-INF/jsp/productlist.jsp

InternalResourceViewResolver は JSP にも使用できます。ベストプラクティスとして、クライアントが直接アクセスできないように、'WEB-INF' ディレクトリのディレクトリに JSP ファイルを配置することを強くお勧めします。

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>
JSP と JSTL

JSP 標準タグライブラリ(JSTL)を使用する場合、JSTL は I18N 機能などが機能する前に準備が必要なので、特別なビュークラス JstlView を使用する必要があります。

Spring の JSP タグライブラリ

Spring は、前の章で説明したように、コマンドオブジェクトへのリクエストパラメーターのデータバインディングを提供します。これらのデータバインディング機能と組み合わせて JSP ページの開発を容易にするために、Spring は物事をさらに簡単にするいくつかのタグを提供します。すべての Spring タグには、文字のエスケープを有効または無効にする HTML エスケープ機能があります。

spring.tld タグライブラリ記述子(TLD)は spring-webmvc.jar に含まれています。個々のタグに関する包括的なリファレンスについては、API 参照を参照するか、タグライブラリの説明を参照してください。

Spring のフォームタグライブラリ

バージョン 2.0 以降、Spring は、JSP および Spring Web MVC を使用するときにフォーム要素を処理するためのデータバインディング対応タグの包括的なセットを提供します。各タグは、対応する HTML タグの対応する属性のセットをサポートするため、使い慣れた直感的なタグになります。タグ生成された HTML は、HTML 4.01/XHTML 1.0 に準拠しています。

他のフォーム / 入力タグライブラリとは異なり、Spring のフォームタグライブラリは Spring Web MVC と統合され、コントローラーが処理するコマンドオブジェクトと参照データにタグがアクセスできるようにします。次の例で示すように、フォームタグを使用すると、JSP の開発、読み取り、および保守が容易になります。

フォームタグを調べて、各タグの使用方法の例を見てみましょう。特定のタグに追加のコメントが必要な生成された HTML スニペットが含まれています。

構成

フォームタグライブラリは spring-webmvc.jar にバンドルされています。ライブラリ記述子は spring-form.tld と呼ばれます。

このライブラリのタグを使用するには、JSP ページの上部に次のディレクティブを追加します。

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

ここで、form は、このライブラリのタグに使用するタグ名のプレフィックスです。

フォームタグ

このタグは、HTML 'form' 要素をレンダリングし、バインディング用の内部タグへのバインディングパスを公開します。コマンドオブジェクトを PageContext に配置して、コマンドオブジェクトに内部タグからアクセスできるようにします。このライブラリの他のすべてのタグは、form タグのネストされたタグです。

User というドメインオブジェクトがあると仮定します。firstName や lastName などのプロパティを持つ JavaBean です。form.jsp を返すフォームコントローラーのフォームバッキングオブジェクトとして使用できます。次の例は、form.jsp がどのように見えるかを示しています。

<form:form>
    <table>
        <tr>
            <td>First Name:</td>
            <td><form:input path="firstName"/></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><form:input path="lastName"/></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form:form>

firstName および lastName の値は、ページコントローラーによって PageContext に配置されたコマンドオブジェクトから取得されます。form タグで内部タグがどのように使用されるかについて、より複雑な例を参照してください。

次のリストは、標準フォームのように見える生成された HTML を示しています。

<form method="POST">
    <table>
        <tr>
            <td>First Name:</td>
            <td><input name="firstName" type="text" value="Harry"/></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><input name="lastName" type="text" value="Potter"/></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form>

上記の JSP では、フォームバッキングオブジェクトの変数名が command であると想定しています。フォームバッキングオブジェクトを別の名前でモデルに配置した場合(間違いなくベストプラクティス)、次の例に示すように、フォームを名前付き変数にバインドできます。

<form:form modelAttribute="user">
    <table>
        <tr>
            <td>First Name:</td>
            <td><form:input path="firstName"/></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><form:input path="lastName"/></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form:form>
input タグ

このタグは、デフォルトでバインドされた値と type='text' を使用して HTML input 要素をレンダリングします。このタグの例については、フォームタグを参照してください。emailteldate などの HTML5 固有のタイプも使用できます。

checkbox タグ

このタグは、type が checkbox に設定された HTML input タグをレンダリングします。

User には、ニュースレターの購読や趣味のリストなどの設定があると仮定します。次の例は、Preferences クラスを示しています。

Java
public class Preferences {

    private boolean receiveNewsletter;
    private String[] interests;
    private String favouriteWord;

    public boolean isReceiveNewsletter() {
        return receiveNewsletter;
    }

    public void setReceiveNewsletter(boolean receiveNewsletter) {
        this.receiveNewsletter = receiveNewsletter;
    }

    public String[] getInterests() {
        return interests;
    }

    public void setInterests(String[] interests) {
        this.interests = interests;
    }

    public String getFavouriteWord() {
        return favouriteWord;
    }

    public void setFavouriteWord(String favouriteWord) {
        this.favouriteWord = favouriteWord;
    }
}
Kotlin
class Preferences(
        var receiveNewsletter: Boolean,
        var interests: StringArray,
        var favouriteWord: String
)

対応する form.jsp は、次のようになります。

<form:form>
    <table>
        <tr>
            <td>Subscribe to newsletter?:</td>
            <%-- Approach 1: Property is of type java.lang.Boolean --%>
            <td><form:checkbox path="preferences.receiveNewsletter"/></td>
        </tr>

        <tr>
            <td>Interests:</td>
            <%-- Approach 2: Property is of an array or of type java.util.Collection --%>
            <td>
                Quidditch: <form:checkbox path="preferences.interests" value="Quidditch"/>
                Herbology: <form:checkbox path="preferences.interests" value="Herbology"/>
                Defence Against the Dark Arts: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/>
            </td>
        </tr>

        <tr>
            <td>Favourite Word:</td>
            <%-- Approach 3: Property is of type java.lang.Object --%>
            <td>
                Magic: <form:checkbox path="preferences.favouriteWord" value="Magic"/>
            </td>
        </tr>
    </table>
</form:form>

checkbox タグには 3 つのアプローチがあり、すべてのチェックボックスのニーズを満たす必要があります。

  • アプローチ 1: バウンド値のタイプが java.lang.Boolean の場合、バウンド値が true の場合、input(checkbox) は checked としてマークされます。value 属性は、setValue(Object) 値プロパティの解決された値に対応します。

  • アプローチ 2: バインドされた値がタイプ array または java.util.Collection である場合、構成された setValue(Object) 値がバインドされた Collection に存在する場合、input(checkbox) は checked としてマークされます。

  • アプローチ 3: 他のバインド値タイプの場合、構成された setValue(Object) がバインド値と等しい場合、input(checkbox) は checked としてマークされます。

アプローチに関係なく、同じ HTML 構造が生成されることに注意してください。次の HTML スニペットは、いくつかのチェックボックスを定義しています。

<tr>
    <td>Interests:</td>
    <td>
        Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch"/>
        <input type="hidden" value="1" name="_preferences.interests"/>
        Herbology: <input name="preferences.interests" type="checkbox" value="Herbology"/>
        <input type="hidden" value="1" name="_preferences.interests"/>
        Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/>
        <input type="hidden" value="1" name="_preferences.interests"/>
    </td>
</tr>

各チェックボックスの後に追加の非表示フィールドが表示されるとは思わないかもしれません。HTML ページのチェックボックスがチェックされていない場合、フォームが送信されると、その値は HTTP リクエストパラメーターの一部としてサーバーに送信されないため、Spring フォームデータバインディングが機能するためには、HTML でこの癖を回避する必要があります。checkbox タグは、各チェックボックスにアンダースコア(_)が前に付いた隠しパラメーターを含めるという既存の Spring 規則に従います。これを行うことにより、Spring に「チェックボックスがフォームに表示されていたため、フォームデータがバインドされるオブジェクトにチェックボックスの状態を反映させたい」と効果的に伝えます。

checkboxes タグ

このタグは、type が checkbox に設定された複数の HTML input タグをレンダリングします。

このセクションは、前の checkbox タグセクションの例を基にしています。場合によっては、JSP ページにすべての可能な趣味をリストする必要はありません。実行時に利用可能なオプションのリストを提供し、それをタグに渡します。それが checkboxes タグの目的です。items プロパティで利用可能なオプションを含む ArrayList、または Map を渡すことができます。通常、バウンドプロパティはコレクションであるため、ユーザーが選択した複数の値を保持できます。次の例は、このタグを使用する JSP を示しています。

<form:form>
    <table>
        <tr>
            <td>Interests:</td>
            <td>
                <%-- Property is of an array or of type java.util.Collection --%>
                <form:checkboxes path="preferences.interests" items="${interestList}"/>
            </td>
        </tr>
    </table>
</form:form>

この例では、interestList が、選択される値の文字列を含むモデル属性として利用可能な List であると想定しています。Map を使用する場合、マップエントリキーが値として使用され、マップエントリの値が表示されるラベルとして使用されます。itemValue を使用して値のプロパティ名を指定し、itemLabel を使用してラベルを指定できるカスタムオブジェクトを使用することもできます。

radiobutton タグ

このタグは、type が radio に設定された HTML input 要素をレンダリングします。

典型的な使用パターンには、次の例に示すように、同じプロパティにバインドされているが値が異なる複数のタグインスタンスが含まれます。

<tr>
    <td>Sex:</td>
    <td>
        Male: <form:radiobutton path="sex" value="M"/> <br/>
        Female: <form:radiobutton path="sex" value="F"/>
    </td>
</tr>
radiobuttons タグ

このタグは、type が radio に設定された複数の HTML input 要素をレンダリングします。

checkboxes タグと同様に、使用可能なオプションをランタイム変数として渡したい場合があります。この使用箇所では、radiobuttons タグを使用できます。items プロパティで利用可能なオプションを含む ArrayList、または Map を渡します。Map を使用する場合、マップエントリキーが値として使用され、マップエントリの値が表示されるラベルとして使用されます。次の例に示すように、itemValue を使用して値のプロパティ名を、itemLabel を使用してラベルのプロパティ名を指定できるカスタムオブジェクトを使用することもできます。

<tr>
    <td>Sex:</td>
    <td><form:radiobuttons path="sex" items="${sexOptions}"/></td>
</tr>
password タグ

このタグは、バインドされた値で password に設定されたタイプで HTML input タグをレンダリングします。

<tr>
    <td>Password:</td>
    <td>
        <form:password path="password"/>
    </td>
</tr>

デフォルトでは、パスワード値は表示されないことに注意してください。パスワード値を表示したい場合は、次の例に示すように、showPassword 属性の値を true に設定できます。

<tr>
    <td>Password:</td>
    <td>
        <form:password path="password" value="^76525bvHGq" showPassword="true"/>
    </td>
</tr>
select タグ

このタグは、HTML 'select' 要素をレンダリングします。ネストされた option および options タグの使用と同様に、選択されたオプションへのデータバインディングをサポートします。

User にはスキルのリストがあると仮定します。対応する HTML は次のとおりです。

<tr>
    <td>Skills:</td>
    <td><form:select path="skills" items="${skills}"/></td>
</tr>

User’s スキルがハーブ学の場合、「スキル」行の HTML ソースは次のようになります。

<tr>
    <td>Skills:</td>
    <td>
        <select name="skills" multiple="true">
            <option value="Potions">Potions</option>
            <option value="Herbology" selected="selected">Herbology</option>
            <option value="Quidditch">Quidditch</option>
        </select>
    </td>
</tr>
option タグ

このタグは、HTML option 要素をレンダリングします。バインドされた値に基づいて selected を設定します。次の HTML は、その典型的な出力を示しています。

<tr>
    <td>House:</td>
    <td>
        <form:select path="house">
            <form:option value="Gryffindor"/>
            <form:option value="Hufflepuff"/>
            <form:option value="Ravenclaw"/>
            <form:option value="Slytherin"/>
        </form:select>
    </td>
</tr>

User’s 家がグリフィンドールにあった場合、「家」行の HTML ソースは次のようになります。

<tr>
    <td>House:</td>
    <td>
        <select name="house">
            <option value="Gryffindor" selected="selected">Gryffindor</option> (1)
            <option value="Hufflepuff">Hufflepuff</option>
            <option value="Ravenclaw">Ravenclaw</option>
            <option value="Slytherin">Slytherin</option>
        </select>
    </td>
</tr>
1selected 属性が追加されていることに注意してください。
options タグ

このタグは、HTML option 要素のリストをレンダリングします。バインドされた値に基づいて、selected 属性を設定します。次の HTML は、その典型的な出力を示しています。

<tr>
    <td>Country:</td>
    <td>
        <form:select path="country">
            <form:option value="-" label="--Please Select"/>
            <form:options items="${countryList}" itemValue="code" itemLabel="name"/>
        </form:select>
    </td>
</tr>

User が英国に住んでいた場合、「Country」行の HTML ソースは次のようになります。

<tr>
    <td>Country:</td>
    <td>
        <select name="country">
            <option value="-">--Please Select</option>
            <option value="AT">Austria</option>
            <option value="UK" selected="selected">United Kingdom</option> (1)
            <option value="US">United States</option>
        </select>
    </td>
</tr>
1selected 属性が追加されていることに注意してください。

前の例が示すように、option タグと options タグを組み合わせて使用すると、同じ標準 HTML が生成されますが、表示専用の JSP で値を明示的に指定できます。例:「- 選択してください」。

items 属性には、通常、アイテムオブジェクトのコレクションまたは配列が設定されます。itemValue および itemLabel は、指定されている場合、それらのアイテムオブジェクトの Bean プロパティを参照します。それ以外の場合、アイテムオブジェクト自体は文字列に変換されます。または、アイテムの Map を指定できます。この場合、マップキーはオプション値として解釈され、マップ値はオプションラベルに対応します。itemValue または itemLabel (または両方)が偶然指定された場合、アイテム値プロパティはマップキーに適用され、アイテムラベルプロパティはマップ値に適用されます。

textarea タグ

このタグは、HTML textarea 要素をレンダリングします。次の HTML は、その典型的な出力を示しています。

<tr>
    <td>Notes:</td>
    <td><form:textarea path="notes" rows="3" cols="20"/></td>
    <td><form:errors path="notes"/></td>
</tr>
hidden タグ

このタグは、バインドされた値で type が hidden に設定された HTML input タグをレンダリングします。非バインドの非表示値を送信するには、type を hidden に設定して HTML input タグを使用します。次の HTML は、その典型的な出力を示しています。

<form:hidden path="house"/>

house 値を隠し値として送信することを選択した場合、HTML は次のようになります。

<input name="house" type="hidden" value="Gryffindor"/>
errors タグ

このタグは、HTML span 要素のフィールドエラーをレンダリングします。コントローラーで作成されたエラー、またはコントローラーに関連付けられたバリデーターによって作成されたエラーへのアクセスを提供します。

フォームを送信した後、firstName および lastName フィールドのすべてのエラーメッセージを表示するとします。次の例が示すように、UserValidator と呼ばれる User クラスのインスタンスのバリデーターがあります。

Java
public class UserValidator implements Validator {

    public boolean supports(Class candidate) {
        return User.class.isAssignableFrom(candidate);
    }

    public void validate(Object obj, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.");
    }
}
Kotlin
class UserValidator : Validator {

    override fun supports(candidate: Class<*>): Boolean {
        return User::class.java.isAssignableFrom(candidate)
    }

    override fun validate(obj: Any, errors: Errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.")
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.")
    }
}

form.jsp は次のようになります。

<form:form>
    <table>
        <tr>
            <td>First Name:</td>
            <td><form:input path="firstName"/></td>
            <%-- Show errors for firstName field --%>
            <td><form:errors path="firstName"/></td>
        </tr>

        <tr>
            <td>Last Name:</td>
            <td><form:input path="lastName"/></td>
            <%-- Show errors for lastName field --%>
            <td><form:errors path="lastName"/></td>
        </tr>
        <tr>
            <td colspan="3">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form:form>

firstName および lastName フィールドの値が空のフォームを送信すると、HTML は次のようになります。

<form method="POST">
    <table>
        <tr>
            <td>First Name:</td>
            <td><input name="firstName" type="text" value=""/></td>
            <%-- Associated errors to firstName field displayed --%>
            <td><span name="firstName.errors">Field is required.</span></td>
        </tr>

        <tr>
            <td>Last Name:</td>
            <td><input name="lastName" type="text" value=""/></td>
            <%-- Associated errors to lastName field displayed --%>
            <td><span name="lastName.errors">Field is required.</span></td>
        </tr>
        <tr>
            <td colspan="3">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form>

特定のページのエラーのリスト全体を表示する場合はどうなるでしょうか?次の例は、errors タグがいくつかの基本的なワイルドカード機能もサポートすることを示しています。

  • path="*": すべてのエラーを表示します。

  • path="lastName"lastName フィールドに関連するすべてのエラーを表示します。

  • path を省略すると、オブジェクトエラーのみが表示されます。

次の例では、ページの上部にエラーのリストが表示され、フィールドの横にフィールド固有のエラーが続きます。

<form:form>
    <form:errors path="*" cssClass="errorBox"/>
    <table>
        <tr>
            <td>First Name:</td>
            <td><form:input path="firstName"/></td>
            <td><form:errors path="firstName"/></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><form:input path="lastName"/></td>
            <td><form:errors path="lastName"/></td>
        </tr>
        <tr>
            <td colspan="3">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form:form>

HTML は次のようになります。

<form method="POST">
    <span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span>
    <table>
        <tr>
            <td>First Name:</td>
            <td><input name="firstName" type="text" value=""/></td>
            <td><span name="firstName.errors">Field is required.</span></td>
        </tr>

        <tr>
            <td>Last Name:</td>
            <td><input name="lastName" type="text" value=""/></td>
            <td><span name="lastName.errors">Field is required.</span></td>
        </tr>
        <tr>
            <td colspan="3">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form>

spring-form.tld タグライブラリ記述子(TLD)は spring-webmvc.jar に含まれています。個々のタグに関する包括的なリファレンスについては、API 参照を参照するか、タグライブラリの説明を参照してください。

HTTP メソッド変換

REST の重要な原則は、「Uniform Interface」の使用です。これは、GET、PUT、POST、および DELETE という同じ 4 つの HTTP メソッドを使用して、すべてのリソース(URL)を操作できることを意味します。HTTP 仕様では、メソッドごとに正確なセマンティクスが定義されています。たとえば、GET は常に安全な操作である必要があります。つまり、副作用がなく、PUT または DELETE は i 等である必要があります。つまり、これらの操作を何度も繰り返すことができますが、最終結果は同じです。HTTP はこれら 4 つのメソッドを定義しますが、HTML は GET と POST の 2 つのみをサポートします。幸いなことに、2 つの回避策があります。JavaScript を使用して PUT または DELETE を実行するか、「実際の」メソッドを追加パラメーターとして POST を実行できます(HTML フォームの非表示入力フィールドとしてモデル化)。Spring の HiddenHttpMethodFilter はこの後者のトリックを使用します。このフィルターはプレーンなサーブレットフィルターであるため、(Spring MVC だけでなく)任意の Web フレームワークと組み合わせて使用できます。このフィルターを web.xml に追加すると、非表示の method パラメーターを持つ POST が対応する HTTP メソッドリクエストに変換されます。

HTTP メソッド変換をサポートするために、Spring MVC フォームタグが更新され、HTTP メソッドの設定がサポートされました。例:次のスニペットは、Pet Clinic サンプルからのものです。

<form:form method="delete">
    <p class="submit"><input type="submit" value="Delete Pet"/></p>
</form:form>

上記の例では、HTTP POST を実行し、「実際の」DELETE メソッドをリクエストパラメーターの背後に隠しています。次の例に示すように、web.xml で定義されている HiddenHttpMethodFilter によって取得されます。

<filter>
    <filter-name>httpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>httpMethodFilter</filter-name>
    <servlet-name>petclinic</servlet-name>
</filter-mapping>

次の例は、対応する @Controller メソッドを示しています。

Java
@RequestMapping(method = RequestMethod.DELETE)
public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
    this.clinic.deletePet(petId);
    return "redirect:/owners/" + ownerId;
}
Kotlin
@RequestMapping(method = [RequestMethod.DELETE])
fun deletePet(@PathVariable ownerId: Int, @PathVariable petId: Int): String {
    clinic.deletePet(petId)
    return "redirect:/owners/$ownerId"
}
HTML5 タグ

Spring フォームタグライブラリを使用すると、動的属性を入力できます。つまり、HTML5 固有の属性を入力できます。

フォーム input タグは、text 以外のタイプ属性の入力をサポートしています。これは、emaildaterange などの新しい HTML5 固有の入力タイプをレンダリングできるようにすることを目的としています。text がデフォルトのタイプであるため、type='text' を入力する必要はありません。

1.10.6. Tiles

Spring を使用する Web アプリケーションでは、Tiles を(他のビューテクノロジーと同様に)統合できます。このセクションでは、その方法を大まかに説明します。

このセクションでは、org.springframework.web.servlet.view.tiles3 パッケージの Tiles バージョン 3 に対する Spring のサポートに焦点を当てています。
依存関係

Tiles を使用できるようにするには、Tiles バージョン 3.0.1 以上への依存関係とその推移的な依存関係 (Apache) をプロジェクトに追加する必要があります。

構成

Tiles を使用するには、定義を含むファイルを使用して構成する必要があります(定義および Tiles のその他の概念の基本情報については、https://tiles.apache.org (英語) を参照してください)。Spring では、これは TilesConfigurer を使用して行われます。次の ApplicationContext 構成の例は、その方法を示しています。

<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
    <property name="definitions">
        <list>
            <value>/WEB-INF/defs/general.xml</value>
            <value>/WEB-INF/defs/widgets.xml</value>
            <value>/WEB-INF/defs/administrator.xml</value>
            <value>/WEB-INF/defs/customer.xml</value>
            <value>/WEB-INF/defs/templates.xml</value>
        </list>
    </property>
</bean>

上記の例では、定義を含む 5 つのファイルを定義しています。ファイルはすべて WEB-INF/defs ディレクトリにあります。WebApplicationContext の初期化時に、ファイルがロードされ、定義ファクトリが初期化されます。それが完了すると、定義ファイルに含まれる Tiles は、Spring Web アプリケーション内のビューとして使用できます。ビューを使用するには、Spring で使用される他のビューテクノロジーと同様に、ViewResolver が必要です。UrlBasedViewResolver と ResourceBundleViewResolver の 2 つの実装のいずれかを使用できます。

次の例に示すように、アンダースコアを追加してからロケールを追加することにより、ロケール固有の Tiles 定義を指定できます。

<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
    <property name="definitions">
        <list>
            <value>/WEB-INF/defs/tiles.xml</value>
            <value>/WEB-INF/defs/tiles_fr_FR.xml</value>
        </list>
    </property>
</bean>

上記の構成では、tiles_fr_FR.xml は fr_FR ロケールのリクエストに使用され、tiles.xml はデフォルトで使用されます。

アンダースコアはロケールを示すために使用されるため、Tiles 定義のファイル名ではアンダースコアを使用しないことをお勧めします。
UrlBasedViewResolver

UrlBasedViewResolver は、解決する必要がある各ビューに対して、指定された viewClass をインスタンス化します。次の Bean は UrlBasedViewResolver を定義しています:

<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/>
</bean>
ResourceBundleViewResolver

ResourceBundleViewResolver には、リゾルバーが使用できるビュー名とビュークラスを含むプロパティファイルを提供する必要があります。次の例は、ResourceBundleViewResolver の Bean 定義と、対応するビュー名とビュークラス(Pet Clinic サンプルから取得)を示しています。

<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
</bean>
    ...
    welcomeView.(class)=org.springframework.web.servlet.view.tiles3.TilesView
    welcomeView.url=welcome (this is the name of a Tiles definition)

    vetsView.(class)=org.springframework.web.servlet.view.tiles3.TilesView
    vetsView.url=vetsView (again, this is the name of a Tiles definition)

    findOwnersForm.(class)=org.springframework.web.servlet.view.JstlView
    findOwnersForm.url=/WEB-INF/jsp/findOwners.jsp
    ...

ResourceBundleViewResolver を使用すると、さまざまなビューテクノロジーを簡単に組み合わせることができます。

TilesView クラスは JSTL(JSP 標準タグライブラリ)をサポートしていることに注意してください。

SimpleSpringPreparerFactory および SpringBeanPreparerFactory

高度な機能として、Spring は 2 つの特別な Tiles PreparerFactory 実装もサポートしています。Tiles 定義ファイルで ViewPreparer 参照を使用する方法の詳細については、Tiles の資料を参照してください。

SimpleSpringPreparerFactory を指定すると、指定された準備クラスに基づいて ViewPreparer インスタンスをオートワイヤーし、Spring のコンテナーコールバックを適用するとともに、構成済みの Spring BeanPostProcessors を適用できます。Spring のコンテキスト全体のアノテーション構成がアクティブになっている場合、ViewPreparer クラスのアノテーションが自動的に検出されて適用されます。デフォルトの PreparerFactory がそうであるように、これは Tiles 定義ファイルの準備クラスを期待することに注意してください。

SpringBeanPreparerFactory を指定して(クラスの代わりに)指定された作成者名で操作し、DispatcherServlet のアプリケーションコンテキストから対応する Spring Bean を取得できます。この場合、完全な Bean 作成プロセスは Spring アプリケーションコンテキストの制御下にあり、明示的な依存関係注入構成、スコープ付き Bean などを使用できます。(Tiles 定義で使用されているように)各作成者名に対して 1 つの Spring Bean 定義を定義する必要があることに注意してください。次の例は、TilesConfigurer Bean で SpringBeanPreparerFactory プロパティを定義する方法を示しています。

<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
    <property name="definitions">
        <list>
            <value>/WEB-INF/defs/general.xml</value>
            <value>/WEB-INF/defs/widgets.xml</value>
            <value>/WEB-INF/defs/administrator.xml</value>
            <value>/WEB-INF/defs/customer.xml</value>
            <value>/WEB-INF/defs/templates.xml</value>
        </list>
    </property>

    <!-- resolving preparer names as Spring bean definition names -->
    <property name="preparerFactoryClass"
            value="org.springframework.web.servlet.view.tiles3.SpringBeanPreparerFactory"/>

</bean>

1.10.7. RSS と Atom

AbstractAtomFeedView と AbstractRssFeedView は両方とも AbstractFeedView 基本クラスを継承し、それぞれ Atom ビューと RSS フィードビューを提供するために使用されます。それらは ROME (英語) プロジェクトに基づいており、パッケージ org.springframework.web.servlet.view.feed にあります。

AbstractAtomFeedView では、buildFeedEntries() メソッドを実装し、オプションで buildFeedMetadata() メソッドをオーバーライドする必要があります(デフォルトの実装は空です)。次の例は、その方法を示しています。

Java
public class SampleContentAtomView extends AbstractAtomFeedView {

    @Override
    protected void buildFeedMetadata(Map<String, Object> model,
            Feed feed, HttpServletRequest request) {
        // implementation omitted
    }

    @Override
    protected List<Entry> buildFeedEntries(Map<String, Object> model,
            HttpServletRequest request, HttpServletResponse response) throws Exception {
        // implementation omitted
    }
}
Kotlin
class SampleContentAtomView : AbstractAtomFeedView() {

    override fun buildFeedMetadata(model: Map<String, Any>,
            feed: Feed, request: HttpServletRequest) {
        // implementation omitted
    }

    override fun buildFeedEntries(model: Map<String, Any>,
            request: HttpServletRequest, response: HttpServletResponse): List<Entry> {
        // implementation omitted
    }
}

次の例が示すように、AbstractRssFeedView の実装にも同様の要件が適用されます。

Java
public class SampleContentRssView extends AbstractRssFeedView {

    @Override
    protected void buildFeedMetadata(Map<String, Object> model,
            Channel feed, HttpServletRequest request) {
        // implementation omitted
    }

    @Override
    protected List<Item> buildFeedItems(Map<String, Object> model,
            HttpServletRequest request, HttpServletResponse response) throws Exception {
        // implementation omitted
    }
}
Kotlin
class SampleContentRssView : AbstractRssFeedView() {

    override fun buildFeedMetadata(model: Map<String, Any>,
                                feed: Channel, request: HttpServletRequest) {
        // implementation omitted
    }

    override fun buildFeedItems(model: Map<String, Any>,
            request: HttpServletRequest, response: HttpServletResponse): List<Item> {
        // implementation omitted
    }
}

buildFeedItems() および buildFeedEntries() メソッドは、ロケールにアクセスする必要がある場合に HTTP リクエストを渡します。HTTP レスポンスは、Cookie または他の HTTP ヘッダーの設定のためにのみ渡されます。フィードは、メソッドが戻った後、レスポンスオブジェクトに自動的に書き込まれます。

Atom ビューの作成例については、Alef Arendsen の Spring チームブログエントリ (英語) を参照してください。

1.10.8. PDF および Excel

Spring は、PDF や Excel スプレッドシートなど、HTML 以外の出力を返す方法を提供します。このセクションでは、これらの機能の使用方法について説明します。

ドキュメントビューの概要

HTML ページは、ユーザーがモデル出力を表示するのに常に最適な方法とは限りません。Spring を使用すると、モデルデータから PDF ドキュメントまたは Excel スプレッドシートを簡単に動的に生成できます。ドキュメントはビューであり、クライアント PC がレスポンスしてスプレッドシートまたは PDF ビューアーアプリケーションを実行できるようにするために、正しいコンテンツタイプでサーバーからストリーミングされます。

Excel ビューを使用するには、Apache POI ライブラリをクラスパスに追加する必要があります。PDF を生成するには、OpenPDF ライブラリを(できれば)追加する必要があります。

可能であれば、基礎となるドキュメント生成ライブラリの最新バージョンを使用する必要があります。特に、古い PDF の iText 2.1.7 ではなく OpenPDF(OpenPDF 1.2.12 など)を強くお勧めします。これは、OpenPDF が積極的に維持され、信頼できない PDF コンテンツの重要な脆弱性を修正するためです。
PDF ビュー

次の例に示すように、単語リストの単純な PDF ビューは org.springframework.web.servlet.view.document.AbstractPdfView を継承し、buildPdfDocument() メソッドを実装できます。

Java
public class PdfWordList extends AbstractPdfView {

    protected void buildPdfDocument(Map<String, Object> model, Document doc, PdfWriter writer,
            HttpServletRequest request, HttpServletResponse response) throws Exception {

        List<String> words = (List<String>) model.get("wordList");
        for (String word : words) {
            doc.add(new Paragraph(word));
        }
    }
}
Kotlin
class PdfWordList : AbstractPdfView() {

    override fun buildPdfDocument(model: Map<String, Any>, doc: Document, writer: PdfWriter,
            request: HttpServletRequest, response: HttpServletResponse) {

        val words = model["wordList"] as List<String>
        for (word in words) {
            doc.add(Paragraph(word))
        }
    }
}

コントローラーは、そのようなビューを外部ビュー定義(名前で参照)から返すか、ハンドラーメソッドから View インスタンスとして返すことができます。

Excel ビュー

Spring Framework 4.2, から、org.springframework.web.servlet.view.document.AbstractXlsView は Excel ビューの基本クラスとして提供されます。これは Apache POI に基づいており、古い AbstractExcelView クラスに取って代わる特殊なサブクラス(AbstractXlsxView および AbstractXlsxStreamingView)を備えています。

プログラミングモデルは AbstractPdfView に似ており、buildExcelDocument() は中心的なテンプレートメソッドであり、コントローラーは外部定義(名前による)またはハンドラーメソッドから View インスタンスからそのようなビューを返すことができます。

1.10.9. Jackson

Spring は、Jackson JSON ライブラリのサポートを提供します。

Jackson ベースの JSON MVC ビュー

MappingJackson2JsonView は、Jackson ライブラリの ObjectMapper を使用して、レスポンスコンテンツを JSON としてレンダリングします。デフォルトでは、モデルマップのコンテンツ全体(フレームワーク固有のクラスを除く)は JSON としてエンコードされます。マップのコンテンツをフィルターする必要がある場合は、modelKeys プロパティを使用して、エンコードするモデル属性の特定のセットを指定できます。extractValueFromSingleKeyModel プロパティを使用して、モデル属性のマップとしてではなく、単一キーモデルの値を直接抽出および直列化することもできます。

Jackson が提供するアノテーションを使用して、必要に応じて JSON マッピングをカスタマイズできます。さらに制御が必要な場合は、特定のタイプのカスタム JSON シリアライザーとデシリアライザーを提供する必要がある場合に、ObjectMapper プロパティを介してカスタム ObjectMapper を挿入できます。

Jackson ベースの XML ビュー

MappingJackson2XmlView は、Jackson XML 拡張機能 (GitHub) XmlMapper を使用して、レスポンスコンテンツを XML としてレンダリングします。モデルに複数のエントリが含まれる場合、modelKey Bean プロパティを使用して、直列化されるオブジェクトを明示的に設定する必要があります。モデルに単一のエントリが含まれる場合、自動的に直列化されます。

JAXB または Jackson が提供するアノテーションを使用して、必要に応じて XML マッピングをカスタマイズできます。さらに制御が必要な場合は、特定のタイプのシリアライザーとデシリアライザーを提供する必要があるカスタム XML の場合に、ObjectMapper プロパティを介してカスタム XmlMapper を挿入できます。

1.10.10. XML マーシャリング

MarshallingView は、XML Marshaller (org.springframework.oxm パッケージで定義)を使用して、レスポンスコンテンツを XML としてレンダリングします。MarshallingView インスタンスの modelKey Bean プロパティを使用して、マーシャリングされるオブジェクトを明示的に設定できます。または、ビューはすべてのモデルプロパティを反復処理し、Marshaller でサポートされている最初のタイプをマーシャリングします。org.springframework.oxm パッケージの機能の詳細については、O/X マッパーを使用した XML のマーシャリングを参照してください。

1.10.11. XSLT ビュー

XSLT は XML の変換言語であり、Web アプリケーション内のビューテクノロジーとして一般的です。XSLT は、アプリケーションが自然に XML を処理する場合、またはモデルを XML に簡単に変換できる場合、ビューテクノロジとして適しています。次のセクションでは、XML データをモデルデータとして生成し、Spring Web MVC アプリケーションで XSLT で変換する方法を示します。

この例は、Controller に単語のリストを作成し、モデルマップに追加する簡単な Spring アプリケーションです。XSLT ビューのビュー名とともに、マップが返されます。Spring Web MVC の Controller インターフェースの詳細については、アノテーション付きコントローラーを参照してください。XSLT コントローラーは、単語のリストを単純な XML ドキュメントに変換し、すぐに変換できるようにします。

Bean

構成は、単純な Spring Web アプリケーションの標準です。MVC 構成では、XsltViewResolver Bean および通常の MVC アノテーション構成を定義する必要があります。次の例は、その方法を示しています。

Java
@EnableWebMvc
@ComponentScan
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public XsltViewResolver xsltViewResolver() {
        XsltViewResolver viewResolver = new XsltViewResolver();
        viewResolver.setPrefix("/WEB-INF/xsl/");
        viewResolver.setSuffix(".xslt");
        return viewResolver;
    }
}
Kotlin
@EnableWebMvc
@ComponentScan
@Configuration
class WebConfig : WebMvcConfigurer {

    @Bean
    fun xsltViewResolver() = XsltViewResolver().apply {
        setPrefix("/WEB-INF/xsl/")
        setSuffix(".xslt")
    }
}
コントローラー

また、単語生成ロジックをカプセル化するコントローラーも必要です。

コントローラーロジックは @Controller クラスにカプセル化され、ハンドラーメソッドは次のように定義されます。

Java
@Controller
public class XsltController {

    @RequestMapping("/")
    public String home(Model model) throws Exception {
        Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        Element root = document.createElement("wordList");

        List<String> words = Arrays.asList("Hello", "Spring", "Framework");
        for (String word : words) {
            Element wordNode = document.createElement("word");
            Text textNode = document.createTextNode(word);
            wordNode.appendChild(textNode);
            root.appendChild(wordNode);
        }

        model.addAttribute("wordList", root);
        return "home";
    }
}
Kotlin
import org.springframework.ui.set

@Controller
class XsltController {

    @RequestMapping("/")
    fun home(model: Model): String {
        val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()
        val root = document.createElement("wordList")

        val words = listOf("Hello", "Spring", "Framework")
        for (word in words) {
            val wordNode = document.createElement("word")
            val textNode = document.createTextNode(word)
            wordNode.appendChild(textNode)
            root.appendChild(wordNode)
        }

        model["wordList"] = root
        return "home"
    }
}

これまでのところ、DOM ドキュメントを作成してモデルマップに追加しただけです。XML ファイルを Resource としてロードし、カスタム DOM ドキュメントの代わりに使用することもできます。

オブジェクトグラフを自動的に「支配」するソフトウェアパッケージがありますが、Spring 内では、選択した方法でモデルから DOM を作成する完全な柔軟性があります。これにより、XML の変換がモデルデータの構造に大きく影響し、DOM 化プロセスを管理するツールを使用する際の危険を防ぎます。

変換

最後に、XsltViewResolver は「ホーム」XSLT テンプレートファイルを解決し、DOM ドキュメントをそれに統合してビューを生成します。XsltViewResolver 構成に示されているように、XSLT テンプレートは WEB-INF/xsl ディレクトリの war ファイルに存在し、xslt ファイル拡張子で終わります。

次の例は、XSLT 変換を示しています。

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="html" omit-xml-declaration="yes"/>

    <xsl:template match="/">
        <html>
            <head><title>Hello!</title></head>
            <body>
                <h1>My First Words</h1>
                <ul>
                    <xsl:apply-templates/>
                </ul>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="word">
        <li><xsl:value-of select="."/></li>
    </xsl:template>

</xsl:stylesheet>

上記の変換は、次の HTML としてレンダリングされます。

<html>
    <head>
        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Hello!</title>
    </head>
    <body>
        <h1>My First Words</h1>
        <ul>
            <li>Hello</li>
            <li>Spring</li>
            <li>Framework</li>
        </ul>
    </body>
</html>

1.11. MVC 構成

MVC Java 構成と MVC XML 名前空間は、ほとんどのアプリケーションに適したデフォルト構成と、それをカスタマイズする構成 API を提供します。

構成 API にはない高度なカスタマイズについては、高度な Java 構成および高度な XML 構成を参照してください。

MVC Java 構成および MVC 名前空間によって作成された基礎となる Bean を理解する必要はありません。詳細については、特別な Bean タイプおよび Web MVC Config を参照してください。

1.11.1. MVC 設定を有効にする

Java 構成では、次の例に示すように、@EnableWebMvc アノテーションを使用して MVC 構成を有効にできます。

Java
@Configuration
@EnableWebMvc
public class WebConfig {
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig

XML 構成では、次の例に示すように、<mvc:annotation-driven> 要素を使用して MVC 構成を有効にできます。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven/>

</beans>

上記の例では、多数の Spring MVC インフラストラクチャ Bean を登録し、クラスパスで使用可能な依存関係(たとえば、JSON、XML などのペイロードコンバーター)に適応します。

1.11.2. MVC Config API

Java 構成では、次の例に示すように、WebMvcConfigurer インターフェースを実装できます。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    // Implement configuration methods...
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
    // Implement configuration methods...
}

XML では、<mvc:annotation-driven/> の属性とサブ要素を確認できます。IDE の Spring MVC XML スキーマ (英語) を表示するか、コード補完機能を使用して、使用可能な属性とサブエレメントを見つけることができます。

1.11.3. 型変換

デフォルトでは、@NumberFormat および @DateTimeFormat アノテーションのサポートを含む、Number および Date タイプのフォーマッターがインストールされます。Joda-Time がクラスパスに存在する場合、Joda-Time フォーマットライブラリの完全サポートもインストールされます。

Java 構成では、次の例に示すように、カスタムフォーマッターとコンバーターを登録できます。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // ...
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun addFormatters(registry: FormatterRegistry) {
        // ...
    }
}

次の例は、XML で同じ構成を実現する方法を示しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven conversion-service="conversionService"/>

    <bean id="conversionService"
            class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="org.example.MyConverter"/>
            </set>
        </property>
        <property name="formatters">
            <set>
                <bean class="org.example.MyFormatter"/>
                <bean class="org.example.MyAnnotationFormatterFactory"/>
            </set>
        </property>
        <property name="formatterRegistrars">
            <set>
                <bean class="org.example.MyFormatterRegistrar"/>
            </set>
        </property>
    </bean>

</beans>
FormatterRegistrar 実装をいつ使用するかの詳細については、FormatterRegistrar SPI および FormattingConversionServiceFactoryBean  を参照してください。

1.11.4. 検証

デフォルトでは、Bean バリデーションがクラスパスに存在する場合(たとえば、Hibernate Validator)、LocalValidatorFactoryBean は、コントローラーメソッドの引数の @Valid および Validated で使用するグローバルバリデーターとして登録されます。

Java 構成では、次の例に示すように、グローバル Validator インスタンスをカスタマイズできます。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public Validator getValidator() {
        // ...
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun getValidator(): Validator {
        // ...
    }
}

次の例は、XML で同じ構成を実現する方法を示しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven validator="globalValidator"/>

</beans>

次の例に示すように、Validator 実装をローカルに登録することもできます。

Java
@Controller
public class MyController {

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

    @InitBinder
    protected fun initBinder(binder: WebDataBinder) {
        binder.addValidators(FooValidator())
    }
}
LocalValidatorFactoryBean をどこかに挿入する必要がある場合は、MVC 構成で宣言されたものとの競合を避けるために、Bean を作成し、@Primary でマークします。

1.11.5. インターセプター

Java 構成では、次の例に示すように、インターセプターを登録して受信リクエストに適用できます。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LocaleChangeInterceptor());
        registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
        registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun addInterceptors(registry: InterceptorRegistry) {
        registry.addInterceptor(LocaleChangeInterceptor())
        registry.addInterceptor(ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**")
        registry.addInterceptor(SecurityInterceptor()).addPathPatterns("/secure/*")
    }
}

次の例は、XML で同じ構成を実現する方法を示しています。

<mvc:interceptors>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/admin/**"/>
        <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/secure/*"/>
        <bean class="org.example.SecurityInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

1.11.6. コンテンツタイプ

Spring MVC がリクエストからリクエストされたメディアタイプを決定する方法を構成できます(たとえば、Accept ヘッダー、URL パス拡張、クエリパラメーターなど)。

デフォルトでは、URL パスの拡張子が最初にチェックされます。jsonxmlrss、および atom は既知の拡張子として登録されます(クラスパスの依存関係によります)。Accept ヘッダーが 2 番目にチェックされます。

これらの既定値を Accept ヘッダーのみに変更することを検討してください。URL ベースのコンテンツタイプ解決を使用する必要がある場合は、パス拡張に対するクエリパラメーター戦略の使用を検討してください。詳細については、サフィックスマッチおよびサフィックスマッチと RFD を参照してください。

Java 構成では、次の例に示すように、リクエストされたコンテンツタイプの解決をカスタマイズできます。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.mediaType("json", MediaType.APPLICATION_JSON);
        configurer.mediaType("xml", MediaType.APPLICATION_XML);
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureContentNegotiation(configurer: ContentNegotiationConfigurer) {
        configurer.mediaType("json", MediaType.APPLICATION_JSON)
        configurer.mediaType("xml", MediaType.APPLICATION_XML)
    }
}

次の例は、XML で同じ構成を実現する方法を示しています。

<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>

<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="mediaTypes">
        <value>
            json=application/json
            xml=application/xml
        </value>
    </property>
</bean>

1.11.7. メッセージコンバーター

Java 構成で HttpMessageConverter をカスタマイズするには、configureMessageConverters()(Javadoc) をオーバーライドする(Spring MVC によって作成されたデフォルトのコンバーターを置き換える)か、extendMessageConverters()(Javadoc) をオーバーライドする(デフォルトのコンバーターをカスタマイズするか、デフォルトのコンバーターに追加のコンバーターを追加する)ことができます。

次の例では、XML コンバーターと Jackson JSON コンバーターを、デフォルトの代わりにカスタマイズされた ObjectMapper で追加します。

Java
@Configuration
@EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
                .indentOutput(true)
                .dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
                .modulesToInstall(new ParameterNamesModule());
        converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
        converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfiguration : WebMvcConfigurer {

    override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
        val builder = Jackson2ObjectMapperBuilder()
                .indentOutput(true)
                .dateFormat(SimpleDateFormat("yyyy-MM-dd"))
                .modulesToInstall(ParameterNamesModule())
        converters.add(MappingJackson2HttpMessageConverter(builder.build()))
        converters.add(MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()))

上記の例では、Jackson2ObjectMapperBuilder(Javadoc) を使用して、インデントを有効にして MappingJackson2HttpMessageConverter と MappingJackson2XmlHttpMessageConverter の両方に共通の構成を作成し、日付形式をカスタマイズし、パラメーター名へのアクセスのサポートを追加する jackson-module-parameter-names (GitHub) の登録を追加します(Java 8 で追加された機能)。

このビルダーは、Jackson のデフォルトプロパティを次のようにカスタマイズします。

また、次の既知のモジュールがクラスパスで検出された場合、自動的に登録します。

Jackson XML サポートでインデントを有効にするには、jackson-dataformat-xml (英語) に加えて woodstox-core-asl (英語) 依存関係が必要です。

他の興味深い Jackson モジュールが利用可能です:

次の例は、XML で同じ構成を実現する方法を示しています。

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper" ref="objectMapper"/>
        </bean>
        <bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
            <property name="objectMapper" ref="xmlMapper"/>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
      p:indentOutput="true"
      p:simpleDateFormat="yyyy-MM-dd"
      p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>

<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>

1.11.8. ビューコントローラー

これは、呼び出されるとすぐにビューに転送される ParameterizableViewController を定義するためのショートカットです。ビューがレスポンスを生成する前に実行する Java コントローラーロジックがない静的な場合に使用できます。

次の Java 構成の例は、/ のリクエストを home というビューに転送します。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("home");
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun addViewControllers(registry: ViewControllerRegistry) {
        registry.addViewController("/").setViewName("home")
    }
}

次の例では、<mvc:view-controller> 要素を使用して、前の例と同じことを XML で実現しています。

<mvc:view-controller path="/" view-name="home"/>

@RequestMapping メソッドが任意の HTTP メソッドの URL にマッピングされている場合、View Controller を使用して同じ URL を処理することはできません。これは、URL によるアノテーション付きコントローラーへの一致が、405(METHOD_NOT_ALLOWED)、415(UNSUPPORTED_MEDIA_TYPE)、または同様のレスポンスをデバッグを支援するためにクライアントに送信できるように、エンドポイント所有権の十分に強力な指標と見なされるためです。このため、アノテーション付きコントローラーと View コントローラー間で URL 処理を分割しないようにすることをお勧めします。

1.11.9. リゾルバーを表示

MVC 構成は、ビューリゾルバーの登録を簡素化します。

次の Java 構成例では、JSP および Jackson を JSON レンダリングのデフォルト View として使用して、コンテンツネゴシエーションビューの解決を構成します。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.enableContentNegotiation(new MappingJackson2JsonView());
        registry.jsp();
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.enableContentNegotiation(MappingJackson2JsonView())
        registry.jsp()
    }
}

次の例は、XML で同じ構成を実現する方法を示しています。

<mvc:view-resolvers>
    <mvc:content-negotiation>
        <mvc:default-views>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </mvc:default-views>
    </mvc:content-negotiation>
    <mvc:jsp/>
</mvc:view-resolvers>

ただし、FreeMarker、Tiles、Groovy マークアップ、およびスクリプトテンプレートも、基礎となるビューテクノロジーの構成を必要とすることに注意してください。

MVC 名前空間は専用の要素を提供します。次の例は、FreeMarker で機能します。

<mvc:view-resolvers>
    <mvc:content-negotiation>
        <mvc:default-views>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </mvc:default-views>
    </mvc:content-negotiation>
    <mvc:freemarker cache="false"/>
</mvc:view-resolvers>

<mvc:freemarker-configurer>
    <mvc:template-loader-path location="/freemarker"/>
</mvc:freemarker-configurer>

Java 構成では、次の例に示すように、それぞれの Configurer Bean を追加できます。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.enableContentNegotiation(new MappingJackson2JsonView());
        registry.freeMarker().cache(false);
    }

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("/freemarker");
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.enableContentNegotiation(MappingJackson2JsonView())
        registry.freeMarker().cache(false)
    }

    @Bean
    fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
        setTemplateLoaderPath("/freemarker")
    }
}

1.11.10. 静的リソース

このオプションは、Resource(Javadoc) ベースの場所のリストから静的リソースを提供する便利な方法を提供します。

次の例では、/resources で始まるリクエストが与えられると、相対パスを使用して、Web アプリケーションルートまたは /static のクラスパス上の /public に関連する静的リソースを見つけて処理します。ブラウザーのキャッシュを最大限に使用し、ブラウザーが行う HTTP リクエストを削減するために、リソースには 1 年間の有効期限があります。Last-Modified ヘッダーも評価され、存在する場合は 304 ステータスコードが返されます。

次のリストは、Java 構成でこれを行う方法を示しています。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

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

    override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public", "classpath:/static/")
                .setCachePeriod(31556926)
    }
}

次の例は、XML で同じ構成を実現する方法を示しています。

<mvc:resources mapping="/resources/**"
    location="/public, classpath:/static/"
    cache-period="31556926" />

リソースハンドラーは、ResourceResolver(Javadoc) 実装と ResourceTransformer(Javadoc) 実装のチェーンもサポートします。これらを使用して、最適化されたリソースを操作するためのツールチェーンを作成できます。

コンテンツ、固定アプリケーションバージョン、またはその他から計算された MD5 ハッシュに基づいて、バージョン付きリソース URL に VersionResourceResolver を使用できます。ContentVersionStrategy (MD5 ハッシュ)は、モジュールローダーで使用される JavaScript リソースなどのいくつかの顕著な例外を除き、適切な選択です。

次の例は、Java 構成で VersionResourceResolver を使用する方法を示しています。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public/")
                .resourceChain(true)
                .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public/")
                .resourceChain(true)
                .addResolver(VersionResourceResolver().addContentVersionStrategy("/**"))
    }
}

次の例は、XML で同じ構成を実現する方法を示しています。

<mvc:resources mapping="/resources/**" location="/public/">
    <mvc:resource-chain resource-cache="true">
        <mvc:resolvers>
            <mvc:version-resolver>
                <mvc:content-version-strategy patterns="/**"/>
            </mvc:version-resolver>
        </mvc:resolvers>
    </mvc:resource-chain>
</mvc:resources>

次に、ResourceUrlProvider を使用して URL を書き換え、リゾルバーとトランスフォーマーのチェーン全体を適用します(たとえば、バージョンを挿入します)。MVC 構成は ResourceUrlProvider Bean を提供するため、他に注入することができます。Thymeleaf、JSP、FreeMarker、および HttpServletResponse#encodeURL に依存する URL タグを使用するその他の場合、ResourceUrlEncodingFilter を使用して書き換えを透過的にすることもできます。

EncodedResourceResolver (たとえば、gzip または brotli でエンコードされたリソースを提供するため)と VersionResourceResolver の両方を使用する場合、この順序で登録する必要があることに注意してください。これにより、エンコードされていないファイルに基づいて、コンテンツベースのバージョンが常に確実に計算されます。

WebJars (英語) は、org.webjars:webjars-locator-core ライブラリがクラスパスに存在するときに自動的に登録される WebJarsResourceResolver でもサポートされます。リゾルバーは、jar のバージョンを含むように URL を書き換えることができ、バージョンのない受信 URL(たとえば、/jquery/jquery.min.js から /jquery/1.2.0/jquery.min.js など)と照合することもできます。

1.11.11. デフォルトのサーブレット

Spring MVC では、DispatcherServlet を / にマッピングできます(コンテナーのデフォルトサーブレットのマッピングをオーバーライドします)が、静的リソースリクエストはコンテナーのデフォルトサーブレットで処理できます。/** の URL マッピングと他の URL マッピングに関連する最低の優先順位で DefaultServletHttpRequestHandler を構成します。

このハンドラーは、すべてのリクエストをデフォルトのサーブレットに転送します。他のすべての URL HandlerMappings の順序で最後のままにしておく必要があります。<mvc:annotation-driven> を使用する場合がそうです。あるいは、独自のカスタマイズされた HandlerMapping インスタンスをセットアップする場合、その order プロパティを DefaultServletHttpRequestHandler の値、つまり Integer.MAX_VALUE よりも低い値に設定してください。

次の例は、デフォルトのセットアップを使用して機能を有効にする方法を示しています。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureDefaultServletHandling(configurer: DefaultServletHandlerConfigurer) {
        configurer.enable()
    }
}

次の例は、XML で同じ構成を実現する方法を示しています。

<mvc:default-servlet-handler/>

/ サーブレットマッピングをオーバーライドする際の注意点は、デフォルトサーブレットの RequestDispatcher はパスではなく名前で取得する必要があるということです。DefaultServletHttpRequestHandler は、ほとんどの主要なサーブレットコンテナー(Tomcat、Jetty、GlassFish、JBoss、Resin、WebLogic、および WebSphere を含む)の既知の名前のリストを使用して、起動時にコンテナーのデフォルトサーブレットを自動検出しようとします。デフォルトのサーブレットが別の名前でカスタム設定されている場合、またはデフォルトのサーブレット名が不明な別のサーブレットコンテナーが使用されている場合、次の例に示すように、デフォルトのサーブレットの名前を明示的に指定する必要があります:

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable("myCustomDefaultServlet");
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureDefaultServletHandling(configurer: DefaultServletHandlerConfigurer) {
        configurer.enable("myCustomDefaultServlet")
    }
}

次の例は、XML で同じ構成を実現する方法を示しています。

<mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/>

1.11.12. パスマッチング

パスの一致と URL の処理に関連するオプションをカスタマイズできます。個々のオプションの詳細については、PathMatchConfigurer(Javadoc) javadoc を参照してください。

次の例は、Java 構成でパスマッチングをカスタマイズする方法を示しています。

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer
            .setUseSuffixPatternMatch(true)
            .setUseTrailingSlashMatch(false)
            .setUseRegisteredSuffixPatternMatch(true)
            .setPathMatcher(antPathMatcher())
            .setUrlPathHelper(urlPathHelper())
            .addPathPrefix("/api",
                    HandlerTypePredicate.forAnnotation(RestController.class));
    }

    @Bean
    public UrlPathHelper urlPathHelper() {
        //...
    }

    @Bean
    public PathMatcher antPathMatcher() {
        //...
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configurePathMatch(configurer: PathMatchConfigurer) {
        configurer
            .setUseSuffixPatternMatch(true)
            .setUseTrailingSlashMatch(false)
            .setUseRegisteredSuffixPatternMatch(true)
            .setPathMatcher(antPathMatcher())
            .setUrlPathHelper(urlPathHelper())
            .addPathPrefix("/api",
                    HandlerTypePredicate.forAnnotation(RestController::class.java))
    }

    @Bean
    fun urlPathHelper(): UrlPathHelper {
        //...
    }

    @Bean
    fun antPathMatcher(): PathMatcher {
        //...
    }
}

次の例は、XML で同じ構成を実現する方法を示しています。

<mvc:annotation-driven>
    <mvc:path-matching
        suffix-pattern="true"
        trailing-slash="false"
        registered-suffixes-only="true"
        path-helper="pathHelper"
        path-matcher="pathMatcher"/>
</mvc:annotation-driven>

<bean id="pathHelper" class="org.example.app.MyPathHelper"/>
<bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>

1.11.13. 高度な Java 構成

@EnableWebMvc は DelegatingWebMvcConfiguration をインポートします。

  • Spring MVC アプリケーションにデフォルトの Spring 構成を提供する

  • WebMvcConfigurer 実装を検出して委譲し、その構成をカスタマイズします。

拡張モードの場合、次の例に示すように、WebMvcConfigurer を実装する代わりに、@EnableWebMvc を削除して DelegatingWebMvcConfiguration から直接拡張できます。

Java
@Configuration
public class WebConfig extends DelegatingWebMvcConfiguration {

    // ...
}
Kotlin
@Configuration
class WebConfig : DelegatingWebMvcConfiguration() {

    // ...
}

既存のメソッドを WebConfig に保持できますが、ベースクラスからの Bean 宣言をオーバーライドすることもでき、クラスパス上に他の WebMvcConfigurer 実装をいくつでも持つことができます。

1.11.14. 高度な XML 構成

MVC 名前空間には拡張モードはありません。他の方法では変更できない Bean のプロパティをカスタマイズする必要がある場合は、次の例に示すように、Spring ApplicationContext の BeanPostProcessor ライフサイクルフックを使用できます。

Java
@Component
public class MyPostProcessor implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
        // ...
    }
}
Kotlin
@Component
class MyPostProcessor : BeanPostProcessor {

    override fun postProcessBeforeInitialization(bean: Any, name: String): Any {
        // ...
    }
}

MyPostProcessor を Bean として宣言する必要があることに注意してください。明示的に XML で、または <component-scan/> 宣言を介して検出させることができます。

1.12. HTTP/2

HTTP/2 をサポートするには Servlet 4 コンテナーが必要であり、Spring Framework 5 はサーブレット API 4 と互換性があります。プログラミングモデルの観点からは、アプリケーションで実行する必要のあることは何もありません。ただし、サーバー構成に関連する考慮事項があります。詳細については、HTTP/2 wiki ページ (GitHub) を参照してください。

サーブレット API は、HTTP/2 に関連する 1 つの構成を公開します。javax.servlet.http.PushBuilder を使用して、リソースをクライアントにプロアクティブにプッシュできます。これは、@RequestMapping メソッドのメソッド引数としてサポートされています。

2. REST クライアント

このセクションでは、REST エンドポイントへのクライアント側アクセスのオプションについて説明します。

2.1. RestTemplate

RestTemplate は、HTTP リクエストを実行する同期クライアントです。これはオリジナルの Spring REST クライアントであり、基礎となる HTTP クライアントライブラリ上でシンプルなテンプレートメソッド API を公開します。

5.0 の時点で、ノンブロッキング、リアクティブ WebClient は、RestTemplate の最新の代替手段を提供し、同期および非同期の両方、およびストリーミングシナリオを効率的にサポートします。RestTemplate は将来のバージョンで非推奨となり、今後主要な新機能は追加されません。

詳細は REST エンドポイントを参照してください。

2.2. WebClient

WebClient は、HTTP リクエストを実行するためのノンブロッキング、リアクティブクライアントです。5.0 で導入され、RestTemplate の最新の代替手段を提供します。同期シナリオと非同期シナリオの両方、およびストリーミングシナリオを効率的にサポートします。

RestTemplate とは対照的に、WebClient は以下をサポートします。

  • ノンブロッキング I/O。

  • Reactive Streams のバックプレッシャー。

  • より少ないハードウェアリソースで高い同時実行性。

  • Java 8 ラムダを活用する関数スタイルの流れるような API。

  • 同期および非同期の相互作用。

  • サーバーへのストリーミングまたはサーバーからのストリーミング。

詳細については、WebClient を参照してください。

3. テスト

このセクションでは、Spring MVC アプリケーション用に spring-test で使用可能なオプションを要約します。

  • サーブレット API モック : ユニットテストコントローラー、フィルター、およびその他の Web コンポーネントのサーブレット API 契約のモック実装。詳細については、サーブレット API モックオブジェクトを参照してください。

  • TestContext フレームワーク : JUnit および TestNG テストでの Spring 構成のロードのサポート。テストメソッド全体でのロードされた構成の効率的なキャッシュや、MockServletContext を使用した WebApplicationContext のロードのサポートを含みます。詳細については、TestContext フレームワークを参照してください。

  • Spring MVC テスト : MockMvc とも呼ばれるフレームワーク。DispatcherServlet (つまり、アノテーションをサポートする)を介してアノテーション付きコントローラーをテストするための、Spring MVC インフラストラクチャーを備え、HTTP サーバーはありません。詳細については、Spring MVC テストを参照してください。

  • クライアント側の REST: spring-test は、RestTemplate を内部的に使用するクライアント側コードをテストするためのモックサーバーとして使用できる MockRestServiceServer を提供します。詳細については、クライアント REST テストを参照してください。

  • WebTestClient: WebFlux アプリケーションのテスト用に構築されていますが、HTTP 接続を介した、あらゆるサーバーへのエンドツーエンドの統合テストにも使用できます。ノンブロッキングのリアクティブクライアントであり、非同期およびストリーミングシナリオのテストに適しています。

4. WebSocket

リファレンスドキュメントのこのパートでは、サーブレットスタック、生の WebSocket インタラクションを含む WebSocket メッセージング、SockJS を介した WebSocket エミュレーション、および WebSocket のサブプロトコルとしての STOMP を介したパブリッシュ / サブスクライブメッセージングのサポートについて説明します。

4.1. WebSocket の概要

WebSocket プロトコルである RFC 6455 (英語) は、単一の TCP 接続を介してクライアントとサーバー間に全二重双方向通信チャネルを確立する標準化された方法を提供します。これは HTTP とは異なる TCP プロトコルですが、ポート 80 および 443 を使用し、既存のファイアウォールルールの再利用を可能にする HTTP 上で動作するように設計されています。

WebSocket の対話は、HTTP Upgrade ヘッダーを使用してアップグレードするか、この場合は WebSocket プロトコルに切り替える HTTP リクエストで始まります。次の例は、このような相互作用を示しています。

GET /spring-websocket-portfolio/portfolio HTTP/1.1
Host: localhost:8080
Upgrade: websocket (1)
Connection: Upgrade (2)
Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==
Sec-WebSocket-Protocol: v10.stomp, v11.stomp
Sec-WebSocket-Version: 13
Origin: http://localhost:8080
1Upgrade ヘッダー。
2Upgrade 接続を使用します。

通常の 200 ステータスコードの代わりに、WebSocket をサポートするサーバーは、次のような出力を返します。

HTTP/1.1 101 Switching Protocols (1)
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=
Sec-WebSocket-Protocol: v10.stomp
1 プロトコルスイッチ

ハンドシェイクに成功した後、HTTP アップグレードリクエストの基になる TCP ソケットは、クライアントとサーバーの両方に対して開いたままになり、メッセージの送受信を継続します。

WebSockets の動作方法の完全な導入は、このドキュメントの範囲外です。RFC 6455、HTML5 の WebSocket の章、または Web に関する多くの導入とチュートリアルのいずれかを参照してください。

WebSocket サーバーが Web サーバー(nginx など)の背後で実行されている場合は、WebSocket アップグレードリクエストを WebSocket サーバーに渡すように構成する必要がある可能性が高いことに注意してください。同様に、アプリケーションがクラウド環境で実行されている場合は、WebSocket サポートに関連するクラウドプロバイダーの指示を確認してください。

4.1.1. HTTP 対 WebSocket

WebSocket は HTTP 互換であるように設計されており、HTTP リクエストで始まりますが、2 つのプロトコルが非常に異なるアーキテクチャとアプリケーションプログラミングモデルにつながることを理解することが重要です。

HTTP および REST では、アプリケーションは多くの URL としてモデル化されます。アプリケーションと対話するために、クライアントはこれらの URL にアクセスし、リクエスト / レスポンススタイルを使用します。サーバーは、HTTP URL、メソッド、およびヘッダーに基づいてリクエストを適切なハンドラーにルーティングします。

対照的に、WebSockets では、通常、最初の接続用の URL は 1 つだけです。その後、すべてのアプリケーションメッセージは同じ TCP 接続で流れます。これは、まったく異なる非同期のイベント駆動型のメッセージングアーキテクチャを指します。

WebSocket は低レベルのトランスポートプロトコルでもあり、HTTP とは異なり、メッセージのコンテンツにセマンティクスを規定していません。つまり、クライアントとサーバーがメッセージのセマンティクスに同意しない限り、メッセージをルーティングまたは処理する方法はありません。

WebSocket クライアントおよびサーバーは、HTTP ハンドシェイクリクエストの Sec-WebSocket-Protocol ヘッダーを介して、より高いレベルのメッセージングプロトコル(たとえば、STOMP)の使用をネゴシエートできます。それがなければ、彼らは彼ら自身の規約を考え出す必要があります。

4.1.2. WebSockets を使用する場合

WebSockets は、Web ページを動的かつインタラクティブにすることができます。ただし、多くの場合、Ajax と HTTP ストリーミングまたはロングポーリングの組み合わせにより、シンプルで効果的なソリューションを提供できます。

例:ニュース、メール、ソーシャルフィードは動的に更新する必要がありますが、数分ごとに更新しても問題ありません。一方、コラボレーション、ゲーム、および金融アプリは、リアルタイムにさらに近づける必要があります。

遅延だけが決定要因ではありません。メッセージの量が比較的少ない場合(ネットワーク障害の監視など)、HTTP ストリーミングまたはポーリングは効果的なソリューションを提供できます。WebSocket の使用に最適なのは、低レイテンシー、高周波数、高ボリュームの組み合わせです。

また、インターネット上では、Upgrade ヘッダーを渡すように構成されていないか、アイドル状態であると思われる長寿命の接続を閉じるため、コントロール外の制限プロキシが WebSocket 相互作用を妨げる可能性があることに注意してください。これは、ファイアウォール内の内部アプリケーションに WebSocket を使用することは、一般公開アプリケーションよりも簡単な決定であることを意味します。

4.2. WebSocket API

Spring Framework は、WebSocket メッセージを処理するクライアント側およびサーバー側のアプリケーションを作成するために使用できる WebSocket API を提供します。

4.2.1. WebSocketHandler

WebSocket サーバーの作成は、WebSocketHandler を実装するのと同じくらい簡単で、おそらくは TextWebSocketHandler または BinaryWebSocketHandler のいずれかを継承します。次の例では、TextWebSocketHandler を使用しています。

import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.TextMessage;

public class MyHandler extends TextWebSocketHandler {

    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) {
        // ...
    }

}

次の例に示すように、専用の WebSocket Java 構成と、前述の WebSocket ハンドラーを特定の URL にマッピングするための XML 名前空間のサポートがあります。

import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/myHandler");
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }

}

次の例は、前述の例に相当する XML 構成を示しています。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <websocket:mapping path="/myHandler" handler="myHandler"/>
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>

上記の例は、Spring MVC アプリケーションで使用するためのものであり、DispatcherServlet の構成に含める必要があります。ただし、Spring の WebSocket サポートは Spring MVC に依存しません。WebSocketHttpRequestHandler(Javadoc) を使用して、WebSocketHandler を他の HTTP サービス環境に統合するのは比較的簡単です。

WebSocketHandler API を直接または間接的に使用する場合、たとえば基礎となる標準 WebSocket セッション(JSR-356)は同時送信を許可しないため、STOMP メッセージングを介して、アプリケーションはメッセージの送信を同期する必要があります。1 つのオプションは、WebSocketSession を ConcurrentWebSocketSessionDecorator(Javadoc) でラップすることです。

4.2.2. WebSocket ハンドシェイク

最初の HTTP WebSocket ハンドシェイクリクエストをカスタマイズする最も簡単な方法は、ハンドシェイクの「前」と「後」のメソッドを公開する HandshakeInterceptor を使用することです。このようなインターセプターを使用して、ハンドシェイクを除外したり、WebSocketSession で属性を使用可能にしたりできます。次の例では、組み込みインターセプターを使用して、HTTP セッション属性を WebSocket セッションに渡します。

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyHandler(), "/myHandler")
            .addInterceptors(new HttpSessionHandshakeInterceptor());
    }

}

次の例は、前述の例に相当する XML 構成を示しています。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <websocket:mapping path="/myHandler" handler="myHandler"/>
        <websocket:handshake-interceptors>
            <bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
        </websocket:handshake-interceptors>
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>

より高度なオプションは、WebSocket ハンドシェイクのステップを実行する DefaultHandshakeHandler を継承することです。これには、クライアントの発信元の検証、サブプロトコルのネゴシエーション、およびその他の詳細が含まれます。アプリケーションは、WebSocket サーバーエンジンとまだサポートされていないバージョンに適応するためにカスタム RequestUpgradeStrategy を構成する必要がある場合、このオプションを使用する必要があります(このテーマの詳細については、デプロイを参照してください)。Java 構成と XML 名前空間の両方により、カスタム HandshakeHandler を構成できます。

Spring は、WebSocketHandler を追加の動作で装飾するために使用できる WebSocketHandlerDecorator 基本クラスを提供します。WebSocket Java 構成または XML 名前空間を使用する場合、デフォルトでロギングおよび例外処理の実装が提供および追加されます。ExceptionWebSocketHandlerDecorator は、WebSocketHandler メソッドから発生するすべてのキャッチされない例外をキャッチし、サーバーエラーを示すステータス 1011 の WebSocket セッションを閉じます。

4.2.3. デプロイ

Spring WebSocket API は、DispatcherServlet が HTTP WebSocket ハンドシェイクと他の HTTP リクエストの両方を処理する Spring MVC アプリケーションに簡単に統合できます。WebSocketHttpRequestHandler を呼び出すことにより、他の HTTP 処理シナリオに簡単に統合することもできます。これは便利で理解しやすいです。ただし、JSR-356 ランタイムに関しては特別な考慮事項が適用されます。

Java WebSocket API(JSR-356)は、2 つのデプロイメカニズムを提供します。1 つ目は、起動時のサーブレットコンテナークラスパススキャン(Servlet 3 機能)を含みます。もう 1 つは、サーブレットコンテナーの初期化で使用する登録 API です。これらのメカニズムのいずれも、Spring MVC の DispatcherServlet など、WebSocket ハンドシェイクと他のすべての HTTP リクエストを含むすべての HTTP 処理に単一の「フロントコントローラー」を使用することを可能にしません

これは、JSR-356 ランタイムの実行中であっても、Spring の WebSocket がサーバー固有の RequestUpgradeStrategy 実装のアドレスをサポートするという JSR-356 の重要な制限です。現在、Tomcat、Jetty、GlassFish、WebLogic、WebSphere、Undertow(および WildFly)にはこのような戦略が存在します。

Java WebSocket API の前述の制限を克服するリクエストが作成され、eclipse-ee4j/websocket-api#211 (GitHub) で追跡できます。Tomcat、Undertow、および WebSphere は、これを可能にする独自の API の代替手段を提供し、Jetty でも可能です。より多くのサーバーが同じことをすることを期待しています。

2 番目の考慮事項は、JSR-356 をサポートするサーブレットコンテナーが ServletContainerInitializer (SCI)スキャンを実行すると予想されることです。これにより、アプリケーションの起動が遅くなる場合があります。JSR-356 をサポートするサーブレットコンテナーバージョンへのアップグレード後に重大な影響が観察された場合、次の例のように、web.xml の <absolute-ordering /> 要素を使用して、Web フラグメント(および SCI スキャン)を選択的に有効または無効にできます。ショー:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/javaee
        https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <absolute-ordering/>

</web-app>

次に、Servlet 3 Java 初期化 API のサポートを提供する Spring 独自の SpringServletContainerInitializer など、名前で Web フラグメントを選択的に有効にできます。次の例は、その方法を示しています。

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/javaee
        https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <absolute-ordering>
        <name>spring_web</name>
    </absolute-ordering>

</web-app>

4.2.4. サーバー構成

基礎となる各 WebSocket エンジンは、メッセージバッファーサイズ、アイドルタイムアウトなどのランタイム特性を制御する構成プロパティを公開します。

Tomcat、WildFly、および GlassFish の場合、次の例に示すように、ServletServerContainerFactoryBean を WebSocket Java 構成に追加できます。

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        container.setMaxTextMessageBufferSize(8192);
        container.setMaxBinaryMessageBufferSize(8192);
        return container;
    }

}

次の例は、前述の例に相当する XML 構成を示しています。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <bean class="org.springframework...ServletServerContainerFactoryBean">
        <property name="maxTextMessageBufferSize" value="8192"/>
        <property name="maxBinaryMessageBufferSize" value="8192"/>
    </bean>

</beans>
クライアント側の WebSocket 構成の場合、WebSocketContainerFactoryBean (XML)または ContainerProvider.getWebSocketContainer() (Java 構成)を使用する必要があります。

Jetty の場合、事前に構成された Jetty WebSocketServerFactory を提供し、WebSocket Java 構成を介して Spring の DefaultHandshakeHandler にプラグインする必要があります。次の例は、その方法を示しています。

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(echoWebSocketHandler(),
            "/echo").setHandshakeHandler(handshakeHandler());
    }

    @Bean
    public DefaultHandshakeHandler handshakeHandler() {

        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
        policy.setInputBufferSize(8192);
        policy.setIdleTimeout(600000);

        return new DefaultHandshakeHandler(
                new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
    }

}

次の例は、前述の例に相当する XML 構成を示しています。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <websocket:mapping path="/echo" handler="echoHandler"/>
        <websocket:handshake-handler ref="handshakeHandler"/>
    </websocket:handlers>

    <bean id="handshakeHandler" class="org.springframework...DefaultHandshakeHandler">
        <constructor-arg ref="upgradeStrategy"/>
    </bean>

    <bean id="upgradeStrategy" class="org.springframework...JettyRequestUpgradeStrategy">
        <constructor-arg ref="serverFactory"/>
    </bean>

    <bean id="serverFactory" class="org.eclipse.jetty...WebSocketServerFactory">
        <constructor-arg>
            <bean class="org.eclipse.jetty...WebSocketPolicy">
                <constructor-arg value="SERVER"/>
                <property name="inputBufferSize" value="8092"/>
                <property name="idleTimeout" value="600000"/>
            </bean>
        </constructor-arg>
    </bean>

</beans>

4.2.5. 許可されたオリジン

Spring Framework 4.1.5, 以降、WebSocket および SockJS のデフォルトの動作は、同一生成元のリクエストのみを受け入れるようになっています。すべてまたは指定したオリジンのリストを許可することもできます。このチェックは、主にブラウザクライアント用に設計されています。他のタイプのクライアントが Origin ヘッダー値を変更することを妨げるものはありません(詳細については、RFC 6454: Web Origin のコンセプト (英語) を参照してください)。

3 つの可能な動作は次のとおりです。

  • 同一オリジンリクエストのみを許可(デフォルト):このモードでは、SockJS が有効になっている場合、リクエストのオリジンのチェックが許可されないため、Iframe HTTP レスポンスヘッダー X-Frame-Options が SAMEORIGIN に設定され、JSONP トランスポートが無効になります。そのため、このモードが有効になっている場合、IE6 および IE7 はサポートされません。

  • 指定されたオリジンのリストを許可:許可された各オリジンは http:// または https:// で始まる必要があります。このモードでは、SockJS を有効にすると、IFrame トランスポートが無効になります。そのため、このモードが有効になっている場合、IE6 から IE9 はサポートされません。

  • すべてのオリジンを許可 : このモードを有効にするには、許可された起点値として * を提供する必要があります。このモードでは、すべてのトランスポートが利用可能です。

次の例に示すように、WebSocket と SockJS が許可するオリジンを設定できます。

import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/myHandler").setAllowedOrigins("https://mydomain.com");
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }

}

次の例は、前述の例に相当する XML 構成を示しています。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers allowed-origins="https://mydomain.com">
        <websocket:mapping path="/myHandler" handler="myHandler" />
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>

4.3. SockJS フォールバック

パブリックインターネット上では、Upgrade ヘッダーを渡すように構成されていないか、アイドル状態にあると思われる長時間の接続を閉じるため、コントロール外の制限プロキシが WebSocket 相互作用を妨げる可能性があります。

この問題の解決策は WebSocket エミュレーションです。つまり、最初に WebSocket を使用してから、WebSocket 相互作用をエミュレートし、同じアプリケーションレベルの API を公開する HTTP ベースの手法にフォールバックしようとします。

サーブレットスタックでは、Spring Framework は SockJS プロトコルの両方のサーバー(およびクライアント)サポートを提供します。

4.3.1. 概要

SockJS のゴールは、アプリケーションが WebSocket API を使用できるようにすることですが、実行時に必要に応じて、アプリケーションコードを変更せずに非 WebSocket の代替にフォールバックすることです。

SockJS の構成:

SockJS は、ブラウザーで使用するために設計されています。さまざまなバージョンのブラウザーをサポートするために、さまざまな手法を使用します。SockJS トランスポートタイプとブラウザの完全なリストについては、SockJS クライアント (GitHub) ページを参照してください。トランスポートは、WebSocket、HTTP ストリーミング、および HTTP ロングポーリングの 3 つの一般的なカテゴリに分類されます。これらのカテゴリの概要については、このブログ投稿 (英語) を参照してください。

SockJS クライアントは、GET /info を送信してサーバーから基本情報を取得することから始めます。その後、使用するトランスポートを決定する必要があります。可能であれば、WebSocket が使用されます。そうでない場合、ほとんどのブラウザには、少なくとも 1 つの HTTP ストリーミングオプションがあります。そうでない場合は、HTTP(ロング)ポーリングが使用されます。

すべてのトランスポートリクエストの URL 構造は次のとおりです。

https://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}

where:

  • {server-id} は、クラスター内のリクエストのルーティングに役立ちますが、それ以外では使用されません。

  • {session-id} は、SockJS セッションに属する HTTP リクエストを関連付けます。

  • {transport} は、トランスポートタイプ(たとえば、websocketxhr-streaming など)を示します。

WebSocket トランスポートは、WebSocket ハンドシェイクを行うために 1 つの HTTP リクエストのみを必要とします。その後のすべてのメッセージは、そのソケットで交換されます。

HTTP トランスポートにはより多くのリクエストが必要です。たとえば、Ajax/XHR ストリーミングは、サーバーからクライアントへのメッセージに対する 1 つの長時間実行リクエストと、クライアントからサーバーへのメッセージに対する追加の HTTP POST リクエストに依存しています。ロングポーリングは似ていますが、各サーバーからクライアントへの送信後に現在のリクエストが終了する点が異なります。

SockJS は最小限のメッセージフレーミングを追加します。例:サーバーは最初に文字 o (「オープン」フレーム)を送信し、メッセージは a["message1","message2"] (JSON エンコード配列)として送信され、文字 h (「ハートビート」フレーム)はメッセージが 25 秒間流れない場合(デフォルト)、セッションを閉じるための文字 c (「閉じる」フレーム)。

詳細については、ブラウザで例を実行し、HTTP リクエストを確認してください。SockJS クライアントでは、トランスポートのリストを修正できるため、各トランスポートを一度に 1 つずつ見ることができます。SockJS クライアントは、ブラウザコンソールで役立つメッセージを有効にするデバッグフラグも提供します。サーバー側では、org.springframework.web.socket の TRACE ロギングを有効にできます。さらに詳細については、SockJS プロトコルのナレーション付きテスト (英語) を参照してください。

4.3.2. SockJS を有効にする

次の例に示すように、Java 構成を介して SockJS を有効にできます。

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/myHandler").withSockJS();
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }

}

次の例は、前述の例に相当する XML 構成を示しています。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <websocket:mapping path="/myHandler" handler="myHandler"/>
        <websocket:sockjs/>
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>

上記の例は、Spring MVC アプリケーションで使用するためのものであり、DispatcherServlet の構成に含める必要があります。ただし、Spring の WebSocket および SockJS のサポートは Spring MVC に依存しません。SockJsHttpRequestHandler(Javadoc) を使用して他の HTTP サービス環境に統合するのは比較的簡単です。

ブラウザ側では、アプリケーションは sockjs-client (GitHub) (バージョン 1.0.x)を使用できます。W3C WebSocket API をエミュレートし、サーバーと通信して、実行するブラウザーに応じて最適なトランスポートオプションを選択します。sockjs-client (GitHub) ページと、ブラウザでサポートされているトランスポートタイプのリストを参照してください。また、クライアントは、含めるトランスポートを指定するなど、いくつかの構成オプションも提供します。

4.3.3. IE 8 および 9

Internet Explorer 8 および 9 は引き続き使用されます。それらは、SockJS を持つ主な理由です。このセクションでは、これらのブラウザーでの実行に関する重要な考慮事項について説明します。

SockJS クライアントは、Microsoft の XDomainRequest (英語) を使用して、IE 8 および 9 で Ajax/XHR ストリーミングをサポートしています。これはドメイン間で機能しますが、Cookie の送信をサポートしません。多くの場合、Cookie は Java アプリケーションに不可欠です。ただし、SockJS クライアントは(Java だけでなく)多くのサーバータイプで使用できるため、Cookie が重要かどうかを知る必要があります。その場合、SockJS クライアントはストリーミングに Ajax/XHR を好みます。それ以外の場合は、iframe ベースの手法に依存します。

SockJS クライアントからの最初の /info リクエストは、クライアントのトランスポートの選択に影響を与える可能性のある情報のリクエストです。それらの詳細の 1 つは、サーバーアプリケーションが Cookie に依存するかどうかです(たとえば、認証目的またはスティッキーセッションでのクラスタリング)。Spring の SockJS サポートには、sessionCookieNeeded というプロパティが含まれています。ほとんどの Java アプリケーションは JSESSIONID Cookie に依存しているため、デフォルトで有効になっています。アプリケーションで必要ない場合は、このオプションをオフにできます。SockJS クライアントは IE 8 および 9 で xdr-streaming を選択する必要があります。

iframe ベースのトランスポートを使用する場合、HTTP レスポンスヘッダー X-Frame-Options を DENYSAMEORIGIN または ALLOW-FROM <origin> に設定することにより、ブラウザーが特定のページで IFrame の使用をブロックするように指示できることに注意してください。これは、クリックジャッキング (英語) を防ぐために使用されます。

Spring Security 3.2+ は、すべてのレスポンスで X-Frame-Options を設定するためのサポートを提供します。デフォルトでは、Spring Security Java 構成はそれを DENY に設定します。3.2 では、Spring Security XML 名前空間はデフォルトでそのヘッダーを設定しませんが、そのように構成できます。将来的には、デフォルトで設定される可能性があります。

X-Frame-Options ヘッダーの設定を構成する方法の詳細については、Spring Security のドキュメントのデフォルトのセキュリティヘッダーを参照してください。追加の背景については、SEC-2501 (英語) も参照できます。

アプリケーションが X-Frame-Options レスポンスヘッダーを追加し(必要な場合)、iframe ベースのトランスポートに依存している場合、ヘッダー値を SAMEORIGIN または ALLOW-FROM <origin> に設定する必要があります。Spring SockJS サポートは、iframe からロードされるため、SockJS クライアントの場所を知る必要もあります。デフォルトでは、iframe は CDN の場所から SockJS クライアントをダウンロードするように設定されています。このオプションを構成して、アプリケーションと同じオリジンからの URL を使用することをお勧めします。

次の例は、Java 構成でこれを行う方法を示しています。

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio").withSockJS()
                .setClientLibraryUrl("http://localhost:8080/myapp/js/sockjs-client.js");
    }

    // ...

}

XML 名前空間は、<websocket:sockjs> 要素を通じて同様のオプションを提供します。

初期開発時に、ブラウザが SockJS リクエスト(iframe など)をキャッシュしないようにする SockJS クライアント devel モードを有効にします。有効にする方法の詳細については、SockJS クライアント (GitHub) ページを参照してください。

4.3.4. ハートビート

SockJS プロトコルでは、サーバーがハートビートメッセージを送信して、プロキシが接続がハングしていると判断するのを防ぐ必要があります。Spring SockJS 構成には heartbeatTime というプロパティがあり、これを使用して周波数をカスタマイズできます。デフォルトでは、その接続で他のメッセージが送信されていないと仮定して、25 秒後にハートビートが送信されます。この 25 秒の値は、パブリックインターネットアプリケーションの次の IETF の推奨事項 (英語) と一致しています。

WebSocket および SockJS で STOMP を使用する場合、STOMP クライアントとサーバーが交換されるハートビートをネゴシエートすると、SockJS ハートビートは無効になります。

Spring SockJS サポートにより、TaskScheduler を構成して、ハートビートタスクをスケジュールすることもできます。タスクスケジューラは、利用可能なプロセッサの数に基づいたデフォルト設定で、スレッドプールに支えられています。特定のニーズに応じて設定をカスタマイズすることを検討してください。

4.3.5. クライアント切断

HTTP ストリーミングおよび HTTP ロングポーリング SockJS トランスポートでは、接続が通常より長く開いたままである必要があります。これらの手法の概要については、このブログ投稿 (英語) を参照してください。

サーブレットコンテナーでは、これは Servlet 3 非同期サポートを通じて行われます。これにより、サーブレットコンテナースレッドを終了し、リクエストを処理し、別のスレッドからのレスポンスへの書き込みを継続できます。

特定の課題は、サーブレット API が、なくなったクライアントに通知を提供しないことです。eclipse-ee4j/servlet-api#44 (GitHub) を参照してください。ただし、サーブレットコンテナーは、レスポンスへの後続の書き込み試行で例外を発生させます。Spring の SockJS サービスはサーバー送信ハートビート(デフォルトでは 25 秒ごと)をサポートしているため、通常はその期間内(またはメッセージがより頻繁に送信される場合はそれ以前)にクライアントの切断が検出されます。

その結果、クライアントが切断されたためにネットワーク I/O エラーが発生する可能性があり、不要なスタックトレースでログがいっぱいになる可能性があります。Spring は、クライアントの切断(各サーバーに固有)を表すネットワーク障害を特定し、専用のログカテゴリ DISCONNECTED_CLIENT_LOG_CATEGORY (AbstractSockJsSession で定義)を使用して最小限のメッセージを記録するように最善を尽くします。スタックトレースを表示する必要がある場合は、そのログカテゴリを TRACE に設定できます。

4.3.6. SockJS と CORS

クロスオリジンリクエストを許可する場合(許可されたオリジンを参照)、SockJS プロトコルは XHR ストリーミングおよびポーリングトランスポートでのクロスドメインサポートに CORS を使用します。CORS ヘッダーは、レスポンスに CORS ヘッダーの存在が検出されない限り、自動的に追加されます。そのため、アプリケーションが既に(たとえば、サーブレットフィルターを介して)CORS サポートを提供するように構成されている場合、Spring の SockJsService はこのパートをスキップします。

Spring の SockJsService で suppressCors プロパティを設定することにより、これらの CORS ヘッダーの追加を無効にすることもできます。

SockJS では、次のヘッダーと値が必要です。

  • Access-Control-Allow-OriginOrigin リクエストヘッダーの値から初期化されます。

  • Access-Control-Allow-Credentials: 常に true に設定します。

  • Access-Control-Request-Headers: 同等のリクエストヘッダーの値から初期化されます。

  • Access-Control-Allow-Methods: トランスポートがサポートする HTTP メソッド(TransportType 列挙型を参照)。

  • Access-Control-Max-Age: 31536000 (1 年に設定)。

正確な実装については、AbstractSockJsService の addCorsHeaders およびソースコードの TransportType 列挙を参照してください。

または、CORS 設定で許可されている場合、SockJS エンドポイントプレフィックスを持つ URL を除外し、Spring の SockJsService で処理できるようにすることを検討してください。

4.3.7. SockJsClient

Spring は、ブラウザーを使用せずにリモート SockJS エンドポイントに接続するための SockJS Java クライアントを提供します。これは、パブリックネットワークを介した 2 つのサーバー間の双方向通信が必要な場合(つまり、ネットワークプロキシが WebSocket プロトコルの使用を排除できる場合)に特に役立ちます。SockJS Java クライアントは、テスト目的(たとえば、多数の同時ユーザーをシミュレートする)にも非常に役立ちます。

SockJS Java クライアントは、websocketxhr-streaming および xhr-polling トランスポートをサポートしています。残りのものは、ブラウザで使用する場合にのみ意味があります。

WebSocketTransport を以下で構成できます。

  • JSR-356 ランタイムの StandardWebSocketClient

  • Jetty 9+ ネイティブ WebSocket API を使用して JettyWebSocketClient

  • Spring の WebSocketClient の実装。

定義上、XhrTransport は xhr-streaming と xhr-polling の両方をサポートします。これは、クライアントの観点からは、サーバーへの接続に使用される URL 以外に違いはないためです。現在、2 つの実装があります。

  • RestTemplateXhrTransport は、HTTP リクエストに Spring の RestTemplate を使用します。

  • JettyXhrTransport は、HTTP リクエストに Jetty の HttpClient を使用します。

次の例は、SockJS クライアントを作成し、SockJS エンドポイントに接続する方法を示しています。

List<Transport> transports = new ArrayList<>(2);
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
transports.add(new RestTemplateXhrTransport());

SockJsClient sockJsClient = new SockJsClient(transports);
sockJsClient.doHandshake(new MyWebSocketHandler(), "ws://example.com:8080/sockjs");
SockJS は、メッセージに JSON 形式の配列を使用します。デフォルトでは、Jackson 2 が使用され、クラスパス上にある必要があります。または、SockJsMessageCodec のカスタム実装を構成して、SockJsClient で構成することもできます。

SockJsClient を使用して多数の同時ユーザーをシミュレートするには、基礎となる HTTP クライアント(XHR トランスポート用)を構成して、十分な数の接続とスレッドを許可する必要があります。次の例は、Jetty でこれを行う方法を示しています。

HttpClient jettyHttpClient = new HttpClient();
jettyHttpClient.setMaxConnectionsPerDestination(1000);
jettyHttpClient.setExecutor(new QueuedThreadPool(1000));

次の例は、カスタマイズを検討する必要があるサーバー側の SockJS 関連のプロパティ(詳細については javadoc を参照)を示しています。

@Configuration
public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/sockjs").withSockJS()
            .setStreamBytesLimit(512 * 1024) (1)
            .setHttpMessageCacheSize(1000) (2)
            .setDisconnectDelay(30 * 1000); (3)
    }

    // ...
}
1streamBytesLimit プロパティを 512KB に設定します(デフォルトは 128KB — 128 * 1024 です)。
2httpMessageCacheSize プロパティを 1,000 に設定します(デフォルトは 100 です)。
3disconnectDelay プロパティを 30 プロパティ秒に設定します(デフォルトは 5 秒 — 5 * 1000 です)。

4.4. STOMP

WebSocket プロトコルは 2 種類のメッセージ(テキストとバイナリ)を定義していますが、その内容は未定義です。このプロトコルは、クライアントとサーバーがサブプロトコル(つまり、より高レベルのメッセージングプロトコル)をネゴシエートするメカニズムを定義し、WebSocket の上で使用して、それぞれが送信できるメッセージの種類、フォーマット、コンテンツを定義します。各メッセージなど。サブプロトコルの使用はオプションですが、いずれにせよ、クライアントとサーバーはメッセージの内容を定義するプロトコルに同意する必要があります。

4.4.1. 概要

STOMP (英語) (Simple Text Oriented Messaging Protocol)はもともと、エンタープライズメッセージブローカーに接続するためのスクリプト言語(Ruby、Python、Perl など)用に作成されます。一般的に使用されるメッセージングパターンの最小限のサブセットに対処するように設計されています。STOMP は、TCP や WebSocket などの信頼性のある双方向ストリーミングネットワークプロトコルで使用できます。STOMP はテキスト指向のプロトコルですが、メッセージペイロードはテキストまたはバイナリのいずれかです。

STOMP は、フレームが HTTP でモデル化されているフレームベースのプロトコルです。次のリストは、STOMP フレームの構造を示しています。

COMMAND
header1:value1
header2:value2

Body^@

クライアントは、SEND または SUBSCRIBE コマンドを使用して、メッセージの内容と受信者を説明する destination ヘッダーとともに、メッセージを送信またはサブスクライブできます。これにより、単純なパブリッシュ / サブスクライブメカニズムが有効になり、ブローカーを介して他の接続済みクライアントにメッセージを送信したり、サーバーにメッセージを送信して作業の実行をリクエストしたりできます。

Spring の STOMP サポートを使用すると、Spring WebSocket アプリケーションはクライアントに対する STOMP ブローカーとして機能します。メッセージは、@Controller メッセージ処理メソッド、またはサブスクリプションを追跡し、サブスクライブしたユーザーにメッセージをブロードキャストする単純なメモリ内ブローカーにルーティングされます。また、メッセージの実際のブロードキャストのために、専用の STOMP ブローカー(RabbitMQ、ActiveMQ など)と連携するように Spring を構成することもできます。その場合、Spring はブローカーへの TCP 接続を維持し、ブローカーにメッセージを中継し、ブローカーからのメッセージを接続された WebSocket クライアントに渡します。Spring Web アプリケーションは、統一された HTTP ベースのセキュリティ、一般的な検証、およびメッセージ処理のための使い慣れたプログラミングモデルに依存できます。

次の例は、クライアントが株価を受信するようにサブスクライブしていることを示しています。サーバーは定期的に(たとえば、SimpMessagingTemplate を介してブローカーにメッセージを送信するスケジュールされたタスクを介して)送信します。

SUBSCRIBE
id:sub-1
destination:/topic/price.stock.*

^@

次の例は、取引リクエストを送信するクライアントを示しています。サーバーは、@MessageMapping メソッドを介して処理できます。

SEND
destination:/queue/trade
content-type:application/json
content-length:44

{"action":"BUY","ticker":"MMM","shares",44}^@

実行後、サーバーは取引確認メッセージと詳細をクライアントにブロードキャストできます。

宛先の意味は、STOMP 仕様では意図的に不透明のままになっています。任意の文字列を使用でき、サポートする宛先のセマンティクスと構文を定義するのは、完全に STOMP サーバー次第です。ただし、宛先がパスのような文字列であるのは非常に一般的です。/topic/.. はパブリッシュ / サブスクライブ(1 対多)を意味し、/queue/ はポイントツーポイント(1 対 1)メッセージ交換を意味します。

STOMP サーバーは、MESSAGE コマンドを使用して、すべてのサブスクライバーにメッセージをブロードキャストできます。次の例は、購読しているクライアントに株価を送信するサーバーを示しています。

MESSAGE
message-id:nxahklf6-1
subscription:sub-1
destination:/topic/price.stock.MMM

{"ticker":"MMM","price":129.45}^@

サーバーは非請求メッセージを送信できません。サーバーからのすべてのメッセージは、特定のクライアントサブスクリプションにレスポンスする必要があり、サーバーメッセージの subscription-id ヘッダーは、クライアントサブスクリプションの id ヘッダーと一致する必要があります。

前述の概要は、STOMP プロトコルの最も基本的な理解を提供することを目的としています。プロトコル仕様 (英語) を完全に確認することをお勧めします。

4.4.2. メリット

STOMP をサブプロトコルとして使用すると、Spring Framework および Spring Security は、生の WebSockets を使用するよりも豊富なプログラミングモデルを提供できます。HTTP 対 raw TCP について、および Spring MVC や他の Web フレームワークがどのように豊富な機能を提供できるようにするかについて、同じことを言えます。利点のリストは次のとおりです。

  • カスタムメッセージングプロトコルとメッセージ形式を考案する必要はありません。

  • Spring Framework の Java クライアントを含む STOMP クライアントが利用可能です。

  • (オプションで)メッセージブローカー(RabbitMQ、ActiveMQ など)を使用して、サブスクリプションとブロードキャストメッセージを管理できます。

  • アプリケーションロジックは、任意の数の @Controller インスタンスで編成でき、特定の接続に対して単一の WebSocketHandler で生の WebSocket メッセージを処理するのに対して、STOMP 宛先ヘッダーに基づいてメッセージをルーティングできます。

  • Spring Security を使用して、STOMP 宛先およびメッセージタイプに基づいてメッセージを保護できます。

4.4.3. STOMP を有効にする

WebSocket を介した STOMP サポートは、spring-messaging および spring-websocket モジュールで使用可能です。これらの依存関係を取得したら、次の例に示すように、SockJS フォールバックを使用して WebSocket を介して STOMP エンドポイントを公開できます。

import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio").withSockJS();  (1)
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.setApplicationDestinationPrefixes("/app"); (2)
        config.enableSimpleBroker("/topic", "/queue"); (3)
    }
}
1/portfolio は、WebSocket(または SockJS)クライアントが WebSocket ハンドシェイクのために接続する必要があるエンドポイントの HTTP URL です。
2 宛先ヘッダーが /app で始まる STOMP メッセージは、@Controller クラスの @MessageMapping メソッドにルーティングされます。
3 組み込みのメッセージブローカーを使用して、サブスクリプションとブロードキャストを行い、宛先ヘッダーが /topic `or `/queue で始まるメッセージをブローカーにルーティングします。

次の例は、前述の例に相当する XML 構成を示しています。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:message-broker application-destination-prefix="/app">
        <websocket:stomp-endpoint path="/portfolio">
            <websocket:sockjs/>
        </websocket:stomp-endpoint>
        <websocket:simple-broker prefix="/topic, /queue"/>
    </websocket:message-broker>

</beans>
組み込みの単純なブローカーの場合、/topic および /queue プレフィックスには特別な意味はありません。それらは、pub-sub メッセージングとポイントツーポイントメッセージング(つまり、多くのサブスクライバーと 1 つのコンシューマー)を区別するための単なる規則です。外部ブローカーを使用する場合は、ブローカーの STOMP ページを確認して、サポートしている STOMP 宛先とプレフィックスの種類を理解しましょう。

ブラウザから接続するには、SockJS の場合、sockjs-client (GitHub) を使用できます。STOMP の場合、多くのアプリケーションが jmesnil/stomp-websocket (GitHub) ライブラリ(stomp.js とも呼ばれます)を使用しています。これは機能が完全であり、長年にわたって運用環境で使用されていますが、現在は維持されていません。現在、JSteunou/webstomp-client (GitHub) は、そのライブラリの後継として最も積極的に維持され進化しています。次のサンプルコードはそれに基づいています。

var socket = new SockJS("/spring-websocket-portfolio/portfolio");
var stompClient = webstomp.over(socket);

stompClient.connect({}, function(frame) {
}

または、WebSocket(SockJS なし)を介して接続する場合、次のコードを使用できます。

var socket = new WebSocket("/spring-websocket-portfolio/portfolio");
var stompClient = Stomp.over(socket);

stompClient.connect({}, function(frame) {
}

上記の例の stompClient は、login および passcode ヘッダーを指定する必要がないことに注意してください。たとえそれが行われたとしても、それらはサーバー側では無視されます(むしろ上書きされます)。認証の詳細については、ブローカーへの接続および認証を参照してください。

その他のサンプルコードについては、以下を参照してください。

4.4.4. WebSocket サーバー

基礎となる WebSocket サーバーを構成するには、サーバー構成の情報が適用されます。Jetty の場合は、StompEndpointRegistry を介して HandshakeHandler および WebSocketPolicy を設定する必要があります。

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio").setHandshakeHandler(handshakeHandler());
    }

    @Bean
    public DefaultHandshakeHandler handshakeHandler() {

        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
        policy.setInputBufferSize(8192);
        policy.setIdleTimeout(600000);

        return new DefaultHandshakeHandler(
                new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
    }
}

4.4.5. Flow のメッセージ

STOMP エンドポイントが公開されると、Spring アプリケーションは接続されたクライアントの STOMP ブローカーになります。このセクションでは、サーバー側のメッセージの流れについて説明します。

spring-messaging モジュールには、Spring Integration に由来し、後に抽出されて Spring Framework に組み込まれたメッセージングアプリケーションの基本的なサポートが含まれており、多くの Spring プロジェクトおよびアプリケーションシナリオで幅広く使用できます。以下のリストは、利用可能なメッセージング抽象化のいくつかを簡単に説明しています。

Java 構成(つまり @EnableWebSocketMessageBroker)と XML 名前空間構成(つまり <websocket:message-broker>)の両方が、前述のコンポーネントを使用してメッセージワークフローを組み立てます。次の図は、単純な組み込みメッセージブローカが有効な場合に使用されるコンポーネントを示しています。

message flow simple broker

上記の図は、3 つのメッセージチャネルを示しています。

  • clientInboundChannel: WebSocket クライアントから受信したメッセージを渡すため。

  • clientOutboundChannel: サーバーメッセージを WebSocket クライアントに送信します。

  • brokerChannel: サーバー側のアプリケーションコード内からメッセージブローカーにメッセージを送信するため。

次の図は、外部ブローカー(RabbitMQ など)がサブスクリプションの管理とメッセージのブロードキャスト用に構成されている場合に使用されるコンポーネントを示しています。

message flow broker relay

上記の 2 つの図の主な違いは、「ブローカーリレー」を使用して、TCP を介して外部 STOMP ブローカーにメッセージを渡し、ブローカーからサブスクライブされたクライアントにメッセージを渡すことです。

WebSocket 接続からメッセージを受信すると、メッセージは STOMP フレームにデコードされ、Spring Message 表現に変換され、さらなる処理のために clientInboundChannel に送信されます。例:宛先ヘッダーが /app で始まる STOMP メッセージは、アノテーション付きコントローラーの @MessageMapping メソッドにルーティングできますが、/topic および /queue メッセージはメッセージブローカーに直接ルーティングできます。

クライアントからの STOMP メッセージを処理するアノテーション付き @Controller は、brokerChannel を介してメッセージブローカーにメッセージを送信でき、ブローカーは clientOutboundChannel を介して一致するサブスクライバーにメッセージをブロードキャストします。同じコントローラーが HTTP リクエストにレスポンスして同じことを行うことができるため、クライアントは HTTP POST を実行でき、@PostMapping メソッドはメッセージブローカーにメッセージを送信して、サブスクライブされたクライアントにブロードキャストできます。

簡単な例でフローをトレースできます。サーバーをセットアップする次の例を検討してください。

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio");
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/app");
        registry.enableSimpleBroker("/topic");
    }
}

@Controller
public class GreetingController {

    @MessageMapping("/greeting") {
    public String handle(String greeting) {
        return "[" + getTimestamp() + ": " + greeting;
    }
}

上記の例は、次のフローをサポートしています。

  1. クライアントは http://localhost:8080/portfolio に接続し、WebSocket 接続が確立されると、STOMP フレームが流れ始めます。

  2. クライアントは、宛先ヘッダー /topic/greeting で SUBSCRIBE フレームを送信します。メッセージを受信してデコードすると、メッセージは clientInboundChannel に送信され、クライアントサブスクリプションを保存するメッセージブローカーにルーティングされます。

  3. クライアントは /app/greeting に aSEND フレームを送信します。/app プレフィックスは、アノテーション付きコントローラーにルーティングできます。/app プレフィックスが除去された後、宛先の残りの /greeting 部分は GreetingController の @MessageMapping メソッドにマップされます。

  4. GreetingController から返された値は、戻り値に基づいたペイロードと /topic/greeting のデフォルトの宛先ヘッダー(/app が /topic に置き換えられた入力宛先から派生)を持つ Spring Message に変換されます。結果のメッセージは brokerChannel に送信され、メッセージブローカーによって処理されます。

  5. メッセージブローカーは一致するすべてのサブスクライバーを見つけ、clientOutboundChannel を介して各ユーザーに MESSAGE フレームを送信します。そこから、メッセージは STOMP フレームとしてエンコードされ、WebSocket 接続で送信されます。

次のセクションでは、サポートされている引数の種類や戻り値など、アノテーション付きメソッドの詳細を説明します。

4.4.6. アノテーション付きコントローラー

アプリケーションは、アノテーション付き @Controller クラスを使用して、クライアントからのメッセージを処理できます。そのようなクラスは、次のトピックで説明するように、@MessageMapping@SubscribeMapping および @ExceptionHandler メソッドを宣言できます。

@MessageMapping

@MessageMapping を使用して、宛先に基づいてメッセージをルーティングするメソッドにアノテーションを付けることができます。メソッドレベルおよび型レベルでサポートされています。型レベルでは、@MessageMapping を使用して、コントローラーのすべてのメソッドで共有マッピングを表現します。

デフォルトでは、マッピング値は Ant スタイルのパスパターン(例: /thing*/thing/**)であり、テンプレート変数(例: /thing/{id})のサポートが含まれます。値は、@DestinationVariable メソッドの引数を介して参照できます。セパレーターとしてのドットに従って、アプリケーションはマッピングのためにドット区切りの宛先規則に切り替えることもできます。

サポートされているメソッド引数

次の表で、メソッドの引数について説明します。

メソッド引数 説明

Message

完全なメッセージにアクセスするため。

MessageHeaders

Message 内のヘッダーへのアクセス用。

MessageHeaderAccessorSimpMessageHeaderAccessor、および StompHeaderAccessor

型指定されたアクセサーメソッドを介してヘッダーにアクセスします。

@Payload

構成された MessageConverter によって(たとえば JSON から)変換されたメッセージのペイロードへのアクセス用。

デフォルトでは、他の引数が一致しない場合に想定されるため、このアノテーションの存在は必要ありません。

ペイロード引数に @javax.validation.Valid または Spring の @Validated のアノテーションを付けると、ペイロード引数が自動的に検証されます。

@Header

必要に応じて org.springframework.core.convert.converter.Converter を使用したタイプ変換とともに、特定のヘッダー値にアクセスします。

@Headers

メッセージ内のすべてのヘッダーにアクセスします。この引数は java.util.Map に割り当て可能でなければなりません。

@DestinationVariable

メッセージ宛先から抽出されたテンプレート変数へのアクセス用。必要に応じて、値は宣言されたメソッドの引数型に変換されます。

java.security.Principal

WebSocket HTTP ハンドシェイク時にログインしたユーザーを反映します。

戻り値

デフォルトでは、@MessageMapping メソッドからの戻り値は、一致する MessageConverter を介してペイロードに直列化され、Message として brokerChannel に送信され、そこからサブスクライバーにブロードキャストされます。送信メッセージの宛先は、受信メッセージの宛先と同じですが、接頭辞 /topic が付いています。

@SendTo および @SendToUser アノテーションを使用して、出力メッセージの宛先をカスタマイズできます。@SendTo は、ターゲット宛先をカスタマイズするため、または複数の宛先を指定するために使用されます。@SendToUser は、入力メッセージに関連付けられたユーザーのみに出力メッセージを送信するために使用されます。ユーザー宛先を参照してください。

同じメソッドで @SendTo と @SendToUser の両方を同時に使用できます。両方ともクラスレベルでサポートされます。この場合、クラスのメソッドのデフォルトとして機能します。ただし、メソッドレベルの @SendTo または @SendToUser アノテーションは、クラスレベルでこのようなアノテーションをオーバーライドすることに注意してください。

メッセージは非同期に処理でき、@MessageMapping メソッドは ListenableFutureCompletableFuture または CompletionStage を返すことができます。

@SendTo と @SendToUser は、単に SimpMessagingTemplate を使用してメッセージを送信することに相当する利便性にすぎないことに注意してください。必要に応じて、より高度なシナリオでは、@MessageMapping メソッドは SimpMessagingTemplate を直接使用してフォールバックできます。これは、値を返す代わりに、または値を返すことに加えて行うことができます。メッセージ送信を参照してください。

@SubscribeMapping

@SubscribeMapping は @MessageMapping に似ていますが、マッピングをサブスクリプションメッセージのみに絞り込みます。@MessageMapping と同じメソッド引数をサポートします。ただし、戻り値の場合、デフォルトでは、メッセージはクライアントに直接送信され(サブスクリプションに応じて clientOutboundChannel を介して)、ブローカーではなく(一致するサブスクリプションへのブロードキャストとして) brokerChannel を介して送信されます。@SendTo または @SendToUser を追加すると、この動作がオーバーライドされ、代わりにブローカーに送信されます。

これはいつ便利ですか?ブローカーは /topic および /queue にマップされ、アプリケーションコントローラーは /app にマップされると仮定します。このセットアップでは、ブローカーは /topic および /queue へのすべてのサブスクリプションを保存します。これらのサブスクリプションは、繰り返しブロードキャストするためのものであり、アプリケーションが関与する必要はありません。クライアントは /app の宛先にサブスクライブすることもでき、コントローラーは、サブスクリプションを格納または使用せずにブローカーを関与させることなく、そのサブスクリプションに応じて値を返すことができます(事実上、1 回限りのリクエストレスポンス交換)。この使用例の 1 つは、起動時に UI に初期データを入力することです。

これが役に立たないのはいつですか?何らかの理由で、サブスクリプションを含むメッセージを両方とも独立して処理する場合を除き、ブローカーとコントローラーを同じ宛先プレフィックスにマップしようとしないでください。受信メッセージは並行して処理されます。ブローカーまたはコントローラーが指定されたメッセージを最初に処理するかどうかの保証はありません。サブスクリプションが保存され、ブロードキャストの準備ができたときにゴールを通知する場合、クライアントは、サーバーがそれをサポートしているかどうかを確認する必要があります(単純なブローカーはサポートしていません)。例:Java STOMP クライアントでは、次を実行して領収書を追加できます。

@Autowired
private TaskScheduler messageBrokerTaskScheduler;

// During initialization..
stompClient.setTaskScheduler(this.messageBrokerTaskScheduler);

// When subscribing..
StompHeaders headers = new StompHeaders();
headers.setDestination("/topic/...");
headers.setReceipt("r1");
FrameHandler handler = ...;
stompSession.subscribe(headers, handler).addReceiptTask(() -> {
    // Subscription ready...
});

サーバー側のオプションは、ExecutorChannelInterceptor を brokerChannel に登録し、サブスクリプションを含むメッセージの処理後に呼び出される afterMessageHandled メソッドを実装することです。

@MessageExceptionHandler

アプリケーションは、@MessageExceptionHandler メソッドを使用して、@MessageMapping メソッドからの例外を処理できます。例外インスタンスへのアクセスを取得する場合は、アノテーション自体で例外を宣言するか、メソッド引数を使用して例外を宣言できます。次の例では、メソッド引数を介して例外を宣言しています。

@Controller
public class MyController {

    // ...

    @MessageExceptionHandler
    public ApplicationError handleException(MyException exception) {
        // ...
        return appError;
    }
}

@MessageExceptionHandler メソッドは、柔軟なメソッドシグネチャーをサポートし、@MessageMapping メソッドと同じメソッド引数タイプと戻り値をサポートします。

通常、@MessageExceptionHandler メソッドは、それらが宣言されている @Controller クラス(またはクラス階層)内で適用されます。そのようなメソッドを(コントローラー間で)よりグローバルに適用したい場合、@ControllerAdvice でマークされたクラスで宣言できます。これは、Spring MVC で利用可能な同様のサポートに匹敵します。

4.4.7. メッセージ送信

アプリケーションの任意の部分から接続されたクライアントにメッセージを送信したい場合はどうしますか?どのアプリケーションコンポーネントも brokerChannel にメッセージを送信できます。これを行う最も簡単な方法は、SimpMessagingTemplate を挿入し、それを使用してメッセージを送信することです。通常、次の例に示すように、タイプごとに注入します。

@Controller
public class GreetingController {

    private SimpMessagingTemplate template;

    @Autowired
    public GreetingController(SimpMessagingTemplate template) {
        this.template = template;
    }

    @RequestMapping(path="/greetings", method=POST)
    public void greet(String greeting) {
        String text = "[" + getTimestamp() + "]:" + greeting;
        this.template.convertAndSend("/topic/greetings", text);
    }

}

ただし、同じタイプの別の Bean が存在する場合、名前(brokerMessagingTemplate)で修飾することもできます。

4.4.8. シンプルなブローカー

組み込みのシンプルなメッセージブローカーは、クライアントからのサブスクリプションリクエストを処理し、メモリに保存し、一致する宛先を持つ接続されたクライアントにメッセージをブロードキャストします。ブローカーは、Ant スタイルの宛先パターンへのサブスクリプションを含む、パスのような宛先をサポートしています。

アプリケーションは、ドットで区切られた(スラッシュ区切りではなく)宛先を使用することもできます。セパレーターとしてのドットを参照してください。

タスクスケジューラで構成されている場合、シンプルブローカーは STOMP ハートビート (英語) をサポートします。そのために、独自のスケジューラーを宣言するか、自動的に宣言されて内部的に使用されるスケジューラーを使用できます。次の例は、独自のスケジューラを宣言する方法を示しています。

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    private TaskScheduler messageBrokerTaskScheduler;

    @Autowired
    public void setMessageBrokerTaskScheduler(TaskScheduler taskScheduler) {
        this.messageBrokerTaskScheduler = taskScheduler;
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {

        registry.enableSimpleBroker("/queue/", "/topic/")
                .setHeartbeatValue(new long[] {10000, 20000})
                .setTaskScheduler(this.messageBrokerTaskScheduler);

        // ...
    }
}

4.4.9. 外部ブローカー

シンプルなブローカーは開始に最適ですが、STOMP コマンドのサブセットのみをサポートし(ACK、受信、およびその他の機能はサポートしていません)、シンプルなメッセージ送信ループに依存しており、クラスタリングには適していません。別の方法として、フル機能のメッセージブローカーを使用するようにアプリケーションをアップグレードできます。

選択したメッセージブローカー(RabbitMQ (英語) ActiveMQ (Apache) など)の STOMP のドキュメントを参照し、ブローカーをインストールして、STOMP サポートを有効にして実行します。次に、Spring 構成で(単純なブローカーの代わりに)STOMP ブローカーリレーを有効にできます。

次の構成例では、フル機能のブローカーを有効にします。

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableStompBrokerRelay("/topic", "/queue");
        registry.setApplicationDestinationPrefixes("/app");
    }

}

次の例は、前述の例に相当する XML 構成を示しています。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:message-broker application-destination-prefix="/app">
        <websocket:stomp-endpoint path="/portfolio" />
            <websocket:sockjs/>
        </websocket:stomp-endpoint>
        <websocket:stomp-broker-relay prefix="/topic,/queue" />
    </websocket:message-broker>

</beans>

上記の構成の STOMP ブローカーリレーは、外部メッセージブローカーにメッセージを転送することでメッセージを処理する Spring MessageHandler(Javadoc) です。そのためには、ブローカーへの TCP 接続を確立し、すべてのメッセージをブローカーに転送し、ブローカーから受信したすべてのメッセージを WebSocket セッションを介してクライアントに転送します。基本的に、メッセージを両方向に転送する「リレー」として機能します。

TCP 接続管理のために、io.projectreactor.netty:reactor-netty および io.netty:netty-all 依存関係をプロジェクトに追加します。

さらに、アプリケーションコンポーネント(HTTP リクエスト処理メソッド、ビジネスサービスなど)は、メッセージ送信に従って、メッセージをブローカーリレーに送信して、サブスクライブされた WebSocket クライアントにメッセージをブロードキャストすることもできます。

実際、ブローカーリレーにより、堅牢でスケーラブルなメッセージブロードキャストが可能になります。

4.4.10. ブローカーへの接続

STOMP ブローカーリレーは、ブローカーへの単一の「システム」TCP 接続を維持します。この接続は、サーバー側アプリケーションから発信されたメッセージにのみ使用され、メッセージの受信には使用されません。この接続の STOMP 資格情報(つまり、STOMP フレーム login および passcode ヘッダー)を構成できます。これは、XML 名前空間と Java 構成の両方で、guest と guest のデフォルト値を持つ systemLogin と systemPasscode プロパティとして公開されます。

STOMP ブローカーリレーは、接続された WebSocket クライアントごとに個別の TCP 接続も作成します。クライアントに代わって作成されるすべての TCP 接続に使用される STOMP 資格情報を構成できます。これは、XML 名前空間と Java 構成の両方で、guest と guest のデフォルト値を持つ clientLogin と clientPasscode プロパティとして公開されます。

STOMP ブローカーリレーは、クライアントに代わってブローカーに転送するすべての CONNECT フレームに、常に login および passcode ヘッダーを設定します。WebSocket クライアントはこれらのヘッダーを設定する必要はありません。それらは無視されます。認証セクションで説明しているように、WebSocket クライアントは、代わりに HTTP 認証に依存して WebSocket エンドポイントを保護し、クライアント ID を確立する必要があります。

STOMP ブローカーリレーは、「システム」TCP 接続を介してメッセージブローカーとの間でハートビートを送受信します。ハートビートを送信および受信する間隔を構成できます(デフォルトではそれぞれ 10 秒)。ブローカーへの接続が失われた場合、ブローカーリレーは、成功するまで 5 秒ごとに再接続を試行し続けます。

Spring Bean は、ApplicationListener<BrokerAvailabilityEvent> を実装して、ブローカーへの「システム」接続が失われ、再確立されたときに通知を受信できます。例:株価をブロードキャストする株価サービスは、アクティブな「システム」接続がない場合にメッセージの送信を停止できます。

デフォルトでは、STOMP ブローカーリレーは常に同じホストとポートに接続し、接続が失われた場合は必要に応じて再接続します。複数のアドレスを提供する場合、接続を試みるたびに、固定ホストとポートの代わりにアドレスのサプライヤを構成できます。次の例は、その方法を示しています。

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    // ...

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableStompBrokerRelay("/queue/", "/topic/").setTcpClient(createTcpClient());
        registry.setApplicationDestinationPrefixes("/app");
    }

    private ReactorNettyTcpClient<byte[]> createTcpClient() {
        return new ReactorNettyTcpClient<>(
                client -> client.addressSupplier(() -> ... ),
                new StompReactorNettyCodec());
    }
}

virtualHost プロパティを使用して STOMP ブローカーリレーを構成することもできます。このプロパティの値は、すべての CONNECT フレームの host ヘッダーとして設定されており、有用な場合があります(たとえば、TCP 接続が確立される実際のホストがクラウドベースの STOMP サービスを提供するホストと異なるクラウド環境で)。

4.4.11. セパレーターとしてのドット

メッセージが @MessageMapping メソッドにルーティングされると、それらは AntPathMatcher と照合されます。デフォルトでは、パターンは区切り文字としてスラッシュ(/)を使用することが期待されています。これは、Web アプリケーションでは適切な規則であり、HTTP URL に似ています。ただし、メッセージング規則に慣れている場合は、区切り文字としてドット(.)を使用するように切り替えることができます。

次の例は、Java 構成でこれを行う方法を示しています。

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    // ...

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setPathMatcher(new AntPathMatcher("."));
        registry.enableStompBrokerRelay("/queue", "/topic");
        registry.setApplicationDestinationPrefixes("/app");
    }
}

次の例は、前述の例に相当する XML 構成を示しています。

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:websocket="http://www.springframework.org/schema/websocket"
        xsi:schemaLocation="
                http://www.springframework.org/schema/beans
                https://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/websocket
                https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:message-broker application-destination-prefix="/app" path-matcher="pathMatcher">
        <websocket:stomp-endpoint path="/stomp"/>
        <websocket:stomp-broker-relay prefix="/topic,/queue" />
    </websocket:message-broker>

    <bean id="pathMatcher" class="org.springframework.util.AntPathMatcher">
        <constructor-arg index="0" value="."/>
    </bean>

</beans>

その後、次の例に示すように、コントローラーは @MessageMapping メソッドの区切り文字としてドット(.)を使用できます。

@Controller
@MessageMapping("red")
public class RedController {

    @MessageMapping("blue.{green}")
    public void handleGreen(@DestinationVariable String green) {
        // ...
    }
}

これで、クライアントは /app/red.blue.green123 にメッセージを送信できます。

前の例では、「ブローカーリレー」のプレフィックスは外部メッセージブローカーに完全に依存しているため、変更しませんでした。使用するブローカーの STOMP ドキュメントページを参照して、宛先ヘッダーに対してサポートしている規則を確認してください。

一方、「単純なブローカー」は設定された PathMatcher に依存するため、セパレーターを切り替えると、その変更はブローカーにも適用され、ブローカーがメッセージから宛先をサブスクリプションのパターンに一致させる方法にも適用されます。

4.4.12. 認証

すべての STOMP over WebSocket メッセージングセッションは、HTTP リクエストで始まります。これは、WebSockets(つまり、WebSocket ハンドシェイク)へのアップグレードリクエスト、または SockJS フォールバックの場合は、一連の SockJS HTTP トランスポートリクエストです。

多くの Web アプリケーションには、HTTP リクエストを保護するための認証と認可がすでに用意されています。通常、ユーザーは、ログインページ、HTTP 基本認証などのメカニズムを使用して、Spring Security を介して認証されます。認証されたユーザーのセキュリティコンテキストは HTTP セッションに保存され、同じ Cookie ベースのセッションの後続のリクエストに関連付けられます。

WebSocket ハンドシェイクまたは SockJS HTTP トランスポートリクエストの場合、通常、HttpServletRequest#getUserPrincipal() を介してアクセス可能な認証済みユーザーが既に存在します。Spring は、そのユーザーを、そのユーザー用に作成された WebSocket または SockJS セッションに自動的に関連付け、その後、ユーザーヘッダーを通じてそのセッションを介して転送されるすべての STOMP メッセージに関連付けます。

要するに、典型的な Web アプリケーションは、すでにセキュリティのために行っていること以外に何もする必要はありません。ユーザーは、Cookie ベースの HTTP セッション(その後、そのユーザー用に作成された WebSocket または SockJS セッションに関連付けられる)によって維持されるセキュリティコンテキストで HTTP リクエストレベルで認証され、すべての Message フローでユーザーヘッダーがスタンプされます。アプリケーションを介して。

STOMP プロトコルには、CONNECT フレームに login および passcode ヘッダーがあることに注意してください。これらはもともと、たとえば STOMP over TCP 向けに設計され、現在も必要です。ただし、WebSocket を介した STOMP の場合、デフォルトでは、Spring は STOMP プロトコルレベルの認証ヘッダーを無視し、ユーザーが HTTP トランスポートレベルで既に認証されていると想定し、WebSocket または SockJS セッションに認証済みユーザーが含まれることを想定しています。

Spring Security は、ChannelInterceptor を使用して、メッセージ内のユーザーヘッダーに基づいてメッセージを承認する WebSocket サブプロトコル認証を提供します。また、Spring Session は、WebSocket セッションがまだアクティブなときにユーザー HTTP セッションが期限切れにならないことを保証する WebSocket 統合 (英語) を提供します。

4.4.13. トークン認証

Spring Security OAuth (GitHub) は、JSON Web トークン(JWT)などのトークンベースのセキュリティのサポートを提供します。これは、前のセクションで説明したように、WebSocket を介した STOMP 対話を含む Web アプリケーションの認証メカニズムとして使用できます(つまり、Cookie ベースのセッションを通じて ID を維持します)。

同時に、Cookie ベースのセッションが常に最適とは限りません(たとえば、サーバー側セッションを維持しないアプリケーションや、認証にヘッダーを使用することが一般的なモバイルアプリケーション)。

WebSocket プロトコル、RFC 6455 (英語) は、「WebSocket ハンドシェイク中にサーバーがクライアントを認証できる特定の方法を規定していません。」ただし、実際には、ブラウザクライアントは標準認証ヘッダー(つまり、基本的な HTTP 認証)または Cookie のみを使用でき、カスタムヘッダーを提供することはできません(たとえば)。同様に、SockJS JavaScript クライアントは、SockJS トランスポートリクエストで HTTP ヘッダーを送信する方法を提供しません。sockjs-client issue 196 (GitHub) を参照してください。代わりに、トークンを送信するために使用できるクエリパラメーターを送信できますが、独自の欠点があります(たとえば、トークンが誤ってサーバーログの URL に記録される場合があります)。

前述の制限はブラウザベースのクライアントに対するものであり、Spring Java ベースの STOMP クライアントには適用されません。SpringJava ベースの STOMP クライアントは、WebSocket リクエストと SockJS リクエストの両方を含むヘッダーの送信をサポートします。

Cookie の使用を避けたいアプリケーションには、HTTP プロトコルレベルでの認証に適した代替手段がない場合があります。Cookie を使用する代わりに、STOMP メッセージングプロトコルレベルのヘッダーで認証することを好む場合があります。そのためには、2 つの簡単な手順が必要です。

  1. STOMP クライアントを使用して、接続時に認証ヘッダーを渡します。

  2. ChannelInterceptor を使用して認証ヘッダーを処理します。

次の例では、サーバー側の構成を使用してカスタム認証インターセプターを登録します。インターセプターは、CONNECT Message でユーザーヘッダーを認証および設定するだけでよいことに注意してください。Spring は、認証されたユーザーを記録して保存し、同じセッションの後続の STOMP メッセージに関連付けます。次の例は、カスタム認証インターセプターを登録する方法を示しています。

@Configuration
@EnableWebSocketMessageBroker
public class MyConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new ChannelInterceptor() {
            @Override
            public Message<?> preSend(Message<?> message, MessageChannel channel) {
                StompHeaderAccessor accessor =
                        MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
                if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                    Authentication user = ... ; // access authentication header(s)
                    accessor.setUser(user);
                }
                return message;
            }
        });
    }
}

また、メッセージに Spring Security の認証を使用する場合、現在、認証 ChannelInterceptor の構成が Spring Security の前にあることを確認する必要があることに注意してください。これは、@Order(Ordered.HIGHEST_PRECEDENCE + 99) でマークされた WebSocketMessageBrokerConfigurer の独自の実装でカスタムインターセプターを宣言することで最適に実行されます。

4.4.14. ユーザー宛先

アプリケーションは特定のユーザーをターゲットとするメッセージを送信でき、Spring の STOMP サポートは、この目的のために /user/ のプレフィックスが付いた宛先を認識します。例:クライアントは /user/queue/position-updates 宛先にサブスクライブする場合があります。この宛先は UserDestinationMessageHandler によって処理され、ユーザーセッションに固有の宛先(/queue/position-updates-user123 など)に変換されます。これにより、一般的な名前の宛先にサブスクライブする利便性が提供されると同時に、同じ宛先にサブスクライブする他のユーザーとの衝突がなくなり、各ユーザーが固有のストック位置の更新を受信できるようになります。

送信側では、/user/{username}/queue/position-updates などの宛先にメッセージを送信できます。この宛先は、UserDestinationMessageHandler によって、ユーザーに関連付けられたセッションごとに 1 つ以上の宛先に変換されます。これにより、アプリケーション内の任意のコンポーネントが、特定のユーザーをターゲットとするメッセージを送信できるようになります。名前と一般的な宛先以外の情報は必要ありません。これは、アノテーションとメッセージングテンプレートによってもサポートされます。

次の例に示すように、メッセージ処理メソッドは、@SendToUser アノテーション(共通の宛先を共有するためにクラスレベルでもサポートされています)を介して、処理中のメッセージに関連付けられたユーザーにメッセージを送信できます。

@Controller
public class PortfolioController {

    @MessageMapping("/trade")
    @SendToUser("/queue/position-updates")
    public TradeResult executeTrade(Trade trade, Principal principal) {
        // ...
        return tradeResult;
    }
}

ユーザーが複数のセッションを持っている場合、デフォルトでは、指定された宛先にサブスクライブされているすべてのセッションがターゲットになります。ただし、処理されるメッセージを送信したセッションのみをターゲットにする必要がある場合があります。これを行うには、次の例に示すように、broadcast 属性を false に設定します。

@Controller
public class MyController {

    @MessageMapping("/action")
    public void handleAction() throws Exception{
        // raise MyBusinessException here
    }

    @MessageExceptionHandler
    @SendToUser(destinations="/queue/errors", broadcast=false)
    public ApplicationError handleException(MyBusinessException exception) {
        // ...
        return appError;
    }
}
一般に、ユーザーの宛先は認証されたユーザーを意味しますが、厳密には必須ではありません。認証されたユーザーに関連付けられていない WebSocket セッションは、ユーザー宛先にサブスクライブできます。このような場合、@SendToUser アノテーションは broadcast=false とまったく同じように動作します(つまり、処理中のメッセージを送信したセッションのみを対象とします)。

たとえば、Java 構成または XML 名前空間によって作成された SimpMessagingTemplate を挿入することにより、任意のアプリケーションコンポーネントからユーザーの宛先にメッセージを送信できます。(@Qualifier での修飾に必要な場合、Bean 名は brokerMessagingTemplate です)次の例は、その方法を示しています。

@Service
public class TradeServiceImpl implements TradeService {

    private final SimpMessagingTemplate messagingTemplate;

    @Autowired
    public TradeServiceImpl(SimpMessagingTemplate messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }

    // ...

    public void afterTradeExecuted(Trade trade) {
        this.messagingTemplate.convertAndSendToUser(
                trade.getUserName(), "/queue/position-updates", trade.getResult());
    }
}
外部メッセージブローカーでユーザー宛先を使用する場合は、非アクティブなキューを管理する方法についてブローカーのドキュメントを確認して、ユーザーセッションが終了するとすべての一意のユーザーキューが削除されるようにする必要があります。例: /exchange/amq.direct/position-updates などの宛先を使用すると、RabbitMQ は自動削除キューを作成します。その場合、クライアントは /user/exchange/amq.direct/position-updates をサブスクライブできます。同様に、ActiveMQ には非アクティブな宛先をパージするための構成オプションがあります (Apache)

マルチアプリケーションサーバーのシナリオでは、ユーザーが別のサーバーに接続しているため、ユーザーの宛先が未解決のままになることがあります。このような場合、未解決のメッセージをブロードキャストするように宛先を構成して、他のサーバーが試行できるようにすることができます。これは、Java 構成の MessageBrokerRegistry の userDestinationBroadcast プロパティおよび XML の message-broker エレメントの user-destination-broadcast 属性を介して実行できます。

4.4.15. メッセージの順序

ブローカーからのメッセージは clientOutboundChannel に発行され、そこから WebSocket セッションに書き込まれます。チャネルが ThreadPoolExecutor によってサポートされているため、メッセージは異なるスレッドで処理され、クライアントが受信する結果のシーケンスは、発行の正確な順序と一致しない場合があります。

これが課題になる場合は、次の例に示すように、setPreservePublishOrder フラグを有効にします。

@Configuration
@EnableWebSocketMessageBroker
public class MyConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    protected void configureMessageBroker(MessageBrokerRegistry registry) {
        // ...
        registry.setPreservePublishOrder(true);
    }

}

次の例は、前述の例に相当する XML 構成を示しています。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:message-broker preserve-publish-order="true">
        <!-- ... -->
    </websocket:message-broker>

</beans>

フラグが設定されると、同じクライアントセッション内のメッセージが 1 つずつ clientOutboundChannel に発行されるため、発行の順序が保証されます。これによりパフォーマンスのオーバーヘッドがわずかに発生するため、必要な場合にのみ有効にする必要があります。

4.4.16. イベント

ApplicationContext のいくつかのイベントが公開されており、Spring の ApplicationListener インターフェースを実装することで受信できます。

  • BrokerAvailabilityEvent: ブローカーが使用可能または使用不可になるときを示します。「単純な」ブローカーは起動時にすぐに利用可能になり、アプリケーションの実行中はそのままになりますが、STOMP「ブローカーリレー」はフル機能のブローカーへの接続を失う可能性があります(たとえば、ブローカーを再起動した場合)。ブローカーリレーには再接続ロジックがあり、ブローカーが戻ったときにブローカーへの「システム」接続を再確立します。その結果、このイベントは、状態が接続状態から切断状態に、またはその逆に変化するたびに公開されます。SimpMessagingTemplate を使用するコンポーネントは、このイベントをサブスクライブし、ブローカーが利用できないときにメッセージを送信しないようにする必要があります。いずれの場合でも、メッセージを送信するときに MessageDeliveryException を処理する準備が必要です。

  • SessionConnectEvent: 新しいクライアントセッションの開始を示す新しい STOMP CONNECT を受信したときに公開されます。イベントには、セッション ID、ユーザー情報(存在する場合)、クライアントが送信したカスタムヘッダーなど、接続を表すメッセージが含まれます。これは、クライアントセッションの追跡に役立ちます。このイベントにサブスクライブされたコンポーネントは、含まれるメッセージを SimpMessageHeaderAccessor または StompMessageHeaderAccessor でラップできます。

  • SessionConnectedEvent: ブローカーが CONNECT へのレスポンスとして STOMP CONNECTED フレームを送信したときに、SessionConnectEvent の直後に公開されました。この時点で、STOMP セッションは完全に確立されたと見なすことができます。

  • SessionSubscribeEvent: 新しい STOMP SUBSCRIBE が受信されたときに公開されます。

  • SessionUnsubscribeEvent: 新しい STOMP UNSUBSCRIBE が受信されたときに公開されます。

  • SessionDisconnectEvent: STOMP セッションが終了すると公開されます。DISCONNECT はクライアントから送信された可能性があります。または、WebSocket セッションが閉じられたときに自動的に生成された可能性があります。場合によっては、このイベントはセッションごとに複数回発行されます。コンポーネントは、複数の切断イベントに関して i 等である必要があります。

フル機能のブローカーを使用する場合、ブローカーが一時的に利用できなくなると、STOMP「ブローカーリレー」が自動的に「システム」接続を再接続します。ただし、クライアント接続は自動的に再接続されません。ハートビートが有効になっていると仮定すると、クライアントは通常、ブローカーが 10 秒以内に応答しないことに気付きます。クライアントは、独自の再接続ロジックを実装する必要があります。

4.4.17. 傍受

イベントは、すべてのクライアントメッセージではなく、STOMP 接続のライフサイクルに関する通知を提供します。アプリケーションは、ChannelInterceptor を登録して、メッセージおよび処理チェーンの任意の部分をインターセプトすることもできます。次の例は、クライアントからの受信メッセージを傍受する方法を示しています。

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new MyChannelInterceptor());
    }
}

次の例に示すように、カスタム ChannelInterceptor は StompHeaderAccessor または SimpMessageHeaderAccessor を使用してメッセージに関する情報にアクセスできます。

public class MyChannelInterceptor implements ChannelInterceptor {

    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
        StompCommand command = accessor.getStompCommand();
        // ...
        return message;
    }
}

アプリケーションは、ExecutorChannelInterceptor を実装することもできます。ExecutorChannelInterceptor は、メッセージが処理されるスレッドでコールバックを使用する ChannelInterceptor のサブインターフェースです。ChannelInterceptor は、チャネルに送信されるメッセージごとに 1 回呼び出されますが、ExecutorChannelInterceptor は、チャネルからのメッセージをサブスクライブする各 MessageHandler のスレッドにフックを提供します。

前述の SesionDisconnectEvent と同様に、DISCONNECT メッセージはクライアントからのものであるか、WebSocket セッションが閉じられたときに自動的に生成されることに注意してください。場合によっては、インターセプターがセッションごとにこのメッセージを複数回インターセプトすることがあります。コンポーネントは、複数の切断イベントに関して i 等である必要があります。

4.4.18. STOMP クライアント

Spring は、WebSocket クライアントを介した STOMP および TCP クライアントを介した STOMP を提供します。

まず、次の例に示すように、WebSocketStompClient を作成および構成できます。

WebSocketClient webSocketClient = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
stompClient.setMessageConverter(new StringMessageConverter());
stompClient.setTaskScheduler(taskScheduler); // for heartbeats

上記の例では、StandardWebSocketClient を SockJsClient に置き換えることができます。これは、WebSocketClient の実装でもあるためです。SockJsClient は、フォールバックとして WebSocket または HTTP ベースのトランスポートを使用できます。詳細については、SockJsClient を参照してください。

次に、次の例に示すように、接続を確立し、STOMP セッションのハンドラーを提供できます。

String url = "ws://127.0.0.1:8080/endpoint";
StompSessionHandler sessionHandler = new MyStompSessionHandler();
stompClient.connect(url, sessionHandler);

セッションの使用準備が整うと、次の例に示すように、ハンドラーに通知されます。

public class MyStompSessionHandler extends StompSessionHandlerAdapter {

    @Override
    public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
        // ...
    }
}

セッションが確立されると、次の例に示すように、ペイロードを送信し、構成された MessageConverter で直列化できます。

session.send("/topic/something", "payload");

宛先にサブスクライブすることもできます。subscribe メソッドは、サブスクリプションのメッセージのハンドラーを必要とし、サブスクライブ解除に使用できる Subscription ハンドルを返します。次の例に示すように、ハンドラーは、受信したメッセージごとに、ペイロードをデシリアライズするターゲット Object タイプを指定できます。

session.subscribe("/topic/something", new StompFrameHandler() {

    @Override
    public Type getPayloadType(StompHeaders headers) {
        return String.class;
    }

    @Override
    public void handleFrame(StompHeaders headers, Object payload) {
        // ...
    }

});

STOMP ハートビートを有効にするには、WebSocketStompClient を TaskScheduler で構成し、オプションでハートビート間隔(ハートビートが送信される書き込み非アクティブの場合は 10 秒、接続を閉じる読み取り非アクティブの場合は 10 秒)をカスタマイズできます。

パフォーマンステストに WebSocketStompClient を使用して同じマシンから数千のクライアントをシミュレートする場合、各接続は独自のハートビートタスクをスケジュールし、同じマシンで実行される多数のクライアントに対して最適化されないため、ハートビートをオフにすることを検討してください。

STOMP プロトコルは受信もサポートします。この場合、クライアントは receipt ヘッダーを追加する必要があり、送信またはサブスクライブの処理後にサーバーが RECEIPT フレームで応答します。これをサポートするために、StompSession は setAutoReceipt(boolean) を提供します。これにより、後続の送信またはサブスクライブイベントごとに receipt ヘッダーが追加されます。あるいは、手動でレシートヘッダーを StompHeaders に追加することもできます。送信とサブスクライブの両方が、Receiptable のインスタンスを返します。これを使用して、受信の成功および失敗のコールバックの登録に使用できます。この機能を使用するには、TaskScheduler と受信期限が切れるまでの時間(デフォルトでは 15 秒)でクライアントを構成する必要があります。

StompSessionHandler 自体が StompFrameHandler であることに注意してください。これにより、メッセージの処理からの例外の handleException コールバックおよび ConnectionLostException を含むトランスポートレベルエラーの handleTransportError に加えて、ERROR フレームを処理できます。

4.4.19. WebSocket スコープ

各 WebSocket セッションには、属性のマップがあります。次の例に示すように、マップは受信クライアントメッセージのヘッダーとして添付され、コントローラーメソッドからアクセスできます。

@Controller
public class MyController {

    @MessageMapping("/action")
    public void handle(SimpMessageHeaderAccessor headerAccessor) {
        Map<String, Object> attrs = headerAccessor.getSessionAttributes();
        // ...
    }
}

websocket スコープで Spring 管理の Bean を宣言できます。WebSocket スコープの Bean を clientInboundChannel に登録されているコントローラーおよびチャネルインターセプターに注入できます。これらは通常シングルトンであり、個々の WebSocket セッションよりも長生きします。次の例に示すように、WebSocket スコープ Bean に対してスコーププロキシモードを使用する必要があります。

@Component
@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBean {

    @PostConstruct
    public void init() {
        // Invoked after dependencies injected
    }

    // ...

    @PreDestroy
    public void destroy() {
        // Invoked when the WebSocket session ends
    }
}

@Controller
public class MyController {

    private final MyBean myBean;

    @Autowired
    public MyController(MyBean myBean) {
        this.myBean = myBean;
    }

    @MessageMapping("/action")
    public void handle() {
        // this.myBean from the current WebSocket session
    }
}

他のカスタムスコープと同様に、Spring は、コントローラーから最初にアクセスしたときに新しい MyBean インスタンスを初期化し、WebSocket セッション属性にインスタンスを保存します。その後、セッションが終了するまで同じインスタンスが返されます。前の例に示すように、WebSocket スコープの Bean では、Spring ライフサイクルメソッドがすべて呼び出されます。

4.4.20. パフォーマンス

パフォーマンスに関しては特効薬はありません。メッセージのサイズと量、アプリケーションメソッドがブロックを必要とする作業を実行するかどうか、および外部要因(ネットワーク速度やその他の課題など)など、多くの要因が影響します。このセクションの目的は、使用可能な構成オプションの概要と、スケーリングをどのように推論するかについての考えを提供することです。

メッセージングアプリケーションでは、メッセージはスレッドプールによってサポートされる非同期実行のチャネルを介して渡されます。このようなアプリケーションを構成するには、チャネルとメッセージの流れに関する十分な知識が必要です。Flow のメッセージを確認することをお勧めします。

開始する明白な場所は、clientInboundChannel と clientOutboundChannel をサポートするスレッドプールを構成することです。デフォルトでは、両方とも使用可能なプロセッサーの数の 2 倍で構成されます。

アノテーション付きメソッドでのメッセージの処理が主に CPU バウンドである場合、clientInboundChannel のスレッドの数はプロセッサーの数に近いままである必要があります。それらが行う作業がより多くの IO バウンドであり、データベースまたは他の外部システムでのブロックまたは待機が必要な場合、おそらくスレッドプールサイズを増やす必要があります。

ThreadPoolExecutor には 3 つの重要なプロパティがあります。コアスレッドプールサイズ、最大スレッドプールサイズ、および利用可能なスレッドがないタスクを格納するキューの容量です。

混乱の一般的なポイントは、コアプールサイズ(たとえば、10)と最大プールサイズ(たとえば、20)を構成すると、スレッドプールが 10 〜 20 スレッドになることです。実際、容量がデフォルト値の Integer.MAX_VALUE のままになっている場合、すべての追加タスクがキューに入れられるため、スレッドプールがコアプールサイズを超えて増加することはありません。

ThreadPoolExecutor の javadoc を参照して、これらのプロパティがどのように機能するかを学び、さまざまなキューイング戦略を理解しましょう。

clientOutboundChannel 側では、WebSocket クライアントにメッセージを送信することがすべてです。クライアントが高速ネットワーク上にある場合、スレッドの数は利用可能なプロセッサーの数に近いままである必要があります。速度が遅い場合や帯域幅が狭い場合、メッセージを消費してスレッドプールに負担をかけるのに時間がかかります。スレッドプールサイズを増やす必要があります。

clientInboundChannel のワークロードを予測することは可能ですが、結局のところ、アプリケーションの制御に依存しない要因に基づいているため、「clientOutboundChannel」の構成方法はより困難です。このため、メッセージの送信には sendTimeLimit と sendBufferSizeLimit の 2 つの追加プロパティが関係しています。これらのメソッドを使用して、送信にかかる時間と、クライアントにメッセージを送信するときにバッファできるデータの量を構成できます。

一般的な考え方は、常に、単一のスレッドのみを使用してクライアントに送信できるということです。その間、すべての追加メッセージはバッファリングされ、これらのプロパティを使用して、メッセージの送信にかかる時間と、その間にバッファリングできるデータの量を決定できます。重要な追加の詳細については、javadoc および XML スキーマのドキュメントを参照してください。

次の例は、可能な構成を示しています。

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
        registration.setSendTimeLimit(15 * 1000).setSendBufferSizeLimit(512 * 1024);
    }

    // ...

}

次の例は、前述の例に相当する XML 構成を示しています。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:message-broker>
        <websocket:transport send-timeout="15000" send-buffer-size="524288" />
        <!-- ... -->
    </websocket:message-broker>

</beans>

また、前述の WebSocket トランスポート構成を使用して、受信 STOMP メッセージの最大許容サイズを構成することもできます。理論的には、WebSocket メッセージのサイズはほぼ無制限です。実際には、WebSocket サーバーは制限を課しています。たとえば、Tomcat では 8K、Jetty では 64K です。このため、STOMP クライアント(JavaScript webstomp-client (GitHub) など)は、より大きな STOMP メッセージを 16K 境界で分割し、複数の WebSocket メッセージとして送信します。これには、サーバーがバッファリングおよび再アセンブルする必要があります。

Spring の STOMP-over-WebSocket サポートがこれを行うため、WebSocket サーバー固有のメッセージサイズに関係なく、アプリケーションは STOMP メッセージの最大サイズを構成できます。WebSocket メッセージのサイズは、必要に応じて自動的に調整され、最小で 16K WebSocket メッセージを伝送できることを忘れないでください。

次の例は、可能な構成の 1 つを示しています。

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
        registration.setMessageSizeLimit(128 * 1024);
    }

    // ...

}

次の例は、前述の例に相当する XML 構成を示しています。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:message-broker>
        <websocket:transport message-size="131072" />
        <!-- ... -->
    </websocket:message-broker>

</beans>

スケーリングに関する重要なポイントには、複数のアプリケーションインスタンスの使用が含まれます。現在、単純なブローカーではできません。ただし、フル機能のブローカー(RabbitMQ など)を使用する場合、各アプリケーションインスタンスはブローカーに接続し、1 つのアプリケーションインスタンスからブロードキャストされるメッセージは、ブローカーを介して他のアプリケーションインスタンスを介して接続される WebSocket クライアントにブロードキャストできます。

4.4.21. モニター

@EnableWebSocketMessageBroker または <websocket:message-broker> を使用すると、重要なインフラストラクチャコンポーネントが自動的に統計とカウンターを収集し、アプリケーションの内部状態に関する重要なインサイトを提供します。また、この構成では、使用可能なすべての情報を 1 か所に収集し、デフォルトで 30 分ごとに INFO レベルでログに記録する、タイプ WebSocketMessageBrokerStats の Bean を宣言しています。この Bean は、実行時に表示するために Spring の MBeanExporter を介して(たとえば、JDK の jconsole を介して)JMX にエクスポートできます。次のリストは、利用可能な情報をまとめたものです。

クライアント WebSocket セッション
カレント

現在存在するクライアントセッションの数を示します。カウントは、WebSocket と HTTP ストリーミングおよび SockJS セッションのポーリングによってさらに分類されます。

合計

確立されたセッションの合計数を示します。

異常閉鎖
接続障害

確立されましたが、60 秒以内にメッセージを受信しなかった後に閉じられたセッション。これは通常、プロキシまたはネットワークの課題を示しています。

送信制限を超えました

設定された送信タイムアウトまたは送信バッファ制限を超えた後にセッションが閉じられました。これは、低速クライアントで発生する可能性があります(前のセクションを参照)。

トランスポートエラー

WebSocket 接続または HTTP リクエストまたはレスポンスの読み取りまたは書き込みの失敗など、トランスポートエラーの後にセッションが閉じられました。

STOMP フレーム

処理された CONNECT、CONNECTED、および DISCONNECT フレームの合計数。STOMP レベルで接続されたクライアントの数を示します。セッションが異常終了した場合、またはクライアントが DISCONNECT フレームを送信せずに閉じた場合、DISCONNECT カウントは低くなることに注意してください。

STOMP ブローカーリレー
TCP 接続

クライアント WebSocket セッションに代わってブローカーに対して確立される TCP 接続の数を示します。これは、クライアント WebSocket セッションの数 + アプリケーション内からメッセージを送信するための 1 つの追加の共有「システム」接続に等しくなければなりません。

STOMP フレーム

クライアントに代わってブローカーに転送された、またはブローカーから受信した CONNECT、CONNECTED、および DISCONNECT フレームの総数。クライアント WebSocket セッションがどのように閉じられたかに関係なく、DISCONNECT フレームがブローカーに送信されることに注意してください。DISCONNECT フレーム数が少ないということは、ブローカーが積極的に接続を閉じていることを示しています(ハートビートが間に合わなかった、無効な入力フレーム、またはその他の課題が原因である可能性があります)。

クライアント受信チャネル

clientInboundChannel をサポートするスレッドプールからの統計情報。受信メッセージ処理の状態に関するインサイトを提供します。ここでのタスクのキューイングは、アプリケーションがメッセージを処理するには遅すぎる可能性があることを示しています。I/O バウンドタスク(たとえば、遅いデータベースクエリ、サードパーティ REST API への HTTP リクエストなど)がある場合は、スレッドプールサイズを増やすことを検討してください。

クライアント送信チャネル

clientOutboundChannel をサポートするスレッドプールからの統計。クライアントへのブロードキャストメッセージの健全性に関するインサイトを提供します。ここでキューイングするタスクは、クライアントが遅すぎてメッセージを消費できないことを示しています。これに対処する 1 つの方法は、スレッドプールサイズを増やして、予想される同時クライアントの数に対応することです。もう 1 つのオプションは、送信タイムアウトと送信バッファサイズの制限を減らすことです(前のセクションを参照)。

SockJS タスクスケジューラ

ハートビートの送信に使用される SockJS タスクスケジューラのスレッドプールからの統計。STOMP レベルでハートビートがネゴシエートされると、SockJS ハートビートが無効になることに注意してください。

4.4.22. テスト

Spring の STOMP-over-WebSocket サポートを使用する場合、アプリケーションをテストするには主に 2 つのアプローチがあります。最初のメソッドは、サーバー側のテストを作成して、コントローラーの機能とアノテーション付きのメッセージ処理メソッドを検証することです。2 つ目は、クライアントとサーバーの実行を伴う完全なエンドツーエンドテストを記述することです。

2 つのアプローチは相互に排他的ではありません。それどころか、それぞれが全体的なテスト戦略に位置しています。サーバー側のテストは、より焦点が絞られており、記述と保守が簡単です。一方、エンドツーエンドの統合テストはより完全であり、より多くのテストが行われますが、記述と保守により複雑になります。

サーバー側テストの最も簡単な形式は、コントローラーユニットテストを記述することです。ただし、コントローラーの機能の多くはアノテーションに依存するため、これは十分に有用ではありません。純粋な単体テストでは、単純にテストできません。

理想的には、Spring MVC テストフレームワークを使用して HTTP リクエストを処理するコントローラーをテストするアプローチと同様に、つまり、サーブレットコンテナーを実行せずに Spring Framework に依存してアノテーションを呼び出すコントローラーと同様に、テスト中のコントローラーを実行時に呼び出す必要があります。コントローラー。Spring MVC テストと同様に、ここでは 2 つの可能な選択肢があります。「コンテキストベース」を使用するか、「スタンドアロン」セットアップを使用します。

  • Spring TestContext フレームワークの助けを借りて実際の Spring 構成をロードし、テストフィールドとして clientInboundChannel を挿入し、それを使用してコントローラーメソッドによって処理されるメッセージを送信します。

  • コントローラー(つまり SimpAnnotationMethodMessageHandler)を呼び出し、コントローラーのメッセージを直接渡すために必要な最小限の Spring フレームワークインフラストラクチャを手動でセットアップします。

これらのセットアップシナリオは両方とも、株式ポートフォリオ (GitHub) のサンプルアプリケーションのテスト (GitHub) で実証されています。

2 番目のアプローチは、エンドツーエンドの統合テストを作成することです。そのためには、WebSocket サーバーを組み込みモードで実行し、STOMP フレームを含む WebSocket メッセージを送信する WebSocket クライアントとしてそれに接続する必要があります。株式ポートフォリオ (GitHub) のサンプルアプリケーションのテスト (GitHub) では、 Tomcat を組み込み WebSocket サーバーとして使用し、テスト目的で単純な STOMP クライアントを使用することで、このアプローチも示しています。

5. その他 Web フレームワーク

この章では、Spring とサードパーティの Web フレームワークとの統合について詳しく説明します。

Spring Framework の中心的な価値提案の 1 つは、選択を可能にすることです。一般的な意味では、Spring は特定のアーキテクチャー、テクノロジー、または方法論の使用や購入を強制しません(確かに他のものよりもいくつかを推奨しています)。開発者とその開発チームに最も関連性のあるアーキテクチャ、テクノロジー、または方法論を選択および選択する自由は、Spring が独自の Web フレームワーク(Spring MVC および Spring WebFlux)を提供する Web エリアで最も明白です多くの一般的なサードパーティの Web フレームワークとの統合をサポートします。

5.1. 共通の構成

サポートされている各 Web フレームワークの統合の詳細に進む前に、まず、1 つの Web フレームワークに固有ではない一般的な Spring 構成を見てみましょう。(このセクションは、Spring 独自の Web フレームワークバリアントにも等しく適用できます。)

Spring の軽量アプリケーションモデルによって支持されている概念の 1 つ(より良い言葉が必要なため)は、階層化されたアーキテクチャの概念です。「古典的な」階層化アーキテクチャでは、Web レイヤーは多くのレイヤーの 1 つにすぎないことに注意してください。サーバー側アプリケーションへのエントリポイントの 1 つとして機能し、サービス固有の(およびプレゼンテーションテクノロジーに依存しない)ユースケースを満たすために、サービスレイヤーで定義されたサービスオブジェクト(ファサード)に委譲します。Spring では、これらのサービスオブジェクト、その他のビジネス固有のオブジェクト、データアクセスオブジェクトなどは、Web またはプレゼンテーションレイヤーオブジェクト(Spring MVC コントローラーなどのプレゼンテーションオブジェクト)を含まない個別の「ビジネスコンテキスト」に存在します。別個の「プレゼンテーションコンテキスト」で構成されます)。このセクションでは、アプリケーションにすべての「ビジネス Bean」を含む Spring コンテナー(WebApplicationContext)を構成する方法について詳しく説明します。

詳細に進み、必要なのは、Web アプリケーションの標準 Java EE サーブレット web.xml ファイルで ContextLoaderListener(Javadoc) を宣言し、Spring XML のセットを定義する contextConfigLocation<context-param /> セクション(同じファイル内)を追加することだけです。ロードする構成ファイル。

以下の <listener/> 構成を検討してください。

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

さらに、次の <context-param/> 構成を検討してください。

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param>

contextConfigLocation コンテキストパラメーターを指定しない場合、ContextLoaderListener は /WEB-INF/applicationContext.xml と呼ばれるファイルを探してロードします。コンテキストファイルがロードされると、Spring は Bean 定義に基づいて WebApplicationContext(Javadoc) オブジェクトを作成し、Web アプリケーションの ServletContext に保存します。

すべての Java Web フレームワークはサーブレット API の上に構築されているため、次のコードスニペットを使用して、ContextLoaderListener によって作成されたこの「ビジネスコンテキスト」 ApplicationContext にアクセスできます。

次の例は、WebApplicationContext を取得する方法を示しています。

WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);

WebApplicationContextUtils(Javadoc) クラスは便宜上のものであるため、ServletContext 属性の名前を覚えておく必要はありません。オブジェクトが WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE キーに存在しない場合、その getWebApplicationContext() メソッドは null を返します。アプリケーションで NullPointerExceptions を取得するリスクよりも、getRequiredWebApplicationContext() メソッドを使用することをお勧めします。ApplicationContext がない場合、このメソッドは例外をスローします。

WebApplicationContext への参照を取得したら、名前またはタイプで Bean を取得できます。ほとんどの開発者は、Bean を名前で取得し、実装されたインターフェースの 1 つにキャストします。

幸いなことに、このセクションのほとんどのフレームワークには、Bean を検索する簡単な方法があります。Spring コンテナーから Bean を簡単に取得できるだけでなく、コントローラーで依存性注入を使用することもできます。各 Web フレームワークセクションには、特定の統合戦略の詳細があります。

5.2. JSF

JavaServer Faces(JSF)は、JCP の標準コンポーネントベースのイベント駆動型 Web ユーザーインターフェースフレームワークです。Java EE 傘の公式部分ですが、個別に使用することもできます。Tomcat に Mojarra または MyFaces を埋め込むことにより。

JSF の最近のバージョンは、アプリケーションサーバーの CDI インフラストラクチャと密接に結びついており、新しい JSF 機能の一部はそのような環境でのみ機能することに注意してください。Spring の JSF サポートはこれ以上積極的に進化せず、主に古い JSF ベースのアプリケーションを近代化する際の移行目的で存在します。

Spring の JSF 統合の重要な要素は、JSF ELResolver メカニズムです。

5.2.1. Spring Bean リゾルバー

SpringBeanFacesELResolver は JSF 準拠の ELResolver 実装であり、JSF および JSP で使用される標準の統一 EL と統合されます。まず Spring の「ビジネスコンテキスト」 WebApplicationContext に委譲され、次に基盤となる JSF 実装のデフォルトリゾルバーに委譲されます。

設定に関しては、次の例に示すように、JSF faces-context.xml ファイルで SpringBeanFacesELResolver を定義できます。

<faces-config>
    <application>
        <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
        ...
    </application>
</faces-config>

5.2.2. FacesContextUtils を使用する

カスタム ELResolver は、プロパティを faces-config.xml の Bean にマッピングするときにうまく機能しますが、場合によっては、明示的に Bean を取得する必要があります。FacesContextUtils(Javadoc) クラスはこれを簡単にします。WebApplicationContextUtils と似ていますが、ServletContext パラメーターではなく FacesContext パラメーターを使用する点が異なります。

次の例は、FacesContextUtils の使用方法を示しています。

ApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance());

5.3. Apache Struts 2.x

Craig McClanahan が発明した Struts (英語) は、Apache Software Foundation がホストするオープンソースプロジェクトです。当時、JSP / サーブレットのプログラミングパラダイムを大幅に簡素化し、独自のフレームワークを使用していた多くの開発者を獲得しました。プログラミングモデルを簡素化し、オープンソース(したがって、ビールのようにフリー)であり、Java の Web 開発者の間でプロジェクトが成長し、人気を博した大きなコミュニティがありました。

元の Struts 1.x, の後継として、Struts 2.x と Struts 提供の Spring プラグイン (Apache) をチェックして、組み込みの Spring 統合を確認してください。

5.4. Apache Tapestry 5.x

Tapestry (Apache) は、「Java で動的、堅牢、スケーラブルな Web アプリケーションを作成するためのコンポーネント指向のフレームワークです。」

Spring には独自の強力な Web レイヤーがありますが、Web ユーザーインターフェースに Tapestry を、下位レイヤーに Spring コンテナーを組み合わせて使用することにより、エンタープライズ Java アプリケーションを構築することには多くのユニークな利点があります。

詳細については、Tapestry の Spring 専用の統合モジュール (Apache) を参照してください。

5.5. その他のリソース

次のリンクは、この章で説明されているさまざまな Web フレームワークに関する詳細なリソースに移動します。

Unofficial Translation by spring.pleiades.io. See the original content.