【Spring MVC + Tomcat】Spring MVC 传统VS现代方式的启动过程对比

1  前言

这节我们来讨论下 Spring MVC 传统和现在的启动方式的不同,可能大家现在上手就是SpringBoot直接给我们内置Tomcat,我们最多也就是改改配置就完事了,我记得我上学的时候写SSM的时候,还要整理各种Jar包和配置,这节我们就来对比下两种启动方式是如何启动Spring MVC的哈。

2  传统和现代

我们先来看下哪些方式的是传统,哪些是现代启动:

  • 传统方式:打成war包并放入Tomcat等Servlet容器下面运行的,都认为是SpringMVC传统的启动方式。基于web.xml(配置文件)的方式启动肯定算传统的,但由于现在web.xml几乎已经绝迹,肯定是传统的,这里就不解释他了。与之相对的就是基于编程(写代码)的方式启动,流行于前几年的SSM(Spring、SpringMVC、MyBatis)中,当然也算传统的。
  • 现代方式:和SpringBoot连用且采用内嵌Web服务器并打成jar包直接运行的,可以认为是SpringMVC现代的启动方式。

3  传统的启动过程

在上一节中讲到,通过一个“桥梁”的接口ServletContainerInitializer(Servlet容器初始化器)把Tomcat的启动和初始化进程带到了SpringMVC里。
在这个“桥式”接口上可以指定“感兴趣”的类或接口,SpringMVC指定的是WebApplicationInitializer(Web应用初始化器)接口,意图已经很明显,就是通过这个初始化器接口来完成SpringMVC应用的启动和初始化。
我们先来看下这个初始化接口,如下图:

它只有一个onStartup方法,方法只有一个参数就是ServletContext,这个ServletContext由Tomcat创建好后提供给SpringMVC,SpringMVC在启动过程中调用这个onStartup方法,在这个方法内完成自身的创建和初始化,还要把Servlet和Filter等注册到ServletContext里。
这些工作都是SpringMVC要做的,而不是我们要做的,所以SpringMVC肯定已经实现了这个接口,我们查看下类型信息,

我们发现了一个看着很重要的类,就是:AbstractAnnotationConfigDispatcherServletInitializer,可惜这个类是抽象的,肯定是不能直接用的,但是它里面已经包含了刚刚上面提到的所有完整的启动逻辑过程。
如果你对SSM很熟悉或Spring的官方文档看的很熟悉的话,你一定知道这个类是怎么用的。是的,我们需要定义一个类来继承它即可。继承之后,我们需要提供三方面信息,一个是用于注册到根容器中的类,一个是用于注册到Servlet容器中的类,一个是核心Servlet的映射URL。
注意,这里说的容器指的是Spring的ApplicationContext这个容器,其中根容器和Servlet容器是父子关系,且在SpringMVC中核心Servlet映射的URL必须是"/"。

另外引申一下:

servlet3.0不再使用web.xml配置文件启动容器,取而代之的一个实现了AbstractAnnotationConfigDispatcherServletInitializer的类,实现3个方法:getRootConfigClasses、getServletConfigClasses、getServletMappings。
3个方法的作用:
(1)getRootConfigClasse:获取根容器的配置类(Spring配置文件)父容器。
(2)getServletConfigClasses:获取web容器的配置类(Spring mvc配置文件)创建子容器。
(3)getServletMappings:获取DispatcherServlet的映射信息。

贴个代码示例:

//SpringMVC只扫描controller组件
@ComponentScan(value = "org.kuku",
        includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class),
useDefaultFilters = false) //禁用默认的过虑规则
@EnableWebMvc
public class MvcConfig {

}
// Spring的容器不扫描controller组件
// 标识配置类
@ComponentScan(value = "org.kuku", excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)) // 配置包扫描Control以外的组件
@PropertySource("classpath:db.properties") // 读取db.properties配置文件
@MapperScan("org.kuku.mapper") // 配置mapper接口包扫描
@EnableAspectJAutoProxy // 开启AOP注解模式
@EnableTransactionManagement // 开启spring事务管理
public class SpringConfig {
}
// web容器启动的时候就会创建对象 调用方法 初始化容器 以及前端控制器
public class MyWebappInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    // 获取根容器的配置类(Spring配置文件)父容器
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    // 获取web容器的配置类(Spring mvc配置文件)创建子容器
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{MvcConfig.class};
    }

    // 获取DispatcherServlet的映射信息
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

