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

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. サーブレット構成

サーブレット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にマップされます。

AbstractDispatcherServletInitializerisAsyncSupported 保護メソッドは、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など)からスローされた場合、DispatcherServletHandlerExceptionResolver 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 の便利なサブクラスと、JstlViewTilesViewなどのサブクラス。 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-Typetext/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 属性を評価および変更します。

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

LocaleChangeInterceptorHandlerMapping 定義の1つに追加することにより、ロケールの変更を有効にできます。リクエスト内のパラメーターを検出し、それに応じてロケールを変更し、ディスパッチャーのアプリケーションコンテキストで LocaleResolversetLocale メソッドを呼び出します。次の例は、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) に基づく実装と、サーブレット3.0マルチパートリクエスト解析に基づく実装があります。

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

Apache Commons FileUpload

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

サーブレット3.0

サーブレット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"))
    }

}

サーブレット3.0の構成が完了したら、タイプ StandardServletMultipartResolver のBeanを名前 multipartResolverで追加できます。

1.1.12. ロギング

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

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

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

機密データ

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

次の例は、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-Host, X-Forwarded-Port, X-Forwarded-Proto, X-Forwarded-SslX-Forwarded-Prefixなど、他の非標準ヘッダーもあります。

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

転送されたヘッダーには、意図したとおりにプロキシによってヘッダーが追加されたか、悪意のあるクライアントによってヘッダーが追加されたかをアプリケーションが認識できないため、セキュリティ上の考慮事項があります。これが、外部からの信頼できない Forwarded ヘッダーを削除するように、信頼の境界にあるプロキシを構成する必要がある理由です。 ForwardedHeaderFilterremoveOnly=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 が発生します。単純型(int, long, Dateなど)はデフォルトでサポートされており、他のデータ型のサポートを登録できます。型変換および 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を入力する場合)。これを有効にするには、ファイル拡張子に伴う課題のほとんどを回避するために、クエリパラメーターベースの戦略をお勧めします。または、ファイル拡張子を使用する必要がある場合は、ContentNegotiationConfigurermediaTypes プロパティを使用して、明示的に登録された拡張子のリストに制限することを検討してください。

サフィックスマッチと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) {
    // ...
}
1 consumes 属性を使用して、コンテンツタイプごとにマッピングを絞り込みます。
Kotlin
@PostMapping("/pets", consumes = ["application/json"]) (1)
fun addPet(@RequestBody pet: Pet) {
    // ...
}
1 consumes 属性を使用して、コンテンツタイプごとにマッピングを絞り込みます。

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

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

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

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

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

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

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

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

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

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

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

Java
@GetMapping(path = "/pets", headers = "myHeader=myValue") (1)
public void findPet(@PathVariable String petId) {
    // ...
}
1 myHeadermyValueと等しいかどうかのテスト。
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と同等です。

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

WebRequest, NativeWebRequest

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

javax.servlet.ServletRequest, javax.servlet.ServletResponse

ServletRequest, HttpServletRequestやSpringの MultipartRequest, MultipartHttpServletRequestなど、特定の要求または応答タイプを選択します。

javax.servlet.http.HttpSession

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

javax.servlet.http.PushBuilder

プログラムによるHTTP / 2リソースプッシュ用のサーブレット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.InputStream, java.io.Reader

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

java.io.OutputStream, java.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.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap

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

RedirectAttributes

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

@ModelAttribute

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

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

Errors, BindingResult

コマンドオブジェクト(つまり、@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.Map, org.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の代替(たとえば、基礎となるサービスがそれらのいずれかを返す場合)。

ResponseBodyEmitter, SseEmitter

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

StreamingResponseBody

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

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

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

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

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

その他の戻り値

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

型変換

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

このような場合、構成されたコンバーターに基づいて型変換が自動的に適用されます。デフォルトでは、単純型(int, long, Dateなど)がサポートされています。 WebDataBinder DataBinder を参照)を介して、または FormattersFormattingConversionServiceに登録することにより、型変換をカスタマイズできます。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構成では、UrlPathHelperremoveSemicolonContent=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)
    //...
}
1 Accept-Encoding ヘッダーの値を取得します。
2 Keep-Alive ヘッダーの値を取得します。
Kotlin
@GetMapping("/demo")
fun handle(
        @RequestHeader("Accept-Encoding") encoding: String, (1)
        @RequestHeader("Keep-Alive") keepAlive: Long) { (2)
    //...
}
1 Accept-Encoding ヘッダーの値を取得します。
2 Keep-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)
    //...
}
1 JSESSIONID Cookieの値を取得します。
Kotlin
@GetMapping("/demo")
fun handle(@CookieValue("JSESSIONID") cookie: String) { (1)
    //...
}
1 JSESSIONID Cookieの値を取得します。

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

