300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > springboot MVC视图解析流程源码分析

springboot MVC视图解析流程源码分析

时间:2023-07-09 06:11:39

相关推荐

springboot MVC视图解析流程源码分析

Servlet的基础知识

为什么要先了解Servlet的知识呢,因为后面你会看到我们所熟悉的SpringMVC其实也是一个Servlet,只是它封装了很多的东西并和Spring进行了整合,后面我们进行的源码分析就是围绕着Servlet的生命周期进行的,所以有必要了解一下Servlet相关的知识。

Servlet概念

全称Java Servlet,是用Java编写的服务器端程序。其主要功能在于 交互式地浏览和修改数据,生成动态Web内容。Servlet运行于支持 Java应用的服务器中。从原理上讲,Servlet可以响应任何类型的请求, 但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。

Servlet的工作原理

下面通过一张时序图来理解Servlet的工作流程

从上面的时序图总结如下:

首先客户端请求接入Servlet容器,比如Tomcat处理请求Tomcat 创建HttpServletRequest对象,将请求的信息封装到这个对象中。Tomcat 创建HttpServletResponse对象, 此时是一个空的对象等待请求返回并填充Tomcat调用service方法,最终传递到子类HttpServlet中HttpServlet从request中获取请求信息,处理完毕后将返回值信息封装到HttpServletResponse对象Tomcat容器返回处理后的信息

Servlet生命周期

打开Servlet源码发现Servlet接口有几个方法:

init()方法Tomcat创建Servlet时会调用每次有请求来时Tomcat都会调用service()方法请求结束时Tomcat会调用destroy()方法

public interface Servlet {//Servlet创建时会调用init方法进行初始化void init(ServletConfig var1) throws ServletException;ServletConfig getServletConfig();// 每次有新的请求来时都会调用void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;String getServletInfo();// 请求结束时调用void destroy();}

一个Servlet例子

写一个AddUserServlet类继承自HttpServlet(为什么要继承这个类后面有说明)

public class AddUserServlet extends HttpServlet {private static final long serialVersionUID = 1L;protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {response.setContentType("text/plain;charset=utf8");response.getWriter().write("添加成功");}protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}

总结:

Servlet实际上是tomcat容器生成的,调用init方法可以初始化。他有别于 普通java的执行过程,普通java需要main方法;但是web项目由于是服务器控 制的创建和销毁,所以servlet的访问也需要tomcat来控制。通过tomcat访问 servlet的机制是通过使用http协议的URL来访问,所以servlet的配置完想要访 问,必须通过URL来访问,所以没有main方法。

下面正式开始mvc过程的源码分析,先看一张完整的DispatcherServlet的流程图

一、方法到达service

当Spring MVC启动完毕,就可以开始准备接受来自客户端的请求了。根据Servlet规范,请求必须先发送到service()方法。在Spring MVC中,该方法实现在DispatcherServlet的父类FrameworkServlet:

protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());if (HttpMethod.PATCH != httpMethod && httpMethod != null) {super.service(request, response);} else {this.processRequest(request, response);}}

根据HttpMethod,即GET、POST、PUT、DELETE等请求类型来分派请求。这里将请求分为两类:PATCH&空,以及其他请求,对于前者,调用processRequest处理,否则调用父类的service方法处理。

processRequest方法

它首先备份了请求的LocaleContext和RequestAttributes,然后copy了一份新的,并绑定到当前线程

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);

initContextHolders就是将localeContext和requestAttributes两个对象存入Holder。并且在方法最后,将备份的数据恢复过来,并触发请求处理完毕事件:

finally {resetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}logResult(request, response, failureCause, asyncManager);publishRequestHandledEvent(request, response, startTime, failureCause);}

备份和绑定操作完成后,调用它的核心方法doService,这是一个抽象方法,在DispatcherServlet实现,首先也是备份属性,并且最后也进行了恢复:

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));}}}...finally {if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {if (attributesSnapshot != null) {restoreAttributesAfterInclude(request, attributesSnapshot);}}}

然后为request绑定Web容器、本地化解析器、主题解析器、主题、FlashMap管理器、inputFlashMap/outputFlashMap等属性,并调用doDispatch方法。

父类service方法

在父类的方法中,根据HttpMethod类型,分别调用了doXXX方法

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String method = req.getMethod();if (method.equals(METHOD_GET)) {...doGet(req, resp);...} else if (method.equals(METHOD_HEAD)) {...doHead(req, resp);} else if (method.equals(METHOD_POST)) {doPost(req, resp); } else if (method.equals(METHOD_PUT)) {doPut(req, resp);} else if (method.equals(METHOD_DELETE)) {doDelete(req, resp);} else if (method.equals(METHOD_OPTIONS)) {doOptions(req,resp);} else if (method.equals(METHOD_TRACE)) {doTrace(req,resp);} else {String errMsg = lStrings.getString("http.method_not_implemented");Object[] errArgs = new Object[1];errArgs[0] = method;errMsg = MessageFormat.format(errMsg, errArgs);resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);}}

