Spring源码学习:SpringMVC(4)DispatcherServlet请求入口分析

目录

  • 前言
  • HttpServlet &FrameworkServlet
    • HttpServlet #service
    • FrameworkServlet#processRequest
  • DispatcherServlet#doService
    • doDispatch
    • checkMultipart
    • getHandler
      • AbstractHandlerMapping#getHandler
      • RequestMappingInfoHandlerMapping#getHandlerInternal
      • AbstractHandlerMethodMapping#getHandlerInternal
      • HandlerMethod#createWithResolvedBean
      • AbstractHandlerMapping#getHandlerExecutionChain
    • noHandlerFound
    • getHandlerAdapter
    • Last-Modified 的缓存处理
      • SimpleControllerHandlerAdapter#getLastModified
      • RequestMappingHandlerAdapter#getLastModifiedInternal
    • 拦截器三个方法的调用
      • HandlerExecutionChain#applyPreHandle
      • HandlerExecutionChain#applyPostHandle
      • HandlerExecutionChain#triggerAfterCompletion
    • HandlerAdapter#handle
      • handleInternal
      • invokeHandlerMethod
    • applyDefaultViewName
      • getDefaultViewName
    • processDispatchResult
      • processHandlerException
        • AbstractHandlerExceptionResolver#resolveException
        • getExceptionHandlerMethod
      • render
  • 总结

前言

​ 通过前面的分析,我们知道DispatcherServlet其本质还是Servlet,那么当客户端的请求到达时,根据Servlet生命周期,其应该会调用其或者其父类中的service方法。

在其父类FrameworkServlet中我们找到了service方法

	protected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {/**	获取HttpMethod类型,*	HttpMethod为枚举类,支持的Http请求类型有GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE*/HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());if (httpMethod == HttpMethod.PATCH || httpMethod == null) {// 若方法为 PATCH 方法或为空则单独处理processRequest(request, response);}else {super.service(request, response);}}

这里有两个方法,一个是processRequest(request, response)super.service(request, response),这里的super就是HttpServlet,我们分别看一下

HttpServlet &FrameworkServlet

HttpServlet #service

protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{String method = req.getMethod();//如果是getif (method.equals(METHOD_GET)) {//lastModified 缓存判断long lastModified = getLastModified(req);if (lastModified == -1) {// servlet doesn't support if-modified-since, no reason// to go through further expensive logicdoGet(req, resp);} else {long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);if (ifModifiedSince < lastModified) {// If the servlet mod time is later, call doGet()// Round down to the nearest second for a proper compare// A ifModifiedSince of -1 will always be lessmaybeSetLastModified(resp, lastModified);doGet(req, resp);} else {resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);}}} else if (method.equals(METHOD_HEAD)) {long lastModified = getLastModified(req);maybeSetLastModified(resp, lastModified);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 {//// Note that this means NO servlet supports whatever// method was requested, anywhere on this server.//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);}}

​ 上面几个方法最常用的就是 doGet()doPost() 。这两个方法被 FrameworkServlet 重写了。我们来看看在 FrameworkServlet 中的实现。

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);
}@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);
}

​ 我们可以很清楚的看到,对于大部分的请求,还是依赖于 HttpServlet#service(HttpServletRequest, HttpServletResponse) 来进行一个请求的分发。对于我们常见的 doGet() 和 doPost() 方法都是直接调用 processRequest(request, response);, 而processRequest方法 的具体实现在FrameworkServlet#processRequest 中 。

