什么,这年头还有人不知道404

写在前面

哥,来帮我看看,这个请求怎么404了,明明接口路径是对的啊!一个下午,组里的小哥突然让我帮忙看这个问题,我不禁一惊,啥,这年头了还有人搞不定404,如有还有,那一定是没看完这篇文章!

一、为何要写这篇文章

作为一名crud工程师,咱们的工作真的就只剩增删改查了吗?在笔者所遇到各类从事软件开发的人群中,工作1-2年甚至3-5年的,在遇到404这类的http异常code时都显得束手无策,经验稍微丰富的点“老”手可能凭经验能看出问题出在哪里,但是又有多少人知道为什么会出现404 code,往深了说,又有多少人知道一个http请求是如何找到controller中的方法并执行呢?更进一步,在你了解到spring mvc 的处理机制前,如果让你来设计这套流程,你会怎么做?

二、举个例子

下面是一个最简单的http接口例子

接口路径为 /api/common/getNumber

@RequestMapping("/api/common")
@Controller
public class CommonController {@RequestMapping("/getNumber")@ResponseBodypublic Object getNumberMethod(@RequestParam("range") Integer range) {return ThreadLocalRandom.current().nextInt(range);}
}

过滤器

public class LogFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;log.info("经过logFilter ==== {}", request);filterChain.doFilter(servletRequest, servletResponse);}@Overridepublic void destroy() {}
}

拦截器

public class LogInterceptor extends HandlerInterceptorAdapter {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("经过拦截器 === {}", request);return true;}
}

执行结果

2023-10-03 19:22:12.373  INFO 54072 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-10-03 19:22:12.373  INFO 54072 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2023-10-03 19:22:15.646  INFO 54072 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 3273 ms
2023-10-03 19:22:19.759  INFO 54072 --- [nio-8080-exec-1] com.example.demo.filter.LogFilter        : 经过logFilter ==== org.apache.catalina.connector.RequestFacade@67d0b80a
2023-10-03 19:22:26.177  INFO 54072 --- [nio-8080-exec-1] c.e.demo.intercpetor.LogInterceptor      : 经过拦截器 === org.apache.catalina.connector.RequestFacade@67d0b80a

三、执行过程

1、运行环境

jdk 1.8

spring-boot-starter-parent 2.1.9.RELEASE

spring-webmvc 5.1.0

2、源码解析

在进行源码解析时,我们先过一眼整个请求处理的过程UML图

0)ApplicationFilterChain # internalDoFilter(ServletRequest request, ServletResponse response)

该方法是tomcat包中的方法,用来执行filter,在filter执行完成后再执行servlet.service方法,而servlet.service方法也是业务的入口方法。servlet对象即为DispatchServlet,其service对应的也就是其父类HttpServlet的service方法。

private void internalDoFilter(ServletRequest request,ServletResponse response)throws IOException, ServletException {// n 表示filter数的总和,pos表示当前位置// Call the next filter if there is oneif (pos < n) { // 如果filter没有执行完成,则走下面的逻辑继续执行// 获取pos位置对应的filterConfig,同时将pos+1ApplicationFilterConfig filterConfig = filters[pos++];try {Filter filter = filterConfig.getFilter();if (request.isAsyncSupported() && "false".equalsIgnoreCase(filterConfig.getFilterDef().getAsyncSupported())) {request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);}if( Globals.IS_SECURITY_ENABLED ) {final ServletRequest req = request;final ServletResponse res = response;Principal principal =((HttpServletRequest) req).getUserPrincipal();Object[] args = new Object[]{req, res, this};SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);} else {// 执行具体的filter逻辑,如本文例子中的LogFilterfilter.doFilter(request, response, this);}} catch (IOException | ServletException | RuntimeException e) {throw e;} catch (Throwable e) {e = ExceptionUtils.unwrapInvocationTargetException(e);ExceptionUtils.handleThrowable(e);throw new ServletException(sm.getString("filterChain.filter"), e);}return;}// We fell off the end of the chain -- call the servlet instance// 如果执行完最后一个filtertry {if (ApplicationDispatcher.WRAP_SAME_OBJECT) {lastServicedRequest.set(request);lastServicedResponse.set(response);}if (request.isAsyncSupported() && !servletSupportsAsync) {request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,Boolean.FALSE);}// Use potentially wrapped request from this pointif ((request instanceof HttpServletRequest) &&(response instanceof HttpServletResponse) &&Globals.IS_SECURITY_ENABLED ) {final ServletRequest req = request;final ServletResponse res = response;Principal principal =((HttpServletRequest) req).getUserPrincipal();Object[] args = new Object[]{req, res};SecurityUtil.doAsPrivilege("service",servlet,classTypeUsedInService,args,principal);} else {// 执行HttpServlet的service方法servlet.service(request, response);}} catch (IOException | ServletException | RuntimeException e) {throw e;} catch (Throwable e) {e = ExceptionUtils.unwrapInvocationTargetException(e);ExceptionUtils.handleThrowable(e);throw new ServletException(sm.getString("filterChain.servlet"), e);} finally {if (ApplicationDispatcher.WRAP_SAME_OBJECT) {lastServicedRequest.set(null);lastServicedResponse.set(null);}}}

1)FrameworkServlet # service(HttpServletRequest request, HttpServletResponse response)