对于未知类型的Http请求,则不进行分派,直接返回错误。这些do方法都实现在FrameworkServlet中,实际上都最终调用了processRequest,体现了集中入口、分协议转发、统一处理的特点。

二、doDispatch方法

先上一张流程图(来源于网络):

很清晰地描述了doDispatch方法的流程。

检查上传请求 —— checkMultipart方法

由于Spring MVC默认不支持文件上传,所以必须在请求处理的最开始就进行检查,以免在处理过程中才发现没有配置文件上传解析器,导致处理失败。checkMultipart方法源码如下:

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");}}else if (hasMultipartException(request)) {logger.debug("Multipart resolution previously failed for current request - " +"skipping re-resolution for undisturbed error rendering");}else {try {return this.multipartResolver.resolveMultipart(request);}catch (MultipartException ex) {if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {logger.debug("Multipart resolution failed for error dispatch", ex);}else {throw ex;}}}}return request;}

首先检查是否配置了文件上传解析器,以及请求中是否包含multipart部分,没有的话就可以不必继续了。

然后检查请求是否已经被处理过,或者曾经处理过但是有未解决的异常,这两种情况也不需要继续处理。如果请求还没处理过,则用文件上传解析器进行解析,成功解析后,将生成一个MultipartHttpServletRequest对象。这里以CommonsMultpartResolver为例说明,其resolveMultipart方法源码如下:

public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {if (this.resolveLazily) {return new DefaultMultipartHttpServletRequest(request) {@Overrideprotected void initializeMultipart() {MultipartParsingResult parsingResult = parseRequest(request);setMultipartFiles(parsingResult.getMultipartFiles());setMultipartParameters(parsingResult.getMultipartParameters());setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());}};}else {MultipartParsingResult parsingResult = parseRequest(request);return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());}}

不难看出,懒加载方式下,只是重写了initializeMultipart方法,真正的解析还要等后面,非懒加载模式下,直接就进行解析了:

protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {String encoding = determineEncoding(request);FileUpload fileUpload = prepareFileUpload(encoding);try {List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);return parseFileItems(fileItems, encoding);}catch (FileUploadBase.SizeLimitExceededException ex) {throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);}catch (FileUploadBase.FileSizeLimitExceededException ex) {throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);}catch (FileUploadException ex) {throw new MultipartException("Failed to parse multipart servlet request", ex);}}

这里有三种异常情况:请求本身太大、上传的文件太大、Request无法解析。

determineEncoding很简单,就是读取请求中character-encoding属性,没有就使用默认编码ISO-8859-1。

prepareFileUpload也很简单,就是创建一个FileUpload实例,赋一些属性:

protected FileUpload prepareFileUpload(@Nullable String encoding) {FileUpload fileUpload = getFileUpload();FileUpload actualFileUpload = fileUpload;if (encoding != null && !encoding.equals(fileUpload.getHeaderEncoding())) {actualFileUpload = newFileUpload(getFileItemFactory());actualFileUpload.setSizeMax(fileUpload.getSizeMax());actualFileUpload.setFileSizeMax(fileUpload.getFileSizeMax());actualFileUpload.setHeaderEncoding(encoding);}return actualFileUpload;}

FileUpload类的parseRequest方法可以用于解析MultipartServletRequest,生成文件对象列表,它的原理就是读取请求中的数据,构造FileItem对象,然后存入列表中,去除了catch块之后的代码如下::

public List<FileItem> parseRequest(RequestContext ctx) throws FileUploadException {List<FileItem> items = new ArrayList<FileItem>();boolean successful = false;FileItemIterator iter = getItemIterator(ctx);FileItemFactory fac = getFileItemFactory();if (fac == null) {throw new NullPointerException("No FileItemFactory has been set.");}while (iter.hasNext()) {final FileItemStream item = iter.next();final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),item.isFormField(), fileName);items.add(fileItem);Streams.copy(item.openStream(), fileItem.getOutputStream(), true);final FileItemHeaders fih = item.getHeaders();fileItem.setHeaders(fih);}successful = true;if (!successful) {for (FileItem fileItem : items) {try {fileItem.delete();} catch (Exception ignored) {}}}}

getItemIterator方法从请求中读取了Content-Type、Content-Length、Character-Encoding等属性,构造了MultipartStream流,并返回了一个迭代器实例。每当调用hasNext时,就会调用findNextItem方法,解析MultipartStream并生成FileItemStream对象。

假如在生成FileItem的过程中抛出异常,则会执行fileItem.delete()方法显式清除临时文件。