FrameworkServlet#processRequest

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {long startTime = System.currentTimeMillis();// 记录抛出的异常~~~(若有的话)Throwable failureCause = null;//国际化设置// 1 提取当前线程的 LocaleContext  属性LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();// 2. 根据当前的request 创建对应的 LocaleContext ,并绑定到当前线程LocaleContext localeContext = buildLocaleContext(request);//构建ServletRequestAttributes对象//3. 提取当前线程的 RequestAttributes 属性RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();//4.根据当前的request 创建对应的 RequestAttributes ,并绑定到当前线程ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);// 拿到异步管理器。这里是首次获取,会new WebAsyncManager(),然后放到request的attr里面WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);//这里需要注意:给异步上下文恒定注册了RequestBindingInterceptor这个拦截器(作用:绑定当前的request、response、local等)asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());//这句话很明显,就是吧request和Local上下文、RequestContext绑定initContextHolders(request, localeContext, requestAttributes);try {//5.模版设计模式:由子类DispatcherServlet去实现实际逻辑doService(request, response);}catch (ServletException | IOException ex) {failureCause = ex;throw ex;}catch (Throwable ex) {failureCause = ex;throw new NestedServletException("Request processing failed", ex);}finally {//这个时候已经全部处理完成,视图已经渲染了//doService()方法完成后,重置上下文,也就是解绑// 6. 请求结束,恢复线程原状resetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}logResult(request, response, failureCause, asyncManager);//关键:不管执行成功与否,都会发布一个事件,我处理了这个请求(有需要监听的,就可以监听这个事件了,每次请求都会有)publishRequestHandledEvent(request, response, startTime, failureCause);}}

​ 由于逻辑都被封装到 doService(request, response); 中,所以这里还是比较简单。而doService又被 DispatcherServlet实现了。因此我们这里来看看 DispatcherServlet#doService

DispatcherServlet#doService

@Overrideprotected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {logRequest(request);// Keep a snapshot of the request attributes in case of an include,// to be able to restore the original attributes after the include./** 如果当前请求是一个 include request,如:<jsp:incluede page="xxx.jsp"/>* 则为此请求属性建立快照,以便include request结束后能够将其恢复*/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.// 说得很清楚,把一些常用对象放进请求域  方便Handler里面可以随意获取// Spring上下文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 {// 【重要】 真正开始处理http请求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);}}}}

​ 很明显,这 “祖孙三代” 一层一层传递,终于传递到了DispatcherServlet 手里,这里我们直接开始看doDispatch(request, response);,这里才是核心逻辑的所在!!!

doDispatch

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {// 此处用processedRequest  需要注意的是:若是处理上传,processedRequest 将和request不再指向同一对象HttpServletRequest processedRequest = request;// 链处理器HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {//1.checkMultipart 判断是否是上传需求。且看下面的具体分析://如果请求是POST请求,并且请求头中的Context-Type是以multipart/开头的就认为是文件上传的请求processedRequest = checkMultipart(request);// 标记一下:是否是文件上传的request了multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.// 2.查找当前请求对应的handler,包括Handler(控制器)本身和Handler拦截器mappedHandler = getHandler(processedRequest);// 未能找到对应的handler,抛出NoHandlerFoundException异常并返回404if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.// 3.查找当前请求对应的HandlerAdapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.// 4.处理last-modified请求头,如果当前请求支持的话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;}}// 5.应用前置拦截器:执行注册拦截器的preHandle方法// 如果有拦截器返回false,则表明该拦截器已经处理了返回结果,直接返回;if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.// 6.调用HandlerAdapter的handler方法,真正开始处理Controller// *****真正执行我们自己书写的controller方法的逻辑。返回一个ModelAndViewmv = ha.handle(processedRequest, response, mappedHandler.getHandler());// 7.如果当前请求是异步处理,直接返回// 如果异步启动了,这里就先直接返回了,也就不会再执行拦截器PostHandle之类的if (asyncManager.isConcurrentHandlingStarted()) {return;}//8. 为返回值设定默认视图名,如果当前返回值中不包含视图名的话applyDefaultViewName(processedRequest, mv);// 9.应用已注册拦截器的后置方法:执行所有的拦截器的postHandle方法,并且把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);}// 10.处理分发调用结果,如视图模型解析、返回等工作// 处理返回结果,包括处理异常、渲染页面,发出完成通知触发Interceptor的afterCompletionprocessDispatchResult(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);}}}}

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);// Keep processing error dispatch with regular request handle below}else {throw ex;}}}}// If not returned before: return original request.return request;}

​ 对于请求的处理,Spring首先考虑的是对 Multipart 多文件上传的处理,如果是 MultipartContent 类型的request 则转换request 为 MultipartHttpServletRequest 类型的request。简单来说,就是判断是否是文件请求。

PS : 注意一下这个赋值代码

multipartRequestParsed = (processedRequest != request);

这里如果为false就证明不是文件上传请求,因为如果为true的话就证明request已经变成了MultipartHttpServletRequest,不是一开始的了

getHandler

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

AbstractHandlerMapping#getHandler

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {// 1.获取当前请求对应的handler,该方法供子类实现Object handler = getHandlerInternal(request);// 未能获取到对应的handler,则使用默认的defaultHandlerif (handler == null) {handler = getDefaultHandler();}// 两者同时未找到,则返回nullif (handler == null) {return null;}// Bean name or resolved handler?// 2.如果获取到的handler是String类型,则以handler为beanName,从IOC容器中获取其实例if (handler instanceof String) {String handlerName = (String) handler;handler = obtainApplicationContext().getBean(handlerName);}// 3.根据handler和request获取对应的HandlerExecutionChain实例// 会将handler封装到HandlerExecutionChain对象中,// 并将系统和自定义的拦截器加入到HandlerExecutionChain中HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);if (logger.isTraceEnabled()) {logger.trace("Mapped to " + handler);}else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {logger.debug("Mapped to " + executionChain.getHandler());}//是不是cors请求,cors是跨域请求if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);//方法上@CrossOrigin注解信息CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);config = (config != null ? config.combine(handlerConfig) : handlerConfig);//往拦截器链里添加new CorsInterceptor(config)executionChain = getCorsHandlerExecutionChain(request, executionChain, config);}return executionChain;}

getHandlerInternal(request)该方法在 AbstractHandlerMethodMapping#getHandlerInternal 中没有具体实现,是供不同的 HandlerMapping 子类自己实现的。这里我们直接看 RequestMappingInfoHandlerMapping#getHandlerInternal

RequestMappingInfoHandlerMapping#getHandlerInternal

	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);try {return super.getHandlerInternal(request);}finally {ProducesRequestCondition.clearMediaTypesAttribute(request);}}

super.getHandlerInternal(request)调用的是AbstractHandlerMethodMapping#getHandlerInternal

AbstractHandlerMethodMapping#getHandlerInternal

	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {//从request对象中获取uri,解析请求路径String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);// 加只读锁request.setAttribute(LOOKUP_PATH, lookupPath);this.mappingRegistry.acquireReadLock();try {// 根据请求路径和当前请求对象,获取最佳匹配的HandlerMethodHandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);// 获取当前Controller的实例,并将获取到的实例封装至HandlerMethod对象中return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);}finally {// 释放只读锁this.mappingRegistry.releaseReadLock();}}...protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {List<Match> matches = new ArrayList<>();// 这里的lookupPath就是请求的url,从urlLookup中获取key完全匹配的List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);// 如果不为空的话,判断其它的信息是否符合,将符合的添加到matches变量中if (directPathMatches != null) {addMatchingMappings(directPathMatches, matches, request);}// 如果上面没有获取到匹配的路径,则只能遍历所有的 mapping。// 由于会遍历所有的 RequestMapping。所以性能会随着 RequestMapping数量的增加降低if (matches.isEmpty()) {// No choice but to go through all mappings...// 如果为空的话,从mappingLookup中查找所有信息是否有符合的addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);}if (!matches.isEmpty()) {// 如果匹配多个的话,会根据相关的算法找到最合适的那个,然后返回它的处理方法Match bestMatch = matches.get(0);// 如果合适的 Mapping 不止一个,则筛选出最合适的if (matches.size() > 1) {Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));matches.sort(comparator);bestMatch = matches.get(0);if (logger.isTraceEnabled()) {logger.trace(matches.size() + " matching mappings: " + matches);}if (CorsUtils.isPreFlightRequest(request)) {return PREFLIGHT_AMBIGUOUS_MATCH;}Match secondBestMatch = matches.get(1);//如果两个RequestMappinginfo什么都相同,报错if (comparator.compare(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(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);handleMatch(bestMatch.mapping, lookupPath, request);return bestMatch.handlerMethod;}else {// 如果没有匹配的话,会直接根据名字找一次// 如果找到的话,会对比其他信息,只要有不符合的就会抛出异常// 如果没有找到,直接返回nullreturn handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);}}private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {for (T mapping : mappings) {T match = getMatchingMapping(mapping, request);if (match != null) {matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));}}}//  this.mappingRegistry.getMappingsByUrl(lookupPath);  // 也就是 AbstractHandlerMethodMapping.MappingRegistry#getMappingsByUrl@Nullablepublic List<T> getMappingsByUrl(String urlPath) {return this.urlLookup.get(urlPath);}

​ 可以看到逻辑主要是通过url从mappingRegistry中获取指定的requestMappingInfo集合,为什么是集合呢,因为可能会存在restFul风格的接口了,设置的url都相同,但是请求方式不同,这种情况集合中就会存在多个,正常如果不是restFul风格的话其实集合就一个元素,

  • List directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); 这个方法是非常直观的根据URL来获取,springMVC会在初始化的时候建立URL和相应RequestMappingInfo的映射。如果不是restful接口,这里就可以直接获取到了。

  • 如果从mappingRegistry中已经获取到,则调用方法addMatchingMappings(directPathMatches, matches, request)进行匹配校验。

  • 如果mappingRegistry中未获取到匹配方法信息,则调用方法addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); 进行全局(all mappings)扫描匹配。且会把所有的RequestMappingInfo都遍历完才会停止,也就是说项目中的@RequestMapping方法越多,这个匹配的效率就越低,性能越差。但是我感觉呀,一般情况下其实都不会走到这里,除非调试阶段输错了url,正常其实上面两步完了以后,基本就能找到了

  • 如果合适的 Mapping 不止一个,则筛选出最合适的,到了最后如果前两个一模一样则报错

