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

news2024/11/20 12:30:57

写在前面

哥,来帮我看看,这个请求怎么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")
    @ResponseBody
    public Object getNumberMethod(@RequestParam("range") Integer range) {
        return ThreadLocalRandom.current().nextInt(range);
    }
}

过滤器

public class LogFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;

        log.info("经过logFilter ==== {}", request);

        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}

拦截器

public class LogInterceptor extends HandlerInterceptorAdapter {

    @Override
    public 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 one
        if (pos < n) { // 如果filter没有执行完成,则走下面的逻辑继续执行
            // 获取pos位置对应的filterConfig,同时将pos+1
            ApplicationFilterConfig 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逻辑,如本文例子中的LogFilter
                    filter.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
        // 如果执行完最后一个filter
        try {
            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 point
            if ((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的进行更新
     *
     */
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        // 如果是 HttpMethod.PATCH 或者 找不到httpMethod
        if (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)) {
            // 默认返回 -1
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                } catch (IllegalArgumentException iae) {
                    // Invalid date header - proceed as if none was set
                    ifModifiedSince = -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 less
                    maybeSetLastModified(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.
                // 真正调用handler
                mv = 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 afterCompletion
                if (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
     */
    @Nullable
    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;
    }

从上图执行过程中可以看出,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
    @Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // 重要, 本例中返回 HandlerMethod 对象,该对象里面包含了目标类的目标method信息以及目标类的bean
        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);
        }

        // 将 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
     */
    @Override
    public 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.
     */
    @Override
    protected 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
    @Nullable
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        return handleInternal(request, response, (HandlerMethod) handler);
    }

handleInternal 方法依赖于子类的实现

@Override
    protected 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 necessary
                mav = 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)
     */
    @Nullable
    protected 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
     */
    @Nullable
    public 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.
     */
    @Nullable
    protected 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()) {
            // 渲染 ModelAndView
            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 forward
            return;
        }

        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
    @Nullable
    public 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
     */
    @Nullable
    protected 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 one
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            ...省略若干行
            // 调用filter的doFilter方法,同时将 this对象传过去,方便将责任链传递下去
            filter.doFilter(request, response, this);
            return;
        }
    }
@Slf4j
public class LogFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {

    }

    @Override
    public 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);
    }

    @Override
    public void destroy() {

    }
}

2、反射

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

    /**
     * Invoke the handler method with the given argument values.
     */
    @Nullable
    protected 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.coloradmin.cn/o/1064419.html

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

相关文章

【二】spring boot-设计思想

spring boot-设计思想 简介&#xff1a;现在越来越多的人开始分析spring boot源码&#xff0c;拿到项目之后就有点无从下手了&#xff0c;这里介绍一下springboot源码的项目结构 一、项目结构 从上图可以看到&#xff0c;源码分为两个模块&#xff1a; spring-boot-project&a…

最强的电脑/手机/汽车/机器人芯片-2023

车规级芯片、手机芯片、电脑芯片比较_汽车芯片和电脑芯片的区别-CSDN博客 全文资源来源网络。 电脑&#xff1a; 图片引用。 CPU 基准测试性能层次结构根据性能对当前和上一代英特尔和 AMD 处理器进行排名&#xff0c;包括所有最适合游戏的 CPU。在 CPU 排名图表和表格下方&…

2023年【安全员-A证】报名考试及安全员-A证免费试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全员-A证报名考试考前必练&#xff01;安全生产模拟考试一点通每个月更新安全员-A证免费试题题目及答案&#xff01;多做几遍&#xff0c;其实通过安全员-A证作业考试题库很简单。 1、【多选题】2014年2月&#xff…

【亲测有效】C盘容量满了,给C盘扩容!!!

前言 相信有很多小伙伴用自己电脑的时候明明不往C盘装东西&#xff0c;但是C盘还是慢慢的变红了&#xff0c;我也是因为C盘满了而备受困扰。又不知道如何解决或者怕自己鼓捣着磁盘数据没了。闲来无事&#xff0c;我查了一些资料&#xff0c;终于将我的C盘容量扩充了且数据保存…

k8s-9 ingress-nginx

nodeport 默认端口 nodeport默认端口是30000-32767&#xff0c;超出会报错 添加如下参数&#xff0c;端口范围可以自定义 externalname ingress-nginx 通过一个外部的vip 地址 访问到集群内的多个service 一种全局的、为了代理不同后端 Service 而设置的负载均衡服务&…

FLASH模拟EEPROM

STM32本身没有自带EEPROM&#xff0c;但是STM32具有IAP&#xff08;在应用编程&#xff09;功能&#xff0c;所以可以把它的FLASH当做EEPROM来使用。 STM32 FLASH简介 不同型号的STM32&#xff0c;其FLASH容量也有所不同&#xff0c;最大的达到1024K字节。 MiniSTM32开发板选…

打破思维局限性,产品背景、需求、功能实现逻辑手拿把掐!