FileItem是apache commons包的类,还需要进一步解析为Spring的MultipartParsingResult对象。此处根据FileItem对象里面封装的数据是一个普通文本表单字段,还是一个文件表单字段采用不同的处理方式。

对于文本表单字段,调用getString方法读取文件内容,然后存入multipartParameters中,读取Content-Type属性,存入multipartParameterContentTypes中

String value;String partEncoding = determineEncoding(fileItem.getContentType(), encoding);try {value = fileItem.getString(partEncoding);}catch (UnsupportedEncodingException ex) {value = fileItem.getString();}String[] curParam = multipartParameters.get(fileItem.getFieldName());if (curParam == null) {multipartParameters.put(fileItem.getFieldName(), new String[] {value});}else {String[] newParam = StringUtils.addStringToArray(curParam, value);multipartParameters.put(fileItem.getFieldName(), newParam);}multipartParameterContentTypes.put(fileItem.getFieldName(), fileItem.getContentType());

对于文件表单字段,则直接创建CommonsMultipartFile对象,并添加到multipartFiles:

CommonsMultipartFile file = createMultipartFile(fileItem);multipartFiles.add(file.getName(), file);protected CommonsMultipartFile createMultipartFile(FileItem fileItem) {CommonsMultipartFile multipartFile = new CommonsMultipartFile(fileItem);multipartFile.setPreserveFilename(this.preserveFilename);return multipartFile;}

无论如何,都将上述三个加了粗的Map对象传入MultipartParsingResult的构造函数,返回一个新实例。

查找处理器 —— getHandler方法

该方法实际就是遍历所有HandlerMapping,逐个调用其getHandler方法,看它对应的Handler能否处理该请求,假如某个HandlerMapping满足条件,则直接返回(也就是执行链),否则继续循环。假如直到遍历完毕还没有结果,则返回null。

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;}

getHandler方法实现在AbstractHandlerMapping类中,关键源码如下:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {Object handler = getHandlerInternal(request);if (handler == null) {handler = getDefaultHandler();}if (handler == null) {return null;}if (handler instanceof String) {String handlerName = (String) handler;handler = obtainApplicationContext().getBean(handlerName);}HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);if (CorsUtils.isCorsRequest(request)) {CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);CorsConfiguration config = (globalConfig != null ? bine(handlerConfig) : handlerConfig);executionChain = getCorsHandlerExecutionChain(request, executionChain, config);}return executionChain;}

defaultHandler如果没有显式注入的话一般都是null,所以不用考虑。显然核心方法就是getHandlerInternal和getHandlerExecutionChain,以及下面的CORS处理逻辑。

RequestMappingHandlerMapping 的 getHandlerInternal 方法实现

这里仍以RequestMappingHandlerMapping为例说明。其getHandlerInternal方法实际上位于父类AbstractHandlerMethodMapping:

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);this.mappingRegistry.acquireReadLock();try {HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);}finally {this.mappingRegistry.releaseReadLock();}}

首先是对URL进行截取:

public String getLookupPathForRequest(HttpServletRequest request) {if (this.alwaysUseFullPath) {return getPathWithinApplication(request);}String rest = getPathWithinServletMapping(request);if (!"".equals(rest)) {return rest;}else {return getPathWithinApplication(request);}}

先来看getPathWithinApplication方法,它获取了request的contextPath属性和URI,然后把URI中和contextPath相同的部分切除,返回剩余的部分:

public String getPathWithinApplication(HttpServletRequest request) {String contextPath = getContextPath(request);String requestUri = getRequestUri(request);String path = getRemainingPath(requestUri, contextPath, true);if (path != null) {return (StringUtils.hasText(path) ? path : "/");}else {return requestUri;}}

举个例子,假如contextPath为"/hello",URI为"/hello/index.html",则返回"/index.html"。

再来看getPathWithinServletMapping方法,首先也是调用了getPathWithinApplication,然后获取了request中的servletPath属性,然后将pathWithinApp中的"//“替换为”/"。servletPath就是URI中分号之前的部分,例如:URI为"/hello/index.html;a=1;b=2",则其servletPath就是"/hello/index.html"。然后把URI中和servletPath相同的部分切除,保留剩余的部分:

public String getPathWithinServletMapping(HttpServletRequest request) {String pathWithinApp = getPathWithinApplication(request);String servletPath = getServletPath(request);String sanitizedPathWithinApp = getSanitizedPath(pathWithinApp);String path;if (servletPath.contains(sanitizedPathWithinApp)) {path = getRemainingPath(sanitizedPathWithinApp, servletPath, false);}else {path = getRemainingPath(pathWithinApp, servletPath, false);}if (path != null) {return path;}else {String pathInfo = request.getPathInfo();if (pathInfo != null) {return pathInfo;}if (!this.urlDecode) {path = getRemainingPath(decodeInternal(request, pathWithinApp), servletPath, false);if (path != null) {return pathWithinApp;}}return servletPath;}}