HandlerMethod#createWithResolvedBean

public HandlerMethod createWithResolvedBean() {Object handler = this.bean;if (this.bean instanceof String) {Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");String beanName = (String) this.bean;handler = this.beanFactory.getBean(beanName);}return new HandlerMethod(this, handler);
}

​ 如果提供的实例包含 Bean 名称而不是对象实例,则先创建好相应的bean之后再返回,这个方法主要是将再将HandlerMethod封装一层,之前bean如果是String类型的beanName的话那就替换成对象Bean,以便之后反射调用方法

AbstractHandlerMapping#getHandlerExecutionChain

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {//判断handler是不是执行器链,如果不是创建一个执行器链HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));//获取uriString lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);//包装拦截器//遍历所有的拦截器,如果拦截器匹配符则加入到执行链中。adaptedInterceptors 是在 Mapping 初始化的时候加载的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;}

​ 主要目的是将配置中对应的拦截器加入到执行链中,以保证这些拦截器可以有效的作用于目标对象

​ 关于 adaptedInterceptors、interceptors 两个拦截器集合:adaptedInterceptors 是 AbstractHandlerMapping 在初始化的时候实现了 ApplicationContextAware 接口,在 ApplicationObjectSupport#setApplicationContext 方法中调用 initApplicationContext 方法,进行了 adaptedInterceptors 的初始化。而 interceptors 则可以通过 set 方法进行注入。

	protected void initApplicationContext() throws BeansException {extendInterceptors(this.interceptors);detectMappedInterceptors(this.adaptedInterceptors);initInterceptors();}protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), MappedInterceptor.class, true, false).values());}protected void initInterceptors() {if (!this.interceptors.isEmpty()) {for (int i = 0; i < this.interceptors.size(); i++) {Object interceptor = this.interceptors.get(i);if (interceptor == null) {throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");}this.adaptedInterceptors.add(adaptInterceptor(interceptor));}}}

​ 到这里就获取到了handler执行器链了,里面即有我们要执行的handler,也有相对应的拦截器,如果没找到就会进去下面的方法报异常

noHandlerFound

​ 正常情况下,每一个请求都应该对应一个 Handler,因为每个请求都应该在后台有对应的处理逻辑。而逻辑的实现就是在Handler 中。但是,如果没有URL匹配的Handler,我们可以通过设置默认的Handler 来解决这一问题,不过如果没有设置默认的Handler。则只能通过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);}
}

getHandlerAdapter

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

​ 这一步的目的是根据 Handler 寻找对应的 HandlerAdapter。这里使用了适配器模式,遍历所有的 Adapter。根据 HandlerAdapter#supports 方法来判断是否支持当前Handler 的解析,如果支持,则返回。

我们这里返回的是RequestMappingHandlerAdapter,其判定条件如下:

	@Overridepublic final boolean supports(Object handler) {// 	supportsInternal((HandlerMethod) handler)) 返回 truereturn (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));}

Last-Modified 的缓存处理

Last-Modified机制流程如下(以下内容来源于百度百科):

  1. 在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是客户端请求的资源,同时响应体里有一个Last-Modified的属性标记此文件在服务器端最后被修改的时间。Last-Modified格式类似这样:Last-Modified : Fri , 12 May 2006 18:53:33 GMT

  2. 客户端第二次请求此URL时,根据HTTP协议的规定,浏览器会向服务器传送If-Modified-Since报头,询问该时间之后文件是否有被修改过:If-Modified-Since : Fri , 12 May 2006 18:53:33 GMT
    如果服务器端的资源没有变化,则自动返回 HTTP 304(Not Changed.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。

Spring通过实现 LastModified 接口即可完成Last-Modified 机制,如下:

PS : 这里是通过实现Controller接口来实现的,我们在日常开发一般都是使用的@RestController+@RequestMapping注解那种方式,那种方式的话就不能只是通过实现 LastModified 接口来完成了,至于细节可以看这篇大佬的博客:https://blog.csdn.net/qq_36882793/article/details/109515781

@Component("/testLastModifiedController")
public class TestLastModifiedController implements Controller, LastModified {private long lastModified;@Overridepublic ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {return new ModelAndView("/hello.html");}@Overridepublic long getLastModified(HttpServletRequest request) {if (lastModified == 0L){lastModified = System.currentTimeMillis();}return lastModified;}
}

在一次请求成功后,第二次请求返回的状态码 304。

在这里插入图片描述

好了,了解了last-Modified机制我们继续来看doDispatcher中相关逻辑:

	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {...HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());String method = request.getMethod();boolean isGet = "GET".equals(method);//主要请求方法是get或者head都会进入if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}...
}

mappedHandler就是上面getHandler方法返回的执行器链,可以看到调用了适配器的getLastModified方法,所以主要逻辑在适配器的getLastModified方法中,

SimpleControllerHandlerAdapter#getLastModified

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

​ 这个适配器其实就是对应了实现Controller接口的控制器,如果你实现了接口,那就调用你重写的逻辑,反之默认返回-1。

RequestMappingHandlerAdapter#getLastModifiedInternal

ps : RequestMappingHandlerAdapter#getLastModified 方法会调用 RequestMappingHandlerAdapter#getLastModifiedInternal 方法

	protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) {return -1;}

我们在getHandlerAdapter方法中可以得知我们通过@RequestMapping注解方式写的接口最后适配器就是获取的RequestMappingHandlerAdapter,它的lastModified直接返回的就是-1,也就是我们这种方式根本没办法修改 lastModified。

接着看下checkNotModified(lastModified)方法

	@Overridepublic boolean checkNotModified(long lastModifiedTimestamp) {return checkNotModified(null, lastModifiedTimestamp);}@Overridepublic boolean checkNotModified(@Nullable String etag, long lastModifiedTimestamp) {HttpServletResponse response = getResponse();//notModified其实表示资源没有被修改的意思,默认是false,没有被修改直接返回true//如果 notModified  已经true || 返回状态码已经不是200直接返回 notModifiedif (this.notModified || (response != null && HttpStatus.OK.value() != response.getStatus())) {return this.notModified;}// Evaluate conditions in order of precedence.// See https://tools.ietf.org/html/rfc7232#section-6//解析校验 If-Unmodified-Since 请求头。这个请求头和If-Modified-Since请求头意义相反if (validateIfUnmodifiedSince(lastModifiedTimestamp)) {if (this.notModified && response != null) {response.setStatus(HttpStatus.PRECONDITION_FAILED.value());}return this.notModified;}//校验 If-None-Match 请求头。这是针对 Etag 缓存。boolean validated = validateIfNoneMatch(etag);if (!validated) {validateIfModifiedSince(lastModifiedTimestamp);}// Update response// 更新 Response。包括状态码等信息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(HttpHeaders.LAST_MODIFIED)) == -1) {response.setDateHeader(HttpHeaders.LAST_MODIFIED, lastModifiedTimestamp);}if (StringUtils.hasLength(etag) && response.getHeader(HttpHeaders.ETAG) == null) {response.setHeader(HttpHeaders.ETAG, padEtagIfNecessary(etag));}}}return this.notModified;}private boolean validateIfUnmodifiedSince(long lastModifiedTimestamp) {if (lastModifiedTimestamp < 0) {return false;}long ifUnmodifiedSince = parseDateHeader(HttpHeaders.IF_UNMODIFIED_SINCE);if (ifUnmodifiedSince == -1) {return false;}// We will perform this validation...//上一次的修改小于最后修改时间就可以this.notModified = (ifUnmodifiedSince < (lastModifiedTimestamp / 1000 * 1000));return true;}private boolean validateIfModifiedSince(long lastModifiedTimestamp) {if (lastModifiedTimestamp < 0) {return false;}long ifModifiedSince = parseDateHeader(HttpHeaders.IF_MODIFIED_SINCE);if (ifModifiedSince == -1) {return false;}// We will perform this validation...//上一次的修改时间大于等于最后修改时间就可以this.notModified = ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000);return true;}

拦截器三个方法的调用

HandlerExecutionChain#applyPreHandle

	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;}public HandlerInterceptor[] getInterceptors() {if (this.interceptors == null && this.interceptorList != null) {this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[0]);}return this.interceptors;}

