spring mvc的流程源代码分析

spring mvc的代码相对比较简单,因为是线性的,并且是单线程的。。。

使用的spring boot debug运行查看源码的。

从DispatcherServlet的doDispatch开始分析,前面的逻辑无关紧要。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

// 这个类里,保存了拦截器,handler(封装了将要调用的方法信息,包括controller,method,parameter等)
    HandlerExecutionChain mappedHandler = null;
// DispatchServlet中初始化了很多HandlerMapping,HandleMapping对应请求的处理方式,这里,找寻一个能够处理这个请求的handleMapping,封装成HandlerExecutionChain
// 一般我们用的是requestHandlermapping,初始化的时候,会添加所有拦截器
// 没有能够处理这个请求的HandleMapping,则报错
// 通过handlerMapping构造HandlerExecutionChain的时候,会通过url,找到适配的拦截器,就是在这个方法里
    mappedHandler = getHandler(processedRequest);
// DispatcchServlet中初始化了很多HandleAdapter,找寻一个支持这个HandleExecutionChain的
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 这个方法,会遍历调用所有的拦截器的preHandle方法
mappedHandler.applyPreHandle(processedRequest, response)
// 实际调用handler,重点
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 设置viewName,请求跳转view展示的,现在都是前后端分离的,忽略
applyDefaultViewName(processedRequest, mv);
// 遍历调用拦截的postHandle方法,调用顺序很调用preHandle的时候相反
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 有异常,则这里异常处理器进行处理
// 没异常,则渲染试图(前后端分离忽略),
// 遍历调用拦截器的afterCompletion,调用顺序很调用preHandle的时候相反
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
image.png
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            for (HandlerMapping hm : this.handlerMappings) {
                if (logger.isTraceEnabled()) {
                    logger.trace(
                            "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
                }
                HandlerExecutionChain handler = hm.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }

小总结:HandlerExecutionChain保存了很多拦截器和实际调用的handler,dispatchServlet中初始化了很多handleMapping,所有的handlerMapping都映射不了这个请求,则报错,否则封装成HandlerExecutionChain,再获取HandlerAdapter,然后调用拦截器的prehandler方法,之后调用handler,之后调用拦截器的posthandler方法,如果有错误,则使用异常处理器处理,之后调用拦截器的afterCompletion方法

顺序是:调用拦截器prehandler-》调用handler-》调用拦截器postHandler-》可能调用异常处理器-》调用拦截器afterCompletion

重点关注AbstractHandlerMethodAdapter调用handler的方法,

    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

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

关键看这里:
// 这里获取参数,根据http流,找一个可用的参数解析器,然后进行解析,如果是@requestBody修饰的,则用对应的参数解析器RequestResponseBodyMethodProcessor处理,然后它会找各个HttpMessageConvertor转换,如果引入了fastjsoncovertor,则几乎都是用这个转换

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
// 这里获取参数,根据http流,遍历所有的方法参数,一个一个找一个可用的参数解析器,然后进行解析,如果是
//@requestBody修饰的,则用对应的参数解析器
//RequestResponseBodyMethodProcessor处理,然后它会找各个
//HttpMessageConvertor转换,如果引入了fastjsoncovertor,则几乎都是用这个转换
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
            logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
                    "' with arguments " + Arrays.toString(args));
        }
// 有参数了,则调用我们的controller类
        Object returnValue = doInvoke(args);
        if (logger.isTraceEnabled()) {
            logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
                    "] returned [" + returnValue + "]");
        }
        return returnValue;
    }

遍历所有的方法参数,一个一个找对应的参数解析器

private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        MethodParameter[] parameters = getMethodParameters();
        Object[] args = new Object[parameters.length];
// 遍历
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = resolveProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
// 通过参数解析器的supportsParameter方法判断是否支持解析
            if (this.argumentResolvers.supportsParameter(parameter)) {
                try {
// 支持,则解析
                    args[i] = this.argumentResolvers.resolveArgument(
                            parameter, mavContainer, request, this.dataBinderFactory);
                    continue;
                }
                catch (Exception ex) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
                    }
                    throw ex;
                }
            }
            if (args[i] == null) {
                throw new IllegalStateException("Could not resolve method parameter at index " +
                        parameter.getParameterIndex() + " in " + parameter.getExecutable().toGenericString() +
                        ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
            }
        }
        return args;
    }

比如,经常会进入这个复合参数解析器:HandlerMethodArgumentResolverComposite
复合参数解析器,里面包含了很多解析器,会一个一个遍历,找是否支持这个参数解析

@Override
    public boolean supportsParameter(MethodParameter parameter) {
        return (getArgumentResolver(parameter) != null);
    }
// 复合参数解析器,里面包含了很多解析器,会一个一个遍历,找是否支持这个参数解析
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
            for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
                            parameter.getGenericParameterType() + "]");
                }
                if (methodArgumentResolver.supportsParameter(parameter)) {
                    result = methodArgumentResolver;
                    this.argumentResolverCache.put(parameter, result);
                    break;
                }
            }
        }
        return result;
    }

比如如果是@requestBody修饰的参数,复合参数解析器,会找来RequestResponseBodyMethodProcessor来对它解析

// RequestResponseBodyMethodProcessor解析器,支持参数注解有requestBody的
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestBody.class);
    }

RequestResponseBodyMethodProcessor的解析

    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        parameter = parameter.nestedIfOptional();
// 就是这个方法,进行的解析,会遍历各个HttpMessageConvertor进行解析
        Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
        String name = Conventions.getVariableNameForParameter(parameter);

        if (binderFactory != null) {
// 解析之后,进行DataBinder进行数据绑定、数据校验
            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());
                }
            }
            if (mavContainer != null) {
                mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
            }
        }

        return adaptArgumentIfNecessary(arg, parameter);
    }

