Spring Web Contexts – Spring的网络语境

最后修改: 2018年 5月 10日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

When using Spring in a web application, we have several options for organizing the application contexts that wire it all up.

当在Web应用中使用Spring时,我们有几个选择来组织应用上下文,将其全部连接起来。

In this article, we’re going to analyze and explain the most common options that Spring offers.

在这篇文章中,我们将分析和解释Spring提供的最常见的选项。

2. The Root Web Application Context

2.根网络应用程序上下文

Every Spring webapp has an associated application context that is tied to its lifecycle: the root web application context.

每个Spring Web应用都有一个与其生命周期相关的应用上下文:根Web应用上下文。

This is an old feature that predates Spring Web MVC, so it’s not tied specifically to any web framework technology.

这是一个早于Spring Web MVC的老功能,所以它并没有专门与任何Web框架技术挂钩。

The context is started when the application starts, and it’s destroyed when it stops, thanks to a servlet context listener. The most common types of contexts can also be refreshed at runtime, although not all ApplicationContext implementations have this capability.

上下文在应用程序启动时被启动,在应用程序停止时被销毁,这要归功于一个servlet上下文监听器。最常见的上下文类型也可以在运行时刷新,尽管不是所有的ApplicationContext实现都有这种能力。

The context in a web application is always an instance of WebApplicationContext. That’s an interface extending ApplicationContext with a contract for accessing the ServletContext.

Web应用程序中的上下文始终是WebApplicationContext的一个实例。这是一个扩展ApplicationContext的接口,具有访问ServletContext的契约。

Anyway, applications usually should not be concerned about those implementation details: the root web application context is simply a centralized place to define shared beans.

无论如何,应用程序通常不应该关注这些实现细节。根网络应用程序上下文只是一个集中的地方,用于定义共享Bean。

2.1. The ContextLoaderListener

2.1.ContextLoaderListener

The root web application context described in the previous section is managed by a listener of class org.springframework.web.context.ContextLoaderListener, which is part of the spring-web module.

上一节描述的根Web应用程序上下文由org.springframework.web.context.ContextLoaderListener类的监听器管理,它是spring-web模块的一部分。

By default, the listener will load an XML application context from /WEB-INF/applicationContext.xml. However, those defaults can be changed. We can use Java annotations instead of XML, for example.

默认情况下,监听器将从/WEB-INF/applicationContext.xml加载一个XML应用上下文。然而,这些默认值可以被改变。例如,我们可以使用 Java 注释而不是 XML。

We can configure this listener either in the webapp descriptor (web.xml file) or programmatically in Servlet 3.x environments.

我们可以在webapp描述符(web.xml文件)中或在Servlet 3.x环境中以编程方式配置这个监听器。

In the following sections, we’ll look at each of these options in detail.

在下面的章节中,我们将详细研究这些选项中的每一个。

2.2. Using web.xml and an XML Application Context

2.2.使用web.xml和一个XML应用上下文

When using web.xml, we configure the listener as usual:

当使用web.xml时,我们照常配置监听器。

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

We can specify an alternate location of the XML context configuration with the contextConfigLocation parameter:

我们可以用contextConfigLocation参数指定XML上下文配置的另一个位置。

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

Or more than one location, separated by commas:

或者一个以上的地点,用逗号隔开。

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

We can even use patterns:

我们甚至可以使用模式。

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

In any case, only one context is defined, by combining all the bean definitions loaded from the specified locations.

在任何情况下,只有一个上下文被定义,通过结合所有从指定位置加载的bean定义。

2.3. Using web.xml and a Java Application Context

2.3.使用web.xml和一个Java应用上下文

We can also specify other types of contexts besides the default XML-based one. Let’s see, for example, how to use Java annotations configuration instead.

除了默认的基于XML的上下文,我们还可以指定其他类型的上下文。例如,让我们看看如何使用Java注解配置来代替。

We use the contextClass parameter to tell the listener which type of context to instantiate:

我们使用contextClass参数来告诉监听器要实例化哪种类型的环境。

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

Every type of context may have a default configuration location. In our case, the AnnotationConfigWebApplicationContext does not have one, so we have to provide it.

每种类型的上下文都可能有一个默认的配置位置。在我们的案例中,AnnotationConfigWebApplicationContext没有配置位置,所以我们必须提供它。