@ModelAttribute

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

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

上記の 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";
    }
    // ...
}
1 Pet インスタンスを検証します。
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 のコンテンツが使用されます。それ以外の場合、モデルのコンテンツが使用されます。

RequestMappingHandlerAdapterignoreDefaultModelOnRedirectと呼ばれるフラグを提供します。これを使用して、コントローラーメソッドがリダイレクトする場合、デフォルトの 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 はフラッシュ属性を保持するために使用され、FlashMapManagerFlashMap インスタンスを保存、取得、および管理するために使用されます。

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>として宣言され、アノテーションにパラメーター名が指定されていない場合、指定された各パラメーター名のマルチパートファイルがマップに入力されます。

サーブレット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 {
    // ...
}

@RequestPartjavax.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構成メッセージコンバーターオプションを使用して、メッセージ変換を構成またはカスタマイズできます。

@RequestBodyjavax.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 メソッドを使用するか、@ModelAttributename 属性(戻り値用)を使用して、明示的な名前をいつでも割り当てることができます。

@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

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

WebRequest, NativeWebRequest

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

javax.servlet.ServletRequest, javax.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.TimeZone, java.time.ZoneId

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

java.io.OutputStream, java.io.Writer

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

java.util.Map, org.springframework.ui.Model, org.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.Map, org.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();
        }
    }

}
1 listPeople は、リポジトリで見つかったすべての Person オブジェクトをJSONとして返すハンドラー関数です。
2 createPerson は、リクエスト本文に含まれる新しい Person を保存するハンドラー関数です。
3 getPerson は、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()

    }
}
1 listPeople は、リポジトリで見つかったすべての Person オブジェクトをJSONとして返すハンドラー関数です。
2 createPerson は、リクエスト本文に含まれる新しい Person を保存するハンドラー関数です。
3 getPerson は、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)
        }
    }
}
1 Validator インスタンスを作成します。
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)
        }
    }
}
1 Validator インスタンスを作成します。
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 /personPersonHandler.listPeopleにルーティングされます
3 追加の述部のないPOST /personPersonHandler.createPersonにマップされます
4 otherRoute は、他の場所で作成され、構築されたルートに追加されるルーター機能です。
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 /personPersonHandler.listPeopleにルーティングされます
3 追加の述部のないPOST /personPersonHandler.createPersonにマップされます
4 otherRoute は、他の場所で作成され、構築されたルートに追加されるルーター機能です。
ネストされたルート

ルーター機能のグループが共有述語(共有パスなど)を持つことは一般的です。上記の例では、共有述語は、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();
1 path の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に要求をルーティングします。

  • HandlerFunctionAdapter : DispatcherHandler が要求にマップされた 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. ハンドラー関数のフィルタリング

ルーティング関数ビルダーで before, afterまたは 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を取ります。これは、ServerRequestHandlerFunction を取り、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変数をエンコードするよう要求します。
4 UriComponentsをビルドします。
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変数をエンコードするよう要求します。
4 UriComponentsをビルドします。
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を作成できます。 UriBuilderFactoryUriBuilder は、ベースURL、エンコード設定、その他の詳細などの共有構成に基づいて、URIテンプレートからURIを構築するプラグ可能なメカニズムを提供します。

RestTemplate および WebClientUriBuilderFactory で構成して、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の時点で、ServletUriComponentsBuilderForwarded および 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 またはプレーンな ObjectString 戻り値)を使用する必要があります。

