前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >嗨,CRUD BOY们,是时候掌握Spring MVC的处理流程了

嗨,CRUD BOY们,是时候掌握Spring MVC的处理流程了

作者头像
程序猿杜小头
发布2022-12-01 21:40:17
3700
发布2022-12-01 21:40:17
举报
文章被收录于专栏:程序猿杜小头程序猿杜小头

Running with Spring Boot v2.5.4, Java 11.0.12

Spring MVC是一款构建于Servlet API之上、基于同步阻塞I/O模型的主流Java Web开发框架,这种I/O模型意味着一个Http请求对应一个线程,即每一个Http请求都是在各自线程上下文中完成处理的;此外,Spring 5.0提供了一款基于异步非阻塞I/O模型的Java Web开发框架,即Spring WebFlux;大家不用纠结Spring官方会不会在将来的某个时间点将Spring MVC置为废弃(deprecated)态,至少目前来看,Spring MVC依然是流行的,在Spring官网关于Reactive的介绍中有一张图相当精致,与大家分享:


笔者自2017年7月毕业后一直服役于某央企一子公司,第一年主要参与集团公司委派的课题性项目,基本没写啥代码;自18年国庆至今,虽然辗转于多个项目组,但角色一直没变,那就是CRUD BOY,还特么挺稳的,哈哈。即使菜如CRUD BOY,也没有理由不掌握Spring MVC的相关知识,为什么这么忽悠呢?以Tomcat为例,它是目前应用最为广泛的Servlet容器,当Tomcat接收到一个Http请求后,底层复杂的Socket解析工作由它代劳了,Http请求解析完成后,它直接将HttpServletRequestHttpServletResponse(这时候还是一个空的对象)对象一并传给Servlet处理,大家只需要面向Servlet编程即可;在Spring MVC框架问世后,Servlet开始退居幕后,Java程序猿的工作变得更加轻松而聚焦了,压根不再需要和HttpServletRequest、HttpServletResponse打交道,会复制粘贴@RestController就行;前辈们做了这么多苦活、累活,如果连Spring MVC内部处理流程还一无所知,实在有点说不过去了···

1. 写在前面

一个Http请求在Spring应用中的执行链路比较长,涉及逻辑也比较多,下面通过阿里的Arthas工具来探测这条执行链路。

1.1 编写Controller并启动应用

代码语言:javascript
复制
@RestController
@RequestMapping(path = "/crimson_typhoon")
public class CrimsonTyphoonController {
    @PostMapping(path = "/v1/fire")
    public Map<String, Object> v1Fire(@RequestBody @Valid UserDto userDto, 
                                      @RequestParam("dryRun") Boolean dryRun) {
        return ImmutableMap.of("status", "success", "code", 200);
    }
}

@Getter
@Setter
@NoArgsConstructor
public class UserDto {
    @NotBlank
    private String name;

    @NotNull
    private int age;
}

1.2 启动Arthas

执行如下命令以监听并在控制台打印CrimsonTyphoonControllerv1Fire()方法的执行路径。

代码语言:javascript
复制
stack com.example.crimson_typhoon.controller.CrimsonTyphoonController v1Fire

1.3 发起Http请求

代码语言:javascript
复制
curl --location --request POST 'http://localhost:8080/crimson_typhoon/v1/fire?dryRun=true' \
--header 'Content-Type: application/json' \
--data-raw '{
    "name": "crimson_typhoon",
    "age": 18
}'

1.4 执行链路

如果有大佬是从上往下看这串执行链路信息的,这边建议您出门左转,谢谢!

代码语言:javascript
复制
ts=2021-11-09 21:22:37;thread_name=http-nio-8080-exec-2;id=37;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@149debbb
    @com.example.crimson_typhoon.controller.CrimsonTyphoonController.v1Fire()
        at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-2)
        at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:566)
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1064)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:681)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1726)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:834)

大家应该对上面这坨信息很熟悉才对,因为在排查问题时经常可以在日志中看到;上述执行链路交代了两件事:

  1. 当一个Http请求到达后,Tomcat从其线程池中捞出一个线程来处理该Http请求的,记得《Go语言学习笔记》这本书中曾经提到:一切Go程序都是基于Channel的,Java又何尝不是呢?一切Java程序都是基于Thread的;
  2. 在Http请求进入Spring MVC之前,先要依次过一遍ApplicationFilterChain中的Filter,默认有4个:OrderedCharacterEncodingFilterOrderedFormContentFilterOrderedRequestContextFilterWsFilter