We can thus list one or more annotated classes:

因此,我们可以列出一个或多个被注释的类。

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

Or we can tell the context to scan one or more packages:

或者我们可以告诉上下文来扫描一个或多个包。

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

And, of course, we can mix and match the two options.

当然,我们也可以将这两个选项混合在一起。

2.4. Programmatic Configuration With Servlet 3.x

2.4.Servlet 3.x的程序化配置

Version 3 of the Servlet API has made configuration through the web.xml file completely optional. Libraries can provide their web fragments, which are pieces of XML configuration that can register listeners, filters, servlets and so on.

Servlet API 的第 3 版使通过 web.xml 文件进行的配置成为完全可选的。图书馆可以提供他们的web片段,这些片段是可以注册监听器、过滤器、servlets等的XML配置。

Also, users have access to an API that allows defining programmatically every element of a servlet-based application.

此外,用户还可以访问一个API,允许以编程方式定义基于servlet的应用程序的每一个元素。

The spring-web module makes use of these features and offers its API to register components of the application when it starts.

spring-web模块利用了这些功能,并在启动时提供其API来注册应用程序的组件。

Spring scans the application’s classpath for instances of the org.springframework.web.WebApplicationInitializer class. This is an interface with a single method, void onStartup(ServletContext servletContext) throws ServletException, that’s invoked upon application startup.

Spring在应用程序的classpath中扫描org.springframework.web.WebApplicationInitializer类的实例。这是一个只有一个方法的接口,void onStartup(ServletContext servletContext) throws ServletException,它在应用程序启动时被调用。

Let’s now look at how we can use this facility to create the same types of root web application contexts that we’ve seen earlier.

现在让我们来看看我们如何使用这一设施来创建与我们前面看到的相同类型的根Web应用程序上下文。

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

2.5.使用Servlet 3.x和一个XML应用上下文

Let’s start with an XML context, just like in Section 2.2.

让我们从一个XML上下文开始,就像第2.2节中那样。

We’ll implement the aforementioned onStartup method:

我们将实现前面提到的onStartup方法。

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

Let’s break the implementation down line by line.

让我们将实施工作逐行分解。

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.

我们首先创建一个根上下文。因为我们想使用XML,所以它必须是一个基于XML的应用上下文,而且因为我们是在一个Web环境中,所以它也必须实现WebApplicationContext

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:

因此,第一行是我们之前遇到的contextClass参数的明确版本,我们用它来决定使用哪个具体的上下文实现。

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:

然后,在第二行中,我们告诉上下文从哪里加载其 bean 定义。同样,setConfigLocationsweb.xmlcontextConfigLocation参数的程序性类似物。

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:

最后,我们用根上下文创建一个ContextLoaderListener,并将其注册到servlet容器。正如我们所看到的,ContextLoaderListener有一个适当的构造函数,它接收一个WebApplicationContext,并使其对应用程序可用。

servletContext.addListener(new ContextLoaderListener(rootContext));

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

2.6.使用Servlet 3.x和Java应用程序上下文

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.

如果我们想使用基于注解的上下文,我们可以改变上一节的代码片段,使其实例化一个AnnotationConfigWebApplicationContext来代替。

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.

我们之前看到的WebApplicationInitializer类是一个通用的接口。事实证明,Spring提供了一些更具体的实现,包括一个名为AbstractContextLoaderInitializer的抽象类。

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

顾名思义,它的工作是创建一个ContextLoaderListener并在servlet容器中注册。

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.

这里我们可以看到,我们不再需要注册ContextLoaderListener,这让我们省去了一点模板代码。

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.

还要注意使用register方法,该方法是特定于AnnotationConfigWebApplicationContext的,而不是更通用的setConfigLocations:通过调用它,我们可以将单个@Configuration注释的类注册到上下文,从而避免包扫描。

3. Dispatcher Servlet Contexts

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特有的功能,而不是Spring的通用Web应用支持的一部分。

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.

Spring MVC应用程序至少配置了一个Dispatcher Servlet(但可能不止一个,我们将在后面讨论这种情况)。这是一个接收传入请求的Servlet,将其分派给适当的控制器方法,并返回视图。

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

