SpringWeb容器的启动

参考 Spring 官方文档:

https://docs.spring.io/spring-framework/docs/5.2.25.RELEASE/spring-framework-reference/web.html#spring-web

1、前置知识:ServletContainerInitializer

在 Servlet3.0 及之后,提供了一个接口:ServletContainerInitializer(Servlet容器的初始化器)

用于提供给第三方组件一个机会,对 Servlet 容器做一些初始化的工作

public interface ServletContainerInitializer {
    
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException; 
}

【大白话总结】

Web容器(Tomcat)会调用 ServletContainerInitializer 的 onStarup 方法对Servlet容器进行初始化,而这些 ServletContainerInitializer 的实现,由第三方提供

问题
Web容器如何发现这些 ServletContainerInitializer 的实现?-----> 使用的是SPI机制

SPI机制

  • ServletContainerInitializer 服务调用方:Web容器(Tomcat)

    利用 ServiceLoader,查找当前项目环境下,所有 jar 包 /META-INF /service 文件夹,是否有一个 javax.servlet.ServletContainerInitializer 文件

  • ServletContainerInitializer 服务提供方:Spring框架

    在 org.springframework:spring-web 包下的 /META-INF /service 文件夹下,提供了一个 javax.servlet.ServletContainerInitializer 文件:
    在这里插入图片描述

【大白话总结】

利用SPI的发现机制,Web容器会调用SpringServletContainerInitializeronStarup方法对Servlet容器进行初始化

2、SpringServletContainerInitializer

可以发现,类上@HandlesTypes注解中标注了 WebApplicationInitializer.class,(感兴趣的的类)
那么 Tomcat 就会将所有 WebApplicationInitializer(Web应用初始化器) 的实现类(包括抽象类),传到 SpringServletContainerInitializer # onStartup() 方法的参数上

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

    @Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {

        List<WebApplicationInitializer> initializers = Collections.emptyList();

        if (webAppInitializerClasses != null) {
            initializers = new ArrayList<>(webAppInitializerClasses.size());
            for (Class<?> waiClass : webAppInitializerClasses) {
                /* 
                	筛选出满足下面三个条件的 Class 类,实例化后,加入到initializers集合
                	条件:"不是接口类型" && "不是抽象类" && "是WebApplicationInitializer的实现类"
                */
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        // 实例化,加入到initializers集合
                        initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass).newInstance());
                    }
                    //...
                }
            }
        }
		//...  
        
        // 遍历这些 WebApplicationInitializer,调用它们的 onStartup() 方法
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }

}

可以发现,在SpringServletContainerInitializer # onStartup()中会调用 WebApplicationInitializer 的 onStartup() 方法

3、 编写自己的 WebApplicationInitializer

Web应用初始化器的实现可以有两种方式:

3.1、方式一:直接实现 WebApplicationInitializer 接口(无父子容器,不推荐):

public class MyAppStarter implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) {

        // 创建SpringWeb容器 context
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        // 注册SpringWeb容器的配置类 SpringMVCConfig
        context.register(SpringMVCConfig.class);

        // 创建并注册 DispatcherServlet 对象
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);   //将 DispatcherServlet 添加到 ServletContext 域中
        registration.setLoadOnStartup(1);  
        registration.addMapping("/");  //设置 DispatcherServlet 的访问路径,这里设置拦截所有请求
        
    }
}

以上的 onStartup() 方法:

①创建了SpringWeb容器,这里创建的 SpringWeb 容器类型是 AnnotationConfigWebApplicationContext

利用 创建好的SpringWeb容器 构建DispatcherServlet: DispatcherServlet servlet = new DispatcherServlet(context);
那么之后Tomcat在初始化 DispatcherServlet 时,进入 init 逻辑,会在 FrameworkServlet # initServletBean() 中进行 SpringWeb 容器的初始化…(initWebApplicationContext())。详情查看【DispatcherServlet的初始化(二)

3.2 方式二:继承 AbstractAnnotationConfigDispatcherServletInitializer (有父子容器,推荐)

public class MyQuickAppStarter extends AbstractAnnotationConfigDispatcherServletInitializer {

    /*
    	父容器:Spring根容器的配置
    */
    @Override
    protected Class<?>[] getRootConfigClasses() {
       return new Class<?>[]{ SpringConfig.class }; //SpringConfig(负责Service、Dao层的配置)
    }

    /*
    	子容器:SpringWeb容器的配置
    */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{ SpringMVCConfig.class }; //SpringMVCConfig(负责Controller层的配置)
    }

    /*
    	映射路径
    */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};   //拦截所有请求
    }

}

【方式二的原理】WebApplicationInitializer 体系结构

在这里插入图片描述
分别给三个重写方法打断点:观察函数栈:
在这里插入图片描述

因为 WebApplicationInitializer 的 onStartup() 方法是调用入口,从 onStartup() 方法开始分析:

一、Spring根容器的创建

1、调用入口: onStartup() 方法最后一次重写发生在 AbstractDispatcherServletInitializer 中
在这里插入图片描述
2、进入父类:
在这里插入图片描述
3、创建Spring根容器、并利用创建好的根容器构造ContextLoaderListener(【详情查看ContextLoaderListener】)
![在这里插入图片描述](https://img-blog.csdnimg.cn/bc90e669e272472a919805db5cf47dd8.png
4、创建Spring根容器
在这里插入图片描述

二、SpringWeb容器的创建
在这里插入图片描述

创建SpringWeb容器、并用创建好的SpringWeb容器构建DispatcherServlet(【详情查看:DispatcherServlet的初始化(二)】)
在这里插入图片描述

创建SpringWeb容器
在这里插入图片描述

总结

在使用JavaConfig的方式配置SpringMVC应用时,底层是借助 Spring 提供的 SpringServletContainerInitializer 来介入到Web容器的初始化过程的。
在这个过程中,可以通过实现 WebApplicationInitializer 接口,来完成DispatcherServlet的初始化,以及Spring容器(父与子)的创建

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值