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的解析等,我才疏学浅就不深究了。如果上面有什么写的不对,请评论告诉我。