每个DispatcherServlet都有一个相关的应用程序上下文。在此类上下文中定义的Bean配置了Servlet,并定义了MVC对象,如控制器和视图解析器。

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

让我们先看看如何配置servlet的上下文。我们将在后面看一些深入的细节。

3.1. Using web.xml and an XML Application Context

3.1.使用web.xml和一个XML应用上下文

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

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>

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.

如果没有另外指定,Servlet的名称将被用来确定要加载的XML文件。在我们的例子中,我们将使用文件WEB-INF/normal-webapp-servlet.xml

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

我们还可以指定一个或多个XML文件的路径,其方式与ContextLoaderListener类似。

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

3.2. Using web.xml and a Java Application Context

3.2.使用web.xml和一个Java应用上下文

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:

当我们想使用不同类型的上下文时,我们也要像ContextLoaderListener那样进行。也就是说,我们指定一个contextClass参数和一个合适的contextConfigLocation

<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. Using Servlet 3.x and an XML Application Context

3.3.使用Servlet 3.x和一个XML应用上下文

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.

再次,我们将看看以编程方式声明DispatcherServlet的两种不同方法,我们将把其中一种应用于XML上下文,另一种应用于Java上下文。

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

因此,让我们从一个通用的WebApplicationInitializer和一个XML应用上下文开始。

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

正如我们之前看到的,我们必须实现onStartup方法。然而,这一次我们也要创建并注册一个调度器servlet。

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.

我们可以很容易地在上述代码和同等的web.xml配置元素之间进行比较。

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

3.4.使用Servlet 3.x和Java应用程序上下文

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

这一次,我们将使用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:

这是一个抽象的类,除了像之前看到的那样创建一个根Web应用程序上下文外,还允许我们用最少的模板注册一个调度器Servlet。

@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.

在这里,我们可以看到一个创建与servlet相关的上下文的方法,与我们之前看到的根上下文完全一样。此外,我们还有一个方法来指定servlet的映射,就像在web.xml中一样。

4. Parent and Child Contexts

4.父母和子女的背景

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?

到目前为止,我们已经看到了两种主要的上下文类型:根Web应用程序上下文和调度器Servlet上下文。那么,我们可能会有一个问题。这些上下文是否相关?

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.

事实证明,是的,它们是。事实上,根上下文是每个调度器 servlet 上下文的父级。因此,在根Web应用程序上下文中定义的Bean对每个dispatcher servlet上下文都是可见的,但反之亦然。

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

因此,通常情况下,根上下文用于定义服务Bean,而调度器上下文包含那些与MVC特别相关的Bean。

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.

注意,我们也看到了以编程方式创建dispatcher servlet上下文的方法。如果我们手动设置它的父级,那么Spring就不会覆盖我们的决定,本节不再适用。

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!

在比较简单的MVC应用程序中,只需将一个上下文与唯一的一个调度器Servlet相关联就足够了。没有必要采用过于复杂的解决方案。

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

不过,当我们配置了多个调度器的Servlet时,父子关系还是变得很有用。但是,我们什么时候应该费力地配置多个?

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:

一般来说,当我们需要多套MVC配置时,我们会声明多个dispatcher servlets 。例如,我们可能在传统的MVC应用程序旁边有一个REST API,或者在一个网站的不安全和安全部分。

contexts

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

注意:当我们扩展AbstractDispatcherServletInitializer (见第3.4节)时,我们同时注册了一个根Web应用程序上下文和一个单一的调度器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.

因此,如果我们想要一个以上的Servlet,我们需要多个AbstractDispatcherServletInitializer实现。然而,我们只能定义一个根上下文,否则应用程序将无法启动。

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.

幸运的是,createRootApplicationContext方法可以返回null。因此,我们可以有一个AbstractContextLoaderInitializer和许多AbstractDispatcherServletInitializer的实现,这些实现并不创建根上下文。在这种情况下,建议用@Order明确地排列初始化器。

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:

另外,请注意,AbstractDispatcherServletInitializer以一个给定的名字(dispatcher)来注册Servlet,当然,我们不能有多个同名的Servlet。因此,我们需要覆盖getServletName

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

5. A Parent and Child Context Example 

5.一个父母和子女的背景例子

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.