​ 首先获取所有的拦截器,然后依次遍历执行preHandle方法,根据方法返回值来决定是否继续下一个拦截器执行,记录上一个执行成功preHandle方法拦截器的下标,如果返回false就停止执行拦截器的preHandle方法,将执行过preHandle方法的拦截器继续执行它的afterCompletion方法

HandlerExecutionChain#applyPostHandle

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = interceptors.length - 1; i >= 0; i--) {HandlerInterceptor interceptor = interceptors[i];interceptor.postHandle(request, response, this.handler, mv);}}
}

​ 执行完handle方法之后执行,逻辑和上面执行preHandle差不多,但是需要注意的是倒序执行postHandle方法,从末尾往前执行,类似于之前多个AOP通知方法的执行过程

HandlerExecutionChain#triggerAfterCompletion

	void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = this.interceptorIndex; i >= 0; i--) {HandlerInterceptor interceptor = interceptors[i];try {interceptor.afterCompletion(request, response, this.handler, ex);}catch (Throwable ex2) {logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);}}}}

​ 调用拦截器的结束方法,视图呈现之后调用的,或者执行前置拦截方法时返回false也会执行到这里

HandlerAdapter#handle

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

handleInternal

@Overrideprotected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {//定义返回值变量ModelAndView mav;// 1.检测当前请求,验证请求方法合法性和session合法性checkRequest(request);// Execute invokeHandlerMethod in synchronized block if required.// 2.根据synchronizeOnSession值判断,当前请求是否需串行化访问。if (this.synchronizeOnSession) {HttpSession session = request.getSession(false);if (session != null) {// 获取最佳互斥锁,即同步当前回话对象;如未能获取到互斥锁,将返回HttpSession对象本身Object mutex = WebUtils.getSessionMutex(session);//加锁,所有请求串行化synchronized (mutex) {mav = invokeHandlerMethod(request, response, handlerMethod);}}else {// No HttpSession available -> no mutex necessary// 即无最佳互斥锁,也未能获取到HttpSession,则当前回话无需串行化访问mav = invokeHandlerMethod(request, response, handlerMethod);}}else {// No synchronization on session demanded at all...// *** 正常调用处理方法mav = invokeHandlerMethod(request, response, handlerMethod);}// 3.相应信息不包含Cache-Controlif (!response.containsHeader(HEADER_CACHE_CONTROL)) {if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);}else {prepareResponse(response);}}return mav;}

invokeHandlerMethod

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {//对HttpServletRequest进行包装,产生ServletWebRequest处理web的request对象ServletWebRequest webRequest = new ServletWebRequest(request, response);try {//WebDataBinderFactory --> 工厂类,为目标对象创建一个WebDataBinder实例// 1.WebDataBinder继承了DataBinder类,为web请求提供了参数绑定服务WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);// 获取ModelFactory:// 2.ModelFactory可以协助控制器在调用方法之前初始化模型,并在调用之后更新模型ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);// 创建ServletInvocableHandlerMethod对象// 3.ServletInvocableHandlerMethod继承并扩展了InvocableHandlerMethodServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);// 4.尝试设置 参数解析器、返回值解析器if (this.argumentResolvers != null) {invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}invocableMethod.setDataBinderFactory(binderFactory);invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);// 5.创建ModelAndViewContainer,并初始化Model对象ModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);// 6.异步请求相关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);if (asyncManager.hasConcurrentResult()) {Object result = asyncManager.getConcurrentResult();mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];asyncManager.clearConcurrentResult();LogFormatUtils.traceDebug(logger, traceOn -> {String formatted = LogFormatUtils.formatValue(result, !traceOn);return "Resume with async result [" + formatted + "]";});invocableMethod = invocableMethod.wrapConcurrentResult(result);}// 7.反射调用Controller中的具体方法并处理返回值invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}// 8.返回ModelAndView对象return getModelAndView(mavContainer, modelFactory, webRequest);}finally {// 完成请求后续处理,并将当前请求置为未激活webRequest.requestCompleted();}}

​ invocableMethod.invokeAndHandle(webRequest, mavContainer); 这个方法里面比较复杂,暂时先不做说明,简单说下:

  • 通过参数解析器来解析参数值,最后形成一个object类型的数组
  • 调用目标方法
  • 通过返回值解析器来解析返回值

大致流程就是这样,了解即可,这里因为篇幅原因,不做细致的讲解了,后面有机会再详细说吧。。。

applyDefaultViewName

private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {// ModelAndView不为空,但是没有View对象则尝试为其生成一个默认的视图名if (mv != null && !mv.hasView()) {String defaultViewName = getDefaultViewName(request);if (defaultViewName != null) {mv.setViewName(defaultViewName);}}
}

​ 如果没有视图则会会生成一个默认的视图名,其实就是请求URL

getDefaultViewName

	protected String getDefaultViewName(HttpServletRequest request) throws Exception {//这里是不为空的,所以会执行到this.viewNameTranslator.getViewNamereturn (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null);}public String getViewName(HttpServletRequest request) {String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);//this.prefix和this.suffix默认都是空的 transformPath里面主要是对URL做一些截取return (this.prefix + transformPath(lookupPath) + this.suffix);}

processDispatchResult

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);}}// Did the handler return a view to render?// 尝试解析视图和模型;// wasCleared:判断当前模型和视图是否已经被标识为清空,且当前视图和模型是否同时为空if (mv != null && !mv.wasCleared()) {// 解析并呈现视图和模型render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}else {if (logger.isTraceEnabled()) {logger.trace("No view rendering, null ModelAndView returned.");}}if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Concurrent handling started during a forwardreturn;}// 处理注册的后置完成拦截器if (mappedHandler != null) {// Exception (if any) is already handled..mappedHandler.triggerAfterCompletion(request, response, null);}
}

