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

2022-12-01 21:40:17 浏览数 (1)

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/

0 人点赞