前の例では、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の時点で、MvcUriComponentsBuilderForwarded および 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を介してデフォルト名が割り当てられます。そのデフォルト実装では、クラスの大文字とメソッド名が使用されます(たとえば、ThingControllergetThing メソッドは「TC#getThing」になります)。名前の衝突がある場合は、@RequestMapping(name="..") を使用して明示的な名前を割り当てるか、独自の HandlerMethodMappingNamingStrategyを実装できます。

1.6. 非同期リクエスト

Spring MVCは、サーブレット3.0非同期要求処理と広範囲に統合されています。

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() を呼び出し、CallableTaskExecutor に送信して、別のスレッドで処理します。

  • 一方、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回通過させるために構築されます。サーブレット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()

ResponseBodyEmitterResponseEntityの本文として使用して、応答のステータスとヘッダーをカスタマイズすることもできます。

emitterIOException をスローした場合(たとえば、リモートクライアントがなくなった場合)、アプリケーションは接続のクリーンアップを担当せず、emitter.complete または emitter.completeWithErrorを呼び出してはなりません。代わりに、サーブレットコンテナーは自動的に AsyncListener エラー通知を開始し、Spring MVCは completeWithError 呼び出しを行います。次に、この呼び出しはアプリケーションへの最後の ASYNC ディスパッチを1回実行し、その間にSpring MVCは構成された例外リゾルバーを呼び出して要求を完了します。

SSE

SseEmitterResponseBodyEmitterのサブクラス)は、サーバー送信イベント(英語) のサポートを提供します。この場合、サーバーから送信されたイベントは、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...
}

StreamingResponseBodyResponseEntity の本文として使用して、応答のステータスとヘッダーをカスタマイズできます。

1.6.5. リアクティブ型

Spring MVCは、コントローラーでのリアクティブクライアントライブラリの使用をサポートしています(WebFluxセクションのリアクティブライブラリも参照してください)。これには、spring-webfluxWebClient および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 に設定して非同期要求処理を有効にする必要があります。さらに、ASYNC javax.servlet.DispatchTypeを処理するためにフィルターマッピングを宣言する必要があります。

Java構成では、AbstractAnnotationConfigDispatcherServletInitializer を使用してサーブレットコンテナーを初期化すると、これは自動的に行われます。

web.xml 構成では、<async-supported>true</async-supported>DispatcherServlet および Filter 宣言に追加し、<dispatcher>ASYNC</dispatcher> をフィルターマッピングに追加できます。

Spring MVC

MVC構成は、非同期要求処理に関連する次のオプションを公開します。

  • Java構成: WebMvcConfigurerconfigureAsyncSupport コールバックを使用します。

  • 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を実装できます)。

グローバル設定とローカル設定を結合するためのルールは、一般的に付加的です。たとえば、すべてのグローバルおよびすべてのローカルオリジン。単一の値しか受け入れられない属性( allowCredentialsmaxAgeなど)の場合、ローカルはグローバル値をオーバーライドします。詳細については、 CorsConfiguration#combine(CorsConfiguration) (Javadoc) を参照してください。

ソースからさらに学習するか、高度なカスタマイズを行うには、コードビハインドを確認します。

  • CorsConfiguration

  • CorsProcessor, DefaultCorsProcessor

  • 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名前空間を使用してこれを実行します。

デフォルトでは、グローバル構成により以下が有効になります。

  • すべての起源。

  • すべてのヘッダー。

  • GET, HEADおよび 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-ModifiedETagなど)を中心に展開します。 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)に設定できます。条件付き POST, PUTおよび 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プロジェクトによって管理されています。構成には、ServletContextTemplateResolver, SpringTemplateEngineThymeleafViewResolverなどのいくつかの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.xmlServletContext パラメーター 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 実装を使用できます。厳密にソートされたマップの場合、適切な ComparatorSortedMapTreeMapなど)を使用できます。また、挿入順序で値を返す必要がある任意のマップでは、commons-collectionsから LinkedHashMap または LinkedMap を使用できます。

  • separator : 複数のオプションが個別の要素(ラジオボタンまたはチェックボックス)として使用可能な場合、リスト内の各オプションを区切るために使用される文字のシーケンス( <br>など)。

  • attributes : HTMLタグ自体に含まれる任意のタグまたはテキストの追加文字列。この文字列は、マクロによって文字通りエコーされます。例: textarea フィールドでは、属性( 'rows = "5" cols = "60"'など)を指定するか、'style = "border:1px solid silver"'などのスタイル情報を渡すことができます。

  • classOrStyle : showErrors マクロの場合、各エラーをラップする 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アップデート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というドメインオブジェクトがあると仮定します。 firstNamelastNameなどのプロパティを持つ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 要素をレンダリングします。このタグの例については、フォームタグを参照してください。 email, tel, dateなどのHTML5固有のタイプも使用できます。