假设我们的应用程序有两个区域,例如,一个是世界上可访问的公共区域,一个是安全区域,有不同的MVC配置。在这里,我们只需定义两个控制器,输出不同的信息。

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

5.1.共享服务

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

在我们的hello world例子中,我们选择了一个更简单的greeter服务,而不是持久性。

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:

我们将在根Web应用程序上下文中声明服务,使用组件扫描。

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

We might prefer XML instead:

我们可能更喜欢XML,而不是。

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

5.2. The Controllers

5.2.控制器

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 = "<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>";

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

5.3.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:

正如我们前面所说,我们将有两个不同的调度器servlet上下文,每个控制器一个。所以,让我们用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:

或者,如果我们愿意的话,用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. Putting It All Together

5.4.把一切放在一起

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.

现在我们有了所有的部件,我们只需要告诉Spring将它们连接起来。回顾一下,我们需要加载根上下文,并定义两个调度器的Servlet。尽管我们已经看到了多种方法,但现在我们将关注两种情况,一种是Java,一种是XML。让我们从Java开始

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

我们将定义一个AbstractContextLoaderInitializer来加载根上下文。

@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:

然后,我们需要创建两个Servlet,因此我们将定义AbstractDispatcherServletInitializer的两个子类。首先,是 “正常 “的那个。

@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.

我们可以用web.xml做同样的事情,同样只是把我们到目前为止讨论的部分结合起来。

Define a root application context:

定义一个根应用程序上下文。

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

A “normal” dispatcher context:

一个 “正常 “的调度器上下文。

<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>

And, finally, a “secure” context:

最后,还有一个 “安全 “的背景。

<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. Combining Multiple Contexts

6.结合多种语境

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.

除了父-子之外,还有其他方法来组合多个配置位置,分割大的上下文并更好地分离不同的关注点。我们已经看到了一个例子:当我们指定contextConfigLocation有多个路径或包时,Spring通过组合所有的Bean定义来构建一个单一的上下文,就像它们被写在一个XML文件或Java类中一样,依次排列。

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

6.1.将一个语境导入另一个语境

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:

在Java中导入一个@Configuration类。

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

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

在Java中加载一些其他类型的资源,例如,XML上下文定义。

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

Finally, including an XML file in another one:

最后,在另一个文件中包括一个XML文件。

<import resource="greeting.xml" />

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

7.Spring Boot网络应用

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

Spring Boot自动配置了应用程序的组件,因此,一般来说,不太需要考虑如何组织这些组件。

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

不过,在引擎盖下,Boot还是使用了Spring的功能,包括我们到目前为止所看到的那些。让我们看看几个值得注意的区别。

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

在嵌入式容器中运行的Spring Boot Web应用程序在设计上不运行任何WebApplicationInitializer

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

如果有必要,我们可以在SpringBootServletInitializerServletContextInitializer中编写相同的逻辑,这取决于所选择的部署策略。

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:

然而,对于本文中所看到的添加servlet、过滤器和监听器来说,没有必要这样做。事实上,Spring Boot会自动将每个与servlet相关的bean注册到容器中:

@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.

这样定义的对象根据惯例被映射:过滤器被自动映射到/*,也就是映射到每个请求。如果我们注册了一个servlet,它就被映射到/,否则,每个servlet都被映射到它的bean名称。

If the above conventions don’t work for us, we can define a FilterRegistrationBean, ServletRegistrationBean, or ServletListenerRegistrationBean instead. Those classes allow us to control the fine aspects of the registration.

如果上述约定对我们不适用,我们可以定义一个FilterRegistrationBeanServletRegistrationBean,或者ServletListenerRegistrationBean来代替。这些类允许我们控制注册的细微部分。

8. Conclusions

8.结论

In this article, we’ve given an in-depth view of the various options available to structure and organize a Spring web application.

在这篇文章中,我们深入介绍了用于结构和组织Spring网络应用的各种选项。

We’ve left out some features, notably the support for a shared context in enterprise applications, which, at the time of writing, is still missing from Spring 5.

我们遗漏了一些功能,特别是对企业应用程序中共享上下文的支持,在撰写本文时,Spring 5中仍未提供该功能

The implementation of all these examples and code snippets can be found in the GitHub project.

所有这些例子和代码片断的实现都可以在GitHub项目中找到。