看不懂你打我全网最全的spring dispatcher请求流程解析不看就亏了

Posted Java分享家

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了看不懂你打我全网最全的spring dispatcher请求流程解析不看就亏了相关的知识,希望对你有一定的参考价值。

servlet

在了解dispatcherServlet的流程前,首先要了解servlet这个技术。

狭义的来说,servlet只是一个接口,广义的来说,它是一个规范。

在传统的Javaweb项目里,通过继承HttpServlet(实现了service接口)重写doGet,doPost方法,再在web.xml里标识。通过tomcat作为servlet容器来管理这些servlet。

tomcat在不考虑io模型通信情况下, 只有两个主要的功能。

  • 封装request,response对象
  • 调用servlet的service方法

这个是理解dispatcher流程的关键,首先要知道tomcat到底是干什么的

我们可以看下HttpServlet的service方法的源码

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }
复制代码

也就是说传统的servlet的项目中,tomcat把请求封装后根据url调用对应路径的servlet,执行service方法,再判断请求类型去调用doGet(),doPost()等。

dispatcherServlet和servlet的关系

dispatcherServlet依赖关系很复杂,简单来说就是他间接的实现了servlet接口。先看下servlet接口的方法

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}
复制代码

我们一会重点说下init和service方法。

对于springmvc来说,需要在web.xml里配置dispatcherServlet,让tomcat去管理。对于spring的项目,tomcat只需要管理spring自带的servlet就可以了,我们不需要去写servlet,我们是通过bean让spring容器去管理。这里的spring容器和tomcat容器不是一个概念。

springboot基于自动配置不需要去手动配置。

根据上面所说,任何路径下的请求,tomcat都会调用spring的dispatcherServlet的service方法去处理,由于依赖关系复杂,service方法是在dispatcher的父类。总结来说, 最后会调用dispatch的doDispatch方法 ,中间会有些过程,这里就忽略不计了。

流程

丑陋的面经只会甩给你一张图

硬背这种八股文,完全不知道他的理念和实现细节,对我们的思想是没有一点好处的。所以要剖析下他的源码。

初始化

首先要对spring容器初始化刷新容器,才能进行交互。对于springmvc来说,tomcat会先执行dispatcher的init方法(其实是HttpServletBean的init方法)来刷新容器,底层就是调用的refresh方法。对于springboot来说,会执行main方法的run方法,里面执行一个refresh方法来刷新容器。虽然仍然会执行init方法,但里面的refresh方法应该是不会再执行的。(虽然不知道springboot为什么可以不让执行,但根据打断点来说,他确实没有执行)。总之殊途同归。

请求流程

我再把之前的捋一遍,请求过来后,先被tomcat封装request,response对象,再调用service方法,即dispatchServlet的doDispatch方法,直接看源码

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);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}
复制代码

这么看复杂的一比,实际上对比丑陋面经的图片,只有几个方法比较重要。

//从Handler映射器获取handlerchain
mappedHandler = getHandler(processedRequest);
//获取适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//执行handler,获取ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//判断mv中的视图是否为空
applyDefaultViewName(processedRequest, mv);
//视图解析
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
复制代码

感觉这应该是spring中最容易理解的源码了,我们逐条分析。

映射器

首先是getHandler()方法

protected HandlerExecutionChain getHandler(HttpServletRequest request) {
        //this.handlerMappings就是一个HandlerMapping数组,Spring容器启动后,会将所有定义好的映射器对象存放在这个数组里
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}
复制代码

这个理解起来很简单,由于有多种的Handler,就有多种的mapping,需要找到正确的mapping

  • RequestMappingUrlHandlerMapping 最常见的使用注解标识的handler映射
  • BeanNameUrlHandlerMapping
  • SimpleUrlHandlerMapping
  • ControllerClassNameHandlerMapping

其中第一个最为常见,使用@requestMapping注解标识的handlermapping,其他的有继承controller,httpservlet的,就不多说了。

通过循环,直到找到正确的处理器映射器,获取到handle,实际上是一个HandlerExecutionChain,他的构成其实就是拦截器数组+handler。

适配器

上一步获取到了处理器链后,获取适配器,通过getHandlerAdapter方法,参数为handler(目前拦截器数组还没有用)

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
复制代码

这里就用上了大名鼎鼎的 适配器模式 了

如果不使用适配器,以后再增加handle种类时,由于不同handle之间的逻辑完全不一样,需要通过在dispatch中使用if else的方式去判断handle的种类,再执行操作。

protected HandlerAdapter getHandlerAdapter(Object handler) {
		if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		};
	}
复制代码

这里的this.handlerAdapters是获取适配器数组,跟上面有点像。获取到合适的适配器返回。

适配器执行handle方法

适配器使用handle方法执行

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
复制代码

最后得到了一个modelandview。在古老的jsp时代,是通过modelandview传递数据并解析jsp视图的。但由于现在前后端分离,使用@ResponseBody注释只传送json串,所以这里的modelandview为空。