由于HttpServlet的service方法只是做了入参的转换,即将ServletRequest转成HttpServletRequest,ServletResponse转成HttpServletResponse,参数转换完后随即调用了子类 FrameworkServlet 的service(HttpServletRequest request, HttpServletResponse response) 方法,而FrameworkServlet 的service方法功能很简单,就是为了适配httpMethod 中的 PATCH模式,非PATCH模式直接走父类HttpServlet的service(HttpServletRequest request, HttpServletResponse response)方法【DispatcherServlet的继承关系依赖图如下】。

/*** Override the parent class implementation in order to intercept PATCH requests.* 主要是为了拦截 httpMethod 中的 PATCH** patch是2010后成为的正式http方法,详见RFC5789,* 它是对put的补充,在没有patch之前,我们都是用put进行更新操作,* 这时候我们的接口中通常会有一个逻辑规则,如:如果对象的的一个字符属性为NULL,* 那么就是不更新该属性(字段)值,如果对象的字符属性是“”,那么就更新该属性(字段)的值,* 通过这种方式来避免全部覆盖的操作。现在有了patch就解决了这种判断,在put接口中不管属性是不是null,* 都进行更新,在patch接口中就对非null的进行更新**/@Overrideprotected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());// 如果是 HttpMethod.PATCH 或者 找不到httpMethodif (httpMethod == HttpMethod.PATCH || httpMethod == null) {processRequest(request, response);}else {// 其他情况则调用父类也就是HttpServlet的service方法,// 在这里,由于我们的请求是get类型的,所以会走到此分支super.service(request, response);}}

2)HttpServlet # service(HttpServletRequest req, HttpServletResponse resp)

进入HttpServlet的service(HttpServletRequest req, HttpServletResponse resp)方法后,该方法主要做了method类型的区分调用,即get post put等对应的doGet,doPost,doPut 由子类实现。