Filter来源于Servlet规范,并不是Spring中的术语。跟Filter紧密联系的还有FilterConfigFilterChain,它们的主要内容如下图所示:

Filter既可以过滤HttpServletRequest,也可以过滤HttpServletResponse;FilterConfig由Servlet容器组装,以初始化Filter;FilterChain同样由Servlet容器组装,为开发人员提供Filter链的执行视图,FilterChain是责任链模式的一个典型应用;在Filter链的结尾,将会调用真正的资源,比如在ApplicationFilterChain中,Filter链执行完毕后,将Http请求委派给DispatcherServlet处理,而DispatcherServlet恰恰就是Spring MVC的门户,如下所示:

代码语言:javascript
复制
public final class ApplicationFilterChain implements FilterChain {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response) {
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = filterConfig.getFilter();
            filter.doFilter(request, response, this);
            return;
        }
        // 过滤器链中的所有过滤器执行完毕后,
        // 将HTTP请求委派给DispatcherServlet处理;
        servlet.service(request, response);
    }
}

关于Filter还有一个比较重要的知识点,那就是Filter的执行流程,类似一种U型链路,如下图所示:

2. Spring MVC处理流程

如果单单看Arthas针对v1Fire()方法输出的执行路径,会造成大家误认为Spring MVC处理流程很简短的假象,事实上,当我们一步一步DEBUG的时候,才知道这特么完全是无底洞啊!刚刚提到DispatcherServlet是Spring MVC的门户,那自然要从它开始了,在介绍DispatcherServlet之前,先来看看它的继承关系:

Front Controller设计模式中,通常由一个核心Controller负责将Http请求路由到其他Controller中处理,Spring MVC实现了这一模式,这个核心Controller就是DispatcherServlet。DispatcherServlet就像一个包工头,只揽活却不干活,当接收到Http请求后,它会将该请求委派给HandlerMappingHandlerAdapterHandlerExceptionResolverViewResolver等小弟处理。doDispatch()方法是其核心逻辑所在,为了更直观地展现Spring MVC整体流程骨架,笔者剔除了一些不相干的逻辑,如下所示:

代码语言:javascript
复制
public class DispatcherServlet extends FrameworkServlet {
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerExecutionChain mappedHandler = null;
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
            try {
                // Determine handler for the current request.
                mappedHandler = getHandler(request);

                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Invoke all HandlerInterceptor preHandle() in HandlerExecutionChain.
                if (!mappedHandler.applyPreHandle(request, response)) {
                    return;
                }

                // Actually invoke the handler.
                mv = ha.handle(request, response, mappedHandler.getHandler());

                // Invoke all HandlerInterceptor postHandle() in HandlerExecutionChain.
                mappedHandler.applyPostHandle(request, response, mv);
            } catch (Exception ex) {
                dispatchException = ex;
            }
            // Handle the result of handler invocation, which is either a ModelAndView 
            // or an Exception to be resolved to a ModelAndView.
            processDispatchResult(request, response, mappedHandler, mv, dispatchException);
        } catch (Exception ex) {
            // Invoke all HandlerInterceptor afterCompletion() in HandlerExecutionChain.
            triggerAfterCompletion(request, response, mappedHandler, ex);
        }
    }
}

Spring MVC的内部逻辑与HandlerMapping、HandlerAdapter息息相关,下面将从两个章节对它们进行分析。

2.1 HandlerMapping

HandlerMapping用于将Http请求映射到对应的HandlerExecutionChain

代码语言:javascript
复制
public interface HandlerMapping {
    /**
     * @param request current HTTP request
     * @return a HandlerExecutionChain instance containing handler object and
     * any interceptors
     */
    HandlerExecutionChain getHandler(HttpServletRequest request);
}

HandlerExecutionChain内部维护了handlerinterceptorList这俩个成员变量。其中,handler一般指的是由@Controller标注的控制器,interceptorList指的是HandlerInterceptor列表。HandlerExecutionChain的精简版源码如下:

代码语言:javascript
复制
public class HandlerExecutionChain {
    private final Object handler;
    private final List<HandlerInterceptor> interceptorList = new ArrayList<>();