主要看它的HttpMessageConverter,
HttpMessage就好像是一个数据包,转换器则是把请求体和响应体进行转换。
HttpMessageConverter,有canRead,read,分别是判断请求是否可以转换,以及进行转换,canwriter和writer分别是判断响应体是否可以转换,以及转换。

注意,比如fastJsonConverter是读取tcp流,然后转换成对象的,所以流读完了就不能重复读了,所以,controller如果还有其他参数,再一次参数解析器解析的时候,就直接null返回了。
相应分析如下:

protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
            Type targetType)  {
        MediaType contentType;
        boolean noContentType = false;
        contentType = inputMessage.getHeaders().getContentType();
        if (contentType == null) {
            noContentType = true;
            contentType = MediaType.APPLICATION_OCTET_STREAM;
        }
        Class<?> contextClass = parameter.getContainingClass();
        Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
        if (targetClass == null) {
            ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
            targetClass = (Class<T>) resolvableType.resolve();
        }
        HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
        Object body = NO_VALUE;
        EmptyBodyCheckingHttpInputMessage message;
        try {
            message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
// 重点在这里,有很多个HttpMessageConverters
// FastJsonHttpMessageConverter:我们自己添加的转换器,转换json的,比如@requestBody输出的json就是它转换的
// StringHttpMessageConverter: 转换成字符串,比如:@requestBody的入参
// String param,StringHttpMessageConverter会把请求体body中的数据转换成
// string,注意是请求体,get请求url中的参数转不了
// MappingJackson2HttpMessageConverter:这个也是转换json的,效果和fastjson差不多,如果没有引入fastjson,就是它转换的json
            for (HttpMessageConverter<?> converter : this.messageConverters) {
                Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
                GenericHttpMessageConverter<?> genericConverter =
                        (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
// 各自转换器,通过canRead判断是否可转换,read进行转换  
// canRead和read是HttpMessageConverter接口的方法,所有转换器都必须实现
// 各自转换器的逻辑canRead一般为根据请求MediaType,class类型判断
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
                        (targetClass != null && converter.canRead(targetClass, contentType))) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
                    }
// 可以看到,转换的是body
                    if (message.hasBody()) {
                        HttpInputMessage msgToUse =
                                getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
                        body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                                ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
                        body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
                    }
                    else {
                        body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
                    }
                    break;
                }
            }
        }

        return body;
    }

看一下fastjson转换器的代码。
在媒体类型兼容的情况下,fastjson可以转换任何类型
所以,@requestBody 任何类型,都可以转换。但是再一次强调,一定是请求的body数据。
而且因为fastjson转换器,是后一个加入的,所以,最先使用的转换器就是fastjson,转换不了,才用其他的

public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
        return supports(clazz) && canRead(mediaType);
    }
 @Override
    protected boolean supports(Class<?> clazz) {
        return true;
    }
// 转换的方法,也就是JSON.parseObject
private Object readType(Type type, HttpInputMessage inputMessage) throws IOException {

        try {
            InputStream in = inputMessage.getBody();
            return JSON.parseObject(in, fastJsonConfig.getCharset(), type, fastJsonConfig.getFeatures());
        } catch (JSONException ex) {
            throw new HttpMessageNotReadableException("JSON parse error: " + ex.getMessage(), ex);
        } catch (IOException ex) {
            throw new HttpMessageNotReadableException("I/O error while reading input message", ex);
        }
    }

read分析完了,writer类似,就不分析了。

总结一下:调用handler的过程,1、会遍历所有的方法参数,然后再遍历找参数解析器,比如会找到复合参数解析器,然后复合参数解析器再遍历找到httprequestbody参数解析器,然后,这个解析器,遍历httpMessageConvertor对参数进行转换,比如FastJsonConvertor则直接对参数进行反系列话,而且,这种转换是读取流的,一个参数进行了参数转换,下一个参数转换就是null了,因为流不能重复读。每个参数转换完之后,会进行DataBinder数据校验等相关工作,最后,之后,才会进行调用我们自己的controller方法。

所以,如果你有个性化的需求,可以添加参数解析器,或者,添加httpMessageConvertor
比如,你如果想多个参数都用requestBody修饰,然后都能注入到值,则可以添加自己的参数解析器,再比如,fastjson就添加了自己的httpMessageConvertor

贴一个例子:
浏览器url访问的时候,会报错,转换不了,因为httpmessage转换的是请求体。
用postman,输入请求体的方式能正常访问。

@RestController
@RequestMapping("test")
public class TestController {

    @GetMapping("test")
    public Perso listProvinceCompany(@RequestBody Perso s) {
        return new Perso().setName("hello");
    }
}

回过头来说一下,就是数据绑定类。
在httpmessage转换了数据之后,还有一个数据绑定类,里面有很多数据转换器,把字符串转换成整形,把字符串转换成对象等,当然这些对requestBody没有用,因为requestBody转换的时候,已经从流中读取完了。
比如:如果get请求url附带参数,controller中,直接使用bean接受,发挥作用的就是这里的类型转换器。


image.png

顺便看一个图:

过滤器自然第一个,servlet第二个,servlet调用拦截器prehandle第三个,调用handler的时候会httpmessage转换第四个,aop是getbean初始化的时候BeanPostProcess做的增强代理,可以看成controller,所以第五个,之后真正controller第六个,aop第七个,消息转换第八个,拦截器第九个。

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,874评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,102评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,676评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,911评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,937评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,935评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,860评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,660评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,113评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,363评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,506评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,238评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,861评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,486评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,674评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,513评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,426评论 2 352

推荐阅读更多精彩内容