Spring源码解析 -- SpringMvc原理

Spring源码解析 -- 读取bean元数据
spring源码解析 -- 构造bean
spring源码解析 -- 注入属性
spring源码解析 -- Spring Context
Spring源码解析 -- AOP原理(1)
Spring源码解析 -- AOP原理(2)
Spring源码解析 -- SpringMvc原理

源码分析基于spring 4.3.x
本文通过阅读Spring MVC的源码,解析Spring MVC实现原理。本文不会深入SpringMvc的细节,关注于分析SpringMvc的各个核心组件以及主要逻辑,以便大家深入SpringMvc以及排查问题。

关于阅读源码的思路,可参考 -- 如何阅读java源码

使用Spring MVC时,我们常编写一个spring-mvc.xml,xml中添加<mvc:annotation-driven/>标签,这个标签是由MvcNamespaceHandler处理。
MvcNamespaceHandler#parse -> AnnotationDrivenBeanDefinitionParser#parse

public BeanDefinition parse(Element element, ParserContext parserContext) {
    ...

    RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);  // #1
    ...

    RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);  // #3
    RuntimeBeanReference validator = getValidator(element, source, parserContext);  // #4
    RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);   // #5

    ...

    ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);    // #6
    ManagedList<?> argumentResolvers = getArgumentResolvers(element, parserContext);    // #7
    ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, parserContext);    // #8
    String asyncTimeout = getAsyncTimeout(element); 
    RuntimeBeanReference asyncExecutor = getAsyncExecutor(element); 
    ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext);
    ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext);

    RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);  // #9
    ...
    addRequestBodyAdvice(handlerAdapterDef);    // #10
    addResponseBodyAdvice(handlerAdapterDef);   // #11

    ...
}

这里主要准备一些SpringMvc使用的组件
#1 构造RequestMappingHandlerMapping
#3 处理conversion-service配置,构造ConversionService,默认使用FormattingConversionService处理数字,日期等属性转化
#4 处理validator配置,构造Validator,验证参数
#5 处理message-codes-resolver配置,构造MessageCodesResolver,负责可以绑定错误码和错误信息
#6 处理message-converters配置,构造HttpMessageConverter,负责对http的body进行读写,解析工作,如MappingJackson2XmlHttpMessageConverter负责json格式的http请求。
这里会根据Java环境中存在的json框架,添加对应的json处理类。例如maven引入了jackson框架,就会添加MappingJackson2HttpMessageConverter。
#7 处理argument-resolvers配置,构造HandlerMethodArgumentResolver,负责解析业务方法的参数
#8 处理return-value-handlers配置,构造HandlerMethodReturnValueHandler,负责处理业务方法返回值
#9 构造RequestMappingHandlerAdapter,RequestMappingHandlerAdapter负责使用HandlerMethod调用业务方法,并且处理方法参数和返回,前面步骤获取的messageConverters,argumentResolvers,returnValueHandlers都要注入到RequestMappingHandlerAdapter的属性中,同时,它也会添加默认的HandlerMethodArgumentResolver,HandlerMethodReturnValueHandler等组件类(参考RequestMappingHandlerAdapter#getDefaultReturnValueHandlers)。
#10 如果Java环境中存在jackson框架,构造JsonViewRequestBodyAdvice,JsonViewRequestBodyAdvice可以在读取http请求body前后做一些额外处理
#11 如果Java环境中存在jackson框架,构造JsonViewResponseBodyAdvice,JsonViewResponseBodyAdvice可以在写入http响应body前后做一些额外处理

RequestMappingHandlerMapping维护了URL,@RequestMapping注解以及业务方法(就是@RequestMapping标注的方法)之间的映射关系,父类AbstractHandlerMethodMapping实现了InitializingBean接口,AbstractHandlerMethodMapping#afterPropertiesSet -> initHandlerMethods

