美文网首页Spring-BootJava 杂谈
【Spring】spring mvc源码分析(一)

【Spring】spring mvc源码分析(一)

作者: eejron | 来源:发表于2018-08-26 21:18 被阅读11次

时间是最好的老师,但遗憾的是——最后他把所有的学生都弄死了

准备花个点时间来写点关于spring mvc,内容主要有

  • 请求如何相应的到控制器的方法
  • 请求参数如何绑定
  • 结果如何返回的

spring mvc基本原理

说起spring mvc, 我们有必要说下 Servlet的基本原理。

回忆下,在学习Servlet时,我们写一个自己的Servlet继承HttpServlet,并重写其中的doGet(),doPost()等方法来实现具体的业务逻辑,还可以重写init()方法进行初始化其它内容,然后配置下web.xml使得相应的请求打到我们写的Servlet上。

而spring mvc整个框架是基于单个Servlet构建起来的,在spring mvc中,这个Servlet叫做DispatcherServlet,DispatcherServlet正如它名字一样,它是一个分发请求的Servlet,只负责调度,这个Servlet拦截了所有的请求,使得请求都打到这个这个Servlet上。然后将请求分发到controller的方法,具体的业务逻辑其实是从controller的方法来实现的。

那么一个请求是如何通过DispatcherServlet分发到特定的controller方法呢?Servlet中是通过配置web.xml的请求地址与Servlet的映射关系来实现的,而spring mvc 则是通过@RequestMapping这个注解映射的;在spring mvc初始化是会将这些映射关系注册起来,形成<Key,Value>的形式,这里的Key指的是请求地址,Value为响应的方法。

本文着重讲spring mvc初始化时请求地址、响应方法的注册,以及一个请求进来时是如何打到相应的controller方法的

spring mvc的初始化

Servlet在初始化时会调用init()方法,这个init()方法最终会调用到DispatcherServlet的initStrategies()方法,该方法内容如下。

    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

其中initHandlerMappings()方法就是用于初始化请求一部分。源码如下

private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        if (this.detectAllHandlerMappings) {//初始化时默认会执行这一分支的逻辑
            // 查找HandlerMapping接口的实现类
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                // We keep HandlerMappings in sorted order.
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            ...
        }

    }

在spring mvc 初始化时,打个断点看下handlerMappings这个属性,里面包含了几个HandlerMapping的实现;

RequestMappingHandlerMapping这个是我们在controller上面加@RequestMapping注解使用的到的HandlerMapping实现类,如下图:

this.handlerMappings.png

我们们再进一步理解下RequestMappingHandlerMapping,看下它是如何处理@RequestMapping这个注解,以实现<请求信息,响应方法>的注册的,它的继承关系其实有些复杂,我们只给出了主干部分结构图,如下:

RequestMappingHandlerMapping.png

重点看RequestMappingHandlerMapping的父类AbstractHandlerMethodMapping,里面有个属性,源码如下:

    private final MappingRegistry mappingRegistry = new MappingRegistry();

MappingRegistry的定义

    class MappingRegistry {

        private final Map<T, MappingRegistration<T>> registry = new HashMap<T, MappingRegistration<T>>();

    }

MappingRegistration的定义

    private static class MappingRegistration<T> {

        private final T mapping; //T为RequestMappingInfo,封装了请求信息,包含请求路径,请求头等

        private final HandlerMethod handlerMethod;//请求对应的处理方法

    }

到此,我们已经大概明白了,spring mvc其实是通过MappingRegistration这个类去封装请求信息和响应方法的,当一个请求进来时,能拿到请求的路径时,其实也就能知道由哪个方法去处理请求了。

在这里还需要注意的是mappingRegistry这个属性的初始化并不是在Servlet初始化进行了,它是依赖于spring容器的,回头看下前面的类图,AbstractHandlerMethodMapping这个类它实现了一个InitializingBean接口,这个接口在spring容器初始化时调用,初始化时最终会调用到initHandlerMethods()方法。方法内容如下:

protected void initHandlerMethods() {
        //获取到所有bean名称
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));

        for (String beanName : beanNames) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                Class<?> beanType = null;
                beanType = getApplicationContext().getType(beanName);
                //如果该bean带有@Controller和@RequestMapping注解,则该bean被封装成HandlerMethod注册到mappingRegistry
                if (beanType != null && isHandler(beanType)) {
                    detectHandlerMethods(beanName);
                }
            }
        }
    }

spring mvc请求的分发

前文有提到,DispatcherServlet这是一个分发请求的Servlet,那么它是如何实现的呢?

DispatcherServlet中有个doDispatch()方法,它是在Servlet处理请求是调用的,doDispatch()的方法很长,这次我们只看下如何将请求分发到相应的controller方法中。源码精简后如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;

        try {           
            // 根据请求来获取相应的HandlerExecutionChain,这个类是响应请求方法的一个封装类
            mappedHandler = getHandler(processedRequest);

            // 获取请求适配器
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // 调用响应方法
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        }
        catch (Exception ex) {
            ...
        }

    }

getHandler()方法:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //获取响应方法
        Object handler = getHandlerInternal(request);
        //将响应方法封装成HandlerExecutionChain
        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        
        return executionChain;
    }

getHandlerInternal()方法

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        //根据请求生成查找的路径
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        this.mappingRegistry.acquireReadLock();
        try {
            //根据查找路径找到响应的请求方法,具体如何查找就不展开了
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        }
        finally {
            this.mappingRegistry.releaseReadLock();
        }
    }

在spring mvc中,所以的请求及响应都是围绕着DispatcherServlet这个类做文章的,而这个类其实就是一个自定义的Servlet,就是那么一个简单的Servlet配合上spring容器也并发出强大的威力。

Spring 相关内容:

相关文章

  • 2018-06-02

    spring源码分析(七) 目录五、源码分析--5.8 Spring MVC 框架设计原理----5.8.1 Sp...

  • Spring Mvc源码分析

    Spring Mvc源码分析 mvc源码图解springmvc经过一系列的调用到DispatcherServlet...

  • spring源码阅读,从springweb开始

    前言 这个系列是java spring mvc 源码阅读与分析的一个系列阅读源码分支为 spring初始化流程 ...

  • READY EVERYDAY #1

    1.Spring, Spring MVC原理, 源码 Spring 简介 https://www.ibm.com/...

  • Spring源码分析(一)

    Spring源码分析 一 基于Spring 5.1.5 前言:要分析Spring源码,首先得知道Spring是怎么...

  • spring mvc源码解析

    源码分析基于spring 4.3.x文章主要记录看spring mvc源码时的一些关键点。不当之处,还望指出。 H...

  • 【Spring】spring mvc源码分析(一)

    时间是最好的老师,但遗憾的是——最后他把所有的学生都弄死了 准备花个点时间来写点关于spring mvc,内容主要...

  • Java之Spring mvc详解(非原创)

    文章大纲 一、Spring mvc介绍二、Spring mvc代码实战三、项目源码下载四、参考文章 一、Sprin...

  • 秒懂Spring源码,轻松手写SpringMVC框架

    1,3分钟读懂Spring核心源码; 2,SpringMVC与Spring框架关系; MVC--Spring的作用...

  • 2018-05-26

    spring源码分析(六) 目录五、spring源码分析--5.7、Spring JDBC 设计原理及二次开发--...

网友评论

    本文标题:【Spring】spring mvc源码分析(一)

    本文链接:https://www.haomeiwen.com/subject/lehaiftx.html