前言:spring mvc 是当前最为流行的一种java WEB 框架。在还没有spring boot以前,通常搭配tomcat等容器进行web项目的开发。而现在spring全家桶越来越完善。慢慢脱离来用容器来启动web项目。那么spring boot 搭配spring mvc的原理是什么。spring是怎么将url映射的具体的controller的。接下来,通过debug 方式一步步的去分析原理。

    spring boot提供来一种自动配置的方式来创建容器上下文,当创建web项目时添加spring-boot-starter-web来开启spring web的自动配置。当添加这个依赖,我们的依赖树如下:

 从上图中,我们可以看出,sping-boot-starter-web需要依赖

    1.spring-boot-starter 启动spring boot自动化配置

    2.hibernate-validator 提供一套spring mvc 参数校验的机制

    3.spring-mvc spring mvc核心组建

    4.spring-boot-starter-tomcat 提供内置的tomcat容器

    5.jackson-databind 在restfull的接口时惊醒对象和json的互转

通过上述依赖便可以创建一个web项目。

我们知道,spring项目核心思维就是控制反转,就是spring创建来一个上下文去管理容器中的对象或组件。

首先,先启动一个spring boot web项目。这个项目中只包含一个类

@RestController
@SpringBootApplication
@RequestMapping("/")
public class SpringbootwebApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootwebApplication.class, args);
    }
    @Resource
    private ApplicationContext applicationContext;
    @RequestMapping("hello")
    public String hello(){

        return "success";
    }
}

在application.properties中指定项目路径

server.servlet.context-path=/springboottest

启动成功用postman访问路径127.0.0.1:8080/springboottest/hello 。如果在return的时候打个断点,则我们可以看到debug工具中一下堆栈信息。我分为3部分

1.第一部分:接收请求

堆栈信息如下

接收请求
图1.接收请求

这里最重要的一个类是org.apache.tomcat.util.net.NioEndpoint。这里自行研究源码可以看出这其实是NIO的一个封装,

方法org.apache.tomcat.util.net.NioEndpoint#initServerSocket就是开启类一个NIO的ServerSocketChannel,并绑定对应地址,端口号。

protected void initServerSocket() throws Exception {
        if (!getUseInheritedChannel()) {
            serverSock = ServerSocketChannel.open();
            socketProperties.setProperties(serverSock.socket());
            InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
            serverSock.socket().bind(addr,getAcceptCount());
        } else {
            // Retrieve the channel provided by the OS
            Channel ic = System.inheritedChannel();
            if (ic instanceof ServerSocketChannel) {
                serverSock = (ServerSocketChannel) ic;
            }
            if (serverSock == null) {
                throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
            }
        }
        serverSock.configureBlocking(true); //mimic APR behavior
    }

预估是sping,容器启动的时候执行类这个方法。这时候我们可以在这里打个断点,重新启动一下堆栈信息如下

 

NioEndpoint 启动过程
图2.NioEndpoint启动过程

 

根据堆栈信息,可以看出,在spring boot容器启动的时候,会同时启动org.springframework.boot.web.embedded.tomcat.TomcatWebServer,在tomcat中去启动NioEndpoint 开起监听。

监听到的客户端请求,这里需要了解一下7层网络协议的概念,socket是一种对传输层协议TCP/IP的封装。而http是更加面向用户的协议,著名的三次握手最终形成一个http请求。从图1.接收请求看出请求会经过一系列的Processor最终在org.apache.coyote.http11.Http11Processor中最终生成servlet的request和response。这个方法代码很多,就不复制上来类,主要的作用就是处理响应的一些状态如503,400,500.同时生成request和response,并封装超时时间。

在得到request和response之后后面的几个类是处理http的一些通用的请求头,或者响应头信息这里不说说明类,可以自己去看看。

可以看出,这里从监听到请求到形成http请求需要用的组件都是tomcat的apache包下面的组件

2.过滤链

在得到request之后,tomcat会处理一个过滤链org.apache.catalina.core.ApplicationFilterChain,用于统一处理,拦截请求。

图3.过滤链

 

这个流程比较简单。与传统tomcat是一样的。但是sping为我们默认添加链很多的Filter去处理业务。最终我们拿到HttpServletRequest和HttpServletResponse

这里用到的组件是tomcat的apache包下面的组件和spring 的filter

3.分发请求

图3.分发请求

分发请求过程用到的第一个类是org.springframework.web.servlet.DispatcherServlet 继承了Servlet类

图4.​DispatcherServlet结构图

 

DispatcherServlet的作用就是分发请求到对应的controller 。

引用源码的注释

Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers
 or HTTP-based remote service exporters. Dispatches to registered handlers for processing
 a web request, providing convenient mapping and exception handling facilities.

百度翻译:

用于HTTP请求处理程序/控制器的中央调度器,例如用于WebUI控制器或者基于HTTP的远程服务导出器。发送给已注册处理程序以进行处理一个web请求,提供方便的映射和异常处理设施。

同时DispatcherServlet继承类Servlet,被ApplicationFilterChain持有。从tomcat完全过渡到spring web。

请求在dispatcherServlet最终被执行到org.springframework.web.servlet.DispatcherServlet#doService的方法。然后调用org.springframework.web.servlet.DispatcherServlet#doDispatch进行分发。

我们来看一下dispatcher中有什么

图5.dispatcherServlet 主要对象
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			/**
            *****省略****
                **/
	}

再结合doDispatch源码。发现dispatcherServlet中维护来一系列的HandlerMapping.而我们controller中申明的requestMapping带在org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping这个类中维护。doDispatch的工作就是在根据request中的url匹配到最合适的handler。这个handler就只对应controller的HandlerMethod。封装在HandlerExecutionChain的handler属性。同时handler还处理来拦截器对应的工作

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);

 

applyPreHandle和applyPostHandle,例如applyPreHandle就是HandlerInterceptor的前置处理

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = 0; i < interceptors.length; i++) {
				HandlerInterceptor interceptor = interceptors[i];
				if (!interceptor.preHandle(request, response, this.handler)) {
					triggerAfterCompletion(request, response, null);
					return false;
				}
				this.interceptorIndex = i;
			}
		}
		return true;
	}

这个通过抓包很容易知道。除了HandlerMethod。spring还提供了HandlerAdapter机制去处理请求。这里我们最主要关注RequestMappingHandlerAdapter这个,因为它是是一般用来处理HandlerMethod的。

从图3可知请求会经过org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal

在这个栈桢,以及后面的几个分别做来session校验,参数处理(argumentResolvers),数据绑定,通过java放射执行HandlerMethod中的方法,返回参数处理(returnValueHandlers),请求缓存等。

 

最终请求到来Controller中。

这里用到的组件是spring的mvc包下面的组件和

以上就是完整的spring boot + spring mvc 请求的流程。最好的学习方式就是看源码,通过debug进行查看每一个流程。并有选择性的关注某几个具体流程。spring 还为我们做来很多事情。例如跨域CORS ,session管理等。其中一些配置可以实现org.springframework.web.servlet.config.annotation.WebMvcConfigurer类来配置。

 

 

 

 

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