在一个完整的测试流程中&#xff0c;测试用例是很核心的一个产出物。一份优秀的测试用例&#xff0c;能确保软件产品质量的可控。 但由于每个人思维局限性&#xff0c;对产品背景、需求、功能实现逻辑等理解深度不一致&#xff0c;编写的测试用例或多或少存在一些遗漏点&#…

C/C++学习 -- SHA-256算法

SHA-256算法概述 SHA-256代表"Secure Hash Algorithm 256-bit"&#xff0c;是一种安全的哈希算法&#xff0c;输出固定长度的256位&#xff08;32字节&#xff09;哈希值。SHA-256被广泛用于加密、数字签名、密码学以及区块链等领域&#xff0c;因为它提供了高度的安…

小程序如何关联视频号小店,实现商品同步

​随着短视频平台的兴起&#xff0c;视频号小店成为了很多商家推广产品和服务的新渠道。下面介绍如何将小程序与视频号小店关联起来&#xff0c;实现商品的同步。 1. 关联视频号小店。在小程序管理员后台->营销管理->视频号小店页面&#xff0c;点击双向箭头&#xff0c…

STM32+USB3300复位枚举异常的问题

关键字&#xff1a;STM32F4&#xff0c;STM32H7&#xff0c;USB3300&#xff0c;USBHS&#xff0c;Reset复位 F4和H7用的都是DWC2的USBIP&#xff0c;我的板子上3300单片机工作的很好&#xff0c;插入枚举一切正常&#xff0c;但是设备收到上位机的复位命令后&#xff0c;单片…

【Java 进阶篇】使用 JDBCTemplate 执行 DML 语句详解

JDBCTemplate 是 Spring 框架中的一个核心模块&#xff0c;用于简化 JDBC 编程&#xff0c;使数据库操作更加便捷和高效。在本文中&#xff0c;我们将重点介绍如何使用 JDBCTemplate 执行 DML&#xff08;Data Manipulation Language&#xff09;语句&#xff0c;包括插入、更新…

面试题:你是如何计划和组织一个大型的软件测试项目的?

今天我们讲个软件测试的面试问题&#xff1a;你是如何计划和组织一个大型的软件测试项目的&#xff1f; 这种题目&#xff0c;就是看你的流程梳理&#xff0c;一定要在回答的步骤前面加上1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;自己就能很清晰&#xff0c;面试…

程序员如何从容地面对裁员?我有6个小建议

2023年3月&#xff0c;世界银行发布了一份题为《长期下行的增长前景&#xff1a;趋势、期望和政策》的报告&#xff0c;首次全面评估了未来全球的经济发展趋势。报告描述的趋势令人担忧&#xff1a;推动过去三十年进步和繁荣的所有经济力量几乎都在消退 2022年至2030年的全球潜…

Windows系统无法激活Python虚拟环境的解决方案:无法加载文件 ,因为在此系统上禁止运行脚本。

原文链接&#xff1a;Windows系统无法激活Python虚拟环境的解决方案 我的个人博客//推广一下w 情况描述 在Windows系统终端激活Python虚拟环境时可能出现以下报错&#xff08;假设你的虚拟环境名为“.venv”&#xff09;&#xff1a; PS (yourpath\yourProj)> .\.venv\S…

CTFHUB SSRF

目录 web351 ​编辑 web352 web353 web354 sudo.cc 代表 127 web355 host长度 web356 web357 DNS 重定向 web358 bypass web359 mysql ssrf web360 web351 POST查看 flag.php即可 web352 <?php error_reporting(0); highlight_file(__FILE__); $url$_…

【德哥说库系列】-Clickhouse集群部署(3分片2副本)

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

【网络安全-信息收集】网络安全之信息收集和信息收集工具讲解(提供工具)

工具下载百度网盘链接(包含所有用到的工具&#xff09;&#xff1a; 百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固&#xff0c;支持教育网加速&#xff0c;支持手机端。注册使用百度网盘即可享受免费存储空间https://pan.…

Cocos Creator3.8 项目实战(六)Combobox控件的实现和使用

在cocoscreator 中&#xff0c;没有Combobox控件&#xff0c;无奈之下只能自己动手写一个。 ⚠️ 文末附 ComboBox.ts 、ComboBoxItem.ts 完整源码&#xff0c; 可直接拿去使用。 实现原理&#xff1a; 1、Combobox 背景图background 是一个sprite 控件&#xff0c;上面放了一…

Python基本功

任何工作&#xff0c;没别的&#xff0c;就是苦练基本功&#xff0c;在篮球场上&#xff0c;我常用非常简单的基本功就可以克敌制胜&#xff0c;工作中也是如此 字符串 1&#xff1a;字符串拼接 a"人民" b123 print("我是"a""str(b))2&#x…

c语言 任意进制数的转换

/* 函数trans将无符号整数n翻译成d&#xff08;2<&#xff1d;d<&#xff1d;16&#xff09;进制表示的字符串s */ #define M sizeof(unsigned int)*8 int trans(unsigned n, int d, char s[]) {static char digits[] "0123456789ABCDEF"; /* 十六进制数字的字…