SpringMVC执行过程解析

2020-04-03 16:08:12来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

SpringMVC执行过程解析

         做过java开发的人都知道Spring,就算目前不知道,慢慢也会知道,由于spring体系庞大,模块众多,我就介绍下业务开发(以多年经历的认识,90%的人都是做业务开发的)时用到的SpringMVC。

        

首先,让我们从 Spring MVC 的四大组件:前端控制器(DispatcherServlet)、处理器映射器(HandlerMapping)、处理器适配器(HandlerAdapter)以及视图解析器(ViewResolver) 的角度来看一下 Spring MVC 对用户请求的处理过程,

SpringMVC 执行过程

  1. 用户请求发送到前端控制器 DispatcherServlet
  2. 前端控制器 DispatcherServlet 接收到请求后,DispatcherServlet 会使用 HandlerMapping 来处理,HandlerMapping 会查找到具体进行处理请求的 Handler 对象
  3. HandlerMapping 找到对应的 Handler 之后,并不是返回一个 Handler 原始对象,而是一个 Handler 执行链(HandlerExecutionChain),在这个执行链中包括了拦截器和处理请求的 Handler。HandlerMapping 返回一个执行链给 DispatcherServlet。
  4. DispatcherServlet 接收到执行链之后,会调用 Handler 适配器去执行 Handler
  5. HandlerAdapter执行完成 Handler之后会得到一个 ModelAndView,并返回给 DispatcherServlet。
  6. DispatcherServlet 接收到 HandlerAdapter 返回的 ModelAndView 之后,会根据其中的视图名调用 ViewResolver。
  7. ViewResolver 根据逻辑视图名解析成一个真正的 View 视图,并返回给 DispatcherServlet。
  8. DispatcherServlet 接收到视图之后,会根据上面的 ModelAndView 中的 model 来进行视图中数据的填充,也就是所谓的视图渲染
  9. 渲染完成之后,DispatcherServlet 就可以将结果返回给用户了。

在了解了大概的执行过程后,让我们一起从源码https://github.com/spring-projects/spring-framework/角度去深入探索。

源码解析

首先当我们访问url的时候,将会把请求发送到前端控制器 DispatcherServlet,DispatcherServlet 是一个 Servlet,我们知道在 Servlet 在处理一个请求的时候会交给 service 方法进行处理,这里也不例外,DispatcherServlet 继承了 FrameworkServlet,首先进入 FrameworkServlet 的 service 方法:

 1 protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {    
 2  // 请求方法   
 3  HttpMethod httpMethod =HttpMethod.resolve(request.getMethod());   
 4  // 若方法为 PATCH 方法或为空则单独处理   
 5  if (httpMethod == HttpMethod.PATCH || httpMethod == null) {        
 6      processRequest(request, response);   
 7  }   else {
 8     // 其他的请求类型的方法经由父类,也就是 HttpServlet 处理        
 9      super.service(request, response);    
10 }
11 }

HttpServlet 中会根据请求类型的不同分别调用 doGet 或者 doPost 等方法,FrameworkServlet 中已经重写了这些方法,在这些方法中会调用 processRequest 进行处理,在 processRequest 中会调用 doService 方法,这个 doService 方法就是在 DispatcherServlet 中实现的。下面就看下 DispatcherServlet 中的 doService 方法的实现。

DispatcherServlet 收到请求