截取URL之后,就可以进行HandlerMethod的匹配了,首先要看下有没有能完全匹配上的:

List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);if (directPathMatches != null) {addMatchingMappings(directPathMatches, matches, request);}

假如没有,则对所有HandlerMethods进行查找,找出其中最好的一个继续处理。Comparator按照请求类型、patternsCondition、paramsCondition、headersCondition、consumesCondition、producesCondition、methodsCondition、customConditionHolder的顺序比较各属性:

Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));matches.sort(comparator);Match bestMatch = matches.get(0);if (matches.size() > 1) {if (CorsUtils.isPreFlightRequest(request)) {return new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));}Match secondBestMatch = matches.get(1);if (pare(bestMatch, secondBestMatch) == 0) {Method m1 = bestMatch.handlerMethod.getMethod();Method m2 = secondBestMatch.handlerMethod.getMethod();String uri = request.getRequestURI();throw new IllegalStateException("Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");}}request.setAttribute(HandlerMapping.class.getName() + ".bestMatchingHandler", bestMatch.handlerMethod);handleMatch(bestMatch.mapping, lookupPath, request);return bestMatch.handlerMethod;

handleMatch方法在子类RequestMappingInfoHandlerMapping进行了重写,除了将匹配/比较结果存放到request中,还从request里解析了@MatrixVariable和@PathVariable注解的参数的值,也一并存入request。

假如没有匹配到HandlerMethod(比如根本就没有注册任何HandlerMethod),就会执行handleNoMatch方法,该方法也被重写,实际逻辑就是再遍历一次RequestMappingInfo列表,看看是不是有漏网之鱼,如果还是没有,则抛出异常。

然后需要对HandlerMethod进行实例化。HandlerMethod在创建时(createHandlerMethod方法),传入的handler参数实际是BeanName,显然不包含实例,也就不可能进行方法调用,所以还需要进行实例化:

public HandlerMethod createWithResolvedBean() {Object handler = this.bean;if (this.bean instanceof String) {String beanName = (String) this.bean;handler = this.beanFactory.getBean(beanName);}return new HandlerMethod(this, handler);}

getHandlerExecutionChain方法

该方法实质就是将Handler拦截器加入到处理链中,以便拦截器生效:

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);for (HandlerInterceptor interceptor : this.adaptedInterceptors) {if (interceptor instanceof MappedInterceptor) {MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {chain.addInterceptor(mappedInterceptor.getInterceptor());}}else {chain.addInterceptor(interceptor);}}return chain;}

CORS配置

首先获取全局CORS配置,实际就是从Map中获取值:

public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);for (Map.Entry<String, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {if (this.pathMatcher.match(entry.getKey(), lookupPath)) {return entry.getValue();}}return null;}

然后是获取处理器的CORS配置,本质上和上面的一样:

// in AbstractHandlerMethodMapping.javaprotected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {CorsConfiguration corsConfig = super.getCorsConfiguration(handler, request);if (handler instanceof HandlerMethod) {HandlerMethod handlerMethod = (HandlerMethod) handler;if (handlerMethod.equals(PREFLIGHT_AMBIGUOUS_MATCH)) {return AbstractHandlerMethodMapping.ALLOW_CORS_CONFIG;}else {CorsConfiguration corsConfigFromMethod = this.mappingRegistry.getCorsConfiguration(handlerMethod);corsConfig = (corsConfig != null ? bine(corsConfigFromMethod) : corsConfigFromMethod);}}return corsConfig;}// in AbstractHandlerMapping.javaprotected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {Object resolvedHandler = handler;if (handler instanceof HandlerExecutionChain) {resolvedHandler = ((HandlerExecutionChain) handler).getHandler();}if (resolvedHandler instanceof CorsConfigurationSource) {return ((CorsConfigurationSource) resolvedHandler).getCorsConfiguration(request);}return null;}

然后将本地配置和全局配置融合,用于创建CORS拦截器,添加到处理器执行链上:

protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,HandlerExecutionChain chain, @Nullable CorsConfiguration config) {if (CorsUtils.isPreFlightRequest(request)) {HandlerInterceptor[] interceptors = chain.getInterceptors();chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);}else {chain.addInterceptor(new CorsInterceptor(config));}return chain;}

和上面的getHandlerExecutionChain原理一致,不再赘述。

DispatcherServlet对没有找到Handler的处理

尽管在查找HandlerMethod的过程中,如果出现noMatch会抛出异常,但是Spring还是在DispatcherServlet进行了处理,原因很简单:服务器抛出异常,仅有自己可以感知到,还必须通过Response返回错误,才能让客户端也感知到:

protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {if (pageNotFoundLogger.isWarnEnabled()) {pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));}if (this.throwExceptionIfNoHandlerFound) {throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),new ServletServerHttpRequest(request).getHeaders());}else {response.sendError(HttpServletResponse.SC_NOT_FOUND);}}