protected void initHandlerMethods() {
    ...
    String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
            getApplicationContext().getBeanNamesForType(Object.class)); // #1

    for (String beanName : beanNames) {
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            Class<?> beanType = null;
            try {
                beanType = getApplicationContext().getType(beanName);
            }
            ...
            if (beanType != null && isHandler(beanType)) {  // #2
                detectHandlerMethods(beanName); // #3
            }
        }
    }
    handlerMethodsInitialized(getHandlerMethods());
}

#1 获取所有的beanName
#2 调用RequestMappingHandlerMapping#isHandler,检查Class是否有@Controller/@RequestMapping注解
#3 构造并注册HandlerMethod(封装了业务方法和对应的Class)
这里不再深入解析RequestMappingHandlerMapping,有兴趣的同学可以继续阅读源码。

DispatcherServlet

使用SpringMVC需要在web.xml添加一个DispatcherServlet处理的servlet,DispatcherServlet是实现SpringMVC的关键组件类。

DispatcherServlet#initStrategies方法将加载Spring Context的SpringMvc组件类,如MultipartResolver,HandlerMapping,设置为自己的属性。
如initHandlerMappings方法获取AnnotationDrivenBeanDefinitionParser中构造的RequestMappingHandlerMapping,作为自己的属性handlerMappings。

DispatcherServlet的父类FrameworkServlet继承于HttpServletBean,
doGet/doPost -> DispatcherServlet#doService -> doDispatch

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); // #1
            multipartRequestParsed = (processedRequest != request);

            mappedHandler = getHandler(processedRequest);   // #2
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  // #3

            String method = request.getMethod();    // #4
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (logger.isDebugEnabled()) {
                    logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                }
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            if (!mappedHandler.applyPreHandle(processedRequest, response)) {    // #5
                return; 
            }

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

            if (asyncManager.isConcurrentHandlingStarted()) {   // #7
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);  // #8
        }
        ...
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);    // #9
    }
    ...
}

#1 处理multipart类型的请求,如文件上传
#2 主要是通过RequestMappingHandlerMapping#getHandler,查找对应的HandlerMethod,再构造HandlerExecutionChain。
HandlerExecutionChain可以添加Interceptor拦截器,执行额外的流程,Cors请求的处理就是通过Interceptor实现的。
#3 这里使用RequestMappingHandlerAdapter。HandlerAdapter负责使用handler处理requests
#4 处理http的lastModified标识
#5 调用HandlerInterceptor#preHandle
#6 使用HandlerAdapter处理请求
#7 判断是否为异步请求
#8 调用HandlerInterceptor#postHandle
#9 渲染ModelAndView以及处理异常

#6步骤 -> AbstractHandlerMethodAdapter#handle -> RequestMappingHandlerAdapter#handleInternal -> invokeHandlerMethod ->
ServletInvocableHandlerMethod#invokeAndHandle

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {

    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);  // #1
    setResponseStatus(webRequest);  // #2

    if (returnValue == null) {  // #3
        if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            mavContainer.setRequestHandled(true);
            return;
        }
    }
    else if (StringUtils.hasText(getResponseStatusReason())) {
        mavContainer.setRequestHandled(true);
        return;
    }

    mavContainer.setRequestHandled(false);
    try {
        this.returnValueHandlers.handleReturnValue(
                returnValue, getReturnValueType(returnValue), mavContainer, webRequest);    // #4
    }
    ...
}

#1 处理请求,注意providedArgs参数是null
#2 设置响应码
#3 处理异常场景
#4 处理业务方法返回结果

public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {

    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);   // #1
    ...
    Object returnValue = doInvoke(args);    // #2
    ...
    return returnValue;
}

#1 从http请求中组装业务方法的参数
#2 调用业务方法

解析业务方法参数

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

    MethodParameter[] parameters = getMethodParameters();   // #1
    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); // #2
        if (args[i] != null) {
            continue;
        }
        if (this.argumentResolvers.supportsParameter(parameter)) {
            try {
                args[i] = this.argumentResolvers.resolveArgument(
                        parameter, mavContainer, request, this.dataBinderFactory);  // #3
                continue;
            }
            ...
        }
        ...
    }
    return args;
}