/*** Receives standard HTTP requests from the public* <code>service</code> method and dispatches* them to the <code>do</code><i>Method</i> methods defined in* this class. This method is an HTTP-specific version of the* {@link javax.servlet.Servlet#service} method. There's no* need to override this method.** @param req   the {@link HttpServletRequest} object that*                  contains the request the client made of*                  the servlet** @param resp  the {@link HttpServletResponse} object that*                  contains the response the servlet returns*                  to the client** @exception IOException   if an input or output error occurs*                              while the servlet is handling the*                              HTTP request** @exception ServletException  if the HTTP request*                                  cannot be handled** @see javax.servlet.Servlet#service*/protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {String method = req.getMethod();if (method.equals(METHOD_GET)) {// 默认返回 -1long 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;try {ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);} catch (IllegalArgumentException iae) {// Invalid date header - proceed as if none was setifModifiedSince = -1;}if (ifModifiedSince < (lastModified / 1000 * 1000)) {// 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);}}

3)DispatcherServlet # doDispatch(HttpServletRequest request, HttpServletResponse response)

HttpServlet的service(HttpServletRequest req, HttpServletResponse resp)方法经过一连串的包装调用后就会进入最重要的DispatcherServlet 的 doDispatch(HttpServletRequest request, HttpServletResponse response)方法,doDispatch顾名思义就是将请求进行分发,包括获取HandlerExecutionChain,执行拦截器,获取执行器适配器,handler调用,视图渲染等工作。

/*** Process the actual dispatching to the handler.* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters* to find the first that supports the handler class.* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers* themselves to decide which methods are acceptable.* @param request current HTTP request* @param response current HTTP response* @throws Exception in case of any kind of processing failure*/protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.// 该方法得到一个 HandlerExecutionChain 处理器执行链,实际上它包含了一个真正的处理handler// 和 若干个拦截器mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.// 获取执行器适配器HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}// 执行拦截器的preHandle方法,如果拦截了则直接返回if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.// 真正调用handlermv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);// 执行拦截器的 postHandle 方法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);}// 处理视图的方法,将逻辑视图转为物理视图的过程,同时执行拦截器的afterCompletion方法processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {// 执行拦截器的afterCompletion方法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);}}}}
a. DispatcherServlet # getHandler(HttpServletRequest request)

该方法得到一个 HandlerExecutionChain 处理器执行链,实际上它包含了一个真正的处理handler和 若干个拦截器

   /*** Return the HandlerExecutionChain for this request.* <p>Tries all handler mappings in order.* @param request current HTTP request* @return the HandlerExecutionChain, or {@code null} if no handler could be found*/@Nullableprotected 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;}

从上图执行过程中可以看出,HandlerExecutionChain的获取主要依赖于HandlerMapping ,那么何为HandlerMapping?HandlerMapping 称为处理器映射器,

从HandlerMapping的继承关系图中可以看出,HandlerMapping可以大致分为 AbstractHandlerMethodMapping 和 AbstractUrlHandlerMapping 两大类,其中AbstractHandlerMethodMapping 映射器主要处理用 @Controller@RequestMapping 这样注解来描述视图控制器的逻辑,也是我们日常开发中用的最多的场景;而AbstractUrlHandlerMapping用的比较少,比如:&lt;mvc:view-controller path=&quot;&quot; view-name=&quot;&quot;/&gt; 标签配置资源不经过视图控制器直接跳转就用到了 SimpleUrlHandlerMapping 这种映射器。

当执行 HandlerExecutionChain handler = mapping.getHandler(request); 时,会跳到 AbstractHandlerMapping类,执行getHandler方法,AbstractHandlerMapping 是个抽象类,提供了模板方法,主要的功能在代码块getHandlerInternal方法,在本例中getHandlerInternal的功能主要就是根据request来获取HandlerMethod,HandlerMethod对象存储于MappingRegistry的mappingLookup<T, HandlerMethod>映射表中,该映射表在容器启动时,探测类上是否有Controller或者RequestMapping注解修饰,如有则生成RequestMappingInfo到HandlerMethod的映射关系。