寻找HandlerAdapter —— getHandlerAdapter方法(用于解析参数)

这里实现很简单:遍历适配器列表,看哪个可以支持找到的Handler,就返回哪个。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {for (HandlerAdapter adapter : this.handlerAdapters) {if (adapter.supports(handler)) {return adapter;}}}throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");}

对于RequestMappingHandlerAdapter来说,仅仅检查了handler是否是HandlerMethod类型,是则返回true。

缓存(LastModified属性)的处理

服务器具有缓存机制,在用户第一次请求某资源时,如果请求成功,服务器会返回HTTP 200,并在响应头中添加一个LastModified字段,记录此资源在服务器上的最后更新时间;当再次请求该资源时,浏览器会在请求头中加上If-Modified-Since字段,询问服务器该资源自此时间起是否更新过,如果没有更新,则直接返回HTTP 304,而不返回资源,这样就节省了带宽。

Spring在获取了HandlerAdapter后,就会检查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;}}

可见资源更新时间是由HandlerAdapter决定的,这里以SimpleControllerHandlerAdapter的实现为例:

public long getLastModified(HttpServletRequest request, Object handler) {if (handler instanceof LastModified) {return ((LastModified) handler).getLastModified(request);}return -1L;}

意思很简单:如果Handler(即Controller类)实现了LastModified接口,则调用其getLastModified方法获取,否则返回-1。

checkNotModified就是验证获取到的时间是否过期,并更新Response:

if (validateIfUnmodifiedSince(lastModifiedTimestamp)) {if (this.notModified && response != null) {response.setStatus(HttpStatus.PRECONDITION_FAILED.value());}return this.notModified;}boolean validated = validateIfNoneMatch(etag);if (!validated) {validateIfModifiedSince(lastModifiedTimestamp);}if (response != null) {boolean isHttpGetOrHead = SAFE_METHODS.contains(getRequest().getMethod());if (this.notModified) {response.setStatus(isHttpGetOrHead ?HttpStatus.NOT_MODIFIED.value() : HttpStatus.PRECONDITION_FAILED.value());}if (isHttpGetOrHead) {if (lastModifiedTimestamp > 0 && parseDateValue(response.getHeader(LAST_MODIFIED)) == -1) {response.setDateHeader(LAST_MODIFIED, lastModifiedTimestamp);}if (StringUtils.hasLength(etag) && response.getHeader(ETAG) == null) {response.setHeader(ETAG, padEtagIfNecessary(etag));}}}return this.notModified;

validateIfUnmodifiedSince首先对刚刚获取到的资源更新时间进行判断,如果小于0直接返回false,否则解析If-Modified-Since属性并进行比较:

private boolean validateIfUnmodifiedSince(long lastModifiedTimestamp) {if (lastModifiedTimestamp < 0) {return false;}long ifUnmodifiedSince = parseDateHeader(IF_UNMODIFIED_SINCE);if (ifUnmodifiedSince == -1) {return false;}this.notModified = (ifUnmodifiedSince < (lastModifiedTimestamp / 1000 * 1000));return true;}

如果validateIfUnmodifiedSince返回了false,则需要进一步判断,由于调用checkNotModified方法时没有传入etag,所以validateIfNoneMatch必定返回false,因此会调用validateIfModifiedSince再次判断,内容和validateIfUnmodifiedSince类似,只是最后面的小于号换成了大于等于号。

如果经过两次判断,确认了资源未修改,则可以对repsonse进行更新。

拦截器发挥作用 ——preHandler、postHandler和afterCompletion

进行了上面一系列预处理后,就可以正式开始使用Handler处理请求了。在Servlet规范中,设计了filter组件,可以在每个Web请求前后对它做处理,显然这种处理粒度太粗。Spring MVC增加了拦截器的概念,从HandlerMapping初始化和Handler查找的过程中,我们可以看到它的身影。

拦截器接口HandlerInterceptor定义了三个方法:preHandle、postHandle、afterCompletion,分别作用于处理器方法调用前后、处理器执行链全部执行后。在doDispatch方法中,通过applyPreHandle、applyPostHandle和triggerAfterCompletion来调用处理器执行链上所有拦截器的对应方法。

applyPreHandle/applyPostHandle就是遍历所有拦截器,触发其preHandle/postHandle方法,比较有意思的是applyPreHandle,当preHandle返回false时,它会尝试调用afterCompletion方法。阅读HandlerInterceptor接口注释后发现,preHandle方法如果返回true,表示应当由下一个拦截器或者处理器继续处理,否则可以认为response对象已经被该处理链处理过了,不需要重复处理,应当中断:

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = 0; i < interceptors.length; i++) {HandlerInterceptor interceptor = interceptors[i];if (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}}return true;}

