1. 序章

SpringをWebアプリケーションで使用する場合、すべてを接続するアプリケーションコンテキストを整理するためのいくつかのオプションがあります。

この記事では、Springが提供する最も一般的なオプションを分析して説明します。

2. ルートWebアプリケーションコンテキスト

すべてのSpringWebアプリケーションには、そのライフサイクルに関連付けられた関連アプリケーションコンテキスト(ルートWebアプリケーションコンテキスト)があります。

これはSpringWebMVCよりも前の古い機能であるため、Webフレームワークテクノロジに特に関連付けられていません。

サーブレットコンテキストリスナーのおかげで、コンテキストはアプリケーションの起動時に開始され、停止時に破棄されます。 すべてのApplicationContext実装にこの機能があるわけではありませんが、最も一般的なタイプのコンテキストも実行時に更新できます。

Webアプリケーションのコンテキストは、常にWebApplicationContextのインスタンスです。 これは、ServletContextにアクセスするためのコントラクトを使用してApplicationContextを拡張するインターフェイスです。

とにかく、アプリケーションは通常、これらの実装の詳細について心配する必要はありません。ルートWebアプリケーションコンテキストは、共有Beanを定義するための単なる一元化された場所です。

2.1. ContextLoaderListener

前のセクションで説明したルートWebアプリケーションコンテキストは、spring-webモジュールの一部であるクラスorg.springframework.web.context.ContextLoaderListenerのリスナーによって管理されます。

デフォルトでは、リスナーは/WEB-INF/applicationContext.xmlからXMLアプリケーションコンテキストをロードします。 ただし、これらのデフォルトは変更できます。 たとえば、XMLの代わりにJavaアノテーションを使用できます。

このリスナーは、webapp記述子( web.xml ファイル)で構成することも、サーブレット3.x環境でプログラムで構成することもできます。

次のセクションでは、これらの各オプションについて詳しく見ていきます。

2.2. web.xmlとXMLアプリケーションコンテキストの使用

web.xml を使用する場合、通常どおりリスナーを構成します。

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

contextConfigLocation パラメーターを使用して、XMLコンテキスト構成の代替の場所を指定できます。

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

または、コンマで区切られた複数の場所:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/context1.xml, /WEB-INF/context2.xml</param-value>
</context-param>

パターンを使用することもできます。

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

いずれの場合も、 1つのコンテキストのみが定義され、は、指定された場所からロードされたすべてのBean定義を組み合わせることによって定義されます。

2.3. web.xmlおよびJavaアプリケーションコンテキストの使用

デフォルトのXMLベースのコンテキスト以外に、他のタイプのコンテキストを指定することもできます。 たとえば、代わりにJavaアノテーション構成を使用する方法を見てみましょう。

contextClass パラメーターを使用して、インスタンス化するコンテキストのタイプをリスナーに通知します。

<context-param>
    <param-name>contextClass</param-name>
    <param-value>
        org.springframework.web.context.support.AnnotationConfigWebApplicationContext
    </param-value>
</context-param>

すべてのタイプのコンテキストにデフォルトの構成場所がある場合があります。この場合、 AnnotationConfigWebApplicationContext にはデフォルトの構成場所がないため、提供する必要があります。

したがって、1つ以上の注釈付きクラスをリストできます。

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        com.baeldung.contexts.config.RootApplicationConfig,
        com.baeldung.contexts.config.NormalWebAppConfig
    </param-value>
</context-param>

または、1つ以上のパッケージをスキャンするようにコンテキストに指示することもできます。

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>com.baeldung.bean.config</param-value>
</context-param>

そしてもちろん、2つのオプションを組み合わせて組み合わせることができます。

2.4. プログラムによる構成 サーブレット3.x

サーブレットAPIのバージョン3では、web.xmlファイルを介した構成が完全にオプションになっています。 ライブラリは、リスナー、フィルタ、サーブレットなどを登録できるXML構成の一部であるWebフラグメントを提供できます。

また、ユーザーは、サーブレットベースのアプリケーションのすべての要素をプログラムで定義できるAPIにアクセスできます。

spring-web モジュールはこれらの機能を利用し、アプリケーションの起動時にアプリケーションのコンポーネントを登録するためのAPIを提供します。

Springは、アプリケーションのクラスパスをスキャンして、org。springframework.web.WebApplicationInitializerクラスのインスタンスを探します。 これは単一のメソッドを持つインターフェースであり、 void onStartup(ServletContext servletContext)は、アプリケーションの起動時に呼び出されるServletExceptionをスローします。