DispatcherServlet 中的 doService方法:

 1 protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
 2     logRequest(request);
 3     // 给 request 中的属性做一份快照,以便能够恢复原始属性
 4     Map<String, Object> attributesSnapshot = null;
 5     if (WebUtils.isIncludeRequest(request)) {
 6         attributesSnapshot = new HashMap<>();
 7         Enumeration<?> attrNames = request.getAttributeNames();
 8         while (attrNames.hasMoreElements()) {
 9             String attrName = (String) attrNames.nextElement();
10             if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
11                 attributesSnapshot.put(attrName, request.getAttribute(attrName));
12             }
13         }
14     }
15     // 如果没有配置本地化或者主题的处理器之类的,SpringMVC 会使用默认的配置文件,即 DispatcherServlet.properties
16     request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
17     request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
18     request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
19     request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
20     if (this.flashMapManager != null) {
21         FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
22         if (inputFlashMap != null) {
23             request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
24         }
25         request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
26         request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
27     }
28 
29     try {
30         // 开始真正的处理
31         doDispatch(request, response);
32     }
33     finally {
34         if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
35             // 恢复原始属性快照
36             if (attributesSnapshot != null) {
37                 restoreAttributesAfterInclude(request, attributesSnapshot);
38             }
39         }
40     }
41 }
接下来 DispatcherServlet 开始真正的处理,让我们来看下 doDispatch 方法,首先会获取当前请求的 Handler 执行链,然后找到合适的 HandlerAdapter,接着调用 RequestMappingHandlerAdapter 的 handle 方法,如下为 doDispatch 方法:
 1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
 2     HttpServletRequest processedRequest = request;
 3     HandlerExecutionChain mappedHandler = null;
 4     boolean multipartRequestParsed = false;
 5     WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
 6     try {
 7         ModelAndView mv = null;
 8         Exception dispatchException = null;
 9         try {
10             // 先检查是不是 Multipart 类型的,比如上传等;如果是 Multipart 类型的,则转换为 MultipartHttpServletRequest 类型
11             processedRequest = checkMultipart(request);
12             multipartRequestParsed = (processedRequest != request);
13 
14             // 获取当前请求的 Handler 执行链
15             mappedHandler = getHandler(processedRequest);
16             if (mappedHandler == null) {
17                 noHandlerFound(processedRequest, response);
18                 return;
19             }
20 
21             // 获取当前请求的 Handler 适配器
22             HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
23 
24             // 对于 header 中 last-modified 的处理
25             String method = request.getMethod();
26             boolean isGet = "GET".equals(method);
27             if (isGet || "HEAD".equals(method)) {
28                 long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
29                 if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
30                     return;
31                 }
32             }
33 
34             // 遍历所有定义的 interceptor,执行 preHandle 方法
35             if (!mappedHandler.applyPreHandle(processedRequest, response)) {
36                 return;
37             }
38 
39             // 实际调用 Handler 的地方
40             mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
41 
42             if (asyncManager.isConcurrentHandlingStarted()) {
43                 return;
44             }
45             // 处理成默认视图名,也就是添加前缀和后缀等
46             applyDefaultViewName(processedRequest, mv);
47             // 拦截器postHandle方法进行处理
48             mappedHandler.applyPostHandle(processedRequest, response, mv);
49         }
50         catch (Exception ex) {
51             dispatchException = ex;
52         }
53         catch (Throwable err) {
54             dispatchException = new NestedServletException("Handler dispatch failed", err);
55         }
56         // 处理最后的结果,渲染之类的都在这里
57         processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
58     }
59     catch (Exception ex) {
60         triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
61     }
62     catch (Throwable err) {
63         triggerAfterCompletion(processedRequest, response, mappedHandler,
64                 new NestedServletException("Handler processing failed", err));
65     }
66     finally {
67         if (asyncManager.isConcurrentHandlingStarted()) {
68             if (mappedHandler != null) {
69                 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
70             }
71         }
72         else {
73             if (multipartRequestParsed) {
74                 cleanupMultipart(processedRequest);
75             }
76         }
77     }
78 }


查找对应的 Handler 对象