所以接下来的视图解析部分我们就不关注了

拦截器

所以handerchain里的拦截器作用是什么呢。

其实在适配器执行handle方法 前后 会进行一个拦截器的处理,只是我没有写而已。

最后

tomcat的处理,我就不说了

小结

springmvc这块的流程如果不深挖其实很简单,基本上有过开发经验的都能够大概了解这块的流程。但其实我上面省略了一部分,就是适配器的 handle 方法和映射器的 getHandler 方法。这块比较难理解,所以我这里只介绍前后端分离和常用注解的情况,我尽量从顶向下的方式去说明源码。

映射器的源码分析

面经简略版

  1. 在ioc初始化时,映射器也会初始化,就把映射对应关系放在一个map里
  2. 映射器的 getHandler 方法,通过url的后缀,从map中获取到对应的handler

详细版

先说下HandlerMapping的继承关系

实际上最常用的就是
RequestMappingHandlerMapping ,他对应的就是@Controller @RequestMapping这种写法的handle映射器。我这里就只介绍这一个映射器了。

我们直接从请求流程来看

首先先是在dispatcher里循环映射器列表,调用getHandler方法(上面有写),实际就是调用AbstractHandlerMapping的getHandler方法

AbstractHandlerMapping

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		//省略版
		Object handler = getHandlerInternal(request);
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		return executionChain;
	}
复制代码

这个getHandlerInternal()返回的就是我们需要的handle,点进去会调用
AbstractHandlerMethodMapping 的方法

getHandlerInternal()

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    //获取url,例如/user/login
    String lookupPath = initLookupPath(request);
    //获取HandlerMethod,核心方法
    HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
    return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
复制代码

先看返回值,返回了一个 HandlerMethod ,这个就是我们的处理器。之前说过,springmvc有多种handler,每个handle的处理逻辑都不一样,通过不同映射器获得的handler都不同,之后需要通过适配器来统一规范执行。这个后面再说,总之 HandlerMethod 这个对象很重要。

HandlerMethod类

看下HandlerMethod类的构造

//省略了很多
public class HandlerMethod {

   private final Object bean;

   private final Method method;
 }
复制代码

就介绍下这两个成员变量吧。bean就是controller的bean,Method是映射到controller的方法,Method不懂的话可以去看下反射的原理。总之在后面适配器那里会执行method的invoke方法,也就是执行controller上映射的方法。

我们在返回刚才的方法,看下核心方法 lookupHandlerMethod

lookupHandlerMethod()

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List<Match> matches = new ArrayList<>();
		//重点
		List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
		addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
		Match bestMatch = matches.get(0);
		handleMatch(bestMatch.mapping, lookupPath, request);
		return bestMatch.handlerMethod;
	}
复制代码

这个代码我进行了疯狂的省略,把一些多映射或者空的情况删掉了,我们先去理解正常代码的一个流程。

我们来分析下第二行重点代码,this. mappingRegistry .getMappingsByDirectPath(lookupPath),我们首先要看这个 mappingRegistry 是什么。

mappingRegistry类

class MappingRegistry {

   private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

   private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();

   private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

   private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();

   private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
}
复制代码

mappingRegistry是一个内部类,里面都是一堆map。这个getMappingsByDirectPath方法只是从 pathLookup 这个map里,以lookupPath(url,例如/user/login)为key获取值而已,这个值就是我们的handler。

这时候问题就来了,这些map是什么时候获取的内容呢

在springioc容器初始化时,映射器也会初始化,就把映射对应关系放在pathLookup这个map里。具体流程就跟容器初始化有关了,我就不详细叙述了。

小结

可以发现,对于这种请求来说,根本就没用到
RequestMappingHandlerMapping的方法,都是他的父类AbstractHandlerMapping的方法。之后的流程就很简单了,只是把handlerMethod封装成handerchain,返回了。

适配器的源码分析

面经简略版

  • 调用 RequestMappingHandlerAdapter 的handle方法,实际是通过反射调用controller的方法,并返回值
  • 根据返回值选择特定的返回值解析器(例如使用@Response注解返回的是对象,则使用RequestResponseBodyMethodProcessor,将对象序列化为json)
  • 最后返回的mv为null,跳过了视图解析器。

详细版

HandlerAdapter

上面流程的模块介绍了适配器有很多种,他们都实现了HandleAdapter接口,看下接口的源码

public interface HandlerAdapter {
  boolean supports(Object handler);

  ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
}

复制代码

同样省略,重要的只有这两个方法。

第一个方法在上文循环判断 是否合适 时调用过,第二个方法就是dispatcher里的执行方法,返回一个modelAndView。这么一看很简单吗,所以我们来看下他的实现类。

由于handle种类很多,就会对应了很多的适配器

我们平常@Controller出来的 HandlerMethod 使用的是
RequestMappingHandlerAdapter ,是这里最复杂的。为什么复杂呢,因为它涉及了很多的url映射,参数和返回值的处理。这里我先举一个简单的例子,
SimpleServletHandlerAdapter,servlet类型的handler适配器。

