那些年背过的题:IOC容器在Web中启动流程

118 阅读6分钟

在 Spring Web 应用中,IOC 容器的启动流程是一个关键部分。这个流程确保了 Spring 的各种组件能够正确初始化和配置,以便处理请求。以下是 IOC 容器在 Spring Web 应用中的启动流程:

1. 配置文件加载

这一步在 web.xml 文件中进行配置。你需要指定 ContextLoaderListenerDispatcherServlet

示例 web.xml

<web-app>
    <!-- 加载应用上下文 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>

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

    <!-- 配置 DispatcherServlet -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

除了通过传统的 web.xml 配置文件来启动 Spring IOC 容器,Spring 还支持多种方式来配置和启动 Web 应用。以下是几种常见的替代方案:

1.1 使用 Java 配置类

Spring 提供了对纯 Java 配置的强大支持,可以完全摆脱 XML 配置文件。这通常是通过实现 WebApplicationInitializer 接口来完成的。

示例:

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

public class MyWebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(AppConfig.class); // AppConfig 是你的配置类

        DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", dispatcherServlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}

在上面的示例中,我们使用 AnnotationConfigWebApplicationContext 和一个 Java 配置类 AppConfig 来初始化 Spring 应用上下文。

2. Spring Boot

Spring Boot 极大地简化了 Spring 应用的配置和部署,尤其适用于快速开发和部署微服务。Spring Boot 默认使用嵌入式的 Tomcat 或其他 servlet 容器,因此不需要单独配置 web.xml

示例:

创建一个主程序入口,可以简单地用注解来启动 Spring Boot 应用:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

使用 @SpringBootApplication 注解,它实际上包含了 @Configuration@EnableAutoConfiguration@ComponentScan 三个注解的组合。通过这种方式,Spring Boot 自动扫描并配置应用上下文中的所有组件和依赖。

2. ContextLoaderListener 初始化

ContextLoaderListener 是第一个被触发的监听器,它的任务是启动根应用上下文(ApplicationContext)。

  • 创建 WebApplicationContextContextLoaderListener 会创建一个 WebApplicationContext,这是一个扩展的 ApplicationContext,专门用于 Web 环境。
  • 加载配置文件:它会读取 contextConfigLocation 参数所指定的配置文件,来初始化根应用上下文。

3. DispatcherServlet 初始化

DispatcherServlet 是 Spring MVC 的核心,它负责处理所有进入的 HTTP 请求并将其分发到相应的控制器。

  • 创建子上下文:每个 DispatcherServlet 都有自己独立的 WebApplicationContext,它是根应用上下文的子上下文。
  • 加载 Spring MVC 配置DispatcherServlet 会读取 contextConfigLocation 参数所指定的配置文件,并根据这些配置初始化 Spring MVC 相关的 bean,如视图解析器、控制器映射等。
  • 调用 initStrategies 方法:在这个过程中,DispatcherServlet 会调用 initStrategies 方法来初始化各种处理程序(如处理器映射、处理器适配器、视图解析器等)。

4. Bean 初始化及依赖注入

WebApplicationContext 初始化期间,Spring 会扫描配置文件中的 bean 定义,并创建相应的 bean 实例。同时,根据配置的依赖关系,进行依赖注入。

5. 完成启动

ContextLoaderListenerDispatcherServlet 完成各自的初始化后,Spring Web 应用就已经准备好接受和处理 HTTP 请求了。

总结

整个启动流程大致可以概括为:

  1. 配置文件加载:通过 web.xml 配置 ContextLoaderListener 和 DispatcherServlet
  2. ContextLoaderListener 初始化:创建根应用上下文 (WebApplicationContext) 并加载配置文件。
  3. DispatcherServlet 初始化:创建子上下文并加载 Spring MVC 配置文件,初始化各种策略对象。
  4. Bean 初始化及依赖注入:根据配置文件创建和初始化各个 bean,并进行依赖注入。
  5. 完成启动:应用准备就绪,可以开始处理 HTTP 请求。

思考题1: 什么场景需要创建多个DispatcherServlet?

创建多个 DispatcherServlet 的场景在一些复杂的 Web 应用中会显得非常有用和必要。以下是几种常见的场景:

1. 区分不同的功能模块或子系统

在大型应用中,不同的功能模块可能需要独立管理。例如,一个电商平台可能包含用户管理、商品管理、订单管理等多个模块。为了更好地隔离这些模块,可以为每个模块创建一个独立的 DispatcherServlet

2. 不同的配置需求

有时不同的 URL 请求路径需要不同的配置。例如,一个区域使用 JSON 格式返回数据,而另一个区域使用 XML 格式。在这种情况下,可以为这些区域提供独立的 DispatcherServlet,并在各自的配置文件中定义特定的视图解析器和消息转换器。

3. 多种协议支持

某些应用可能需要同时支持 Web 和 RESTful API。这时可以为传统的 Web 页面请求和 RESTful API 请求分别设置不同的 DispatcherServlet,针对性地配置它们所需的处理器映射、消息转换器以及异常处理机制。

4. 多租户应用

在多租户(SaaS)应用中,不同的客户可能需要独立的配置、数据源甚至业务逻辑。通过为每个租户创建单独的 DispatcherServlet 可以实现这一目标,每个租户可以有自己独立的上下文配置。

5. 性能优化

在某些高负载的应用中,通过将不同类型的请求(如静态资源请求和动态内容请求)分配给不同的 DispatcherServlet,可以实现更细粒度的性能调优和监控。

6. 安全与权限控制

不同的 DispatcherServlet 可以配置不同的安全策略。例如,可以为管理后台和普通用户区域设置不同的安全过滤链和认证授权机制。

7. 版本控制

在 API 版本管理中,不同版本的 API 可能需要不同的处理逻辑和配置。通过独立的 DispatcherServlet 管理不同版本的 API,可以简化版本升级和迁移工作。

思考题2: 如何创建多个DispatcherServlet?

假设我们有两个模块,一个是用户管理模块,另一个是商品管理模块。我们将为每个模块创建一个独立的 DispatcherServlet

第一步:创建配置类

我们需要为每个模块创建各自的 Spring 配置类:

UserConfig.java

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "com.example.user")
public class UserConfig {
    // 配置用户管理模块相关的 Bean
}

ProductConfig.java

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "com.example.product")
public class ProductConfig {
    // 配置商品管理模块相关的 Bean
}

第二步:创建 WebApplicationInitializer 实现类

实现 WebApplicationInitializer 接口,并在 onStartup 方法中配置多个 DispatcherServlet

MyWebAppInitializer.java

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

public class MyWebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // 配置第一个 DispatcherServlet (用户管理模块)
        AnnotationConfigWebApplicationContext userContext = new AnnotationConfigWebApplicationContext();
        userContext.register(UserConfig.class);
        
        DispatcherServlet userDispatcherServlet = new DispatcherServlet(userContext);
        ServletRegistration.Dynamic userServlet = servletContext.addServlet("userDispatcher", userDispatcherServlet);
        userServlet.setLoadOnStartup(1);
        userServlet.addMapping("/user/*");
        
        // 配置第二个 DispatcherServlet (商品管理模块)
        AnnotationConfigWebApplicationContext productContext = new AnnotationConfigWebApplicationContext();
        productContext.register(ProductConfig.class);
        
        DispatcherServlet productDispatcherServlet = new DispatcherServlet(productContext);
        ServletRegistration.Dynamic productServlet = servletContext.addServlet("productDispatcher", productDispatcherServlet);
        productServlet.setLoadOnStartup(1);
        productServlet.addMapping("/product/*");
    }
}