    public HandlerExecutionChain(Object handler, List<HandlerInterceptor> interceptorList) {}

    // Apply preHandle methods of registered interceptors.
    public boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) {}

    // Apply postHandle methods of registered interceptors.
    public void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) {}

    // Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
    public void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) {}
}

紧接着,我们再来看一下HandlerInterceptor。顾名思义,这个拦截器可以在handler执行前后进行拦截操作,具体通过下面三个方法:

代码语言:javascript
复制
public interface HandlerInterceptor {
    // Interception point before the execution of a handler.
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        return true;
    }

    // Interception point after successful execution of a handler.
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                            ModelAndView modelAndView) {
    }

    // Callback after completion of request processing.
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, 
                                 Exception ex) {
    }
}

HandlerInterceptor的执行流程也很重要,尤其是postHandle()afterCompletion()这俩方法,具体参见下图:


那HandlerMapping需要开发人员自定义吗?一般是不需要这么做的。默认地,Spring MVC会提供一些不同映射规则的HandlerMapping,这些HandlerMapping会通过initHandlerMappings()方法提前填充到DispatcherServlet中的handlerMappings成员变量中:

代码语言:javascript
复制
private void initHandlerMappings(ApplicationContext context) {
    Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class);
    if (!matchingBeans.isEmpty()) {
        this.handlerMappings = new ArrayList<>(matchingBeans.values());
        AnnotationAwareOrderComparator.sort(this.handlerMappings);
    }
}

handlerMappings中各元素如下:

代码语言:javascript
复制
0 = RequestMappingHandlerMapping
1 = BeanNameUrlHandlerMapping
2 = RouterFunctionMapping
3 = SimpleUrlHandlerMapping
4 = WelcomePageHandlerMapping

RequestMappingHandlerMapping无疑是最重要的一个HandlerMapping,从其名称大概能猜测到其映射规则一定与@RequestMapping注解有关,刚好CrimsonTyphoonController也由@RequestMapping标注。下面就来聊聊它是如何根据Http请求获取到相匹配的HandlerExecutionChain的。抽象类AbstractHandlerMethodMapping是RequestMappingHandlerMapping的父类,它实现了InitializingBean接口,那应该会覆盖afterPropertiesSet()方法,否则不是闲得无聊吗?从其源码来看果然如此:

代码语言:javascript
复制
@Override
public void afterPropertiesSet() {
    initHandlerMethods();
}

initHandlerMethods()方法主要用于初始化AbstractHandlerMethodMapping中MappingRegistry类型的成员变量。具体地,通过遍历所有Bean,判断当前Bean是否由@Controller@RequestMapping注解标注;若是,则填充MappingRegistry类中HashMap<RequestMappingInfo, MappingRegistration>类型的成员变量,即registry;HashMap中key/value信息如下:

代码语言:javascript
复制
key   = RequestMappingInfo{POST /crimson_typhoon/v1/fire}
value = MappingRegistration{
            HandlerMethod{
                public java.util.Map com.example.crimson_typhoon.controller.CrimsonTyphoonController.v1Fire(
                    com.example.crimson_typhoon.dto.UserDto,
                    java.lang.Boolean
                )
            }
        }

有了上面这层映射关系,根据Http请求获取对应的HandlerMethod就是轻而易举的事了(在HandlerMethod中,有两个比较重要的成员变量,分别是Object类型的bean变量和java.lang.reflect.Method类型的method变量);最后,将HandlerMethod与HandlerInterceptor封装到HandlerExecutionChain实例中去就结束了。

handler其实是被封装在HandlerMethod实例中的。

2.2 HandlerAdapter

上一小节主要介绍如何根据Http请求获取相匹配的HandlerExecutionChain实例;在本小节中,将重点关注HandlerExecutionChain中HandlerMethod的执行流程。既然HandlerMethod中的method变量是java.lang.reflect.Method类型的,那这架势肯定是奔着反射去的了,回顾下java.lang.reflect.Method的核心API:

代码语言:javascript
复制
public class Method {
    public Object invoke(Object obj, Object... args) {
        // 实现细节忽略
    }
}

很显然,method实例要想顺利调用其invoke()方法,现在还差一个方法参数,那就从参数解析开始分析吧。