SimpleServletHandlerAdapter

public class SimpleServletHandlerAdapter implements HandlerAdapter {
   public boolean supports(Object handler) {
      return (handler instanceof Servlet);
   }

   public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
      ((Servlet) handler).service(request, response);
      return null;
   }
}
复制代码

这里先说明一点,spring也是可以使用servlet规范的写法的,例如继承httpservlet,只是还会走mvc的流程,因为这种servlet不归tomcat管理,而是作为一个bean被spring容器管理。

可以看到这个实现类和他的名字一样简单。supports只是判断了handler是否是servlet类型,handle只是调用了service。

AbstractHandlerMethodAdapter

//简化版
public abstract class AbstractHandlerMethodAdapter implements HandlerAdapter {
    public final boolean supports(Object handler) {
        return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
    }

    protected abstract boolean supportsInternal(HandlerMethod handlerMethod);

    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler){
		return handleInternal(request, response, (HandlerMethod) handler);
	}

    protected abstract ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;
}
复制代码

先看
RequestMappingHandlerAdapter的抽象类。

  • supports判断类型是否是 HandlerMethod ,除此之外还有个新方法supportsInternal,但是对于RequestMappingHandlerAdapter来说,这个方法一直返回true。所以我们可以认为, 当一个Handler是HandlerMethod 类型的时候, 就会被这个适配器处理。这里的重点就是 HandlerMethod ,他代表着我们平常使用的handler类型
  • handle调用新方法handleInternal,作为参数的handler被强转为 HandlerMethod 类型

handle方法流程

刚才简单介绍下父类适配器的接口,接下来看流程

上面说到,dispatcher会先循环获取对应的handleAdapeter,通过的就是supports方法,我就忽略掉了。

然后会调用handle方法,其实就是
RequestMappingHandlerAdapter的 handleInternal 方法

RequestMappingHandlerAdapter

handleInternal()

protected ModelAndView handleInternal(){
    ModelAndView mav;
    mav = invokeHandlerMethod(request, response, handlerMethod);
    return mav;
}
复制代码

在抽象的省略后,只剩下invokeHandlerMethod方法

invokeHandlerMethod()

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
      HttpServletResponse response, HandlerMethod handlerMethod) {
    
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        //ServletInvocableHandlerMethod是HandlerMethod的子类
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        //核心
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        return getModelAndView(mavContainer, modelFactory, webRequest);
}
复制代码

这里有大量异步和mavcontainer的部分我都删掉了,没意义的同时也是因为我看不懂,我就不分析了。

这块代码核心就是
invocableMethod.invokeAndHandle(webRequest, mavContainer),分析下参数。

  • webRequest request的封装
  • mavcontainer在前后端分离情况都是为空的。

HandlerMethod子类

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
        //获得返回值,providedArgs为参数
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        //返回值解析
		this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	}
复制代码
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {
	//参数解析器,例如@RequestBody,对象,变量之类的,对参数解析之后放入args数组,
   Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
   return doInvoke(args);
}
复制代码
protected Object doInvoke(Object... args) throws Exception {
        //获取method,这个method就是controller对应url的方法
		Method method = getBridgedMethod();
        //反射调用controller的方法
		return method.invoke(getBean(), args);
	}
复制代码

这是请求到controller之前的最后一步,这是InvocableHandlerMethod的 doInvoke 方法。这里只进行了一个简单的反射而已。获取到Method对象后调用invoke方法。也就是执行controller下映射url的方法,我这里举一个例子

@GetMapping
public ApiMsg get(@RequestParam(value = "current", required = false, defaultValue = "1") int current,
                  @RequestParam(value = "size", required = false, defaultValue = "10") int size){
    return adminReportService.selectAllReport(current,size);
}
复制代码

一个极其标准的controller,返回的是一个Object对象

对于返回值和参数值的解析我就不研究了。

小结

对于前后端分离传递json这种写法,modelandview都为空,都是直接对返回值处理的,我就没有关注这个modelandview的处理。

总结

作为最常使用的mvc,应该是对于我们这种web开发来说最容易理解的spring源码了。里面还有很多的学问,包括对不同handler的处理或者不同类之间的区别,jsp的解析等,我才疏学浅就不深究了。如果上面有什么写得不对,请评论告诉我。

以上是关于看不懂你打我全网最全的spring dispatcher请求流程解析不看就亏了的主要内容,如果未能解决你的问题,请参考以下文章

Git的简介和安装过程,看不懂你打我

Innodb的RR到底有没有解决幻读?看不懂你打我!

告别动态规划,连刷 40 道题,我总结了这些套路,看不懂你打我(万字长文)

大端模式小端模式详解(不懂你打我,略略~~)

这次让你彻底学会redis中跳表原理,不懂你打我

面试必问的 volatile 关键字,通俗易懂,看完还不懂你打我。