Spring 웹 컨텍스트

1. 소개

웹 애플리케이션에서 Spring을 사용할 때 모든 것을 연결하는 애플리케이션 컨텍스트를 구성하는 몇 가지 옵션이 있습니다.

이 기사에서는 Spring이 제공하는 가장 일반적인 옵션을 분석하고 설명 할 것입니다.

2. 루트 웹 애플리케이션 컨텍스트

모든 Spring 웹앱에는 라이프 사이클에 연결된 관련 애플리케이션 컨텍스트 인 루트 웹 애플리케이션 컨텍스트가 있습니다.

이것은 Spring Web MVC 이전의 오래된 기능이므로 특별히 웹 프레임 워크 기술과 관련이 없습니다.

컨텍스트는 애플리케이션이 시작될 때 시작되고 서블릿 컨텍스트 리스너 덕분에 중지되면 삭제됩니다. 모든 ApplicationContext 구현에이 기능이있는 것은 아니지만 가장 일반적인 유형의 컨텍스트도 런타임에 새로 고칠 수 있습니다 .

웹 애플리케이션의 컨텍스트는 항상 WebApplicationContext 의 인스턴스입니다 . 이는 ServletContext 액세스를위한 계약 으로 ApplicationContext 를 확장하는 인터페이스 입니다.

어쨌든 응용 프로그램은 일반적으로 이러한 구현 세부 사항에 대해 관심을 가져서는 안됩니다. 루트 웹 응용 프로그램 컨텍스트는 단순히 공유 빈을 정의하는 중앙 집중식 장소입니다.

2.1. 의 ContextLoaderListener

이전 섹션에서 설명한 루트 웹 애플리케이션 컨텍스트 는 spring-web 모듈의 일부인 org.springframework.web.context.ContextLoaderListener 클래스의 리스너에 의해 관리됩니다 .

기본적으로 리스너는 /WEB-INF/applicationContext.xml 에서 XML 애플리케이션 컨텍스트를로드합니다 . 그러나 이러한 기본값은 변경할 수 있습니다. 예를 들어 XML 대신 Java 주석을 사용할 수 있습니다.

이 리스너는 webapp 디스크립터 ( web.xml 파일)에서 구성하거나 Servlet 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 

어떤 경우 든 지정된 위치에서로드 된 모든 Bean 정의를 결합하여 하나의 컨텍스트 만 정의됩니다.

2.3. 사용 의 web.xml 과 자바 응용 프로그램 컨텍스트

기본 XML 기반 컨텍스트 외에 다른 유형의 컨텍스트를 지정할 수도 있습니다. 예를 들어 Java 주석 구성을 대신 사용하는 방법을 살펴 보겠습니다.

contextClass 매개 변수를 사용하여 리스너에게 인스턴스화 할 컨텍스트 유형을 알려줍니다.

 contextClass  org.springframework.web.context.support.AnnotationConfigWebApplicationContext  

모든 유형의 컨텍스트에는 기본 구성 위치가있을 수 있습니다. 우리의 경우 AnnotationConfigWebApplicationContext 에는 하나가 없으므로 제공해야합니다.

따라서 하나 이상의 주석이 달린 클래스를 나열 할 수 있습니다.

 contextConfigLocation  com.baeldung.contexts.config.RootApplicationConfig, com.baeldung.contexts.config.NormalWebAppConfig  

또는 컨텍스트에 하나 이상의 패키지를 스캔하도록 지정할 수 있습니다.

 contextConfigLocation com.baeldung.bean.config 

물론 두 가지 옵션을 혼합하고 일치시킬 수 있습니다.

2.4. Servlet 3.x를 사용한 프로그래밍 방식 구성

Servlet API 버전 3은 web.xml 파일을 통한 구성을 완전히 선택적으로 만들었습니다 . 라이브러리는 리스너, 필터, 서블릿 등을 등록 할 수있는 XML 구성의 일부인 웹 조각을 제공 할 수 있습니다.