处理请求 ——handle方法、applyDefaultViewName方法

当执行链中所有的preHandle方法都触发之后,就可以调用处理器来真正处理请求了。这里调用了HandlerAdapter的handle方法处理,以RequestMappingHandlerAdapter为例,它的handle方法又调用了handleInternal方法

protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ModelAndView mav;checkRequest(request);if (this.synchronizeOnSession) {HttpSession session = request.getSession(false);if (session != null) {Object mutex = WebUtils.getSessionMutex(session);synchronized (mutex) {mav = invokeHandlerMethod(request, response, handlerMethod);}}else {mav = invokeHandlerMethod(request, response, handlerMethod);}}else {mav = invokeHandlerMethod(request, response, handlerMethod);}...}

synchronizedOnSession的作用是,让同一个Session的请求串行执行,默认false。

6.1 请求检查 checkRequest

首先进行正式处理前的最后一次检查,这里检查了请求类型是否被支持,以及是否包含必需的Session对象:

protected final void checkRequest(HttpServletRequest request) throws ServletException {String method = request.getMethod();if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {throw new HttpRequestMethodNotSupportedException(method, this.supportedMethods);}if (this.requireSession && request.getSession(false) == null) {throw new HttpSessionRequiredException("Pre-existing session required but none found");}}

6.2 触发处理器方法 invokeHandlerMethod

在该方法的第一段,对传入的三个参数:request、response、handlerMethod进行了包装和解析,创建了接下来触发方法需要的组件:

ServletWebRequest webRequest = new ServletWebRequest(request, response);WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) {invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}invocableMethod.setDataBinderFactory(binderFactory);invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

getDataBinderFactory主要做了三件事:

取出handlerMethod所在类下被注解了@InitBinder的方法,以及@ControllerAdvice类下被注解了@InitBinder的方法

将这些方法封装为InvocableHandlerMethod,设置参数绑定解析器和参数绑定工厂,并添加到列表中

使用上一步得到的列表创建一个ServletRequestDataBinderFactory并返回

getModelFactory方法的主要流程和getDataBidnerFactory差不多,只不过第一步搜索的是@ModelAttribute注解的方法,第三步创建的是ModelFactory而已。

createInvocableHandlerMethod则是将传入的HandlerMethod对象直接封装为ServletInvocableHandlerMethod。

接下来,将初始化HandlerAdapter时配置的各种解析器,以及刚刚创建的binderFactory对象绑定到处理器方法上。

方法的第二段主要创建和配置了逻辑视图容器及异步管理器对象:

ModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);asyncWebRequest.setTimeout(this.asyncRequestTimeout);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.setTaskExecutor(this.taskExecutor);asyncManager.setAsyncWebRequest(asyncWebRequest);asyncManager.registerCallableInterceptors(this.callableInterceptors);asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

中间还处理器方法的ModelMap进行了处理,实际就是触发刚刚找到的所有@ModelAttribute方法,将可以触发的都触发,把该添加到ModelMap的属性都添加进来。

假如需要异步处理,则将ServletInvocableHandlerMethod对象进一步封装为ConcurrentResultHandlerMethod:

if (asyncManager.hasConcurrentResult()) {Object result = asyncManager.getConcurrentResult();mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];asyncManager.clearConcurrentResult();invocableMethod = invocableMethod.wrapConcurrentResult(result);}

第三段就是调用处理器方法,并取出处理结果(逻辑视图),如果是异步处理,则先返回null,等异步处理完了再获取:

invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}return getModelAndView(mavContainer, modelFactory, webRequest);

invokeAndHandle逻辑很简单:使用Method.invoke方法反射调用,然后调用初始化HandlerAdapter时配置的返回值处理器进行处理,中间还处理了一下无返回值或出现响应错误的情况:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);setResponseStatus(webRequest);if (returnValue == null) {if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {mavContainer.setRequestHandled(true);return;}}else if (StringUtils.hasText(getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);try {this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);}catch (Exception ex) {throw ex;}}

可以看出,如果出现无返回值或者响应错误,则对逻辑视图容器标记处理完成,而顺利执行反而不做标记,这么做的原因在getModelAndView方法里:

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {modelFactory.updateModel(webRequest, mavContainer);if (mavContainer.isRequestHandled()) {return null;}ModelMap model = mavContainer.getModel();ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());if (!mavContainer.isViewReference()) {mav.setView((View) mavContainer.getView());}if (model instanceof RedirectAttributes) {Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);if (request != null) {RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);}}return mav;}

如果标记了请求已处理,则不会获取处理结果。这里将ModelMap和ViewName、Status等属性封装为ModelAndView逻辑视图对象,进行返回。对于视图引用和重定向请求,还进行了特殊处理。值得一提的是重定向的处理,这里获取到了在本篇1.1节配置的outputFlashMap对象。