ここで、この機能を使用して、前に見たのと同じタイプのルートWebアプリケーションコンテキストを作成する方法を見てみましょう。

2.5. サーブレット3.xとXMLアプリケーションコンテキストの使用

セクション2.2と同様に、XMLコンテキストから始めましょう。

前述のonStartupメソッドを実装します。

public class ApplicationInitializer implements WebApplicationInitializer {
    
    @Override
    public void onStartup(ServletContext servletContext) 
      throws ServletException {
        //...
    }
}

実装を1行ずつ分割してみましょう。

まず、ルートコンテキストを作成します。 XMLを使用するため、XMLベースのアプリケーションコンテキストである必要があります。また、Web環境を使用しているため、WebApplicationContextも実装する必要があります。

したがって、最初の行は、以前に遭遇した contextClass パラメーターの明示的なバージョンであり、これを使用して、使用する特定のコンテキスト実装を決定します。

XmlWebApplicationContext rootContext = new XmlWebApplicationContext();

次に、2行目で、Bean定義をどこからロードするかをコンテキストに指示します。 ここでも、 setConfigLocations は、web.xmlcontextConfigLocationパラメーターにプログラム的に類似しています。

rootContext.setConfigLocations("/WEB-INF/rootApplicationContext.xml");

最後に、ルートコンテキストを使用して ContextLoaderListener を作成し、サーブレットコンテナに登録します。 ご覧のとおり、 ContextLoaderListener には、 WebApplicationContext を受け取り、アプリケーションで使用できるようにする適切なコンストラクターがあります。

servletContext.addListener(new ContextLoaderListener(rootContext));

2.6. サーブレット3.xとJavaアプリケーションコンテキストの使用

アノテーションベースのコンテキストを使用する場合は、前のセクションのコードスニペットを変更して、代わりにAnnotationConfigWebApplicationContextをインスタンス化することができます。

ただし、同じ結果を得るためのより専門的なアプローチを見てみましょう。

前に見たWebApplicationInitializerクラスは、汎用インターフェイスです。 Springは、 AbstractContextLoaderInitializer と呼ばれる抽象クラスを含む、いくつかのより具体的な実装を提供します。

その名前が示すように、その仕事は ContextLoaderListener を作成し、それをサーブレットコンテナに登録することです。

ルートコンテキストを構築する方法を説明するだけです。

public class AnnotationsBasedApplicationInitializer 
  extends AbstractContextLoaderInitializer {
 
    @Override
    protected WebApplicationContext createRootApplicationContext() {
        AnnotationConfigWebApplicationContext rootContext
          = new AnnotationConfigWebApplicationContext();
        rootContext.register(RootApplicationConfig.class);
        return rootContext;
    }
}

ここでは、 ContextLoaderListener を登録する必要がなくなったことがわかります。これにより、ボイラープレートコードを少し節約できます。

より一般的なsetConfigLocationsの代わりに、AnnotationConfigWebApplicationContextに固有のregisterメソッドの使用にも注意してください。これを呼び出すことで、個々の@を登録できます。 Configuration はクラスにコンテキストで注釈を付け、パッケージのスキャンを回避します。

3. ディスパッチャサーブレットコンテキスト

次に、別のタイプのアプリケーションコンテキストに焦点を当てましょう。 今回は、Springの汎用Webアプリケーションサポートの一部ではなく、SpringMVCに固有の機能について説明します。

Spring MVCアプリケーションには少なくとも1つのディスパッチャーサーブレットが構成されています(ただし、複数の場合もありますが、その場合については後で説明します)。 これは、着信要求を受信し、それらを適切なコントローラーメソッドにディスパッチして、ビューを返すサーブレットです。

各DispatcherServletには、関連付けられたアプリケーションコンテキストがあります。 このようなコンテキストで定義されたBeanは、サーブレットを構成し、コントローラーやビューリゾルバーなどのMVCオブジェクトを定義します。

まず、サーブレットのコンテキストを構成する方法を見てみましょう。 詳細については後で説明します。

3.1. web.xmlとXMLアプリケーションコンテキストの使用

DispatcherServlet は通常、web.xmlで名前とマッピングを使用して宣言されます。