让我们去探索下是如何获取当前请求的 Handler 执行链,对应着这句代码 mappedHandler = getHandler(processedRequest);,看下 DispatcherServlet 具体的 getHandler 方法,该方法主要是遍历所有的 handlerMappings 进行处理,handlerMappings 是在启动的时候预先注册好的,在循环中会调用 AbstractHandlerMapping 类中的 getHandler 方法来获取 Handler 执行链,若获取的 Handler 执行链不为 null,则返回当前请求的 Handler 执行链,DispatcherServlet 类的 getHandler 方法如下:

 1 protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
 2     if (this.handlerMappings != null) {
 3         // 遍历所有的 handlerMappings 进行处理,handlerMappings 是在启动的时候预先注册好的
 4         for (HandlerMapping mapping : this.handlerMappings) {
 5             HandlerExecutionChain handler = mapping.getHandler(request);
 6             if (handler != null) {
 7                 return handler;
 8             }
 9         }
10     }
11     return null;
12 }

 

在循环中,根据 mapping.getHandler(request);,继续往下看 AbstractHandlerMapping 类中的 getHandler 方法
 1 public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
 2     // 根据 request 获取 handler
 3     Object handler = getHandlerInternal(request);
 4     if (handler == null) {
 5         // 如果没有找到就使用默认的 handler
 6         handler = getDefaultHandler();
 7     }
 8     if (handler == null) {
 9         return null;
10     }
11     // 如果 Handler 是 String,表明是一个 bean 名称,需要寻找对应 bean
12     if (handler instanceof String) {
13         String handlerName = (String) handler;
14         handler = obtainApplicationContext().getBean(handlerName);
15     }
16     // 封装 Handler 执行链
17     return getHandlerExecutionChain(handler, request);
18 }
View Code

 


AbstractHandlerMapping 类中的 getHandler 方法中首先根据 requrst 获取 handler,主要是调用了 AbstractHandlerMethodMapping 类中的 getHandlerInternal 方法,该方法首先获取 request 中的 url,即 /testSpringMvc,用来匹配 handler 并封装成 HandlerMethod,然后根据 handlerMethod 中的 bean 来实例化 Handler 并返回。
 1 protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
 2     // 获取 request 中的 url,用来匹配 handler
 3     String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
 4     request.setAttribute(LOOKUP_PATH, lookupPath);
 5     this.mappingRegistry.acquireReadLock();
 6     try {
 7         // 根据路径寻找 Handler,并封装成 HandlerMethod
 8         HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
 9         // 根据 handlerMethod 中的 bean 来实例化 Handler,并添加进 HandlerMethod
10         return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
11     }
12     finally {
13         this.mappingRegistry.releaseReadLock();
14     }
15 }
View Code

接下来,我们看 lookupHandlerMethod 的逻辑,主要逻辑委托给了 mappingRegistry 这个成员变量来处理:
 1 protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
 2     List<Match> matches = new ArrayList<>();
 3     // 通过 lookupPath 属性中查找。如果找到了,就返回对应的RequestMappingInfo
 4     List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
 5     if (directPathMatches != null) {
 6         // 如果匹配到了,检查其他属性是否符合要求,如请求方法,参数,header 等
 7         addMatchingMappings(directPathMatches, matches, request);
 8     }
 9     if (matches.isEmpty()) {
10         // 没有直接匹配到,则遍历所有的处理方法进行通配符匹配
11         addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
12     }
13 
14     if (!matches.isEmpty()) {
15         // 如果方法有多个匹配,不同的通配符等,则排序选择出最合适的一个
16         Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
17         matches.sort(comparator);
18         Match bestMatch = matches.get(0);
19         // 如果有多个匹配的,会找到第二个最合适的进行比较
20         if (matches.size() > 1) {
21             if (logger.isTraceEnabled()) {
22                 logger.trace(matches.size() + " matching mappings: " + matches);
23             }
24             if (CorsUtils.isPreFlightRequest(request)) {
25                 return PREFLIGHT_AMBIGUOUS_MATCH;
26             }
27             Match secondBestMatch = matches.get(1);
28             if (comparator.compare(bestMatch, secondBestMatch) == 0) {
29                 Method m1 = bestMatch.handlerMethod.getMethod();
30                 Method m2 = secondBestMatch.handlerMethod.getMethod();
31                 String uri = request.getRequestURI();
32                 // 不能有相同的最优 Match
33                 throw new IllegalStateException(
34                         "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
35             }
36         }
37         request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
38         // 设置 request 参数(RequestMappingHandlerMapping 对其进行了覆写)
39         handleMatch(bestMatch.mapping, lookupPath, request);
40         // 返回匹配的 url 的处理的方法
41         return bestMatch.handlerMethod;
42     }
43     else {
44         // 调用 RequestMappingHandlerMapping 类的 handleNoMatch 方法再匹配一次
45         return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
46     }
47 }
View Code