在整个方法调用完后,通过finally块执行了ServletWebRequest的requestCompleted方法,该方法源码如下:

public void requestCompleted() {executeRequestDestructionCallbacks();updateAccessedSessionAttributes();this.requestActive = false;}

这里执行了request中设置的析构回调,并更新了会话中的属性。

6.3 响应头处理

在handle方法处理完毕后,handleInternal方法还对响应头进行了处理:

if (!response.containsHeader("Cache-Control")) {if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);}else {prepareResponse(response);}}return mav;

getSessionAttributesHandler返回了当前处理器类型对应的SessionAttributesHandler,如果没有就新建一个,不过实际上在创建ModelFactory的时候就已经调用过这个方法了,也就是说,进行到响应头处理这一步时,是肯定存在SessionAttributesHandler的,而且在上一步最后获取处理结果时,调用的updateModel已经将会话属性sessionAttributes保存进去了。也就是说,除非会话中不包含任何属性,否则是肯定会进入applyCacheSeconds分支的。

applyCacheSeconds方法中,最外层的if语句判断的两个条件,默认情况下都不成立,即会进入else分支。cacheSeconds参数(即 RequestMappingHandlerAdapter 的 cacheSecondsForSessionAttributeHandlers 属性)默认为0,useCacheControlNoStore默认为true,所以cControl在默认情况下是通过noStore方法创建的一个noStore属性为true的实例:

protected final void applyCacheSeconds(HttpServletResponse response, int cacheSeconds) {if (this.useExpiresHeader || !this.useCacheControlHeader) {if (cacheSeconds > 0) {cacheForSeconds(response, cacheSeconds);}else if (cacheSeconds == 0) {preventCaching(response);}}else {CacheControl cControl;if (cacheSeconds > 0) {cControl = CacheControl.maxAge(cacheSeconds, TimeUnit.SECONDS);if (this.alwaysMustRevalidate) {cControl = cControl.mustRevalidate();}}else if (cacheSeconds == 0) {cControl = (this.useCacheControlNoStore ? CacheControl.noStore() : CacheControl.noCache());}else {cControl = CacheControl.empty();}applyCacheControl(response, cControl);}}

applyCacheControl就是根据传入的CacheControl对象,对response进行修改:

protected final void applyCacheControl(HttpServletResponse response, CacheControl cacheControl) {String ccValue = cacheControl.getHeaderValue();if (ccValue != null) {response.setHeader("Cache-Control", ccValue);if (response.containsHeader("Pragma")) {response.setHeader("Pragma", "");}if (response.containsHeader("Expires")) {response.setHeader("Expires", "");}}}

至于prepareResponse分支,实际也调用了applyCacheControl和applyCacheSeconds两个方法:

protected final void prepareResponse(HttpServletResponse response) {if (this.cacheControl != null) {applyCacheControl(response, this.cacheControl);}else {applyCacheSeconds(response, this.cacheSeconds);}if (this.varyByRequestHeaders != null) {for (String value : getVaryRequestHeadersToAdd(response, this.varyByRequestHeaders)) {response.addHeader("Vary", value);}}}

6.4 doDispatch的异步处理

在6.2中,如果请求需要异步处理,则不会获取处理结果,而是直接返回,在doDispatch中也是这样处理的:

if (asyncManager.isConcurrentHandlingStarted()) {return;}

不过doDispatch在finally块中又进行了一次处理:

finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}

mappedHandler在applyAfterConcurrentHandlingStarted方法中,调用了AsyncHandlerInterceptor实例对请求和响应进行处理。

6.5 设置默认视图名 —— applyDefaultViewName

这里的逻辑很简单,假如逻辑视图不是视图引用(即其view属性是个String对象),则用RequestToViewNameTranslator生成一个默认名称:

private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {if (mv != null && !mv.hasView()) {String defaultViewName = getDefaultViewName(request);if (defaultViewName != null) {mv.setViewName(defaultViewName);}}}

处理分派结果 —— processDispatchResult

处理器执行链对请求处理完成后,产生了ModelAndView逻辑视图对象,但是:

逻辑视图还不是我们看到的网页或数据,还需要进一步渲染;如果处理过程中出现了异常,也需要进行集中处理;afterCompletion方法还没有触发

因此Spring MVC提供了processDispatchResult对上述三个问题进行处理。

7.1 异常处理

在doDispatch方法中,如果捕获了异常,就把异常赋给dispatchException对象,通过方法调用传递过来:

catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {dispatchException = new NestedServletException("Handler dispatch failed", err);}

在processDispatchResult方法最开始,就对异常进行了处理:

boolean errorView = false;if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}