<servlet>
    <servlet-name>normal-webapp</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>normal-webapp</servlet-name>
    <url-pattern>/api/*</url-pattern>
</servlet-mapping>

特に指定がない限り、サーブレットの名前は、ロードするXMLファイルを決定するために使用されます。 この例では、ファイル WEB-INF /normal-webapp-servlet.xmlを使用します。

ContextLoaderListener と同様の方法で、XMLファイルへの1つ以上のパスを指定することもできます。

<servlet>
    ...
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/normal/*.xml</param-value>
    </init-param>
</servlet>

3.2. web.xmlおよびJavaアプリケーションコンテキストの使用

別のタイプのコンテキストを使用する場合は、ContextLoaderListenerのように続行します。 つまり、適切なcontextConfigLocationとともにcontextClassパラメーターを指定します。

<servlet>
    <servlet-name>normal-webapp-annotations</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </init-param>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.baeldung.contexts.config.NormalWebAppConfig</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

3.3. サーブレット3.xとXMLアプリケーションコンテキストの使用

ここでも、 DispatcherServlet をプログラムで宣言するための2つの異なるメソッドを見て、1つをXMLコンテキストに適用し、もう1つをJavaコンテキストに適用します。

それでは、一般的なWebApplicationInitializerとXMLアプリケーションコンテキストから始めましょう。

前に見たように、onStartupメソッドを実装する必要があります。 ただし、今回はディスパッチャサーブレットも作成して登録します。

XmlWebApplicationContext normalWebAppContext = new XmlWebApplicationContext();
normalWebAppContext.setConfigLocation("/WEB-INF/normal-webapp-servlet.xml");
ServletRegistration.Dynamic normal
  = servletContext.addServlet("normal-webapp", 
    new DispatcherServlet(normalWebAppContext));
normal.setLoadOnStartup(1);
normal.addMapping("/api/*");

上記のコードと同等のweb.xml構成要素の間に簡単に類似点を描くことができます。

3.4. サーブレット3.xとJavaアプリケーションコンテキストの使用

今回は、 WebApplicationInitializerの特殊な実装AbstractDispatcherServletInitializer を使用して、注釈ベースのコンテキストを構成します。

これは抽象クラスであり、前述のようにルートWebアプリケーションコンテキストを作成するだけでなく、最小限のボイラープレートで1つのディスパッチャーサーブレットを登録できます。

@Override
protected WebApplicationContext createServletApplicationContext() {
 
    AnnotationConfigWebApplicationContext secureWebAppContext
      = new AnnotationConfigWebApplicationContext();
    secureWebAppContext.register(SecureWebAppConfig.class);
    return secureWebAppContext;
}

@Override
protected String[] getServletMappings() {
    return new String[] { "/s/api/*" };
}

ここでは、ルートコンテキストについて前に見たのとまったく同じように、サーブレットに関連付けられたコンテキストを作成するためのメソッドを見ることができます。 また、 web.xml のように、サーブレットのマッピングを指定するメソッドがあります。

4. 親と子のコンテキスト

これまで、ルートWebアプリケーションコンテキストとディスパッチャサーブレットコンテキストの2つの主要なタイプのコンテキストを見てきました。 次に、質問があるかもしれません:それらのコンテキストは関連していますか?

はい、そうです。 実際には、 ルートコンテキストは、すべてのディスパッチャサーブレットコンテキストの親です。 したがって、ルートWebアプリケーションコンテキストで定義されたBeanは、各ディスパッチャサーブレットコンテキストに表示されますが、その逆は表示されません。

したがって、通常、ルートコンテキストはサービスBeanを定義するために使用されますが、ディスパッチャコンテキストにはMVCに特に関連するBeanが含まれます。

プログラムでディスパッチャサーブレットコンテキストを作成する方法も見てきたことに注意してください。 親を手動で設定した場合、Springは決定を上書きせず、このセクションは適用されなくなります。

より単純なMVCアプリケーションでは、単一のコンテキストを1つのディスパッチャーサーブレットに関連付けるだけで十分です。 過度に複雑なソリューションは必要ありません!

それでも、複数のディスパッチャーサーブレットが構成されている場合は、親子関係が役立ちます。 しかし、いつ私たちは複数を持っていることをわざわざする必要がありますか?

一般に、MVC構成の複数のセットが必要な場合は複数のディスパッチャーサーブレットを宣言します。たとえば、従来のMVCアプリケーションまたは安全でない安全なセクションと一緒にRESTAPIを使用できます。 Webサイトの:

注: AbstractDispatcherServletInitializer (セクション3.4を参照)を拡張すると、ルートWebアプリケーションコンテキストと単一のディスパッチャーサーブレットの両方が登録されます。

したがって、複数のサーブレットが必要な場合は、複数のAbstractDispatcherServletInitializer実装が必要です。 ただし、定義できるルートコンテキストは1つだけです。そうしないと、アプリケーションが起動しません。

幸い、createRootApplicationContextメソッドはnullを返すことができます。 したがって、1つの AbstractContextLoaderInitializer と、ルートコンテキストを作成しない多くのAbstractDispatcherServletInitializer実装を持つことができます。 このようなシナリオでは、初期化子を@Orderで明示的に注文することをお勧めします。

また、 AbstractDispatcherServletInitializer は、指定された名前( dispatcher )でサーブレットを登録します。もちろん、同じ名前のサーブレットを複数持つことはできません。 したがって、getServletNameをオーバーライドする必要があります。

@Override
protected String getServletName() {
    return "another-dispatcher";
}

5. A 親と子のコンテキスト 例 

アプリケーションの2つの領域があるとします。たとえば、誰でもアクセスできるパブリック領域と、MVC構成が異なる安全な領域です。 ここでは、異なるメッセージを出力する2つのコントローラーを定義します。

また、一部のコントローラーが重要なリソースを保持するサービスを必要としているとします。 遍在するケースは永続性です。 次に、そのサービスを1回だけインスタンス化して、リソースの使用量が2倍にならないようにします。また、Do n’tRepeatYourselfの原則を信じているためです。

例を進めましょう。

5.1. 共有サービス

hello worldの例では、永続性ではなく、より単純なグリーターサービスを選択しました。

package com.baeldung.contexts.services;

@Service
public class GreeterService {
    @Resource
    private Greeting greeting;
    
    public String greet() {
        return greeting.getMessage();
    }
}

コンポーネントスキャンを使用して、ルートWebアプリケーションコンテキストでサービスを宣言します。

@Configuration
@ComponentScan(basePackages = { "com.baeldung.contexts.services" })
public class RootApplicationConfig {
    //...
}

代わりにXMLを好むかもしれません:

<context:component-scan base-package="com.baeldung.contexts.services" />

5.2. コントローラー

サービスを使用して挨拶を出力する2つの単純なコントローラーを定義しましょう。

package com.baeldung.contexts.normal;

@Controller
public class HelloWorldController {

    @Autowired
    private GreeterService greeterService;
    
    @RequestMapping(path = "/welcome")
    public ModelAndView helloWorld() {
        String message = "<h3>Normal " + greeterService.greet() + "</h3>";
        return new ModelAndView("welcome", "message", message);
    }
}

//"Secure" Controller
package com.baeldung.contexts.secure;

String message = "<h3>Secure " + greeterService.greet() + "</h3>";

ご覧のとおり、コントローラーは2つの異なるパッケージにあり、異なるメッセージを出力します。1つは「通常」、もう1つは「安全」です。

5.3. ディスパッチャサーブレットコンテキスト

前に述べたように、コントローラーごとに1つずつ、2つの異なるディスパッチャーサーブレットコンテキストがあります。 それでは、Javaでそれらを定義しましょう。

//Normal context
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.baeldung.contexts.normal" })
public class NormalWebAppConfig implements WebMvcConfigurer {
    //...
}

//"Secure" context
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.baeldung.contexts.secure" })
public class SecureWebAppConfig implements WebMvcConfigurer {
    //...
}

または、必要に応じて、XMLで:

<!-- normal-webapp-servlet.xml -->
<context:component-scan base-package="com.baeldung.contexts.normal" />

<!-- secure-webapp-servlet.xml -->
<context:component-scan base-package="com.baeldung.contexts.secure" />

5.4. すべてを一緒に入れて

すべてのピースが揃ったので、Springにそれらを配線するように指示する必要があります。 ルートコンテキストをロードし、2つのディスパッチャサーブレットを定義する必要があることを思い出してください。 これを行う方法は複数ありますが、ここではJavaシナリオとXMLシナリオの2つのシナリオに焦点を当てます。 Javaから始めましょう。

AbstractContextLoaderInitializer を定義して、ルートコンテキストをロードします。

@Override
protected WebApplicationContext createRootApplicationContext() {
    AnnotationConfigWebApplicationContext rootContext
      = new AnnotationConfigWebApplicationContext();
    rootContext.register(RootApplicationConfig.class);
    return rootContext;
}

次に、2つのサーブレットを作成する必要があるため、AbstractDispatcherServletInitializerの2つのサブクラスを定義します。 まず、「通常の」もの:

@Override
protected WebApplicationContext createServletApplicationContext() {
    AnnotationConfigWebApplicationContext normalWebAppContext
      = new AnnotationConfigWebApplicationContext();
    normalWebAppContext.register(NormalWebAppConfig.class);
    return normalWebAppContext;
}

@Override
protected String[] getServletMappings() {
    return new String[] { "/api/*" };
}

@Override
protected String getServletName() {
    return "normal-dispatcher";
}

次に、別のコンテキストをロードし、別のパスにマップされる「安全な」もの:

@Override
protected WebApplicationContext createServletApplicationContext() {
    AnnotationConfigWebApplicationContext secureWebAppContext
      = new AnnotationConfigWebApplicationContext();
    secureWebAppContext.register(SecureWebAppConfig.class);
    return secureWebAppContext;
}

@Override
protected String[] getServletMappings() {
    return new String[] { "/s/api/*" };
}

@Override
protected String getServletName() {
    return "secure-dispatcher";
}

これで完了です。 前のセクションで触れたものを適用しました。

これまでに説明した部分を組み合わせるだけで、web.xmlでも同じことができます。

ルートアプリケーションコンテキストを定義します。

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

「通常の」ディスパッチャコンテキスト:

<servlet>
    <servlet-name>normal-webapp</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>normal-webapp</servlet-name>
    <url-pattern>/api/*</url-pattern>
</servlet-mapping>

そして最後に、「安全な」コンテキスト:

<servlet>
    <servlet-name>secure-webapp</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>secure-webapp</servlet-name>
    <url-pattern>/s/api/*</url-pattern>
</servlet-mapping>

6. 複数のコンテキストを組み合わせる

親子以外に複数の構成場所を組み合わせる方法、大きなコンテキストを分割し、さまざまな懸念事項をより適切に分離する方法があります。すでに1つの例を見てきました:contextConfigLocationを複数で指定する場合パスまたはパッケージの場合、Springは、すべてのbean定義を、単一のXMLファイルまたはJavaクラスで順番に記述されているかのように組み合わせることにより、単一のコンテキストを構築します。

ただし、他の手段でも同様の効果を得ることができ、異なるアプローチを一緒に使用することもできます。 オプションを調べてみましょう。

1つの可能性は、コンポーネントのスキャンです。これについては、別の記事について説明しています。

6.1. コンテキストを別のコンテキストにインポートする

または、コンテキスト定義に別のコンテキスト定義をインポートさせることもできます。 シナリオに応じて、さまざまな種類のインポートがあります。

Javaでの@Configurationクラスのインポート:

@Configuration
@Import(SomeOtherConfiguration.class)
public class Config { ... }

他のタイプのリソース(たとえば、XMLコンテキスト定義)をJavaでロードします。

@Configuration
@ImportResource("classpath:basicConfigForPropertiesTwo.xml")
public class Config { ... }

最後に、XMLファイルを別のファイルに含めます。

<import resource="greeting.xml" />

したがって、サービス、コンポーネント、コントローラーなどを整理して、すばらしいアプリケーションを作成する方法はたくさんあります。 そして素晴らしいことは、IDEがそれらすべてを理解していることです!

7. SpringBootWebアプリケーション

Spring Bootは、アプリケーションのコンポーネントを自動的に構成します。したがって、一般的に、それらをどのように編成するかを考える必要はほとんどありません。

それでも、内部では、Bootはこれまでに見たものを含むSpring機能を使用します。 いくつかの注目すべき違いを見てみましょう。

Spring Boot組み込みコンテナで実行されているWebアプリケーションは、設計上、WebApplicationInitializerを実行しません。

必要に応じて、選択した展開戦略に応じて、代わりにSpringBootServletInitializerまたはServletContextInitializerで同じロジックを記述できます。

ただし、この記事に示されているサーブレット、フィルタ、およびリスナーを追加する場合は、追加する必要はありません。 実際、Spring Bootはすべてのサーブレット関連のBeanをコンテナに自動的に登録します:

@Bean
public Servlet myServlet() { ... }

そのように定義されたオブジェクトは、規則に従ってマップされます。フィルターは、/ *、つまりすべての要求に自動的にマップされます。 単一のサーブレットを登録する場合、それは/にマップされます。それ以外の場合、各サーブレットはそのBean名にマップされます。

上記の規則が機能しない場合は、代わりに FilterRegistrationBean ServletRegistrationBean、、またはServletListenerRegistrationBeanを定義できます。 これらのクラスにより、登録の細かい側面を制御できます。

8. 結論

この記事では、SpringWebアプリケーションの構造化と編成に使用できるさまざまなオプションについて詳しく説明しました。

いくつかの機能、特にエンタープライズアプリケーションでの共有コンテキストのサポートは省略しましたが、執筆時点では、Spring5にはまだありません。

これらすべての例とコードスニペットの実装は、GitHubプロジェクトにあります。