또한 사용자는 서블릿 기반 애플리케이션의 모든 요소를 ​​프로그래밍 방식으로 정의 할 수있는 API에 액세스 할 수 있습니다.

스프링 웹 모듈 차종은 이러한 기능을 사용하여 시작할 때 응용 프로그램의 구성 요소를 등록하기 위해 API를 제공합니다.

Spring은 org.springframework.web.WebApplicationInitializer 클래스의 인스턴스에 대해 애플리케이션의 클래스 경로를 스캔합니다 . 이것은 단일 메소드가있는 인터페이스입니다. void onStartup (ServletContext servletContext)는 애플리케이션 시작시 호출되는 ServletException 을 발생시킵니다.

이제이 기능을 사용하여 이전에 본 것과 동일한 유형의 루트 웹 애플리케이션 컨텍스트를 만드는 방법을 살펴 보겠습니다.

2.5. Servlet 3.x 및 XML 응용 프로그램 컨텍스트 사용

2.2 절 에서처럼 XML 컨텍스트로 시작해 보겠습니다.

앞서 언급 한 onStartup 메서드를 구현합니다 .

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

구현을 한 줄씩 세분화합시다.

We first create a root context. Since we want to use XML, it has to be an XML-based application context, and since we're in a web environment, it has to implement WebApplicationContext as well.

The first line, thus, is the explicit version of the contextClass parameter that we've encountered earlier, with which we decide which specific context implementation to use:

XmlWebApplicationContext rootContext = new XmlWebApplicationContext();

Then, in the second line, we tell the context where to load its bean definitions from. Again, setConfigLocations is the programmatic analogous of the contextConfigLocation parameter in web.xml:

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

Finally, we create a ContextLoaderListener with the root context and register it with the servlet container. As we can see, ContextLoaderListener has an appropriate constructor that takes a WebApplicationContext and makes it available to the application:

servletContext.addListener(new ContextLoaderListener(rootContext));

2.6. Using Servlet 3.x and a Java Application Context

If we want to use an annotation-based context, we could change the code snippet in the previous section to make it instantiate an AnnotationConfigWebApplicationContext instead.

However, let's see a more specialized approach to obtain the same result.

The WebApplicationInitializer class that we've seen earlier is a general-purpose interface. It turns out that Spring provides a few more specific implementations, including an abstract class called AbstractContextLoaderInitializer.

Its job, as the name implies, is to create a ContextLoaderListener and register it with the servlet container.

We only have to tell it how to build the root context:

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

Here we can see that we no longer need to register the ContextLoaderListener, which saves us from a little bit of boilerplate code.

Note also the use of the register method that is specific to AnnotationConfigWebApplicationContext instead of the more generic setConfigLocations: by invoking it, we can register individual @Configuration annotated classes with the context, thus avoiding package scanning.

3. Dispatcher Servlet Contexts

Let's now focus on another type of application context. This time, we'll be referring to a feature which is specific to Spring MVC, rather than part of Spring's generic web application support.