顾名思义,HandlerAdapter是一个适配器,它为Spring MVC带来了拓展性,可以适配任意类型的handler,绝不仅仅是那些由@Controller注解标注的handler。与HandlerMapping类似,开发人员若无特殊需求,一般也无需自定义HandlerAdapter。默认地,Spring MVC会提供一些可以适配不同handler的HandlerAdapter,比如:适配org.springframework.web.servlet.mvc.Controller接口的SimpleControllerHandlerAdapter,适配javax.servlet.Servlet接口的SimpleServletHandlerAdapter等;此外,这些HandlerAdapter是通过initHandlerAdapters()方法提前填充到DispatcherServlet中这一handlerAdapters成员变量中的:

代码语言:javascript
复制
private void initHandlerAdapters(ApplicationContext context) {
    Map<String, HandlerAdapter> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class);
    if (!matchingBeans.isEmpty()) {
        this.handlerAdapters = new ArrayList<>(matchingBeans.values());
        AnnotationAwareOrderComparator.sort(this.handlerAdapters);
    }
}

handlerAdapters中各元素如下:

代码语言:javascript
复制
0 = RequestMappingHandlerAdapter
1 = HandlerFunctionAdapter
2 = HttpRequestHandlerAdapter
3 = SimpleControllerHandlerAdapter

RequestMappingHandlerAdapter既然能排在第一把交椅,那足以说明它的重要性,它主要用于适配HandlerMethod类型的handler,如下:

代码语言:javascript
复制
public class RequestMappingHandlerAdapter {
    /**
     * This implementation expects the handler to be an HandlerMethod.
     */
    @Override
    public final boolean supports(Object handler) {
        return handler instanceof HandlerMethod;
    }
}

DispatcherServlet发现RequestMappingHandlerAdapter的supports()方法返回true,就不再继续寻找适配器了,因为最匹配的适配器已经找到。

此外,RequestMappingHandlerAdapter同样实现了InitializingBean接口,用于初始化argumentResolversreturnValueHandlers这俩成员变量,如下所示:

代码语言:javascript
复制
@Override
public void afterPropertiesSet() {
    if (this.argumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.returnValueHandlers == null) {
        List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
}

argumentResolvers中各元素如下:

代码语言:javascript
复制
 0 = RequestParamMethodArgumentResolver
 1 = RequestParamMapMethodArgumentResolver
 2 = PathVariableMethodArgumentResolver
 3 = PathVariableMapMethodArgumentResolver
 4 = MatrixVariableMethodArgumentResolver
 5 = MatrixVariableMapMethodArgumentResolver
 6 = ServletModelAttributeMethodProcessor
 7 = RequestResponseBodyMethodProcessor
 8 = RequestPartMethodArgumentResolver
 9 = RequestHeaderMethodArgumentResolver
10 = RequestHeaderMapMethodArgumentResolver
11 = ServletCookieValueMethodArgumentResolver
12 = ExpressionValueMethodArgumentResolver
13 = SessionAttributeMethodArgumentResolver
14 = RequestAttributeMethodArgumentResolver
15 = ServletRequestMethodArgumentResolver
16 = ServletResponseMethodArgumentResolver
17 = HttpEntityMethodProcessor
18 = RedirectAttributesMethodArgumentResolver
19 = ModelMethodProcessor
20 = MapMethodProcessor
21 = ErrorsMethodArgumentResolver
22 = SessionStatusMethodArgumentResolver
23 = UriComponentsBuilderMethodArgumentResolver
24 = PrincipalMethodArgumentResolver
25 = RequestParamMethodArgumentResolver
26 = ServletModelAttributeMethodProcessor

argumentResolversHandlerMethodArgumentResolverComposite类型的,后者是一个复合类,持有多个HandlerMethodArgumentResolver类型的方法参数解析器。当需要获取方法参数解析器时,HandlerMethodArgumentResolverComposite会遍历其所持有的所有参数解析器,若HandlerMethodArgumentResolver的supportsParameter()方法返回true,这意味着找到了最匹配的方法参数解析器,不再继续查找。在本文中,Http请求携带了两种参数,具体如下:

代码语言:javascript
复制
------------------------------
{
    "name": "crimson_typhoon",
    "age": 28
}
------------------------------
dryRun=true
------------------------------

对于Http请求体中的JSON串,交由argumentResolvers中的RequestResponseBodyMethodProcessor负责解析;它有两个作用,一是将Http请求体中的JSON串封装到由@RequestBody标注的参数实例中,即UserDto,二是将由@ResponseBody标注的handler中方法的返回值写入到Http响应体中,参数解析相关源码如下:

代码语言:javascript
复制
public class RequestResponseBodyMethodProcessor {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestBody.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
        String name = Conventions.getVariableNameForParameter(parameter);

        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
            if (arg != null) {
                validateIfApplicable(binder, parameter);
                if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                    throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
                }
            }
        }
        return adaptArgumentIfNecessary(arg, parameter);
    }
}