通过上面的过程,我们就获取到了 Handler,就开始封装执行链了,就是将我们配置的拦截器加入到执行链中去,getHandlerExecutionChain 方法如下:
 1 protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
 2     // 如果当前 Handler 不是执行链类型,就使用一个新的执行链实例封装起来
 3     HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
 4 
 5     String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
 6     // 遍历拦截器,找到跟当前 url 对应的,添加进执行链中去
 7     for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
 8         if (interceptor instanceof MappedInterceptor) {
 9             MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
10             if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
11                 chain.addInterceptor(mappedInterceptor.getInterceptor());
12             }
13         }
14         else {
15             chain.addInterceptor(interceptor);
16         }
17     }
18     return chain;
19 }
View Code

到此为止,我们就获取了当前请求的 Handler 执行链,接下来看下是如何获取请求的 Handler 适配器,主要依靠 DispatcherServlet 类的 getHandlerAdapter 方法,该方法就是遍历所有的 HandlerAdapter,找到和当前 Handler 匹配的就返回,在这里匹配到的为 RequestMappingHandlerAdapter。

DispatcherServlet 类的 getHandlerAdapter 方法如下:

 1 protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
 2     if (this.handlerAdapters != null) {
 3         // 遍历所有的 HandlerAdapter,找到和当前 Handler 匹配的就返回
 4         for (HandlerAdapter adapter : this.handlerAdapters) {
 5             if (adapter.supports(handler)) {
 6                 return adapter;
 7             }
 8         }
 9     }
10     throw new ServletException("No adapter for handler [" + handler +
11             "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
12 }
View Code

HandlerAdapter 执行当前的 Handler

再获取完当前请求的 Handler 适配器后,接着进行缓存处理,也就是对 last-modified 的处理,然后调用 applyPreHandle 方法执行拦截器的 preHandle 方法,即遍历所有定义的 interceptor,执行 preHandle 方法,然后就到了实际执行 handle 的地方,doDispatch 方法中 handle 方法是执行当前 Handler,我们这里使用的是 RequestMappingHandlerAdapter,首先会进入 AbstractHandlerMethodAdapter 的 handle 方法:

1 public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
2     return handleInternal(request, response, (HandlerMethod) handler);
3 }

在 AbstractHandlerMethodAdapter 的 handle 方法中又调用了 RequestMappingHandlerAdapter 类的 handleInternal 方法
 1 protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
 2     ModelAndView mav;
 3     checkRequest(request);
 4     if (this.synchronizeOnSession) {
 5         HttpSession session = request.getSession(false);
 6         if (session != null) {
 7             Object mutex = WebUtils.getSessionMutex(session);
 8             synchronized (mutex) {
 9                 mav = invokeHandlerMethod(request, response, handlerMethod);
10             }
11         }
12         else {
13             mav = invokeHandlerMethod(request, response, handlerMethod);
14         }
15     }
16     else {
17         // 执行方法,封装 ModelAndView
18         mav = invokeHandlerMethod(request, response, handlerMethod);
19     }
20     if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
21         if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
22             applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
23         }
24         else {
25             prepareResponse(response);
26         }
27     }
28     return mav;
29 }
View Code

在执行完 handle 方法后,然后调用 applyDefaultViewName 方法组装默认视图名称,将前缀和后缀名都加上,接着调用 applyPostHandle 方法执行拦截器的 preHandle 方法,也就是遍历所有定义的 interceptor,执行preHandle 方法。