​ 关于spring的几种异常处理方案可以看这篇博客:
Spring源码学习(拓展篇):SpringMVC中的异常处理,我们这里对 @ExpceptionHander+ @ControllerAdvice这种方式来进行分析下,我们都知道最后肯定会调用在全局异常处理类中和当前发生异常相匹配的异常方法来执行,主要逻辑在processHandlerException

processHandlerException

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {// Success and error responses may use different content typesrequest.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);// Check registered HandlerExceptionResolvers...//检查已注册的 HandlerExceptionResolversModelAndView 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;}// We might still need view name translation for a plain error model...if (!exMv.hasView()) {String defaultViewName = getDefaultViewName(request);if (defaultViewName != null) {exMv.setViewName(defaultViewName);}}if (logger.isTraceEnabled()) {logger.trace("Using resolved error view: " + exMv, ex);}else if (logger.isDebugEnabled()) {logger.debug("Using resolved error view: " + exMv);}WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());return exMv;}throw ex;}

​ 我们看到会遍历已经注册的异常解析器,这里是什么时候注册的,其实就是解析mvc:annotation-driven标签的时候注册的,一般会有三个

  • ExceptionHandlerExceptionResolver : 这个其实就是我们使用全局异常处理类的时候会用到的,也就是@ExpceptionHander+ @ControllerAdvice这种方式会使用该类来进行解析,在项目初始化时,ExceptionHandlerExceptionResolver@ControllerAdvice@ExceptionHandler标注的异常处理方法进行缓存,异常-处理方法的映射。

  • ResponseStatusExceptionResolver :解析有@ResponseStatus注解的异常。

  • DefaultHandlerExceptionResolver : 通过配置的异常类和view的对应关系来解析异常。

我们主要来看ExceptionHandlerExceptionResolver

AbstractHandlerExceptionResolver#resolveException
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {if (shouldApplyTo(request, handler)) {prepareResponse(ex, response);ModelAndView result = doResolveException(request, response, handler, ex);if (result != null) {// Print debug message when warn logger is not enabled.if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));}// Explicitly configured warn logger in logException method.logException(ex, request);}return result;}else {return null;}}

​ 可以看到会进入AbstractHandlerExceptionResolver#resolveException方法中,这里面主要逻辑在doResolveException,这是一个模板方法,由子类AbstractHandlerMethodExceptionResolver实现,AbstractHandlerMethodExceptionResolver中的doResolveException会调用ExceptionHandlerExceptionResolver#doResolveHandlerMethodException

ExceptionHandlerExceptionResolver#doResolveHandlerMethodException

protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {//获取异常对应的异常处理方法ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);if (exceptionHandlerMethod == null) {return null;}//设置参数解析器和返回值解析器if (this.argumentResolvers != null) {exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}ServletWebRequest webRequest = new ServletWebRequest(request, response);ModelAndViewContainer mavContainer = new ModelAndViewContainer();try {if (logger.isDebugEnabled()) {logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);}Throwable cause = exception.getCause();if (cause != null) {// Expose cause as provided argument as well//执行目标方法exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);}else {//执行目标方法// Otherwise, just the given exception as-isexceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);}}catch (Throwable invocationEx) {// Any other than the original exception (or its cause) is unintended here,// probably an accident (e.g. failed assertion or the like).if (invocationEx != exception && invocationEx != exception.getCause() && logger.isWarnEnabled()) {logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);}// Continue with default processing of the original exception...return null;}if (mavContainer.isRequestHandled()) {return new ModelAndView();}else {ModelMap model = mavContainer.getModel();HttpStatus status = mavContainer.getStatus();ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);mav.setViewName(mavContainer.getViewName());if (!mavContainer.isViewReference()) {mav.setView((View) mavContainer.getView());}if (model instanceof RedirectAttributes) {Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);}return mav;}}

这里面第一段就是获取异常对应的异常处理方法,获取到了之后就去执行了

getExceptionHandlerMethod
		private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =new LinkedHashMap<>();protected ServletInvocableHandlerMethod getExceptionHandlerMethod(@Nullable HandlerMethod handlerMethod, Exception exception) {Class<?> handlerType = null;if (handlerMethod != null) {// Local exception handler methods on the controller class itself.// To be invoked through the proxy, even in case of an interface-based proxy.handlerType = handlerMethod.getBeanType();//this.exceptionHandlerCache 这个map维护了controller类中加了@ExceptionHandler注解方法的关系ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);if (resolver == null) {resolver = new ExceptionHandlerMethodResolver(handlerType);this.exceptionHandlerCache.put(handlerType, resolver);}Method method = resolver.resolveMethod(exception);if (method != null) {return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);}// For advice applicability check below (involving base packages, assignable types// and annotation presence), use target class instead of interface-based proxy.if (Proxy.isProxyClass(handlerType)) {handlerType = AopUtils.getTargetClass(handlerMethod.getBean());}}//上面其实主要是对controller类内部加了@ExceptionHandler注解方法来进行处理了,不算全局异常处理//下面其实就是对全局异常来进行处理了//this.exceptionHandlerAdviceCache这个map维护了@ControllerAdvice的类中所有修饰了@ExceptionHandler注解方法的关系//map的key为修饰了@ControllerAdvice的类,value为ExceptionHandlerMethodResolver对象,内部有一个methodsfor (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {ControllerAdviceBean advice = entry.getKey();//检测下当前全局异常处理类能否处理当前handler中的请求,一般都是允许的if (advice.isApplicableToBeanType(handlerType)) {ExceptionHandlerMethodResolver resolver = entry.getValue();//resolver中不是有mathods吗,找到和异常相匹配的方法返回就可以了Method method = resolver.resolveMethod(exception);if (method != null) {//advice.resolveBean()返回的就是全局异常处理类的bean对象return new ServletInvocableHandlerMethod(advice.resolveBean(), method);}}}return null;}

可以从源码中看出来获取异常方法主要有两个逻辑,第一是先找controller类内部有没有对应方法,如果没有再看下全局异常处理类中有没有对应的方法,哪个找到了就返回,至此,异常处理就结束了

render

​ 在最后的处理中,一定会涉及页面的跳转问题。而在render(mv, request, response); 完成了页面的跳转。

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();//  如果viewname不为null,则需要通过viewName 解析出来对应的 Viewif (viewName != null) {// We need to resolve the view name.//解析视图名称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() + "'");}}// 如果viewName 为null,则认为 ModelAndView 直接指定了View。不需要解析了。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.isTraceEnabled()) {logger.trace("Rendering view [" + view + "] ");}try {// 设置返回状态码if (mv.getStatus() != null) {response.setStatus(mv.getStatus().value());}// 调用View对象的render方法完成跳转逻辑view.render(mv.getModelInternal(), request, response);}catch (Exception ex) {if (logger.isDebugEnabled()) {logger.debug("Error rendering view [" + view + "]", ex);}throw ex;}
}

resolveViewName 是 通过视图解析器进行视图解析。返回合适视图。具体实现如下。

	// org.springframework.web.servlet.DispatcherServlet#resolveViewName@Nullableprotected 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;}

我们看一下 viewResolver.resolveViewName方法。这里我们看InternalResourceViewResolver#resolveViewName 方法,其方法是在父类AbstractCachingViewResolver#resolveViewName中实现,如下:

	@Override@Nullablepublic View resolveViewName(String viewName, Locale locale) throws Exception {// 如果没有缓存,则直接创建 Viewif (!isCache()) {return createView(viewName, locale);}else {//this.viewAccessCache 操作时不需要加锁的//this.viewCreationCache 操作时需要加锁的// 从不需要加锁的map缓存中获取视图Object cacheKey = getCacheKey(viewName, locale);View view = this.viewAccessCache.get(cacheKey);//如果缓存中没有则加锁去创建,最后放到两个map中来if (view == null) {synchronized (this.viewCreationCache) {view = this.viewCreationCache.get(cacheKey);if (view == null) {// Ask the subclass to create the View object.view = createView(viewName, locale);if (view == null && this.cacheUnresolved) {view = UNRESOLVED_VIEW;}if (view != null && this.cacheFilter.filter(view, viewName, locale)) {this.viewAccessCache.put(cacheKey, view);this.viewCreationCache.put(cacheKey, view);}}}}else {if (logger.isTraceEnabled()) {logger.trace(formatKey(cacheKey) + "served from cache");}}//返回视图return (view != UNRESOLVED_VIEW ? view : null);}}

createView 方法 被 UrlBasedViewResolver 重写了。UrlBasedViewResolver#createView具体如下:

protected View createView(String viewName, Locale locale) throws Exception {// If this resolver is not supposed to handle the given view,// return null to pass on to the next resolver in the chain.// 如果当前视图解析器无法解析该视图,则返回nullif (!canHandle(viewName, locale)) {return null;}// Check for special "redirect:" prefix.// 处理前缀为  "redirect:" (重定向)的情况if (viewName.startsWith(REDIRECT_URL_PREFIX)) {String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());RedirectView view = new RedirectView(redirectUrl,isRedirectContextRelative(), isRedirectHttp10Compatible());String[] hosts = getRedirectHosts();if (hosts != null) {view.setHosts(hosts);}return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);}// Check for special "forward:" prefix.// 处理前缀为  "forward:" 的情况if (viewName.startsWith(FORWARD_URL_PREFIX)) {String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());InternalResourceView view = new InternalResourceView(forwardUrl);return applyLifecycleMethods(FORWARD_URL_PREFIX, view);}// Else fall back to superclass implementation: calling loadView.// 调用父类的方法创建视图return super.createView(viewName, locale);}

super.createView(viewName, locale); 调用 AbstractCachingViewResolver#createView 如下:

	protected View createView(String viewName, Locale locale) throws Exception {return loadView(viewName, locale);}// org.springframework.web.servlet.view.UrlBasedViewResolver#loadView@Overrideprotected View loadView(String viewName, Locale locale) throws Exception {AbstractUrlBasedView view = buildView(viewName);View result = applyLifecycleMethods(viewName, view);return (view.checkResource(locale) ? result : null);}// org.springframework.web.servlet.view.UrlBasedViewResolver#buildViewprotected AbstractUrlBasedView buildView(String viewName) throws Exception {Class<?> viewClass = getViewClass();Assert.state(viewClass != null, "No view class");AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);// 设置视图 url 添加前缀和后缀view.setUrl(getPrefix() + viewName + getSuffix());view.setAttributesMap(getAttributesMap());String contentType = getContentType();if (contentType != null) {view.setContentType(contentType);}String requestContextAttribute = getRequestContextAttribute();if (requestContextAttribute != null) {view.setRequestContextAttribute(requestContextAttribute);}Boolean exposePathVariables = getExposePathVariables();if (exposePathVariables != null) {view.setExposePathVariables(exposePathVariables);}Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();if (exposeContextBeansAsAttributes != null) {view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);}String[] exposedContextBeanNames = getExposedContextBeanNames();if (exposedContextBeanNames != null) {view.setExposedContextBeanNames(exposedContextBeanNames);}return view;}

这里设置了视图的url,拼接了前缀和后缀,最后返回。

​ 接下来继续回到DispatcherServlet中的render方法中,拿到了view对象,调用view对象的render方法,在 org.springframework.web.servlet.view.AbstractView#render 中完成了视图跳转。对于ModelView 的使用,我们可以将一些属性放入其中,然后在页面上通过 JSTL 语法或者 request 获取属性,这个功能的实现就是在这里完成的。实现原理很简单,就是将要用到的属性方法request中,以便在其他地方可以获取到。

public void render(@Nullable Map<String, ?> model, HttpServletRequest request,HttpServletResponse response) throws Exception {if (logger.isDebugEnabled()) {logger.debug("View " + formatViewName() +", model " + (model != null ? model : Collections.emptyMap()) +(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));}// 合并模型Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);// 如果当前请求为下载的话,预先处理请求头prepareResponse(request, response);// 处理页面跳转。同时将 mergedModel  保存到request中renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);}protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {// Expose the model object as request attributes.// 曝光模型exposeModelAsRequestAttributes(model, request);// Expose helpers as request attributes, if any.// 空的模板方法 //todoexposeHelpers(request);// Determine the path for the request dispatcher.// 获取转发路径String dispatcherPath = prepareForRendering(request, response);// Obtain a RequestDispatcher for the target resource (typically a JSP).// 获取可应用于 forward/include 的RequestDispatcheRequestDispatcher 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.// 处理includeif (useInclude(request, response)) {response.setContentType(getContentType());if (logger.isDebugEnabled()) {logger.debug("Including [" + getUrl() + "]");}rd.include(request, response);}// 处理转发else {// Note: The forwarded resource is supposed to determine the content type itself.if (logger.isDebugEnabled()) {logger.debug("Forwarding to [" + getUrl() + "]");}rd.forward(request, response);}}protected void exposeModelAsRequestAttributes(Map<String, Object> model,HttpServletRequest request) throws Exception {model.forEach((name, value) -> {if (value != null) {request.setAttribute(name, value);}else {request.removeAttribute(name);}});}

​ 可以看到在view对象中又调用了renderMergedOutputModel方法,这个方法里首先会曝光模型,就是将我们向model中存放的数据给放到request域中,这样jsp页面就可以通过requestScope来获取了,最后转发到相应页面即可

总结

  1. 用户向服务器发送HTTP请求,请求被Spring中的DispatcherServlet给捕获,最后会进入service方法中来进行处理
  2. dispatcherServlet先通过handlerMapping获取当前request对应的HandlerExecutionChain,也就是执行器链,里面包含了请求对应的目标方法和请求相匹配的拦截器
  3. dispatcherServlet根据获取的handler来选择指定的handlerAdapter,获取到以后首先通过handlerAdapter来执行拦截器中的preHandler方法
  4. 再通过handlerAdapter来执行目标handle方法,执行之前需要先解析参数,这一步需要参数解析器,会返回一个object类型的参数数组,接着反射执行目标方法,执行完以后,通过返回值解析器来解析返回值,最后返回ModelAndView对象
  5. 目标方法执行完以后再执行拦截器中的postHandle方法
  6. 如果目标方法执行出现了异常的话,就会去执行用户自定义的异常处理方法,常用的就是全局异常处理类@ControllerAdvice+@ExceptionHandler,主要是通过异常解析器来完成,因为异常解析器中已经维护了异常和指定方法的关系,key为异常,value为对应方法,调用即可
  7. 通过异常解析器来解析视图,包括对视图名的拼接,以及将model中的数据放到request域中,dispathcerServlet将解析好的视图返回给客户端
    在这里插入图片描述
    如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎各位大佬指正

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/440290.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Dit架构 diffusion范式分类+应用

1.ping 网址 2.ssh nscc/l20 3.crtl,打开vscode的setting 4.win 10修改ssh配置文件及其密钥权限为600 - 晴云孤魂 - 博客园 整体来看&#xff1a; 使用transformer作为其主干网络&#xff0c;代替了原先的UNet 在latent space进行训练&#xff0c;通过transformer处理潜…

Spring MVC__@RequestMapping注解、获取请求参数、域对象共享数据、视图、Restful

目录 一、RequestMapping注解1、RequestMapping注解的功能2、RequestMapping注解的位置3、RequestMapping注解的value属性4、RequestMapping注解的method属性5、RequestMapping注解的params属性&#xff08;了解&#xff09;6、RequestMapping注解的headers属性&#xff08;了解…

动手学大模型应用开发之大模型简介

动手学大模型应用开发之大模型简介 主要学习目标什么是大语言模型大模型的能力和特点涌现能力作为基座模型支持多元应用的能力支持对话作为统一入口的能力大模型特点 常见大模型ChatGpt通义千问 LangChainLangChain的核心模块 总结相关学习链接 主要学习目标 学习如何进行大模…

【实战】Nginx+Lua脚本+Redis 实现自动封禁访问频率过高IP

大家好&#xff0c;我是冰河~~ 自己搭建的网站刚上线&#xff0c;短信接口就被一直攻击&#xff0c;并且攻击者不停变换IP&#xff0c;导致阿里云短信平台上的短信被恶意刷取了几千条&#xff0c;加上最近工作比较忙&#xff0c;就直接在OpenResty上对短信接口做了一些限制&am…

GeoCue与Xer Technologies合作推动无人机测绘技术革新

GeoCue与Xer Technologies合作推动无人机测绘技术革新 近期,LiDAR测绘硬件和软件开发商GeoCue与瑞士长航时混合动力无人机制造商Xer Technologies AG携手合作,成功将GeoCue的TrueView 720 LiDAR和图像传感器集成至Xer X8无人机平台。这一里程碑式的合作不仅标志着无人机测绘技…

Excel下拉菜单制作及选项修改

Excel下拉菜单 1、下拉菜单制作2、下拉菜单修改 下拉框&#xff08;选项菜单&#xff09;是十分常见的功能。Excel支持下拉框制作&#xff0c;通过预设选项进行菜单选择&#xff0c;可以避免手动输入错误和重复工作&#xff0c;提升数据输入的准确性和效率 1、下拉菜单制作 步…

【简码短链】使用Selenium实现UI自动化测试

1.环境准备 Chrome浏览器 版本为版本 129.0.6668.90&#xff08;正式版本&#xff09; &#xff08;64 位&#xff09; 129版本的Chrome浏览器的驱动,将webdriver放到jdk所在的bin目录下 在命令行中输入:chromedriver验证是否成功 打开IDEA,创建Maven项目,在pom.xml导入所需…

idea 同一个项目不同模块如何设置不同的jdk版本

在IntelliJ IDEA中&#xff0c;可以为同一个项目中的不同模块设置不同的JDK版本。这样做可以让你在同一个项目中同时使用多个Java版本&#xff0c;这对于需要兼容多个Java版本的开发非常有用。以下是设置步骤&#xff1a; 打开项目设置&#xff1a; 在IDEA中&#xff0c;打开你…

自建RustDesk服务器:详细步骤与操作指南

在远程办公和协作日益普及的今天&#xff0c;远程桌面软件成为了不可或缺的工具。然而&#xff0c;许多知名的远程桌面软件&#xff0c;在免费使用一段时间后&#xff0c;会通过限制连接数量、时长或在特定网络环境下的可用性来促使用户付费升级&#xff0c;而且其会员非常昂贵…

【stm32】ADC的介绍与使用

ADC的介绍与使用 1、ADC介绍2、逐次逼近型ADC3、ADC电路4、ADC基本结构程序代码编写&#xff1a;ADC 通道和引脚复用的关系 5、转换模式&#xff08;1&#xff09;单次转换&#xff0c;非扫描模式转换流程&#xff1a;程序编写&#xff1a; &#xff08;2&#xff09;连续转换&…

详细分析Spring Security OAuth2中的JwtAccessTokenConverter基本知识(附Demo)

目录 前言1. 基本知识2. Demo3. 实战 前言 java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#xff09;【Java项目】实战CRUD的功能整理&#xff08;持续更新&#xff09; 1. 基本知识 JwtAccessTokenConverter 是 Spring Security OAuth2 中的一…

一、Python(介绍、环境搭建)

一、介绍 Python 是一种高级编程语言&#xff0c;具有简洁易读的语法、丰富的库和强大的功能。Python是解释型语言&#xff0c;运行代码必须依赖安装好的解释器。Python目前存在两个版本&#xff1a;Python2、Python3&#xff08;主流使用&#xff09; 二、环境搭建 1.安装P…

<<迷雾>> 第8章 学生时代的走马灯(3)--走马灯 示例电路

几个首尾相连的触发器使用同一个控制端&#xff0c;能同时触发 info::操作说明 鼠标单击开关切换开合状态 注: 其中 CP 为按钮开关, 每点击一次, Q 的输出前进一级 注: 第一个触发器的输出端 Q 需要先置入高电平. 如果重置了电路, 可外接电源先使第一个 Q 置入高电平. 另: 因为…

深度学习:5种经典神经网络模型介绍

目录 1. LeNet&#xff1a;CNN的鼻祖 2. AlexNet&#xff1a;深度学习的开山之作 3. VGGNet&#xff1a;深度与简洁的结合 4. GoogLeNet&#xff1a;Inception模块的创新 5. ResNet&#xff1a;残差学习的革命 卷积神经网络&#xff08;CNN&#xff09;已经发展为图像识别…

棋牌灯控计时计费系统软件免费试用版怎么下载 佳易王计时收银管理系统操作教程

一、前言 【试用版软件下载&#xff0c;可以点击本文章最下方官网卡片】 棋牌灯控计时计费系统软件免费试用版怎么下载 佳易王计时收银管理系统操作教程 棋牌计时计费软件的应用也提升了顾客的服务体验&#xff0c;顾客可以清晰的看到自己的消费时间和费用。增加了消费的透明…

梯度下降学习

前言&#xff1a;初步学习梯度下降&#xff0c; 不断根据梯度修改我们的参数&#xff0c;经过多次轮次得到使得我们损失函数最小的时候参数&#xff0c;后续我们可以通过类似的道理&#xff0c;更新我们的参数 假设我们的损失函数是 y x 1 2 x 2 2 y x1^2 x2^2 yx12x22,我…

用Python实现运筹学——Day 14: 线性规划总结与案例复习

一、学习内容 在本节中&#xff0c;我们将复习之前所学的线性规划模型与求解方法&#xff0c;并通过一个综合案例将这些知识应用于求解一个多阶段的生产计划问题。 主要复习内容包括&#xff1a; 线性规划的基础概念&#xff1a;目标函数、约束条件、决策变量。求解方法&…

什么是 HTTP 请求中的 preflight 类型请求

在浏览器的 HTTP 请求中&#xff0c;当我们使用 fetch API 或者 XMLHttpRequest 来进行跨域请求时&#xff0c;浏览器有时会发送一种称为 Preflight 的请求。这种请求是浏览器在实际发送跨域请求前&#xff0c;先与目标服务器进行的一次 “探测” 请求&#xff0c;以确认服务器…

组合式API

1.入口&#xff1a;setup setup中的数据和方法必须return出去&#xff0c;模板才能使用 <script> export default {setup () {console.log(setup);const message this is a messageconst logMessage () > {console.log(message);}return {message,logMessage}},be…

Visual Studio 2017编译libexpat源码过程

一、编译环境 操作系统&#xff1a;Windows 10 企业版 64位 编译工具&#xff1a;Visual Studio 2017 构建工具&#xff1a;CMake3.22 源码版本&#xff1a;libexpat-R_2_4_0 二、CMake生成解决方案 解压libexpat源码&#xff0c;然后启动CMake选择libexpat源码目录&#xff1…