文章目录
- 一、SpringMVC
- 二、Web容器选择
- 2.1 代码依赖
- 2.2 ServletWebServerFactoryConfiguration
- 三、DispatcherServlet初始化
- 3.1 装配过程
- 3.2 加载到Servlet上下文
- 3.2.1 回调TomcatStarter的onStartup
- 3.2.2 执行初始化器
- 3.3 执行init方法
一、SpringMVC
先解释下MVC,MVC是一种WEB开发的分层思想。
MVC分层:
- Model:提供数据读写操作的业务模型
- View:面向用户的视图层,根据处理后的模型数据渲染可视化页面
- Controller:用于接收用户请求和操作数据的控制层
SpringMVC是Spring框架里的一个模块,实现了MVC这一分层思想。SpringMVC分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让它们更容易进行定制。下面看下在SpringBoot中,MVC是怎么实现的
二、Web容器选择
我们知道前端提交的Http请求,需要Web容器进行处理,例如Nginx、Apache。但是他们只能处理静态资源的请求,如果要把动态数据从数据库取出来返回,就不行了。Java规定处理请求Http请求需要遵循Servlet规范,Servlet容器就是Servlet规范的实现。请求流程大致如下图
SpringBoot支持Tomcat、Jetty、Undertow三种Servlet容器,它们同时具备Web容器的功能,因此他们也是Web容器。那么哪个才是SpringBoot真正使用的Web容器呢?
2.1 代码依赖
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 版本号继承至spring-boot-starter-parent,spring-boot-dependencies -->
</dependency>
在pom.xml中引入spring-boot-starter-web的依赖,SpringBoot就好自动帮我们装配好WebMVC需要的Bean,最重要的就是DispatcherServlet。在SpringMVC单独使用时,需要手动配置一个DispatcherServlet,它实现了Servlet接口,就是一个Servlet。
2.2 ServletWebServerFactoryConfiguration
run的启动流程中讲过,Web容器的启动发生在,Spring容器启动流程中的onRefresh()方法。onRefresh会调用createWebServer(),getWebServerFactory从容器中取出ServletWebServerFactory,它是通过配置类ServletWebServerFactoryConfiguration被容器加载的。ServletWebServerFactory最终产生Web容器
SpringBoot的自动装配读取的是spring.factories里面的配置类,看下spring-boot-autoconfigure的该文件里面
没有ServletWebServerFactoryConfiguration,但是有一个ServletWebServerFactoryAutoConfiguration
在ServletWebServerFactoryAutoConfiguration上Import了ServletWebServerFactoryConfiguration的三个内部类。先看下EmbeddedTomcat
它也是个配置类同样会被自动装配,但是它有个条件,需要Tomcat相关的类,才产生TomcatServletWebServerFactory,它实现了ServletWebServerFactory接口。也就是说,如果引入了Tomcat相关的依赖,前面提到的getWebServerFactory就能获取到TomcatServletWebServerFactory,用它来产生一个Web容器。
再看下EmbeddedJetty
和EmbeddedTomcat一样的原理,但是这块没有依赖Jetty相关的包,所以报红了,也就不会产生JettyServletWebServerFactory。EmbeddedUndertow也是一样的,就不看了。那么tomcat相关的依赖在哪呢?
就是spring-boot-starter-web默认依赖了tomcat。如果想改用jetty,只需添加jetty的依赖,并将tomcat的依赖排除。如果两个依赖都存在会发生什么呢?进入getWebServerFactory
只要超过一个ServletWebServerFactory,就会报错。因此,得到最终结论:SpringBoot选择Tomcat作为默认的Web容器,同时也是Servlet容器。
三、DispatcherServlet初始化
Tomcat启动会加载所有的Servlet(包含DispatcherServlet),第一次请求时调用Servlet的生命周期函数init(),init()只执行一次。DispatcherServlet会控制整个请求的处理,所以又叫做前端控制器。先看看它的装配过程
3.1 装配过程
DispatcherServletAutoConfiguration进行了DispatcherServlet、DispatcherServletRegistrationBean的装配,DispatcherServletRegistrationBean封装了DispatcherServlet。dispatcherServlet到这里只是添加到了Spring容器,怎么添加到Servlet容器呢?
3.2 加载到Servlet上下文
SpringBoot是基于Servlet3.1规范的,那么在这个规范中指出:如果要添加一个Servlet,不光可以在web.xml中进行配置,还可以通过代码的方式servletContext.addServlet(name, this.servlet);
。
3.2.1 回调TomcatStarter的onStartup
再看Tomcat的启动过程,进入createWebServer
这行首先通过getSelfInitializer()得到一个初始化器
这是一个lambda表达式,调用selfInitialize方法,这块的封装回调函数也可以借鉴
lambda表达式实现的是ServletContextInitializer的onStartup,因此在调用onStartup方法时就会进入执行selfInitialize。回到createWebServer,进入factory.getWebServer();
initializers就是前面的初始化器,看看它是怎么封装的,进入prepareContext(tomcat.getHost(), initializers);
在TomcatServletWebServerFactory的prepareContext方法里面,先创建TomcatEmbeddedContext的实例context,context封装进了host对象。再处理初始化器。进入configureContext
在这里先将初始化器封装到启动器TomcatStarter的实例starter里面,再将启动器封装到了context。可以看到初始化器经过层层封装是进入了tomcat对象。接着一直跳过,返回到getWebServer,跟进去
getTomcatWebServer(tomcat);–>return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());–>initialize();–>this.tomcat.start();
tomcat.start()里面是tomcat的源码就不看了,他里面维护了线程池,在子线程中启动tomcat。启动过程中,会回调TomcatStarter的onStartup方法。前面提到的TomcatStarter也是被封装进入了tomcat。我们直接进入onStartup方法
看这个调用链,说明tomcat在子线程中回调了TomcatStarter的onStartup方法。
3.2.2 执行初始化器
看名称就知道第三个lambda就是前面ServletWebServerApplicationContext的getSelfInitializer返回的。循环之后进入到了lambda实现方法selfInitialize里
可以看到getServletContextInitializerBeans()返回了DispatcherServletRegistrationBean,他里面封装了DispatcherServlet
进入
beans.onStartup(servletContext);–>register(description, servletContext);–>registration = addRegistration(description, servletContext);
DispatcherServlet被添加进Servlet上下文。
3.3 执行init方法
写一个HelloController
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello!";
}
}
在浏览器访问http://localhost:8080/hello,进入init方法
这个方法在DispatcherServlet的父类HttpServletBean里面,进入initServletBean();initServletBean()是模板方法,进入到子类FrameworkServlet的实现
initServletBean()–>initWebApplicationContext()–>onRefresh(wac);
onRefresh是一个模板方法,进入子类DispatcherServlet
执行完initStrategies,就完成了DispatcherServlet的初始化。具体细节自行研究