Spring MVC applications have at least one Dispatcher Servlet configured (but possibly more than one, we'll talk about that case later). This is the servlet that receives incoming requests, dispatches them to the appropriate controller method, and returns the view.

Each DispatcherServlet has an associated application context. Beans defined in such contexts configure the servlet and define MVC objects like controllers and view resolvers.

Let's see how to configure the servlet's context first. We'll look at some in-depth details later.

3.1. Using web.xml and an XML Application Context

DispatcherServlet is typically declared in web.xml with a name and a mapping:

 normal-webapp  org.springframework.web.servlet.DispatcherServlet  1   normal-webapp /api/* 

If not otherwise specified, the name of the servlet is used to determine the XML file to load. In our example, we'll use the file WEB-INF/normal-webapp-servlet.xml.

We can also specify one or more paths to XML files, in a similar fashion to ContextLoaderListener:

 ...  contextConfigLocation /WEB-INF/normal/*.xml  

3.2. Using web.xml and a Java Application Context

When we want to use a different type of context we proceed like with ContextLoaderListener, again. That is, we specify a contextClass parameter along with a suitable contextConfigLocation:

 normal-webapp-annotations  org.springframework.web.servlet.DispatcherServlet   contextClass  org.springframework.web.context.support.AnnotationConfigWebApplicationContext    contextConfigLocation com.baeldung.contexts.config.NormalWebAppConfig  1 

3.3. Using Servlet 3.x and an XML Application Context

Again, we'll look at two different methods for programmatically declaring a DispatcherServlet, and we'll apply one to an XML context and the other to a Java context.

So, let's start with a generic WebApplicationInitializer and an XML application context.

As we've seen previously, we have to implement the onStartup method. However, this time we'll create and register a dispatcher servlet, too:

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/*");

We can easily draw a parallel between the above code and the equivalent web.xml configuration elements.

3.4. Using Servlet 3.x and a Java Application Context

This time, we'll configure an annotations-based context using a specialized implementation of WebApplicationInitializer: AbstractDispatcherServletInitializer.

That's an abstract class that, besides creating a root web application context as previously seen, allows us to register one dispatcher servlet with minimum boilerplate:

@Override protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext secureWebAppContext = new AnnotationConfigWebApplicationContext(); secureWebAppContext.register(SecureWebAppConfig.class); return secureWebAppContext; } @Override protected String[] getServletMappings() { return new String[] { "/s/api/*" }; }

Here we can see a method for creating the context associated with the servlet, exactly like we've seen before for the root context. Also, we have a method to specify the servlet's mappings, as in web.xml.

4. Parent and Child Contexts

So far, we've seen two major types of contexts: the root web application context and the dispatcher servlet contexts. Then, we might have a question: are those contexts related?

It turns out that yes, they are. In fact, the root context is the parent of every dispatcher servlet context. Thus, beans defined in the root web application context are visible to each dispatcher servlet context, but not vice versa.

So, typically, the root context is used to define service beans, while the dispatcher context contains those beans that are specifically related to MVC.

Note that we've also seen ways to create the dispatcher servlet context programmatically. If we manually set its parent, then Spring does not override our decision, and this section no longer applies.

In simpler MVC applications, it's sufficient to have a single context associated to the only one dispatcher servlet. There's no need for overly complex solutions!

Still, the parent-child relationship becomes useful when we have multiple dispatcher servlets configured. But when should we bother to have more than one?

In general, we declare multiple dispatcher servlets when we need multiple sets of MVC configuration. For example, we may have a REST API alongside a traditional MVC application or an unsecured and a secure section of a website:

Note: when we extend AbstractDispatcherServletInitializer (see section 3.4), we register both a root web application context and a single dispatcher servlet.

So, if we want more than one servlet, we need multiple AbstractDispatcherServletInitializer implementations. However, we can only define one root context, or the application won't start.

Fortunately, the createRootApplicationContext method can return null. Thus, we can have one AbstractContextLoaderInitializer and many AbstractDispatcherServletInitializer implementations that don't create a root context. In such a scenario, it is advisable to order the initializers with @Order explicitly.

Also, note that AbstractDispatcherServletInitializer registers the servlet under a given name (dispatcher) and, of course, we cannot have multiple servlets with the same name. So, we need to override getServletName:

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

5. A Parent and Child Context Example

Suppose that we have two areas of our application, for example a public one which is world accessible and a secured one, with different MVC configurations. Here, we'll just define two controllers that output a different message.

Also, suppose that some of the controllers need a service that holds significant resources; a ubiquitous case is persistence. Then, we'll want to instantiate that service only once, to avoid doubling its resource usage, and because we believe in the Don't Repeat Yourself principle!

Let's now proceed with the example.

5.1. The Shared Service

In our hello world example, we settled for a simpler greeter service instead of persistence:

package com.baeldung.contexts.services; @Service public class GreeterService { @Resource private Greeting greeting; public String greet() { return greeting.getMessage(); } }

We'll declare the service in the root web application context, using component scanning:

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

We might prefer XML instead:

5.2. The Controllers

Let's define two simple controllers which use the service and output a greeting:

package com.baeldung.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.baeldung.contexts.secure; String message = "

Secure " + greeterService.greet() + "

";

As we can see, the controllers lie in two different packages and print different messages: one says “normal”, the other “secure”.

5.3. The Dispatcher Servlet Contexts

As we said earlier, we're going to have two different dispatcher servlet contexts, one for each controller. So, let's define them, in 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 { //... }

Or, if we prefer, in XML:

5.4. Putting It All Together

Now that we have all the pieces, we just need to tell Spring to wire them up. Recall that we need to load the root context and define the two dispatcher servlets. Although we've seen multiple ways to do that, we'll now focus on two scenarios, a Java one and an XML one. Let's start with Java.

We'll define an AbstractContextLoaderInitializer to load the root context:

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

Then, we need to create the two servlets, thus we'll define two subclasses of AbstractDispatcherServletInitializer. First, the “normal” one:

@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"; } 

Then, the “secure” one, which loads a different context and is mapped to a different path:

@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"; }

And we're done! We've just applied what we touched in previous sections.

We can do the same with web.xml, again just by combining the pieces we've discussed so far.

Define a root application context:

  org.springframework.web.context.ContextLoaderListener   

A “normal” dispatcher context:

 normal-webapp  org.springframework.web.servlet.DispatcherServlet  1   normal-webapp /api/*  

And, finally, a “secure” context:

 secure-webapp  org.springframework.web.servlet.DispatcherServlet  1   secure-webapp /s/api/* 

6. Combining Multiple Contexts

There are other ways than parent-child to combine multiple configuration locations, to split big contexts and better separate different concerns. We've seen one example already: when we specify contextConfigLocation with multiple paths or packages, Spring builds a single context by combining all the bean definitions, as if they were written in a single XML file or Java class, in order.

However, we can achieve a similar effect with other means and even use different approaches together. Let's examine our options.

One possibility is component scanning, which we explain in another article.

6.1. Importing a Context Into Another

Alternatively, we can have a context definition import another one. Depending on the scenario, we have different kinds of imports.

Importing a @Configuration class in Java:

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

Loading some other type of resource, for example, an XML context definition, in Java:

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

Finally, including an XML file in another one:

Thus, we have many ways to organize the services, components, controllers, etc., that collaborate to create our awesome application. And the nice thing is that IDEs understand them all!

7. Spring Boot Web Applications

Spring Boot automatically configures the components of the application, so, generally, there is less need to think about how to organize them.

Still, under the hood, Boot uses Spring features, including those that we've seen so far. Let's see a couple of noteworthy differences.

Spring Boot web applications running in an embedded container don't run any WebApplicationInitializer by design.

Should it be necessary, we can write the same logic in a SpringBootServletInitializer or a ServletContextInitializer instead, depending on the chosen deployment strategy.

However, for adding servlets, filters, and listeners as seen in this article, it is not necessary to do so. In fact, Spring Boot automatically registers every servlet-related bean to the container:

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

The objects so defined are mapped according to conventions: filters are automatically mapped to /*, that is, to every request. If we register a single servlet, it is mapped to /, otherwise, each servlet is mapped to its bean name.

위의 규칙이 작동하지 않으면 대신 FilterRegistrationBean , ServletRegistrationBean 또는 ServletListenerRegistrationBean을 정의 할 수 있습니다 . 이러한 클래스를 통해 등록의 세부 사항을 제어 할 수 있습니다.

8. 결론

이 기사에서는 Spring 웹 애플리케이션을 구조화하고 구성하는 데 사용할 수있는 다양한 옵션에 대해 심층적으로 살펴 보았습니다.

우리는 일부 기능, 특히 엔터프라이즈 애플리케이션에서 공유 컨텍스트에 대한 지원을 생략했습니다.이 기능은 작성 당시 아직 Spring 5에서 누락되었습니다.

이러한 모든 예제 및 코드 조각의 구현은 GitHub 프로젝트에서 찾을 수 있습니다.