文章结构
- 获取对应的handler
- 获取对应的handlerAdapter
- 调用handle
- 执行拦截器
- 处理返回结果
类的背景
Servlet规范Tomcat做了实现,Tomcat下的javax.servlet包下的类是对Tomcat的Servlet规范的实现,jdk1.8中没有Servlet类


父类的FrameworkServlet重写了HttpServlet的doPost()和doGet()方法
HandlerMapping的继承关系图
DispatchServlet调用关系图:
FrameworkServlet.processRequest()
->DispatcherServlet.doService()
->DispatcherServlet.doDispatch()
->DispatcherServlet.getHandler()
->具体HandlerMapping的.getHandlerInternal() (以AbstractUrlHandlerMapping为例)
->lookupHandler()
->buildPathExposingHandler()
->DispatcherServlet.getHandlerAdapter()
->具体的HandlerAdapter的.supports() (以SimpleControllerHandlerAdapterl为例)
->handle()
->AbstractController.handleRequest()
->DispatcherServlet.processDispatchResult()
->DispatcherServlet.render()
->具体View视图的render()
在上篇文章SpringMVC源码解析(一)中,我们搭建了一个SpringBoot的启动demo,分析了SpringBoot中SpringMVC的自动配置原理以及DispatcherServlet的初始化流程。本篇文章就分析一次请求在SpringMVC中的处理流程
在日常开发中,我们最常用的请求方式大概就是Get和Post了,Tomcat或者Jetty等web服务器在接受到请求后会调用到DispatcherServlet对应的方法
@Overrideprotected final void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);}@Overrideprotected final void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);}
可以看到其实最终都是调用的同一个方法
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {//记录开始时间long startTime = System.currentTimeMillis();Throwable failureCause = null;//记录当前线程的信息LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();LocaleContext localeContext = buildLocaleContext(request);RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());initContextHolders(request, localeContext, requestAttributes);try {//核心处理,往下看doService(request, response);}catch (ServletException | IOException ex) {failureCause = ex;throw ex;}catch (Throwable ex) {failureCause = ex;throw new NestedServletException("Request processing failed", ex);}finally {//清除线程绑定信息resetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}if (logger.isDebugEnabled()) {if (failureCause != null) {this.logger.debug("Could not complete request", failureCause);}else {if (asyncManager.isConcurrentHandlingStarted()) {logger.debug("Leaving response open for concurrent processing");}else {this.logger.debug("Successfully completed request");}}}//发送事件通知publishRequestHandledEvent(request, response, startTime, failureCause);}}protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {if (logger.isDebugEnabled()) {String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");}// Keep a snapshot of the request attributes in case of an include,// to be able to restore the original attributes after the include.Map<String, Object> attributesSnapshot = null;if (WebUtils.isIncludeRequest(request)) {attributesSnapshot = new HashMap<>();Enumeration<?> attrNames = request.getAttributeNames();while (attrNames.hasMoreElements()) {String attrName = (String) attrNames.nextElement();if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {attributesSnapshot.put(attrName, request.getAttribute(attrName));}}}// Make framework objects available to handlers and view objects.request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());if (this.flashMapManager != null) {FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);if (inputFlashMap != null) {request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));}request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);}try {doDispatch(request, response);}finally {if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Restore the original attribute snapshot, in case of an include.if (attributesSnapshot != null) {restoreAttributesAfterInclude(request, attributesSnapshot);}}}}
可以看到上方的大段代码都是做的一些准备工作,具体的逻辑接着往下看吧,这个核心流程都在下面这个方法里了
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);// 1.获取对应的handlermappedHandler = getHandler(processedRequest);if (mappedHandler == null) {//如果没有获取到对应的handler则往response中写入错误信息noHandlerFound(processedRequest, response);return;}// 2. 获取对应的handlerAdapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 处理last-modified情况String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (logger.isDebugEnabled()) {logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);}if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 3.调用handlemv = 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);}//4. 处理返回结果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 afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}
1. 获取handler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {//遍历所有的handlerMapping,这里的handlemapping就是初始化阶段构造的三个for (HandlerMapping hm : this.handlerMappings) {if (logger.isTraceEnabled()) {logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");}//这里调用具体的handler,哪个handler能够处理就直接返回HandlerExecutionChain handler = hm.getHandler(request);if (handler != null) {return handler;}}}return null;}public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {//1. 调用具体的实现去获取handlerObject handler = getHandlerInternal(request);//如果为空使用默认的if (handler == null) {handler = getDefaultHandler();}//没有默认的返回空if (handler == null) {return null;}// 尝试通过BeanName去获取handlerif (handler instanceof String) {String handlerName = (String) handler;handler = obtainApplicationContext().getBean(handlerName);}//2. 获取handler执行链HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);if (CorsUtils.isCorsRequest(request)) {CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);executionChain = getCorsHandlerExecutionChain(request, executionChain, config);}return executionChain;}
1. 获取具体的handler
这里以AbstractUrlHandlerMapping为例解读一下,顾明思议,这个类是根据请求url获取响应的handler的
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {//截取urlString lookupPath = getUrlPathHelper().getLookupPathForRequest(request);//根据url寻找handlerObject handler = lookupHandler(lookupPath, request);if (handler == null) {// 如果请求路径为/则使用RootHandlerObject rawHandler = null;if ("/".equals(lookupPath)) {rawHandler = getRootHandler();}if (rawHandler == null) {//使用默认rawHandler = getDefaultHandler();}if (rawHandler != null) {// 根据beanName尝试获取Handlerif (rawHandler instanceof String) {String handlerName = (String) rawHandler;rawHandler = obtainApplicationContext().getBean(handlerName);}//校验validateHandler(rawHandler, request);handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);}}if (handler != null && logger.isDebugEnabled()) {logger.debug("Mapping [" + lookupPath + "] to " + handler);}else if (handler == null && logger.isTraceEnabled()) {logger.trace("No handler mapping found for [" + lookupPath + "]");}return handler;}protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {// 直接根据url匹配Object handler = this.handlerMap.get(urlPath);if (handler != null) {// Bean name or resolved handler?if (handler instanceof String) {String handlerName = (String) handler;handler = obtainApplicationContext().getBean(handlerName);}validateHandler(handler, request);//封装执行链return buildPathExposingHandler(handler, urlPath, urlPath, null);}// 正则匹配List<String> matchingPatterns = new ArrayList<>();for (String registeredPattern : this.handlerMap.keySet()) {if (getPathMatcher().match(registeredPattern, urlPath)) {matchingPatterns.add(registeredPattern);}else if (useTrailingSlashMatch()) {if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {matchingPatterns.add(registeredPattern +"/");}}}String bestMatch = null;Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);if (!matchingPatterns.isEmpty()) {matchingPatterns.sort(patternComparator);if (logger.isDebugEnabled()) {logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);}bestMatch = matchingPatterns.get(0);}if (bestMatch != null) {handler = this.handlerMap.get(bestMatch);if (handler == null) {if (bestMatch.endsWith("/")) {handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));}if (handler == null) {throw new IllegalStateException("Could not find handler for best pattern match [" + bestMatch + "]");}}// Bean name or resolved handler?if (handler instanceof String) {String handlerName = (String) handler;handler = obtainApplicationContext().getBean(handlerName);}validateHandler(handler, request);String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);// There might be multiple 'best patterns', let's make sure we have the correct URI template variables// for all of themMap<String, String> uriTemplateVariables = new LinkedHashMap<>();for (String matchingPattern : matchingPatterns) {if (patternComparator.compare(bestMatch, matchingPattern) == 0) {Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);uriTemplateVariables.putAll(decodedVars);}}if (logger.isDebugEnabled()) {logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);}return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);}// No handler found...return null;}
2. 封装执行链
当获取到相应的handler后,查看是否存在拦截器,如果存在的话则加入执行链中
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) {HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));if (!CollectionUtils.isEmpty(uriTemplateVariables)) {chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));}return chain;}
2. 获取handlerAdpter
根据handler获取匹配的handlerAdpter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {for (HandlerAdapter ha : this.handlerAdapters) {if (logger.isTraceEnabled()) {logger.trace("Testing handler adapter [" + ha + "]");}//不同的handlerAdapter的判断方法不同if (ha.supports(handler)) {return ha;}}}throw new ServletException("No adapter for handler [" + handler +"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");}以SimpleControllerHandlerAdapter为例,判断是否实现Controller接口public boolean supports(Object handler) {return (handler instanceof Controller);}
3. 执行请求
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return ((Controller) handler).handleRequest(request, response);}public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)throws Exception {if (HttpMethod.OPTIONS.matches(request.getMethod())) {response.setHeader("Allow", getAllowHeader());return null;}// Delegate to WebContentGenerator for checking and preparing.checkRequest(request);prepareResponse(response);// 如果需要同步sessionif (this.synchronizeOnSession) {HttpSession session = request.getSession(false);if (session != null) {Object mutex = WebUtils.getSessionMutex(session);synchronized (mutex) {return handleRequestInternal(request, response);}}}调用Controller方法return handleRequestInternal(request, response);}
4.处理返回结果
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;//是否包含异常信息if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {//异常视图处理Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}if (mv != null && !mv.wasCleared()) {// 页面跳转处理render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}else {if (logger.isDebugEnabled()) {logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +"': assuming HandlerAdapter completed request handling");}}if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Concurrent handling started during a forwardreturn;}if (mappedHandler != null) {mappedHandler.triggerAfterCompletion(request, response, null);}}if (mv != null && !mv.wasCleared()) {
5. 页面跳转的逻辑
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {// Determine locale for request and apply it to the response.Locale locale =(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());response.setLocale(locale);View view;String viewName = mv.getViewName();if (viewName != null) {//1.解析视图名view = resolveViewName(viewName, mv.getModelInternal(), locale, request);if (view == null) {throw new ServletException("Could not resolve view with name '" + mv.getViewName() +"' in servlet with name '" + getServletName() + "'");}}else {// No need to lookup: the ModelAndView object contains the actual View object.view = mv.getView();if (view == null) {throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +"View object in servlet with name '" + getServletName() + "'");}}// Delegate to the View object for rendering.if (logger.isDebugEnabled()) {logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");}try {if (mv.getStatus() != null) {response.setStatus(mv.getStatus().value());}//2.跳转view.render(mv.getModelInternal(), request, response);}catch (Exception ex) {if (logger.isDebugEnabled()) {logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +getServletName() + "'", ex);}throw ex;}}
1. 解析视图名称
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,Locale locale, HttpServletRequest request) throws Exception {if (this.viewResolvers != null) {for (ViewResolver viewResolver : this.viewResolvers) {View view = viewResolver.resolveViewName(viewName, locale);if (view != null) {return view;}}}return null;}
2. 页面跳转
具体的跳转逻辑是根据当前使用的渲染引擎决定的,比如html、jsp、Thymeleaf等,这里简单 列举一个Thymeleaf的逻辑吧
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {this.renderFragment(this.markupSelectors, model, request, response);}protected void renderFragment(Set<String> markupSelectorsToRender, Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {ServletContext servletContext = this.getServletContext();String viewTemplateName = this.getTemplateName();ISpringTemplateEngine viewTemplateEngine = this.getTemplateEngine();if (viewTemplateName == null) {throw new IllegalArgumentException("Property 'templateName' is required");} else if (this.getLocale() == null) {throw new IllegalArgumentException("Property 'locale' is required");} else if (viewTemplateEngine == null) {throw new IllegalArgumentException("Property 'templateEngine' is required");} else {Map<String, Object> mergedModel = new HashMap(30);Map<String, Object> templateStaticVariables = this.getStaticVariables();if (templateStaticVariables != null) {mergedModel.putAll(templateStaticVariables);}if (pathVariablesSelector != null) {Map<String, Object> pathVars = (Map)request.getAttribute(pathVariablesSelector);if (pathVars != null) {mergedModel.putAll(pathVars);}}if (model != null) {mergedModel.putAll(model);}ApplicationContext applicationContext = this.getApplicationContext();RequestContext requestContext = new RequestContext(request, response, this.getServletContext(), mergedModel);SpringWebMvcThymeleafRequestContext thymeleafRequestContext = new SpringWebMvcThymeleafRequestContext(requestContext, request);addRequestContextAsVariable(mergedModel, "springRequestContext", requestContext);addRequestContextAsVariable(mergedModel, "springMacroRequestContext", requestContext);mergedModel.put("thymeleafRequestContext", thymeleafRequestContext);ConversionService conversionService = (ConversionService)request.getAttribute(ConversionService.class.getName());ThymeleafEvaluationContext evaluationContext = new ThymeleafEvaluationContext(applicationContext, conversionService);mergedModel.put("thymeleaf::EvaluationContext", evaluationContext);IEngineConfiguration configuration = viewTemplateEngine.getConfiguration();WebExpressionContext context = new WebExpressionContext(configuration, request, response, servletContext, this.getLocale(), mergedModel);String templateName;Set markupSelectors;if (!viewTemplateName.contains("::")) {templateName = viewTemplateName;markupSelectors = null;} else {IStandardExpressionParser parser = StandardExpressions.getExpressionParser(configuration);FragmentExpression fragmentExpression;try {fragmentExpression = (FragmentExpression)parser.parseExpression(context, "~{" + viewTemplateName + "}");} catch (TemplateProcessingException var24) {throw new IllegalArgumentException("Invalid template name specification: '" + viewTemplateName + "'");}ExecutedFragmentExpression fragment = FragmentExpression.createExecutedFragmentExpression(context, fragmentExpression);templateName = FragmentExpression.resolveTemplateName(fragment);markupSelectors = FragmentExpression.resolveFragments(fragment);Map<String, Object> nameFragmentParameters = fragment.getFragmentParameters();if (nameFragmentParameters != null) {if (fragment.hasSyntheticParameters()) {throw new IllegalArgumentException("Parameters in a view specification must be named (non-synthetic): '" + viewTemplateName + "'");}context.setVariables(nameFragmentParameters);}}String templateContentType = this.getContentType();Locale templateLocale = this.getLocale();String templateCharacterEncoding = this.getCharacterEncoding();Set processMarkupSelectors;if (markupSelectors != null && markupSelectors.size() > 0) {if (markupSelectorsToRender != null && markupSelectorsToRender.size() > 0) {throw new IllegalArgumentException("A markup selector has been specified (" + Arrays.asList(markupSelectors) + ") for a view that was already being executed as a fragment (" + Arrays.asList(markupSelectorsToRender) + "). Only one fragment selection is allowed.");}processMarkupSelectors = markupSelectors;} else if (markupSelectorsToRender != null && markupSelectorsToRender.size() > 0) {processMarkupSelectors = markupSelectorsToRender;} else {processMarkupSelectors = null;}response.setLocale(templateLocale);if (!this.getForceContentType()) {String computedContentType = SpringContentTypeUtils.computeViewContentType(request, templateContentType != null ? templateContentType : "text/html;charset=ISO-8859-1", templateCharacterEncoding != null ? Charset.forName(templateCharacterEncoding) : null);response.setContentType(computedContentType);} else {if (templateContentType != null) {response.setContentType(templateContentType);} else {response.setContentType("text/html;charset=ISO-8859-1");}if (templateCharacterEncoding != null) {response.setCharacterEncoding(templateCharacterEncoding);}}viewTemplateEngine.process(templateName, processMarkupSelectors, context, response.getWriter());}}