这一过程是用异常视图替换原始视图的过程,核心逻辑在processHandlerException方法,实际就是调用所有注册了的处理器异常解析器的resolveException方法产生异常视图,并对请求属性进行配置:

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {ModelAndView exMv = null;if (this.handlerExceptionResolvers != null) {for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {exMv = resolver.resolveException(request, response, handler, ex);if (exMv != null) {break;}}}if (exMv != null) {if (exMv.isEmpty()) {request.setAttribute(EXCEPTION_ATTRIBUTE, ex);return null;}if (!exMv.hasView()) {String defaultViewName = getDefaultViewName(request);if (defaultViewName != null) {exMv.setViewName(defaultViewName);}}WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());return exMv;}throw ex;}

7.2 页面渲染 —— render方法

在方法的开头,先使用本地化解析器决定了本地化属性:

Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());response.setLocale(locale);

然后根据逻辑视图中,view对象究竟是视图引用还是名称引用,采用不同的处理方式。首先是对名称引用的处理:

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() + "'");}

需要靠名称解析出实际的视图对象,实质就是调用合适的ViewResolver的resolveViewName方法:

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;}

如果逻辑视图采用视图引用,即内部已包含View对象,直接取出就可以。

接下来,为response设置HTTP状态码,并调用View的render方法进行页面渲染。以InternalResourceView为例,其render方法实现在AbstractView中:

public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);prepareResponse(request, response);renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);}

prepareResponse方法虽然不是空方法,但是由于if条件永远不满足,所以实际不做任何处理。实际起作用的只有createMergedOutputModel和renderMergedOutputModel两个方法。

首先看前者,该方法同样实现在AbstractView类,作用是将静态和动态变量添加到模型中:

protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {Map<String, Object> pathVars = (this.exposePathVariables ?(Map<String, Object>) request.getAttribute(View.class.getName() + ".pathVariables") : null);int size = this.staticAttributes.size();size += (model != null ? model.size() : 0);size += (pathVars != null ? pathVars.size() : 0);Map<String, Object> mergedModel = new LinkedHashMap<>(size);mergedModel.putAll(this.staticAttributes);if (pathVars != null) {mergedModel.putAll(pathVars);}if (model != null) {mergedModel.putAll(model);}if (this.requestContextAttribute != null) {mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));}return mergedModel;}

exposePathVariables变量默认为true。可以看到,添加属性时,按照先静态属性再动态属性的顺序,即动态属性优先级更高。

然后看后者,它的一个参数是getRequestToExpose的结果,它的两个if判断条件默认都不满足,即不作处理直接返回:

protected HttpServletRequest getRequestToExpose(HttpServletRequest originalRequest) {if (this.exposeContextBeansAsAttributes || this.exposedContextBeanNames != null) {WebApplicationContext wac = getWebApplicationContext();Assert.state(wac != null, "No WebApplicationContext");return new ContextExposingHttpServletRequest(originalRequest, wac, this.exposedContextBeanNames);}return originalRequest;}

renderMergedOutputModel方法实现在InternalResourceView类,它首先将模型数据绑定到request上:

exposeModelAsRequestAttributes(model, request);exposeHelpers(request);

第二个方法是个扩展点,没有默认实现。接下来解析View的位置:

protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response) throws Exception {String path = getUrl();Assert.state(path != null, "'url' not set");if (this.preventDispatchLoop) {String uri = request.getRequestURI();if (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) {throw new ServletException("Circular view path [" + path + "]: would dispatch back " + "to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " + "(Hint: This may be the result of an unspecified view, due to default view name generation.)");}}return path;}

这里抛出的异常肯定很多人都遇到过,使用SpringBoot时,如果没有配置themeleaf且有如下代码,在访问时就会抛出这个异常:

@Controllerpublic class TestController{@RequestMapping("/index")public String index(){return "index";}}

不过在Spring MVC中,由于preventDispatchLoop默认为false,所以在此处不会触发回环检查。

接下来就是获取一个RequestDispatcher(使用InternalResourceView时,一般对应JSP文件),由它将刚刚设置到request中的模型数据发送到实际页面:

// Obtain a RequestDispatcher for the target resource (typically a JSP).RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);if (rd == null) {throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!");}// If already included or response already committed, perform include, else forward.if (useInclude(request, response)) {response.setContentType(getContentType());rd.include(request, response);}else {// Note: The forwarded resource is supposed to determine the content type itself.rd.forward(request, response);

至此,页面渲染部分完成。

在这之后,调用处理器执行链的afterCompletion方法,处理异步事件或调用cleanupMultipart清理资源。

三、重点总结一下视图的解析流程

首先Springboot默认不支持jsp引擎视图处理的方式

视图解析:

• 返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forward(request, response);

• 返回值以 redirect: 开始: new RedirectView() --> render就是重定向

• 返回值是普通字符串: new ThymeleafView()视图解析原理流程 目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。processDispatchResult 处理派发结果

文章部分转自

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。