checkbox タグ

このタグは、typecheckboxに設定された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 タグ

このタグは、typecheckboxに設定された複数の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 タグ

このタグは、typeradioに設定された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 タグ

このタグは、typeradioに設定された複数の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>
1 selected 属性が追加されていることに注意してください。
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>
1 selected 属性が追加されていることに注意してください。

前の例が示すように、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 タグ

このタグは、バインドされた値で typehidden に設定されたHTML input タグをレンダリングします。非バインドの非表示値を送信するには、typehiddenに設定して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以外のタイプ属性の入力をサポートしています。これは、email, date, rangeなどの新しい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 が必要です。 UrlBasedViewResolverResourceBundleViewResolverの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.xmlfr_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

AbstractAtomFeedViewAbstractRssFeedView は両方とも 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 Marshallerorg.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パスの拡張子が最初にチェックされます。json, xml, rss、および 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) を使用して、インデントを有効にして MappingJackson2HttpMessageConverterMappingJackson2XmlHttpMessageConverter の両方に共通の構成を作成し、日付形式をカスタマイズし、パラメーター名へのアクセスのサポートを追加する 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構成

@EnableWebMvcDelegatingWebMvcConfigurationをインポートします。

  • 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 ApplicationContextBeanPostProcessor ライフサイクルフックを使用できます。

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

サーブレット4コンテナーはHTTP / 2をサポートするために必要であり、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。

  • リアクティブストリームのバックプレッシャー。

  • より少ないハードウェアリソースで高い同時実行性。

  • Java 8ラムダを活用する機能的なスタイルの流fluentなAPI。

  • 同期および非同期の相互作用。

  • サーバーへのストリーミングまたはサーバーからのストリーミング。

詳細については、WebClientを参照してください。

3. テスト

このセクションでは、Spring MVCアプリケーション用に spring-test で使用可能なオプションを要約します。

  • サーブレットAPIモック:ユニットテストコントローラー、フィルター、その他のWebコンポーネントのサーブレットAPI契約のモック実装。詳細については、サーブレットAPIモックオブジェクトを参照してください。

  • TestContext フレームワーク:JUnitおよびTestNGテストでのSpring構成のロードのサポート。テストメソッド全体でロードされた構成の効率的なキャッシング、MockServletContextを使用した WebApplicationContext のロードのサポートを含みます。詳細については、TestContext フレームワークを参照してください。

  • Spring MVCテスト: DispatcherServlet (つまり、サポートするアノテーション)を介してアノテーション付きコントローラーをテストするための、MockMvcとも呼ばれるフレームワーク。SpringMVCインフラストラクチャーを備え、HTTPサーバーはありません。詳細については、Spring MVCテストを参照してください。

  • クライアント側のREST: spring-test は、RestTemplateを内部的に使用するクライアント側コードをテストするためのモックサーバーとして使用できる MockRestServiceServer を提供します。詳細については、クライアントRESTテストを参照してください。

  • WebTestClient : WebFluxアプリケーションのテスト用に構築されていますが、HTTP接続を介した、あらゆるサーバーへのエンドツーエンドの統合テストにも使用できます。ノンブロッキングのリアクティブクライアントであり、非同期およびストリーミングシナリオのテストに適しています。

4. WebSockets

リファレンスドキュメントのこの部分では、サーブレットスタック、生の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
1 Upgrade ヘッダー。
2 Upgrade 接続を使用します。

