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

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

准备花个点时间来写点关于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为响应的方法。

本文着重讲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 matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList(matchingBeans.values());
                // We keep HandlerMappings in sorted order.
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            ...
        }

    }

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

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

【Spring】spring mvc源码分析(一)_第1张图片
this.handlerMappings.png

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

【Spring】spring mvc源码分析(一)_第2张图片
RequestMappingHandlerMapping.png

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

    private final MappingRegistry mappingRegistry = new MappingRegistry();

MappingRegistry的定义

    class MappingRegistry {

        private final Map> registry = new HashMap>();

    }

MappingRegistration的定义

    private static class MappingRegistration {

        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 相关内容:

  • Ioc容器的实现(一)-- BeanFactory
  • Ioc容器的实现(二)-- ApplicationContext

你可能感兴趣的:(【Spring】spring mvc源码分析(一))