赞
踩
前言: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部分
堆栈信息如下
这里最重要的一个类是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,容器启动的时候执行类这个方法。这时候我们可以在这里打个断点,重新启动一下堆栈信息如下
根据堆栈信息,可以看出,在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包下面的组件
在得到request之后,tomcat会处理一个过滤链org.apache.catalina.core.ApplicationFilterChain,用于统一处理,拦截请求。
这个流程比较简单。与传统tomcat是一样的。但是sping为我们默认添加链很多的Filter去处理业务。最终我们拿到HttpServletRequest和HttpServletResponse
这里用到的组件是tomcat的apache包下面的组件和spring 的filter
分发请求过程用到的第一个类是org.springframework.web.servlet.DispatcherServlet 继承了Servlet类
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中有什么
- 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类来配置。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。