通常の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つ目は、起動時のサーブレットコンテナークラスパススキャン(サーブレット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-OptionsSAMEORIGINに設定され、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} は、トランスポートタイプ(たとえば、websocket, xhr-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.socketTRACE ロギングを有効にできます。さらに詳細については、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-OptionsDENY, SAMEORIGINまたは 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トランスポートでは、接続が通常より長く開いたままである必要があります。これらの手法の概要については、このブログ投稿(英語) を参照してください。

サーブレットコンテナーでは、これは、サーブレットコンテナースレッドの終了、要求の処理、および別のスレッドからの応答への書き込みの継続を可能にするサーブレット3非同期サポートによって行われます。

特定の課題は、サーブレットAPIが、なくなったクライアントに通知を提供しないことです。eclipse-ee4j / servlet-api#44(GitHub) を参照してください。ただし、サーブレットコンテナーは、応答への後続の書き込み試行で例外を発生させます。SpringのSockJSサービスはサーバー送信ハートビート(デフォルトでは25秒ごと)をサポートしているため、通常はその期間内(またはメッセージがより頻繁に送信される場合はそれ以前)にクライアントの切断が検出されます。

その結果、クライアントが切断されたためにネットワークI/Oエラーが発生する可能性があり、不要なスタックトレースでログがいっぱいになる可能性があります。Springは、クライアントの切断(各サーバーに固有)を表すネットワーク障害を特定し、専用のログカテゴリ DISCONNECTED_CLIENT_LOG_CATEGORYAbstractSockJsSessionで定義)を使用して最小限のメッセージを記録するように最善を尽くします。スタックトレースを表示する必要がある場合は、そのログカテゴリをTRACEに設定できます。

4.3.6. SockJSとCORS

クロスオリジンリクエストを許可する場合(許可されたオリジンを参照)、SockJSプロトコルはXHRストリーミングおよびポーリングトランスポートでのクロスドメインサポートにCORSを使用します。CORSヘッダーは、応答にCORSヘッダーの存在が検出されない限り、自動的に追加されます。そのため、アプリケーションが既に(たとえば、サーブレットフィルターを介して)CORSサポートを提供するように構成されている場合、Springの SockJsService はこの部分をスキップします。

SpringのSockJsServiceで suppressCors プロパティを設定することにより、これらのCORSヘッダーの追加を無効にすることもできます。

SockJSでは、次のヘッダーと値が必要です。

  • Access-Control-Allow-Origin : Origin 要求ヘッダーの値から初期化されます。

  • Access-Control-Allow-Credentials : 常に trueに設定します。

  • Access-Control-Request-Headers : 同等のリクエストヘッダーの値から初期化されます。

  • Access-Control-Allow-Methods : トランスポートがサポートするHTTPメソッド( TransportType 列挙型を参照)。

  • Access-Control-Max-Age : 31536000 (1年に設定)。

正確な実装については、AbstractSockJsServiceaddCorsHeaders およびソースコードの TransportType 列挙を参照してください。

または、CORS設定で許可されている場合、SockJSエンドポイントプレフィックスを持つURLを除外し、Springの SockJsService で処理できるようにすることを検討してください。

4.3.7. SockJsClient

Springは、ブラウザーを使用せずにリモート SockJSエンドポイントに接続するためのSockJS Javaクライアントを提供します。これは、パブリックネットワークを介した2つのサーバー間の双方向通信が必要な場合(つまり、ネットワークプロキシがWebSocketプロトコルの使用を排除できる場合)に特に役立ちます。SockJS Javaクライアントは、テスト目的(たとえば、多数の同時ユーザーをシミュレートする)にも非常に役立ちます。

SockJS Javaクライアントは、websocket, xhr-streamingおよび xhr-polling トランスポートをサポートしています。残りのものは、ブラウザで使用する場合にのみ意味があります。

WebSocketTransport を以下で構成できます。

  • JSR-356ランタイムのStandardWebSocketClient

  • Jetty 9+ネイティブWebSocket APIを使用したJettyWebSocketClient

  • Springの WebSocketClientの実装。

定義上、XhrTransportxhr-streamingxhr-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)
    }

    // ...
}
1 streamBytesLimit プロパティを512KBに設定します(デフォルトは128KB — 128 * 1024です)。
2 httpMessageCacheSize プロパティを1,000に設定します(デフォルトは 100です)。
3 disconnectDelay プロパティを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内のヘッダーへのアクセス用。

MessageHeaderAccessor, SimpMessageHeaderAccessor、および 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 メソッドは ListenableFuture, CompletableFutureまたは 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...
});

サーバー側のオプションは、ExecutorChannelInterceptorbrokerChannel登録し、サブスクリプションを含むメッセージの処理後に呼び出される 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構成の両方で、guestguestのデフォルト値を持つ systemLoginsystemPasscode プロパティとして公開されます。