这就是以编程的方式来完成SpringMVC的启动。我们自己定义的这个类就是前文提到的"感兴趣"的类。
这个类是不用(或不能)向Spring容器注册的,因为这个类是感兴趣的类,所以Tomcat会从jar包里把它找出来,这样SpringMVC就拿到了我们定义的这个类。
其实最主要的是这时候根本还没有Spring容器呢,因为Spring容器就是在这个类里才创建出来的。
整个启动过程要做的事情:
(1)创建根容器。

(2)然后把根容器放入ServletContext中,因为ServletContext在应用运行期间一直存在,所以根容器是一个全局性的,也一直存在。

(3)接着创建Servlet容器,容器类也是基于注解的,和根容器类是一样的。

(4)然后使用Servlet容器去创建核心Servlet。

(5)接着把核心Servlet注册到ServletContext中。

(6)接着再注册一些过滤器。

4  现代的启动过程

当SpringMVC遇上SpringBoot后,确实方便了开发人员,那是因为SpringBoot把所有的代码都写好了,并通过自动配置来应用这些代码。因为是基于SpringBoot的,所以就要按照SpringBoot的Style了,一切都要从入口类SpringApplication说起。
首先创建容器:

这个容器类的名称是:AnnotationConfigServletWebServerApplicationContext,它是SpringBoot为自己专门“量身打造”的,与之前相比这个容器类最大的不同就是,它要去创建和启动Tomcat。
容器刷新时创建并启动Tomcat:

跟进去我们看下创建,通过工厂方式来创建:

这里有个小细节,看getWebServer里的方法,getSelfInitializer() 方法获取的是 ServletContextInitializer也就是ServletContext初始化器类型的,

然后我们继续看Tomcat的创建:

跟进去prepareContext方法:

我们继续看configureContext方法,这里边有很重要的一步就是设置桥梁接口:

继而Tomcat启动的时候就会执行到TomcatStarter,遍历执行每个ServletContext初始化器,也就是会执行到下边的selfInitialize,在启动的过程中,要把ServletContext对象从Tomcat里带出来,因为它是由Tomcat创建出来的。在创建Tomcat时,实际是把Spring容器实例自己本身传进去了,然后通过一个方法参数带了出来。如下方法:

然后再把Spring容器放入ServletContext中,同时ServletContext被保存到Spring容器实例的一个字段中,方便后续使用如下图:

我们知道Springmvc核心就是一个servlet,那servlet怎么放进tomcat的servletContext里的呢,我们继续看selfInitialize方法里的:

5  现代方式 VS 传统方式

相同点:
都需要创建Spring容器
都需要把该容器放入ServletContext中
都需要把核心Servlet注册到ServletContext中。

不同点:
传统方式可以创建两个Spring容器,现代方式只有一个容器。
传统方式是Tomcat先启动,然后带动Spring容器的创建,现代方式是容器先创建,在刷新时再带动Tomcat的启动。

重要点:
虽然启动Tomcat的时机和方式不同,但是把ServletContext从Tomcat里取出来的方式是一样的,都要用到上一篇提到的“桥梁”接口。
它是用来触发一些初始化工作,主要就是注册核心Servlet到ServletContext里:

给开发人员剩下些什么呢?在一般不复杂的情况下,就是剩一个application.yml配置文件了。开发人员在里面配置就可以了,这些配置项肯定都是提前预设好的,在启动时会去读取这些配置值,并应用在初始化中,

框架越来越完善和智能,留给普通开发人员的工作几乎只剩CRUD了,努力吧,不然就真的只会这些了,好心酸。

6  小结

本文我们对比了下传统方式和现在启动方式的区别,有理解不对的地方欢迎指正哈。

posted @ 2023-04-17 18:05  酷酷-  阅读(437)  评论(0编辑  收藏  举报