看不懂你打我全网最全的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 方法。这块比较难理解,所以我这里只介绍前后端分离和常用注解的情况,我尽量从顶向下的方式去说明源码。
映射器的源码分析
面经简略版
- 在ioc初始化时,映射器也会初始化,就把映射对应关系放在一个map里
- 映射器的 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请求流程解析不看就亏了的主要内容,如果未能解决你的问题,请参考以下文章