/*** Look up a handler for the given request, falling back to the default* handler if no specific one is found.* @param request current HTTP request* @return the corresponding handler instance, or the default handler* @see #getHandlerInternal*/@Override@Nullablepublic final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {// 重要, 本例中返回 HandlerMethod 对象,该对象里面包含了目标类的目标method信息以及目标类的beanObject handler = getHandlerInternal(request);if (handler == null) {handler = getDefaultHandler();}if (handler == null) {return null;}// Bean name or resolved handler?if (handler instanceof String) {String handlerName = (String) handler;handler = obtainApplicationContext().getBean(handlerName);}// 将 handler(本例中对应的是HandlerMethod 对象)以及拦截器信息封装到 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());}if (CorsUtils.isCorsRequest(request)) {CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);executionChain = getCorsHandlerExecutionChain(request, executionChain, config);}return executionChain;}
b. DispatcherServlet # getHandlerAdapter(Object handler)

该方法是获取处理器适配器,那么为什么要有处理器适配器,直接执行handler不行吗?原因就是处理器 handler 的类型是 Object 类型。Spring 中的handler实现多变,比如用户的处理器可以实现 Controller 接口或者 HttpRequestHandler 接口,也可以用 @RequestMapping 注解将方法作为一个处理器等,这就导致 Spring MVC 无法直接执行这个处理器。所以这里需要一个处理器适配器,由它去执行处理。获取处理器适配的方法寥寥数语,最主要的逻辑就是 adapter.supports(handler),根据语句猜测大概就是根据条件匹配对应的适配器。在我们弄清楚这个逻辑前,先来看看第一条语句if (this.handlerAdapters != null),那么这个this.handlerAdapters 的值从哪里来?

    /*** Return the HandlerAdapter for this handler object.* @param handler the handler object to find an adapter for* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.*/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");}

根据下图的执行过程可以看出,有三个满足条件的handlerAdapter。

那么,这三个handlerAdapter是如何确定的呢?如下:

    /*** Initialize the HandlerAdapters used by this class.* <p>If no HandlerAdapter beans are defined in the BeanFactory for this namespace,* we default to SimpleControllerHandlerAdapter.*/private void initHandlerAdapters(ApplicationContext context) {this.handlerAdapters = null;if (this.detectAllHandlerAdapters) {// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.Map<String, HandlerAdapter> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerAdapters = new ArrayList<>(matchingBeans.values());// We keep HandlerAdapters in sorted order.AnnotationAwareOrderComparator.sort(this.handlerAdapters);}}else {try {HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);this.handlerAdapters = Collections.singletonList(ha);}catch (NoSuchBeanDefinitionException ex) {// Ignore, we'll add a default HandlerAdapter later.}}// Ensure we have at least some HandlerAdapters, by registering// default HandlerAdapters if no other adapters are found.if (this.handlerAdapters == null) {this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}

上述代码的大体含义如下:

  1. 如果“开启”探测功能,则扫描已注册的 HandlerAdapter 的 Bean 们,添加到 handlerAdapters 中,默认 开启 ,这里会进行排序,可以通过实现 Order 接口设置排序值

  2. 如果“关闭”探测功能,则获得 Bean 名称为 "handlerAdapter" 对应的 Bean ,将其添加至 handlerAdapters

  3. 如果未获得到,则获得默认配置的 HandlerAdapter 类,调用 getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) 方法,就是从 DispatcherServlet.properties 文件中读取 HandlerAdapter 的默认实现类,如下:

那么回到getHandlerAdapter方法中的adapter.supports(handler)语句,依次通过boolean supports(Object handler)方法判断使用哪个adapter。HandlerAdapter即采用适配器模式, 用于统一不同handler的接口调用。在本文例子中,最后采用的是RequestMappingHandlerAdapter,其对应的supports方法是

/*** This implementation expects the handler to be an {@link HandlerMethod}.* @param handler the handler instance to check* @return whether or not this adapter can adapt the given handler*/@Overridepublic final boolean supports(Object handler) {return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));}
/*** Always return {@code true} since any method argument and return value* type will be processed in some way. A method argument not recognized* by any HandlerMethodArgumentResolver is interpreted as a request parameter* if it is a simple type, or as a model attribute otherwise. A return value* not recognized by any HandlerMethodReturnValueHandler will be interpreted* as a model attribute.*/@Overrideprotected boolean supportsInternal(HandlerMethod handlerMethod) {return true;}
c. AbstractHandlerMethodAdapter # handle(HttpServletRequest request, HttpServletResponse response, Object handler)

该方法就是最终要执行业务方法,也就是Controller类中的某个方法的入口。

/*** This implementation expects the handler to be an {@link HandlerMethod}.*/@Override@Nullablepublic 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;checkRequest(request);// Execute invokeHandlerMethod in synchronized block if required.if (this.synchronizeOnSession) {HttpSession session = request.getSession(false);if (session != null) {Object mutex = WebUtils.getSessionMutex(session);synchronized (mutex) {mav = invokeHandlerMethod(request, response, handlerMethod);}}else {// No HttpSession available -> no mutex necessarymav = invokeHandlerMethod(request, response, handlerMethod);}}else {// No synchronization on session demanded at all...mav = invokeHandlerMethod(request, response, handlerMethod);}if (!response.containsHeader(HEADER_CACHE_CONTROL)) {if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);}else {prepareResponse(response);}}return mav;}

在以上方法中,我们只需要关注invokeHandlerMethod(request, response, handlerMethod) 即可,接着看:

/*** Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}* if view resolution is required.* @since 4.2* @see #createInvocableHandlerMethod(HandlerMethod)*/@Nullableprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);.... 省去若干代码// 只需关注这行即可invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();}} 
/*** Invoke the method and handle the return value through one of the* configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.* @param webRequest the current request* @param mavContainer the ModelAndViewContainer for this request* @param providedArgs "given" arguments matched by type (not resolved)*/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()) {disableContentCachingIfNecessary(webRequest);mavContainer.setRequestHandled(true);return;}}else if (StringUtils.hasText(getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers != null, "No return value handlers");try {// 处理返回值this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);}catch (Exception ex) {if (logger.isTraceEnabled()) {logger.trace(formatErrorForReturnValue(returnValue), ex);}throw ex;}}
/*** Invoke the method after resolving its argument values in the context of the given request.* <p>Argument values are commonly resolved through* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.* The {@code providedArgs} parameter however may supply argument values to be used directly,* i.e. without argument resolution. Examples of provided argument values include a* {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance.* Provided argument values are checked before argument resolvers.* <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the* resolved arguments.* @param request the current request* @param mavContainer the ModelAndViewContainer for this request* @param providedArgs "given" arguments matched by type, not resolved* @return the raw value returned by the invoked method* @throws Exception raised if no suitable argument resolver can be found,* or if the method raised an exception* @see #getMethodArgumentValues* @see #doInvoke*/@Nullablepublic Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 解析参数值Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace("Arguments: " + Arrays.toString(args));}// 执行调用 return doInvoke(args);}
/*** Invoke the handler method with the given argument values.*/@Nullableprotected Object doInvoke(Object... args) throws Exception {// 改变方法的可见性,这就是为什么即使controller中的方法是private的也能正常访问ReflectionUtils.makeAccessible(getBridgedMethod());try {// 这就是精髓所在,熟悉的配方,熟悉的味道,这不就是反射调用吗!!!return getBridgedMethod().invoke(getBean(), args);}catch (IllegalArgumentException ex) {assertTargetBean(getBridgedMethod(), getBean(), args);String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");throw new IllegalStateException(formatInvokeError(text, args), ex);}catch (InvocationTargetException ex) {// Unwrap for HandlerExceptionResolvers ...Throwable targetException = ex.getTargetException();if (targetException instanceof RuntimeException) {throw (RuntimeException) targetException;}else if (targetException instanceof Error) {throw (Error) targetException;}else if (targetException instanceof Exception) {throw (Exception) targetException;}else {throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);}}}
d. DispatchServlet # processDispatchResult

对于某些接口需要渲染ModelAndView的,需要在下面这个方法里处理,例如,有个接口采用的是thymeleaf模板引擎来渲染接口数据。如下例子

@RequestMapping("/testHtml")public String testHtml(Map<String, Object> map) {map.put("msg","<h1>Hello,SpringBoot</h1>");map.put("users", Arrays.asList("zhangsan","lisi"));return "testHtml";}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>测试</title></head>
<body><h1>测试页面</h1>
<div th:text="${msg}"></div>
<div th:utext="${msg}"></div>
<h4 th:each="user :${users}"th:text="${user}">
</h4>
</body>
</html>

效果如下:

针对上面这个例子,执行完testHtml方法后,拿到返回的ModelAndView对象后执行下面processDispatchResult中的render方法渲染页面信息

/*** Handle the result of handler selection and handler invocation, which is* either a ModelAndView or an Exception to be resolved to a ModelAndView.*/private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;... 省略若干代码        // Did the handler return a view to render?if (mv != null && !mv.wasCleared()) {// 渲染 ModelAndViewrender(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) {// 执行拦截器的afterCompletion方法mappedHandler.triggerAfterCompletion(request, response, null);}}

从下图中可以看到 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 返回的是mv 非空,说明有对应的ModelAndView需要渲染。

3、总结

从以上的执行过程来看,一个完整的http get 请求大概会经过执行 filter、从HandlerMapping中获取HandlerExecutionChain,HandlerExecutionChain里面包含了一个真正的处理handler(HandlerMethod,HandlerMethod包含了要执行方法的method信息以及类实例对象) 和若干个拦截器interceptors,然后根据handler获取对应的HandlerAdapter去执行,在执行过程中通过反射机制调用对应Controller的方法拿到结果,拿到结果后进行返回值的回写以及页面的渲染(如果有必要),在执行过程的前后会分别执行接口的拦截器preHandle以及postHandle方法。

那么,这整个过程的示意图如下

四、用到的技术点

1、设计模式

1) 模版模式 HandlerMapping​​​​​​​

比如 HandlerMapping的实现抽象类AbstractHandlerMapping中有个getHanlder 方法,其中getHandlerInternal定义了模版方法,具体由子类实现

/*** Look up a handler for the given request, falling back to the default* handler if no specific one is found.* @param request current HTTP request* @return the corresponding handler instance, or the default handler* @see #getHandlerInternal*/@Override@Nullablepublic final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {// 该方法则是模版方法,具体由子类实现Object handler = getHandlerInternal(request);if (handler == null) {handler = getDefaultHandler();}if (handler == null) {return null;}// Bean name or resolved handler?if (handler instanceof String) {String handlerName = (String) handler;handler = obtainApplicationContext().getBean(handlerName);}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());}if (CorsUtils.isCorsRequest(request)) {CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);executionChain = getCorsHandlerExecutionChain(request, executionChain, config);}return executionChain;}
/*** Look up a handler for the given request, returning {@code null} if no* specific one is found. This method is called by {@link #getHandler};* a {@code null} return value will lead to the default handler, if one is set.* <p>On CORS pre-flight requests this method should return a match not for* the pre-flight request but for the expected actual request based on the URL* path, the HTTP methods from the "Access-Control-Request-Method" header, and* the headers from the "Access-Control-Request-Headers" header thus allowing* the CORS configuration to be obtained via {@link #getCorsConfiguration(Object, HttpServletRequest)},* <p>Note: This method may also return a pre-built {@link HandlerExecutionChain},* combining a handler object with dynamically determined interceptors.* Statically specified interceptors will get merged into such an existing chain.* @param request current HTTP request* @return the corresponding handler instance, or {@code null} if none found* @throws Exception if there is an internal error*/@Nullableprotected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;

2) 责任链模式 Filter

http请求中会执行filter, filter采用的是责任链模式,整个过程沿着链条上的各个有序的filter执行

    /***  ApplicationFilterChain*/public void doFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {... 省略若干行internalDoFilter(request,response);}private void internalDoFilter(ServletRequest request,ServletResponse response)throws IOException, ServletException {// Call the next filter if there is oneif (pos < n) {ApplicationFilterConfig filterConfig = filters[pos++];...省略若干行// 调用filter的doFilter方法,同时将 this对象传过去,方便将责任链传递下去filter.doFilter(request, response, this);return;}}
@Slf4j
public class LogFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;log.info("经过logFilter ==== {}", request);// 接收上一个filter传过来的filterChain,同时调用filterChain的doFilter方法filterChain.doFilter(servletRequest, servletResponse);}@Overridepublic void destroy() {}
}

2、反射

在执行handler时,handler处理过程中,会把流量转发到各个controller中的方法执行,为了统一调用逻辑,这里采用了反射的方式处理

    /*** Invoke the handler method with the given argument values.*/@Nullableprotected Object doInvoke(Object... args) throws Exception {// 改变方法的可见性,这就是为什么即使controller中的方法是private的也能正常访问ReflectionUtils.makeAccessible(getBridgedMethod());try {// 这就是精髓所在,熟悉的配方,熟悉的味道,这不就是反射调用吗!!!return getBridgedMethod().invoke(getBean(), args);}catch (IllegalArgumentException ex) {assertTargetBean(getBridgedMethod(), getBean(), args);String text = (ex.getMessage() != null ? ex.getMessage() : &quot;Illegal argument&quot;);throw new IllegalStateException(formatInvokeError(text, args), ex);}catch (InvocationTargetException ex) {// Unwrap for HandlerExceptionResolvers ...Throwable targetException = ex.getTargetException();if (targetException instanceof RuntimeException) {throw (RuntimeException) targetException;}else if (targetException instanceof Error) {throw (Error) targetException;}else if (targetException instanceof Exception) {throw (Exception) targetException;}else {throw new IllegalStateException(formatInvokeError(&quot;Invocation failure&quot;, args), targetException);}}}

五、回顾

那么回过头来你能回答文章最前面提出的问题了吗?

1、http请求出现404等状态码时,知道从哪里开始排查了吗?

2、一个http请求是如何找到controller中的方法并执行呢?

3、在你了解到spring mvc 的处理机制前,如果让你来设计这套流程,你会怎么做?

针对前面三个问题,在你了解spring mvc 的处理机制后,你觉得这个流程设计到怎么样呢?

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

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

相关文章

FFmpeg:打印音/视频信息(Meta信息)

多媒体文件基本概念 多媒体文件其实是个容器在容器里面有很多流(Stream/Track)每种流是由不同的编码器编码的从流中读出的数据称为包在一个包中包含着一个或多个帧 几个重要的结构体 AVFormatContextAVStreamAVPacket FFmpeg操作流数据的基本步骤 打印音/视频信息(Meta信息…

LVGL_基础控件滚轮roller

LVGL_基础控件滚轮roller 1、创建滚轮roller控件 /* 创建一个 lv_roller 部件(对象) */ lv_obj_t * roller lv_roller_create(lv_scr_act()); // 创建一个 lv_roller 部件(对象),他的父对象是活动屏幕对象// 将部件(对象)添加到组&#xff0c;如果设置了默认组&#xff0c…

不断优化的素数算法

前言&#xff1a;素数判断是算法中重要的一环&#xff0c;掌握优秀的素数判断方法是算法player的必修课。本文介绍的是由简到繁的素数算法&#xff0c;便于初学者从入门到精通。 素数&#xff08;质数&#xff09;&#xff1a;只能被 1 和它本身整除的数称作素数&#xff0c;如…

总结二:linux面经

文章目录 1、 Linux中查看进程运行状态的指令、查看内存使用情况的指令、tar解压文件的参数。2、文件权限怎么修改&#xff1f;3、说说常用的Linux命令&#xff1f;4、说说如何以root权限运行某个程序&#xff1f;5、 说说软链接和硬链接的区别&#xff1f;6、说说静态库和动态…

(四)正点原子STM32MP135移植——u-boot移植

一、概述 u-boot概述就不概述了&#xff0c;u-boot、kernel、dtb三件套&#xff0c;dddd 经过国庆艰苦奋战&#xff0c;已经成功把所有功能移植好了 二、编译官方代码 进入u-boot的目录 2.1 解压源码、打补丁 /* 解压源码 */ tar xf u-boot-stm32mp-v2022.10-stm32mp-r1-r0.…

充分理清限制与条件+构造二分图+最小割:ARC142E

https://www.luogu.com.cn/problem/AT_arc142_e 他的充要条件是是什么&#xff1a; a i , a j ≥ m i n ( b i , b j ) a_i,a_j\ge min(b_i,b_j) ai​,aj​≥min(bi​,bj​)存在 a i ≥ m a x ( b i , b j ) a_i\ge max(b_i,b_j) ai​≥max(bi​,bj​) 第一个条件直接预处理一…

Springcloud支付模块

客户端消费者80 order 微服务提供者8001 payment 订单模块可以调动支付模块 步骤&#xff1a; 1、建moudle 2、改写pom 3、写yml 4、主启类 5、业务类

【LinuxC】时间、时区,相关命令、函数

文章目录 一、序1.1 时间和时区1.11 时间1.12 时区 1.2 查看时间时区的命令1.21 Windows1.22 Linux 二、C语言函数2.1 通用2.11 函数简介2.12 数据类型简介 2.2 windows 和 Linux特有函数2.3 C语言示例 一、序 1.1 时间和时区 1.11 时间 时间是一种用来描述物体运动变化的量…

黑马点评-01基于Redis实现短信登陆的功能

环境准备 当前模型 nginx服务器的作用 手机或者app端向nginx服务器发起请求,nginx基于七层模型走的是HTTP协议,可以实现基于Lua直接绕开tomcat访问Redis nginx也可以作为静态资源服务器,轻松扛下上万并发并负载均衡到下游的tomcat服务器,利用集群支撑起整个项目 使用nginx部…

黑马JVM总结(二十七)

&#xff08;1&#xff09;synchronized代码块 synchronized代码块的底层原理&#xff0c;它是给一个对象进行一个加锁操作&#xff0c;它是如何保证如果你出现了synchronized代码块中出现了问题&#xff0c;它需要给这个对象有一个正确的解锁操作呢&#xff0c;加锁解锁是成对…

【c++_containers】10分钟带你学会list

前言 链表作为一个像是用“链子”链接起来的容器&#xff0c;在数据的存储等方面极为便捷。虽然单链表单独在实际的应用中没用什么作用&#xff0c;但是当他可以结合其他结构&#xff0c;比如哈希桶之类的。不过今天学习的list其实是一个带头双向链表。 言归正传&#xff0c;让…

overleaf在线编辑工具使用教程

文章目录 1 用 orcid注册overleaf获取模板2 使用模板 1 用 orcid注册overleaf获取模板 通常来说&#xff0c;在期刊投稿网站information for author中找template 。下载压缩包后上传到over leaf中。 加入找不到官方模板&#xff0c;用overleaf中的 2 使用模板 .bib文件&…

3D孪生场景SDK:Viwer 孪生世界

NSDT 编辑器 提供三维场景构建、场景效果设计、场景服务发布全流程工具等&#xff0c;其场景编辑器支持资产管理、灯光设置、骨骼动画等功能&#xff1b;致力于协助资源不足的中小企业及个人快速开发数字孪生场景&#xff0c;帮助企业提高生产力、实现降本增效。 NSDT编辑器简…

adb详细教程(四)-使用adb启动应用、关闭应用、清空应用数据、获取设备已安装应用列表

adb对于安卓移动端来说&#xff0c;是个非常重要的调试工具。本篇介绍常用的adb指令 文章目录 一、启动应用&#xff1a;adb shell am start二、使用浏览器打开指定网址&#xff1a;adb shell am start三、杀死应用进程adb shell am force-stop/adb shell am kill四、删除应用所…

【AI视野·今日CV 计算机视觉论文速览 第262期】Fri, 6 Oct 2023

AI视野今日CS.CV 计算机视觉论文速览 Fri, 6 Oct 2023 Totally 73 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computer Vision Papers Improved Baselines with Visual Instruction Tuning Authors Haotian Liu, Chunyuan Li, Yuheng Li, Yong Jae Lee大型多模…

python开发幸运水果抽奖大转盘

概述 当我女朋友跟我说要吃水果&#xff0c;又不知道吃啥水果时候&#xff0c;她以为难为到我了&#xff0c;有啥事难为到程序员的呢&#xff01; 今天用python利用第三方tkinterthreadingtime库开发一个幸运水果抽奖大转盘&#xff01;抽到啥吃啥 详细 老规矩&#xff01;咱…

基于A4988/DRV8825的四路步进电机驱动器

概述 简化板的CNC sheild V3.0&#xff0c;仅保留步进电机速度与方向的控制引脚STEP/DIR、使能端EN、芯片供电VCC\GND&#xff0c;共计11个引脚。PCB四周开设四个M3通孔&#xff0c;以便于安装固定。此外&#xff0c;将板载的焊死的保险丝更改为可更换的保险座保险丝&#xff…

Labview 实战 99乘法表

基于新手小白&#xff0c;使用Labview实现99乘法表&#xff0c;敢于发表自己的一点方法&#xff0c;还请各位大侠放过&#xff01; 如下&#xff1a; 运行效果如下&#xff1a; 思路为&#xff1a;将要显示出来的数据&#xff0c;全部转换为字符串形式&#xff0c;再塞入到数组…

Java基于SSM+Vue的平时成绩管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

频次直方图、KDE和密度图

Seaborn的主要思想是用高级命令为统计数据探索和统计模型拟合创建各种图形&#xff0c;下面将介绍一些Seaborn中的数据集和图形类型。 虽然所有这些图形都可以用Matplotlib命令实现&#xff08;其实Matplotlib就是Seaborn的底层&#xff09;&#xff0c;但是用 Seaborn API会更…