STOMPブローカーリレーは、接続されたWebSocketクライアントごとに個別のTCP接続も作成します。クライアントに代わって作成されるすべてのTCP接続に使用されるSTOMP資格情報を構成できます。これは、XML名前空間とJava構成の両方で、guestguestのデフォルト値を持つ clientLoginclientPasscode プロパティとして公開されます。

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構成の MessageBrokerRegistryuserDestinationBroadcast プロパティおよび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());
    }
}

次の例に示すように、カスタム ChannelInterceptorStompHeaderAccessor または 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

上記の例では、StandardWebSocketClientSockJsClientに置き換えることができます。これは、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ハートビートを有効にするには、WebSocketStompClientTaskScheduler で構成し、オプションでハートビート間隔(ハートビートが送信される書き込み非アクティブの場合は10秒、接続を閉じる読み取り非アクティブの場合は10秒)をカスタマイズできます。

パフォーマンステストに WebSocketStompClient を使用して同じマシンから数千のクライアントをシミュレートする場合、各接続は独自のハートビートタスクをスケジュールし、同じマシンで実行される多数のクライアントに対して最適化されないため、ハートビートをオフにすることを検討してください。

STOMPプロトコルは受信もサポートします。この場合、クライアントは receipt ヘッダーを追加する必要があり、送信またはサブスクライブの処理後にサーバーがRECEIPTフレームで応答します。これをサポートするために、StompSessionsetAutoReceipt(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のメッセージを確認することをお勧めします。

開始する明白な場所は、clientInboundChannelclientOutboundChannelをサポートするスレッドプールを構成することです。デフォルトでは、両方とも使用可能なプロセッサーの数の2倍で構成されます。

アノテーション付きメソッドでのメッセージの処理が主にCPUバウンドである場合、clientInboundChannel のスレッドの数はプロセッサーの数に近いままである必要があります。それらが行う作業がより多くのIOバウンドであり、データベースまたは他の外部システムでのブロックまたは待機が必要な場合、おそらくスレッドプールサイズを増やす必要があります。

ThreadPoolExecutor には3つの重要なプロパティがあります。コアスレッドプールサイズ、最大スレッドプールサイズ、および利用可能なスレッドがないタスクを格納するキューの容量です。

混乱の一般的なポイントは、コアプールサイズ(たとえば、10)と最大プールサイズ(たとえば、20)を構成すると、スレッドプールが10〜20スレッドになることです。実際、容量がデフォルト値のInteger.MAX_VALUEのままになっている場合、すべての追加タスクがキューに入れられるため、スレッドプールがコアプールサイズを超えて増加することはありません。

ThreadPoolExecutor のjavadocを参照して、これらのプロパティがどのように機能するかを学び、さまざまなキューイング戦略を理解しましょう。

clientOutboundChannel 側では、WebSocketクライアントにメッセージを送信することがすべてです。クライアントが高速ネットワーク上にある場合、スレッドの数は利用可能なプロセッサーの数に近いままである必要があります。速度が遅い場合や帯域幅が狭い場合、メッセージを消費してスレッドプールに負担をかけるのに時間がかかります。スレッドプールサイズを増やす必要があります。

clientInboundChannel のワークロードを予測することは可能ですが、結局のところ、アプリケーションの制御に依存しない要因に基づいているため、「clientOutboundChannel」の構成方法はより困難です。このため、メッセージの送信には sendTimeLimitsendBufferSizeLimitの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のセットを定義する contextConfigLocation <context-param />セクション(同じファイル内)を追加することだけです。ロードするXML構成ファイル。

以下の <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

クレイグMcClanahanが発明したStruts(英語) は、ApacheソフトウェアFoundationがホストするオープンソースプロジェクトです。当時、JSP /サーブレットのプログラミングパラダイムを大幅に簡素化し、独自のフレームワークを使用していた多くの開発者を獲得しました。プログラミングモデルを簡素化し、オープンソース(したがって、ビールのように無料)であり、JavaのWeb開発者の間でプロジェクトが成長し、人気を博した大きなコミュニティがありました。

元のStruts 1.xの後継として、Struts 2.xと、組み込みのSpring統合用のStruts提供のSpringプラグイン(Apache) を確認してください。

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.