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

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

作者头像
程序猿杜小头
发布于 2022-12-01 13:40:17
发布于 2022-12-01 13:40:17
45600
代码可运行
举报
文章被收录于专栏:程序猿杜小头程序猿杜小头
运行总次数:0
代码可运行

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
代码运行次数:0
运行
AI代码解释
复制
@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
代码运行次数:0
运行
AI代码解释
复制
stack com.example.crimson_typhoon.controller.CrimsonTyphoonController v1Fire

1.3 发起Http请求

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
运行
AI代码解释
复制
0 = RequestMappingHandlerMapping
1 = BeanNameUrlHandlerMapping
2 = RouterFunctionMapping
3 = SimpleUrlHandlerMapping
4 = WelcomePageHandlerMapping

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
public void afterPropertiesSet() {
    initHandlerMethods();
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
运行
AI代码解释
复制
0 = RequestMappingHandlerAdapter
1 = HandlerFunctionAdapter
2 = HttpRequestHandlerAdapter
3 = SimpleControllerHandlerAdapter

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
@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
运行
AI代码解释
复制
 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
代码运行次数:0
运行
AI代码解释
复制
------------------------------
{
    "name": "crimson_typhoon",
    "age": 28
}
------------------------------
dryRun=true
------------------------------

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
运行
AI代码解释
复制
 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
代码运行次数:0
运行
AI代码解释
复制
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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
转行大数据 1 个月,我麻了。。。
大家好,我是鱼皮。因为种种原因,最近我接手了组内部分大数据开发工作,对我来说是一个几乎完全陌生的领域;大学虽然也自学过,但也都是浅尝辄止,面对企业项目还是有点虚的,所以最近抽了很多时间在自学大数据,很少写文章了。
程序员鱼皮
2022/11/07
7960
转行大数据 1 个月,我麻了。。。
大数据十年,十年爬坑之路
近年来,大数据技术的发展,不论是技术迭代,还是生态圈的繁荣,都远超我们的想象。从 Spark 成为 Hadoop 生态的一部分,到 Flink 横空出世挑战 Spark 成为大数据处理领域的新星,大数据技术的发展可谓跌宕起伏,波澜壮阔。
挖数
2019/11/12
1.1K0
【云+社区年度征文】2021秋招,我从Java开发劈腿了大数据
今年秋招之前,我曾以为我以后会是一名Java开发,但是在真正的秋招过程中,我出轨了大数据(呵呵,男人!),既然将它作为第一份职业,那就要好好来了解下它,要对现有的大数据的生态有个直观的理解,所以在此基础上列出自己的学习计划和自己的职业规划。在这里,要特别感谢韩顺平老师B站2020大数据公开课,受益匪浅,视频链接在参考文献中,感兴趣的小伙伴可以看看。
Simon郎
2020/12/03
3610
从后端到大数据,这里帮你规划一条高薪之路!
大数据(big data),是近几年很火的一共概念。 **什么是大数据?**就举一个生活中很常见的一个例子,平常我们使用APP在各大商城进行商品浏览购物的时候,你会发现,当你在一类商品停留的时间较长时,回到首页,轮播图推荐跟猜你喜欢那一栏就有很大的可能给你推荐你刚刚浏览过去商品的同类。这里面就涉及到了大数据的一个概念,APP通过你的浏览记录,分析用户行为,再根据大数据的推荐系统,就完成了从点击浏览,到秒处理推荐的一个过程。 大数据,说白了就是大量数据的一个集合,来源于海量用户的一次次行为数据。大数据的核心意义不在于获取掌握庞大的数据信息,而在于对这些具有巨大价值的数据进行处理,进而得到这些数据的价值。
大数据小禅
2021/08/16
3830
从后端到大数据,这里帮你规划一条高薪之路!
你为什么从java开发转大数据? 大数据方向能走的更远吗?
看到这个问题,想到了几年前,学校刚毕业时,在传统行业做java开发,一直想加入bat,没有门路,在当时,对于双非渣二本学历、又没有什么互联网大厂经验的同学来说,还是很难的,基本面试的机会都不会给吧。
数据仓库践行者
2020/04/20
1.1K0
AI时代就业指南:Java 程序员如何转行做大数据?
随着大数据的爆发,中国IT业内环境也将面临新一轮的洗牌,不仅是企业,更是从业人员转型可遇而不可求的机遇。如果将IT人士统一比作一条船上的海员,大数据就是最大的浪潮,借浪潮之势而为之,可成功从IT程序员转行成为大数据专家。 在美国,大数据工程师平均年薪达17.5万美元,在中国顶尖的互联网公司里,大数据工程师的薪酬比同级别的其他职位高出30%以上。DT时代来得太突然了,国内发展势头很猛,而大数据相关的人才却非常地有限,在未来若干年内都会是供不应求的状况,因此程序员们,你们的春天到了! 当然,专
小莹莹
2018/04/19
1.3K0
AI时代就业指南:Java 程序员如何转行做大数据?
2023-2024年最新大数据学习路线
新路线图在Spark一章不再以Java,而把Python语言作为第一语言,更适应未来的发展趋势,路线图主要分为六大模块,根据以下内容对照自己掌握了多少大数据的知识,查缺补漏!文末送全套视频+源码资料。
Maynor
2023/09/23
8970
2023-2024年最新大数据学习路线
听说懂java的人学大数据更容易上手?
最近两年,大数据这个词非常火,以大数据为基础和核心的人工智能也以迅雷不掩耳之势蔓延到各个领域,无人驾驶,无人超市,智慧城市等等。毫无疑问,火爆的大数据已然成为当今互联网世界中的新宠儿,创造着巨大的商业价值,是当今互联网巨头的必争之地。
Java团长
2018/08/03
1.4K0
【吐血整理】Java项目源码分享
java servlet+jsp+bean开发开源宅商城系统,未用任何java开源框架
全栈程序员站长
2022/09/08
2.6K0
【吐血整理】Java项目源码分享
8年Java开发大佬告诉你,程序员不加班就没有前途吗?
我目前工作与一家500强外企(欧美),职称为高级软件开发专家。我在这家公司(500强外企)已经服务了8年,所有加班次数合起来不超过一个月天,最近2年则一次都没有。
Java技术栈
2019/05/17
8290
8年Java开发大佬告诉你,程序员不加班就没有前途吗?
211硕士Java实习全挂!不想卷后端了,大数据方向想快速入门找实习,该怎么做?
今天给大家分享的是一个球友的提问,如果你也正好是应届生再找实习,那么也可以照着这个方向去学习。
王知无-import_bigdata
2023/11/14
3880
211硕士Java实习全挂!不想卷后端了,大数据方向想快速入门找实习,该怎么做?
大数据开发需要学习哪些技术?
Java开发介绍、熟悉Eclipse开发工具、Java语言基础、Java流程控制、Java字符串、Java数组与类和对象、数字处理类与核心技术、I/O与反射、多线程、Swing程序与集合类
加米谷大数据
2019/04/19
3690
大数据开发需要学习哪些技术?
我是个Java开发者,我到底要不要学大数据开发?
一入编程深似海,从此女神是路人。没办法,这行就这样。你不学Spring,总不是跑去学JVM/微服务架构/分布式去了,不断学习根本避免不了。所以关键在于把时间投在学什么上比较划算。
程序猿DD
2019/05/24
7440
什么是大数据?大数据学习路线和就业方向
大数据又称巨量资料,就是数据量大、来源广、种类繁多(日志、视频、音频),大到PB级别,现阶段的框架就是为了解决PB级别的数据。
一起学习大数据
2019/06/25
1.5K0
大数据开发和java开发有什么不同?
最近发现有些同学并不太了解大数据开发工程师这个职位,所以想简单介绍一下什么是大数据开发工程师,当前互联网公司的数据开发到底是什么样子的?和一般的Java或者PHP工程师在工作上有什么区别?
全栈程序员站长
2022/09/01
4320
Java转行大数据可行吗?
提到大数据,很多人会想到Java,提到Java,也会想到大数据,二者有什么关系呢?哪个发展更好?
加米谷大数据
2019/04/19
5520
Java转行大数据可行吗?
❤️大数据专业的学妹问我大数据怎么入门,我总结了亲身体验的学习路线推荐给她【推荐收藏】❤️
前言 大家好,我是程序员Manor,我希望自己能成为国家复兴道路的铺路人,大数据领域的耕耘者,平凡但不甘于平庸的人。 前两天有学妹私信我说,她已经上完大一,大数据专业的,只学过大数据导论,问我大
Maynor
2021/07/23
1.1K0
小米大模型数据开发工程师-武汉
首先是对于大模型的理解,最经典的就是chatgpt,PI AI,claude2, Bard,这些是市面上使用体验最好的大语言模型,这也是我平日生活中工作学习必不可少的部分,从它诞生开始我就一直持续的使用,不论是官网的还是二次创作的插件。
GeekLiHua
2025/01/21
1210
小米大模型数据开发工程师-武汉
2019金三银四已过,送你一篇Java面经
趁着五一假期前,把自己最近的求职经历整理了一下,也顺便分享给有需要还在求职的小伙伴。小编要趁着假期出去放松一下紧张学习的心情,归来整理好心情重新开始新的旅程!也祝成功找到心仪offer的小伙伴们,愉快的去度假。作为技术开发人员,跳槽是一种很普遍和正常的事情;尤其是在互联网行业中更为凸显,而Java作为昔日的第一大编程语言,某招聘网站上收集了一些从实习到初级(1-3年),中高级(3-5年),高级(5-10)及以上Java开发工程师的岗位和薪资待遇范围统计(仅作为普通大众水平,BAT一线远不止这个数):
程序大视界
2020/07/21
3590
Java大数据开发怎么学习比较好?
另外,你也要考虑时间、精力、金钱等各方面的投入情况。学习和掌握大数据相关技术也非一朝一夕之事,不可能一蹴而就,一般的培训课程只能达到入门级别的介绍和讲解,真正要学会并很好地运用大数据技术你还需要后续更深入的学习和大量的实践。所以需要你一个良好的学习规划。
加米谷大数据
2020/04/21
1K0
Java大数据开发怎么学习比较好?
推荐阅读
相关推荐
转行大数据 1 个月,我麻了。。。
更多 >
LV.0
狼鸽教育班主任
目录
  • 1. 写在前面
    • 1.1 编写Controller并启动应用
    • 1.2 启动Arthas
    • 1.3 发起Http请求
    • 1.4 执行链路
  • 2. Spring MVC处理流程
    • 2.1 HandlerMapping
    • 2.2 HandlerAdapter
  • 3. 总结
  • 参考文档
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档