Spring Webコンテキスト
1. 前書き
WebアプリケーションでSpringを使用する場合、すべてを結び付けるアプリケーションコンテキストを整理するためのオプションがいくつかあります。
この記事では、Springが提供する最も一般的なオプションを分析して説明します。
2. ルートWebアプリケーションコンテキスト
すべてのSpringWebアプリケーションには、そのライフサイクルに関連付けられたアプリケーションコンテキストが関連付けられています。ルートWebアプリケーションコンテキストです。
これはSpringWeb MVCより前の古い機能であるため、Webフレームワークテクノロジーに特に結び付けられていません。
サーブレットコンテキストリスナーのおかげで、コンテキストはアプリケーションの起動時に開始され、停止すると破棄されます。 すべてのApplicationContextの実装にこの機能があるわけではありませんが、最も一般的なタイプのコンテキストは実行時に更新することもできます。
Webアプリケーションのコンテキストは、常にWebApplicationContextのインスタンスです。 これは、ServletContextにアクセスするためのコントラクトを使用してApplicationContextを拡張するインターフェイスです。
とにかく、アプリケーションは通常、これらの実装の詳細について心配する必要はありません:the root web application context is simply a centralized place to define shared beans.
2.1. ContextLoaderListener
前のセクションで説明したルートWebアプリケーションコンテキストは、spring-webモジュールの一部であるクラスorg.springframework.web.context.ContextLoaderListenerのリスナーによって管理されます。
By default, the listener will load an XML application context from /WEB-INF/applicationContext.xml.ただし、これらのデフォルトは変更できます。 たとえば、XMLの代わりにJavaアノテーションを使用できます。
このリスナーは、webapp記述子(web.xmlファイル)で構成することも、サーブレット3.x環境でプログラムで構成することもできます。
次のセクションでは、これらの各オプションについて詳しく見ていきます。
2.2. web.xmlとXMLアプリケーションコンテキストの使用
web.xmlを使用する場合、通常どおりリスナーを構成します。
org.springframework.web.context.ContextLoaderListener
contextConfigLocationパラメータを使用して、XMLコンテキスト構成の代替の場所を指定できます。
contextConfigLocation
/WEB-INF/rootApplicationContext.xml
または、コンマで区切られた複数の場所:
contextConfigLocation
/WEB-INF/context1.xml, /WEB-INF/context2.xml
パターンを使用することもできます。
contextConfigLocation
/WEB-INF/*-context.xml
いずれの場合も、only one context is defined,は、指定された場所からロードされたすべてのBean定義を組み合わせます。
2.3. web.xmlとJavaアプリケーションコンテキストの使用
デフォルトのXMLベースのコンテキストに加えて、他のタイプのコンテキストを指定することもできます。 たとえば、代わりにJavaアノテーション構成を使用する方法を見てみましょう。
contextClassパラメータを使用して、インスタンス化するコンテキストのタイプをリスナーに通知します。
contextClass
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
Every type of context may have a default configuration location.この場合、AnnotationConfigWebApplicationContextには1つがないため、提供する必要があります。
したがって、1つ以上の注釈付きクラスをリストできます。
contextConfigLocation
com.example.contexts.config.RootApplicationConfig,
com.example.contexts.config.NormalWebAppConfig
または、1つ以上のパッケージをスキャンするようにコンテキストに指示できます。
contextConfigLocation
org.example.bean.config
そして、もちろん、2つのオプションを組み合わせて使用できます。
2.4. とのプログラム構成 サーブレット3.x
Version 3 of the Servlet API has made configuration through the web.xml file completely optional.ライブラリは、リスナー、フィルタ、サーブレットなどを登録できるXML構成の一部であるWebフラグメントを提供できます。
また、ユーザーは、サーブレットベースのアプリケーションのすべての要素をプログラムで定義できるAPIにアクセスできます。
spring-webモジュールはこれらの機能を利用し、アプリケーションの起動時にアプリケーションのコンポーネントを登録するためのAPIを提供します。
Springは、アプリケーションのクラスパスをスキャンして、org.springframework.web.WebApplicationInitializerクラスのインスタンスを探します。 これは、アプリケーションの起動時に呼び出される単一のメソッドvoid onStartup(ServletContext servletContext) throws 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.xmlのcontextConfigLocationパラメータのプログラム上の類似物です。
rootContext.setConfigLocations("/WEB-INF/rootApplicationContext.xml");
最後に、ルートコンテキストを使用してContextLoaderListenerを作成し、サーブレットコンテナに登録します。 ご覧のとおり、ContextLoaderListenerには、WebApplicationContextを受け取り、アプリケーションで使用できるようにする適切なコンストラクターがあります。
servletContext.addListener(new ContextLoaderListener(rootContext));
2.6. サーブレット3.xとJavaアプリケーションコンテキストの使用
アノテーションベースのコンテキストを使用する場合は、前のセクションのコードスニペットを変更して、代わりにAnnotationConfigWebApplicationContextをインスタンス化することができます。
ただし、同じ結果を得るためのより専門的なアプローチを見てみましょう。
The WebApplicationInitializer class that we’ve seen earlier is a general-purpose interface. 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 applications have at least one Dispatcher Servlet configured(ただし、複数ある可能性があります。その場合については後で説明します)。 これは、着信要求を受信し、それらを適切なコントローラーメソッドにディスパッチし、ビューを返すサーブレットです。
Each DispatcherServlet has an associated application context.このようなコンテキストで定義されたBeanは、サーブレットを構成し、コントローラーやビューリゾルバーなどのMVCオブジェクトを定義します。
まず、サーブレットのコンテキストを構成する方法を見てみましょう。 詳細については後で説明します。
3.1. web.xmlとXMLアプリケーションコンテキストの使用
DispatcherServletは通常、名前とマッピングを使用してweb.xmlで宣言されます。
normal-webapp
org.springframework.web.servlet.DispatcherServlet
1
normal-webapp
/api/*
特に指定されていない場合、サーブレットの名前を使用して、ロードするXMLファイルが決定されます。 この例では、ファイルWEB-INF/normal-webapp-servlet.xmlを使用します。
ContextLoaderListenerと同様の方法で、XMLファイルへの1つ以上のパスを指定することもできます。
...
contextConfigLocation
/WEB-INF/normal/*.xml
3.2. web.xmlとJavaアプリケーションコンテキストの使用
別のタイプのコンテキストを使用する場合は、ContextLoaderListenerのように続行します。 つまり、適切なcontextConfigLocationとともにcontextClassパラメータを指定します。
normal-webapp-annotations
org.springframework.web.servlet.DispatcherServlet
contextClass
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
contextConfigLocation
com.example.contexts.config.NormalWebAppConfig
1
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つの主要なタイプのコンテキストを見てきました。 次に、質問があるかもしれません:are those contexts related?
はい、そうです。 実際、the root context is the parent of every dispatcher servlet context.したがって、ルートWebアプリケーションコンテキストで定義されたBeanは、各ディスパッチャサーブレットコンテキストに表示されますが、その逆は表示されません。
したがって、通常、ルートコンテキストはサービスBeanを定義するために使用されますが、ディスパッチャコンテキストにはMVCに特に関連するBeanが含まれます。
プログラムでディスパッチャサーブレットコンテキストを作成する方法も見てきたことに注意してください。 親を手動で設定した場合、Springは決定をオーバーライドせず、このセクションは適用されません。
より単純なMVCアプリケーションでは、単一のコンテキストを1つのディスパッチャーサーブレットに関連付けるだけで十分です。 過度に複雑なソリューションは必要ありません。
それでも、複数のディスパッチャサーブレットを構成している場合は、親子関係が役立ちます。 しかし、いつ複数の機能を使用する必要がありますか?
一般に、we declare multiple dispatcher servletswhen we need multiple sets of MVC configuration.たとえば、従来のMVCアプリケーションまたはWebサイトの安全でない安全なセクションと一緒にRESTAPIがある場合があります。
注:AbstractDispatcherServletInitializerを拡張する場合(セクション3.4を参照)、ルートWebアプリケーションコンテキストと単一のディスパッチャーサーブレットの両方を登録します。
したがって、複数のサーブレットが必要な場合は、複数のAbstractDispatcherServletInitializerの実装が必要です。 ただし、定義できるルートコンテキストは1つだけです。そうしないと、アプリケーションが起動しません。
幸い、createRootApplicationContextメソッドはnullを返すことができます。 したがって、ルートコンテキストを作成しない1つのAbstractContextLoaderInitializerと多数のAbstractDispatcherServletInitializerの実装を持つことができます。 このようなシナリオでは、@Orderを使用して初期化子を明示的に注文することをお勧めします。
また、AbstractDispatcherServletInitializerは指定された名前(dispatcher)でサーブレットを登録します。もちろん、同じ名前のサーブレットを複数持つことはできません。 したがって、getServletNameをオーバーライドする必要があります。
@Override
protected String getServletName() {
return "another-dispatcher";
}
5. 親と子のコンテキストの例
アプリケーションに2つの領域があるとします。たとえば、公開されている領域と、MVC構成が異なる安全な領域があります。 ここでは、異なるメッセージを出力する2つのコントローラーを定義します。
また、一部のコントローラーには、重要なリソースを保持するサービスが必要であるとします。ユビキタスなケースは永続性です。 次に、そのサービスを1回だけインスタンス化して、リソースの使用量が2倍にならないようにします。また、Do n’t RepeatYourselfの原則を信じているためです。
例を進めましょう。
5.1. 共有サービス
こんにちは世界の例では、永続化ではなく、より簡単なグリーターサービスに決めました。
package com.example.contexts.services;
@Service
public class GreeterService {
@Resource
private Greeting greeting;
public String greet() {
return greeting.getMessage();
}
}
コンポーネントスキャンを使用して、ルートWebアプリケーションコンテキストでサービスを宣言します。
@Configuration
@ComponentScan(basePackages = { "com.example.contexts.services" })
public class RootApplicationConfig {
//...
}
代わりにXMLを好む場合があります。
5.2. コントローラー
サービスを使用して挨拶を出力する2つの単純なコントローラーを定義しましょう。
package com.example.contexts.normal;
@Controller
public class HelloWorldController {
@Autowired
private GreeterService greeterService;
@RequestMapping(path = "/welcome")
public ModelAndView helloWorld() {
String message = "Normal " + greeterService.greet() + "
";
return new ModelAndView("welcome", "message", message);
}
}
//"Secure" Controller
package com.example.contexts.secure;
String message = "Secure " + greeterService.greet() + "
";
ご覧のとおり、コントローラーは2つの異なるパッケージにあり、異なるメッセージを出力します。1つは「normal」、もう1つは「secure」と表示されます。
5.3. ディスパッチャサーブレットコンテキスト
前に述べたように、コントローラーごとに1つずつ、2つの異なるディスパッチャーサーブレットコンテキストがあります。 それでは、Javaでそれらを定義しましょう。
//Normal context
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.example.contexts.normal" })
public class NormalWebAppConfig implements WebMvcConfigurer {
//...
}
//"Secure" context
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.example.contexts.secure" })
public class SecureWebAppConfig implements WebMvcConfigurer {
//...
}
または、XMLの場合:
5.4. すべてを一緒に入れて
すべてのピースが揃ったので、Springにそれらを接続するように指示するだけです。 ルートコンテキストをロードし、2つのディスパッチャサーブレットを定義する必要があることを思い出してください。 これを行うには複数の方法がありますが、ここでは、JavaシナリオとXMLシナリオの2つのシナリオに焦点を当てます。 Let’s start with 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";
}
これで完了です! 前のセクションで触れた内容を適用しました。
We can do the same with web.xml、これもこれまでに説明した部分を組み合わせただけです。
ルートアプリケーションコンテキストを定義します。
org.springframework.web.context.ContextLoaderListener
「通常の」ディスパッチャコンテキスト:
normal-webapp
org.springframework.web.servlet.DispatcherServlet
1
normal-webapp
/api/*
そして最後に、「安全な」コンテキスト:
secure-webapp
org.springframework.web.servlet.DispatcherServlet
1
secure-webapp
/s/api/*
6. 複数のコンテキストを組み合わせる
親子以外にも、複数の構成場所to split big contexts and better separate different concerns.を組み合わせる方法があります。すでに1つの例を見てきました。複数のパスまたはパッケージでcontextConfigLocationを指定すると、Springはすべての単一のXMLファイルまたはJavaクラスで順番に記述されているかのようにBean定義。
ただし、他の手段で同様の効果を達成し、異なるアプローチを一緒に使用することもできます。 オプションを調べてみましょう。
1つの可能性は、コンポーネントのスキャンです。これについては、in another articleについて説明します。
6.1. コンテキストを別のコンテキストにインポートする
あるいは、コンテキスト定義に別のコンテキスト定義をインポートさせることもできます。 シナリオに応じて、さまざまな種類のインポートがあります。
Javaで@Configurationクラスをインポートする:
@Configuration
@Import(SomeOtherConfiguration.class)
public class Config { ... }
JavaでのXMLコンテキスト定義など、他のタイプのリソースのロード:
@Configuration
@ImportResource("classpath:basicConfigForPropertiesTwo.xml")
public class Config { ... }
最後に、XMLファイルを別のファイルに含めます。
したがって、サービス、コンポーネント、コントローラーなどを整理して、すばらしいアプリケーションを作成するために協力する多くの方法があります。 そして、素晴らしい点は、IDEがそれらすべてを理解することです!
7. Spring BootWebアプリケーション
Spring Boot automatically configures the components of the application,なので、一般的に、それらをどのように整理するかを考える必要はほとんどありません。
それでも、内部では、Bootはこれまでに見たものを含むSpring機能を使用しています。 いくつかの注目すべき違いを見てみましょう。
設計上、組み込みコンテナdon’t run any WebApplicationInitializerで実行されているSpring BootWebアプリケーション。
必要に応じて、選択した導入戦略に応じて、代わりに同じロジックをSpringBootServletInitializer またはServletContextInitializerで記述できます。
ただし、この記事で見られるように、サーブレット、フィルター、およびリスナーを追加する場合、追加する必要はありません。 In fact, Spring Boot automatically registers every servlet-related bean to the container:
@Bean
public Servlet myServlet() { ... }
そのように定義されたオブジェクトは、規則に従ってマップされます。フィルターは、/ *、つまりすべてのリクエストに自動的にマップされます。 単一のサーブレットを登録する場合、/にマップされます。登録しない場合、各サーブレットはそのBean名にマップされます。
上記の規則が機能しない場合は、代わりにFilterRegistrationBean、ServletRegistrationBean, 、またはServletListenerRegistrationBeanを定義できます。 これらのクラスにより、登録の細かい側面を制御できます。
8. 結論
この記事では、SpringWebアプリケーションを構造化および編成するために使用できるさまざまなオプションについて詳しく説明しました。
いくつかの機能、特にsupport for a shared context in enterprise applicationsを省略しました。これは、執筆時点ではまだmissing from Spring 5です。
これらすべての例とコードスニペットの実装はthe GitHub projectにあります。これはMavenプロジェクトであるため、そのままインポートして実行するのは簡単です。