RequestResponseBodyMethodProcessor持有的messageConverters成员变量中内嵌了近10个HttpMessageConverter类型的转换器,因此具体的参数解析工作将由其中一个转换器负责,至于究竟是哪一个转换器,这要看canRead()方法是否能返回true了,若返回值为true,则会通过该转换器的read()方法解析出最终的参数;本文所提及的JSON串最终是由MappingJackson2CborHttpMessageConverter负责填充到UserDto实例中去的(由于@Valid注解的存在,还会紧接着进行Bean Validation操作)。此外,HttpMessageConverter具备双向转换能力,其源码如下:

代码语言:javascript
复制
public interface HttpMessageConverter<T> {
    // Indicates whether the given class can be read by this converter.
    boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

    // Indicates whether the given class can be written by this converter.
    boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

    // Read an object of the given type from the given input message, and returns it.
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage);

    // Write an given object to the given output message.
    void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage);
}

对于Http请求URL中的参数,交由argumentResolvers中的RequestParamMethodArgumentResolver负责,具体借助org.springframework.core.convert.support.Converter接口的实现类StringToBooleanConverter将字符串类型值转换为布尔值。

argumentResolvers中各元素如下:

代码语言:javascript
复制
 0 = ModelAndViewMethodReturnValueHandler
 1 = ModelMethodProcessor
 2 = ViewMethodReturnValueHandler
 3 = ResponseBodyEmitterReturnValueHandler
 4 = StreamingResponseBodyReturnValueHandler
 5 = HttpEntityMethodProcessor
 6 = HttpHeadersReturnValueHandler
 7 = CallableMethodReturnValueHandler
 8 = DeferredResultMethodReturnValueHandler
 9 = AsyncTaskMethodReturnValueHandler
10 = ServletModelAttributeMethodProcessor
11 = RequestResponseBodyMethodProcessor
12 = ViewNameMethodReturnValueHandler
13 = MapMethodProcessor
14 = ServletModelAttributeMethodProcessor

returnValueHandlersHandlerMethodReturnValueHandlerComposite类型的,后者也是一个复合类,持有多个HandlerMethodReturnValueHandler类型的方法返回值解析器。由于CrimsonTyphoonController由@RestController标注,而@RestController注解接口又由@ResponseBody标注,因此RequestResponseBodyMethodProcessor最终从这些候选解析器中脱颖而出,很熟悉对不对?废话不多说,这一解析器依然会委派MappingJackson2CborHttpMessageConverter将v1Fire()方法的返回值转换为JSON串,然后写入到Http响应体中去。

代码语言:javascript
复制
public class RequestResponseBodyMethodProcessor {
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
                returnType.hasMethodAnnotation(ResponseBody.class));
    }

    @Override
    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                                  ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
        mavContainer.setRequestHandled(true);
        ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
        ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

        writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
    }
}

目前,前后端分离已成为业界开发与部署的标准模式,压根不会将html、jsp等页面嵌入在后端应用中,因此视图渲染就不再赘述了。

3. 总结

事实上,本文也只是粗略分析了Spring MVC的处理流程,还有一些重要的细节没有覆盖,比如:统一异常处理,限于篇幅,后续再介绍它的原理与最佳实践方案吧。

参考文档

  1. https://spring.io/reactive
  2. https://docs.spring.io/spring-framework/docs/current/reference/html/
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-11-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序猿杜小头 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 写在前面
    • 1.1 编写Controller并启动应用
      • 1.2 启动Arthas
        • 1.3 发起Http请求
          • 1.4 执行链路
          • 2. Spring MVC处理流程
            • 2.1 HandlerMapping
              • 2.2 HandlerAdapter
              • 3. 总结
              • 参考文档
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档