处理最终结果以及渲染

最终调用 DispatcherServlet 类中的 processDispatchResult 方法,此方法主要是处理最终结果的,包括异常处理、渲染页面和发出完成通知触发拦截器的 afterCompletion() 方法执行等。processDispatchResult()方法代码如下:

 1 private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
 2     boolean errorView = false;
 3     if (exception != null) {
 4         if (exception instanceof ModelAndViewDefiningException) {
 5             logger.debug("ModelAndViewDefiningException encountered", exception);
 6             mv = ((ModelAndViewDefiningException) exception).getModelAndView();
 7         }
 8         else {
 9             Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
10             mv = processHandlerException(request, response, handler, exception);
11             errorView = (mv != null);
12         }
13     }
14     if (mv != null && !mv.wasCleared()) {
15         // 渲染
16         render(mv, request, response);
17         if (errorView) {
18             WebUtils.clearErrorRequestAttributes(request);
19         }
20     }
21     else {
22         if (logger.isTraceEnabled()) {
23             logger.trace("No view rendering, null ModelAndView returned.");
24         }
25     }
26     if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
27         return;
28     }
29     if (mappedHandler != null) {
30         mappedHandler.triggerAfterCompletion(request, response, null);
31     }
32 }
View Code
接下来让我们看下 DispatcherServlet 类的 render 方法是如何完成渲染的,DispatcherServlet 类的 render 方法渲染过程如下:
  1. 判断 ModelAndView 中 view 是否为 view name,没有获取其实例对象:如果是根据 name,如果是则需要调用 resolveViewName 从视图解析器获取对应的视图(View)对象;否则 ModelAndView 中使用 getview 方法获取 view 对象。
  2. 然后调用 View 类的 render 方法。

DispatcherServlet 类的 render 方法如下:

 1 protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
 2     // 设置本地化
 3     Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
 4     response.setLocale(locale);
 5     View view;
 6     String viewName = mv.getViewName();
 7     if (viewName != null) {
 8         // 解析视图名,得到视图
 9         view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
10         if (view == null) {
11             throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
12                     "' in servlet with name '" + getServletName() + "'");
13         }
14     }
15     else {
16         view = mv.getView();
17         if (view == null) {
18             throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
19                     "View object in servlet with name '" + getServletName() + "'");
20         }
21     }
22 
23     if (logger.isTraceEnabled()) {
24         logger.trace("Rendering view [" + view + "] ");
25     }
26     try {
27         if (mv.getStatus() != null) {
28             response.setStatus(mv.getStatus().value());
29         }
30         // 委托给视图进行渲染
31         view.render(mv.getModelInternal(), request, response);
32     }
33     catch (Exception ex) {
34         if (logger.isDebugEnabled()) {
35             logger.debug("Error rendering view [" + view + "]", ex);
36         }
37         throw ex;
38     }
39 }
View Code

假设我们用的是 Thymeleaf 模版引擎(也有其他模板引擎比如freemarker等),所以 view.render 找到对应的视图 ThymeleafView 的 render 方法进行渲染。
1 public void render(final Map<String, ?> model, final HttpServletRequest request, final HttpServletResponse response) throws Exception {
2     renderFragment(this.markupSelectors, model, request, response);
3 }

ThymeleafView 的 render 方法又调用 renderFragment 方法进行视图渲染,渲染完成之后,DispatcherServlet 就可以将结果返回给我们了。

 

总结

springmvc的执行流程简单介绍完了(不足之处多多指教),web框架的处理流程可以说一样的思路(本人以前用django,python的web框架)做过开发,如果不是看编程语言的话,会以为说的是django流程呢!!!,都是为了快速开发快速迭代业务系统,不像早期需要编写原生的servlet,比如

 


原文链接:https://www.cnblogs.com/dongguangming/p/12624070.html
如有疑问请与原作者联系

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:SpringBoot打包成war包

下一篇:面试官说,你会堆排序吗?会,那好手写一个吧。