#1 获取业务方法参数信息
#2 从providedArgs获取对应的方法参数(providedArgs参数是null,这里都是返回null)
#3 从http请求中解析方法参数

HandlerMethodArgumentResolverComposite#resolveArgument

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

    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);    // #1
    if (resolver == null) {
        throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
    }
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

#1 根据参数的注解,从HandlerMethodArgumentResolverComposite#argumentResolvers选择正确的HandlerMethodArgumentResolver处理参数。
RequestParamMethodArgumentResolver处理@RequestParam注解的参数
PathVariableMethodArgumentResolver处理@PathVariable注解的参数
RequestResponseBodyMethodProcessor处理@RequestBody注解的参数
注意:RequestResponseBodyMethodProcessor调用父类方法AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters方法,该方法会根据HttpReqeust的contentType,查找对应的HttpMessageConverter进行处理。

处理业务方法的结果

ServletInvocableHandlerMethod#invokeAndHandle方法#4步骤 -> HandlerMethodReturnValueHandlerComposite#handleReturnValue

public void handleReturnValue(Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);   // #1
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    }
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);   // #2
}

#1 根据业务方法注解,执行结果类型等信息,从returnValueHandlers从选择一个HandlerMethodReturnValueHandler
#2 使用HandlerMethodReturnValueHandler处理结果。
RequestResponseBodyMethodProcessor#handleReturnValue负责处理@ResponseBody注解的方法结果,也是通过HttpMessageConverter转化数据,并将转化后的数据写入到http响应中。

HandlerInterceptor

HandlerInterceptor也是我们常用的功能。HandlerInterceptor类似于Servlet规范中的过滤器Filter,可以对每个request进行预处理和后处理。通过HandlerInterceptor,我们可以实现身份认证,日志等功能。

DispatcherServlet#doDispatch方法调用了mappedHandler#applyPreHandle, mappedHandler#applyPostHandle,在processDispatchResult方法中会调用mappedHandler#triggerAfterCompletion。

异常处理

DispatcherServlet#processDispatchResult -> processHandlerException
如果抛出的异常是ModelAndViewDefiningException,获取ModelAndView并渲染,否则使用HandlerExceptionResolver进行处理。
SpringMvc默认提供了三种HandlerExceptionResolver实现,
ExceptionHandlerExceptionResolver:查找Spring Context中@ControllerAdvice标注的类,并使用它们@ExceptionHandler标注的方法处理异常。
DefaultHandlerExceptionResolver:处理常见的异常,如业务方法不存放,http请求内容解析失败等
ResponseStatusExceptionResolver:解析@ResponseStatus标注的异常类

DispatcherServlet会加载spring-webmvc.jar下DispatcherServlet.properties配置,获取一些组件接口的默认实现类,HandlerExceptionResolver的默认实现类就在该文件中配置了。

我们也可以自定义HandlerExceptionResolver的实现,进行异常处理。

Spring Content层次

除了spring-mvc.xml的配置,我们还可以定义一个spring.xml,存放除了非WEB的bean以及配置,然后使用在web.xml中使用ContextLoaderListener加载该配置。
ContextLoaderListener#contextInitialized -> ContextLoader#initWebApplicationContext,这里会构造一个RootSpringContext,并且添加到servletContext的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性中。
FrameworkServlet#initWebApplicationContext也会构造一个WebSpringContext,并将servletContext的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE指向的SpringContext作为父Content。
注意:WebSpringContext可以使用RootSpringContext的bean,但RootSpringContext不能使用WebSpringContext定义的bean。

SpringBoot中,复用了DispatcherServlet,HttpMessageConverter,RequestMappingHandlerAdapter,RequestMappingHandlerMapping等组件,主要的处理流程并没有变化,后面也写文章解析对应的内容。

如果您觉得本文不错,欢迎关注我的微信公众号,您的关注是我坚持的动力!


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