• 欢迎访问 winrains 的个人网站!
  • 本网站主要从互联网整理和收集了与Java、网络安全、Linux等技术相关的文章,供学习和研究使用。如有侵权,请留言告知,谢谢!

Spring MVC功能介绍

Spring MVC winrains 来源:FrancisQ 11个月前 (10-15) 29次浏览

Servlet 与 MVC

什么是Spring MVC 其实应该说 什么是 MVC ?
Model 数据,View 视图,Controller 控制器。啪!三个东西合在一起,MVC就出来了。
这么简单? 没错,其实就是这么简单。
当然如果你对MVC不太熟悉的话还是乖乖往下看吧。
其实MVC就是处理Web请求的一种框架模式,我们思考一下用户请求的流程:

  1. 输入url
  2. 发送请求
  3. 接受响应

对于用户来说其实也就这三个步骤,但是对于服务端来说需要做很多,这里我画了一张图供大家理解。这里其实忽略了很多 Tomcat 本身已经为我们做的,而且 Tomcat 并不仅仅只有 Host,Context。

用户请求

我来解释一下,用户发送请求的 url 其实对应着很多东西。
比如说 localhost ,当然这个就是 ip 地址。这个 ip 地址对应着 Tomcat 里面的 Host (站点) 层。
Context 代表着一个 web 应用,还记得当初写 Servlet 项目的时候有一个 webapp 文件夹(里面还有个WEB-INF,最里面是web.xml)吗?也可以理解为当初写的 servlet 项目就是一个 web 应用,而用户通过 ip 地址的端口映射去找到了这个应用。
这时候我们已经通过 ip 和端口寻找到了指定的 web 应用,我们知道一个 web 应用中存在多个 servlet ,而我们如何去寻找每个请求对应的 servlet 呢? 答案还是 url ,我们通过后面的/ news 去web.xml里面寻找已经注册到应用中的 Servlet 类。
具体我再配合图中解释一下: 找到了指定的 web 应用之后,通过请求的路径 /news 去 web.xml 中寻找是否有对应的 标签,其中这个标签的子标签 标签的值需要匹配到请求的路径,这个时候 标签的值为/ news 正好匹配到了,所以我们获取了上面的标签的值然后再寻找是否有 标签的子标签 和这个值相等,如果有则获取到底下的 对应的类 并通过这个类去解析请求
总结来说就是通过 url 从 web.xml 文件中寻找到匹配的 servlet 的类
其实这就是原生的 servlet ,那么 MVC 的影子在哪呢?
别急,你要记住的是 MVC 就是对 Servlet 的封装,想要理解 MVC 就必须理解 Servlet 和 MVC 与 Servlet 的关系

SpringMVC中的DispatcherServlet

DispatcherServlet的继承结构

有没有发现这个 DispatcherServlet 其实就是一个 Servlet。也就是说 Spring MVC中最核心的部分其实就是一个 Servlet
我来简单解释一下相应的部分(先简单了解一下)

  • FrameworkServlet : 是 DispatcherServlet 的一个抽象父类。其中提供了加载某个对应的 web 应用程序环境的功能,还有将 GET、POST、DELETE、PUT等方法统一交给 DispatcherServlet 处理。
  • Servlet : 一个规范,用来解决 HTTP服务器和业务代码之间的耦合问题
  • GenericServlet : 提升了 ServletConfig 的作用域,在init(servletConfig)方法中调用了init()无参方法,子类可以重写这个无参初始化方法来做一些初始化自定义工作(后面分析源码中会讲到)。
  • HttpServletBean : 可以将 Servlet 配置信息作为 Bean 的属性 自动赋值给 Servlet 的属性。
  • DispatcherServlet :整个继承链中的最后一个也是最重要的一个类,是SpringMVC 实现的核心类。MVC 通过在 web.xml 中配置 DispatcherServlet 来拦截所有请求,然后通过这个 DispatcherServlet 来进行请求的分发,并调用相应的处理器去处理请求和响应消息。

有没有想起来在 SSM 框架配置的时候在 web.xml 中的配置

<servlet>
  <servlet-name>dispatcherServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>
<!-- 把所以请求都交给DispatcherServlet处理-->
<servlet-mapping>
  <servlet-name>dispatcherServlet</servlet-name>
  <!-- 拦截所有 -->
  <url-pattern>/</url-pattern>
</servlet-mapping>

好的,现在我们知道了 springMVC 中使用了一个 DispatcherServlet 去处理所有请求,而我们知道真正处理的肯定不是 DispatcherServlet ,而是具体我们在 Controller 层中写的带有 @Controller @RequestMapping 注解的类和底下的方法。DispatcherServlet 只是一个为我们分发请求到具体处理器的一个分发Servlet
那么,这个 DispatcherServlet 具体怎么工作的呢?它是如何分发请求的呢? 且听我慢慢道来。

和 DispatcherServlet 一起工作的一些组件

首先我先将这些组件简单化,并把一些不必要的先省略为了便于理解。
其实要分发请求处理请求并相应,我们可以肯定的是 我们需要使用一个映射关系Mapping 来表示 url 和对应的 处理器,使用一个 处理器Handler 来处理对应的请求。这样,我们就出来了两个最根本的角色: HandlerMapping Handler
我们再来强调一下这两者的工作。

  • HandlerMapping : 建立请求和处理器的映射关系,即我们可以通过请求去获取对应的 handler
  • Handler : 处理请求的处理器。

这样,我们就可以再画出一个简单的流程图了。

简单的处理流程

有没有疑惑,这个 HandlerMapping 集合从哪来?HandlerMapping 的类结构是啥样的?Handler的类结构又是什么样的?
如果有,那么就带着这些问题往下看。
首先,这个 handlerMapping 的集合从哪来的?甭说集合,连单个你都不知道从哪来。 那么我们就从源码中找答案吧。为了你省力,我直接告诉你,DispatcherServlet 中的 doDispatch 方法中进行了 分发的主要流程。
这里我给出了简化版的 doDispatch 方法

public void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    // 通过request在处理器映射HandlerMapping中获取相应处理器
    Object handler = getHandler(req);
    if (handler != null) {
        ... 调用handler的处理方法
    }
}

那么这个 getHandler(request) 方法又是什么样的呢?这里我直接放 DispatcherServlet 类的源码

@Nullable
// 这里返回的是 HandlerExecutionChain
// 其实这是一个处理器和拦截器链的组合
// 现在你就理解为返回的是一个 handler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        // 遍历 handlerMapping 调用它的getHanlder获取处理器
        // 如果不为空直接返回
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

我们继续追踪 HandlerMappinggetHandler(request) 方法。
其实进入源码你会发现,HandlerMapping 是一个接口,故这里给出一个简单的 HandlerMapping 接口代码,如果有能力可以去看源码。

public interface HandlerMapping {
    /**
     * 获取请求对应的处理器
     * @param request 请求
     * @return 处理器
     * @throws Exception 异常
     */
    Object getHandler(HttpServletRequest request) throws Exception;
}

那么,具体的实现类又是什么呢?我们思考一下,这个mapping是一个请求和处理器的映射,它是如何存的?我们当初怎么做的?
想必,你已经有答案了,在我们使用 SSM 框架的时候我们是通过 给类和方法 配置相应的注解(@Controller,@ReqeustMapping)来建立相应的 url 和 处理器方法的映射关系的。
我们再回来看源码 在idea中 可以使用 ctrl+alt+B 来查看方法实现和类实现继承。我们查看 HandlerMapping 接口的 getHandler 方法的实现,我们会发现直接跳到了 AbstractHandlerMapping 这个抽象类的方法,我们查看该方法的源码

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 获取 handler 这其实是一个抽象方法
    // 子类可以通过不同实现来获取handler
    // 例如通过 url 和 name的映射逻辑
    // 通过 requestMapping 中的 url 和对应方法的映射逻辑
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        // 如果获取为null 的获取默认的处理器
        // 这里子类也可以设置自己的默认处理器
        handler = getDefaultHandler();
    }
    // 如果还是没有则返回 这时候 DispatcherServlet会返回 404
    if (handler == null) {
        return null;
    }
    // Bean name or resolved handler?
    // 如果返回的处理器是字符串 则认为它是一个beanName
    if (handler instanceof String) {
        String handlerName = (String) handler;
        // 通过beanName从IOC容器中获取相应的处理器
        handler = obtainApplicationContext().getBean(handlerName);
    }
    // 下面是将处理器 和 拦截器封装成处理器执行链
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    if (logger.isTraceEnabled()) {
        logger.trace("Mapped to " + handler);
    } else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
        logger.debug("Mapped to " + executionChain.getHandler());
    }
    if (CorsUtils.isCorsRequest(request)) {
        CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
        CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }
    // 返回处理器执行链
    return executionChain;
}

如果其他的你看不懂,你只要理解我注释区域的代码就行了。我们从上面得到最重要的信息就是:真正的handler获取是在子类实现的getHandlerInternal(request),那我们来看一下有哪些子类。

我们可以看到其中有 AbstractHandlerMethodMappingAbstractUrlHandlerMappingWelcomeHandlerMapping
我们主要关注 AbstractHandlerMethodMapping (提供方法处理器) 和 AbstractUrlHandlerMapping(提供url对应处理器映射),这里为了不耽误时间,我们直接分析 AbstractHandlerMethodMapping ,它是注解方法的映射的一个抽象类。

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 获取请求的路径
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    this.mappingRegistry.acquireReadLock();
    try {
        // 通过 lookupPath 来从中获取 HandlerMethod
        // 这个HandlerMethod 又是什么?
        // 先不用管 我们继续看lookupHandlerMethod源码
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    } finally {
        this.mappingRegistry.releaseReadLock();
    }
}
// 这里的逻辑稍微有些复杂
// 你只要知道它通过请求来匹配返回处理器方法
// 如果有多个处理器方法可以处理当前Http请求 那么返回最佳匹配的处理器
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        // No choice but to go through all mappings...
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    }
    if (!matches.isEmpty()) {
        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
        matches.sort(comparator);
        Match bestMatch = matches.get(0);
        if (matches.size() > 1) {
            if (logger.isTraceEnabled()) {
                logger.trace(matches.size() + " matching mappings: " + matches);
            }
            if (CorsUtils.isPreFlightRequest(request)) {
                return PREFLIGHT_AMBIGUOUS_MATCH;
            }
            Match secondBestMatch = matches.get(1);
            if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                Method m1 = bestMatch.handlerMethod.getMethod();
                Method m2 = secondBestMatch.handlerMethod.getMethod();
                String uri = request.getRequestURI();
                throw new IllegalStateException(
                        "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
            }
        }
        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
        handleMatch(bestMatch.mapping, lookupPath, request);
        // 返回最佳匹配
        return bestMatch.handlerMethod;
    } else {
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}

到现在逻辑慢慢变得复杂起来,我们做一个小结:DispatcherServlet 中我们通过遍历 handlerMapping 集合并调用它的 getHandler 方法来获取handler ,这个handler 是一个 Object (因为spring会整合其他框架的处理器,并使用这些处理器处理请求 所以这里选择Object)。而 HandlerMapping 仅仅是一个接口 为了方便 抽象类 AbstractHandlerMapping 实现了这个方法并且为子类提供了自定义获取handlergetHandlerInternal(request) 方法。 对于我们通用方式注解来标识控制器方法和url请求路径的映射是通过 AbstractHandlerMethodMapping 来获取请求对应的 HandlerMethod

那么,疑问又来了,HandlerMethod是什么?
还记得刚刚上面的问题么,这个 HandlerMapping 集合从哪来?HandlerMapping 的类结构是啥样的?Handler的类结构又是什么样的
我们现在可以来回答一下Handler的类结构了,Handler是一个Object,为了第三方框架的处理器能够接入来处理请求,spring使用了Object,而对于注解形式来说 一个处理器是一个 HandlerMethod。这里我给出 HandlerMethod 的简单实现形式,如果有能力可以查看源码。

@Data
public class HandlerMethod {
    // bean 其实这个是标识 Controller 注解的类的对象
    private Object bean;
    // 该对象的类型
    private Class<?> beanType;
    // 该类上被标识RequestMapping注解的方法
    private Method method;
}

HandlerMethod 中存放了控制器类和对应的方法。为什么要存放他们?你想一下,我们用@RequestMapping注解标识的方法不就是处理方法吗,HandlerMethod 中存放他们,到时候调用处理方法只需要通过反射调用这个beanmethod就行了。如果不理解可以看一下我下面写的代码。

// 这里先不用管 ModelAndView
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    ModelAndView modelAndView = null;
    HandlerMethod handlerMethod = ((HandlerMethod) handler);
    // 获取HandlerMethod的method
    Method method = handlerMethod.getMethod();
    if (method != null) {
        // 通过反射调用方法并返回视图对象(这就是处理方法)
        modelAndView = (ModelAndView)method.invoke(BeanFactory.getBean(handlerMethod.getBeanType()));
    }
    return modelAndView;
}

再来看看上面的问题。这个 HandlerMapping 集合从哪来?HandlerMapping 的类结构是啥样的?Handler的类结构又是什么样的
第三个问题解决了,第二个问题上面也解决了,那么第一个问题来了。
我们从一开始就只讨论了如何在HandlerMapping中取出handler 并且调用handler的处理方法,那么我们一开始遍历的这个handlerMappings集合到底从哪儿来,或者说它是什么时候被初始化的
这个时候,我们又得回到根源。我再来放这张图,不知道你们是否还记得

你能想到什么呢?我这里假设你对servlet还是有一些了解的。
我们知道 DispatcherServlet 是一个 servlet 。一个 servlet 肯定有init()方法 (还记得我上面讲的GenericServlet的作用吗?现在来了,如果不是很懂init(),建议去了解一下servlet的生命周期)。
我们可以大胆的猜测,对 handlerMappings 的初始化就是在 servlet 的初始化方法中进行的。
很遗憾我们没有能在 DispatcherServlet 中找到 init 方法,那么就找他爹,找不到再找他爷爷,曾爷爷。我们知道因为 DispatcherServlet 继承了 GenericServlet 所以我们需要找到 实现的 init() 无参方法。所以我们找到了 HttpServletBean 中重写的 init() 方法了

@Override
// 这家伙还不允许被重写 final
public final void init() throws ServletException {
    // Set bean properties from init parameters.
    // 将servlet配置信息存入bean的属性中
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        } catch (BeansException ex) {
            if (logger.isErrorEnabled()) {
                logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            }
            throw ex;
        }
    }
    // 重点在这里 这里子类才可以自由发挥
    // 该方法不能被重写是因为 上面的步骤是必须的
    // 别忘了上面的步骤是 HttpServletBean 的职责
    // 接下去继续看
    // Let subclasses do whatever initialization they like.
    initServletBean();
}
// 进入FrameworkServlet 查看实现的initServletBean方法
@Override
protected final void initServletBean() throws ServletException {
    // log不用管
    getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
    if (logger.isInfoEnabled()) {
        logger.info("Initializing Servlet '" + getServletName() + "'");
    }
    long startTime = System.currentTimeMillis();
    // 重点来了
    try {
        // 初始化容器和上下文
        // 我们要记得现在在 FrameworkServlet中执行呢
        // 我们进入initWebApplicationContext方法
        this.webApplicationContext = initWebApplicationContext();
        // 初始化FrameworkServlet 这里没给实现 子类也没给
        // 所以不用管
        initFrameworkServlet();
    }
    // log不用管
    catch (ServletException | RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        throw ex;
    }
    if (logger.isDebugEnabled()) {
        String value = this.enableLoggingRequestDetails
                ? "shown which may lead to unsafe logging of potentially sensitive data"
                : "masked to prevent unsafe logging of potentially sensitive data";
        logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails
                + "': request parameters and headers will be " + value);
    }
    if (logger.isInfoEnabled()) {
        logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
    }
}
// 初始化容器和上下文
protected WebApplicationContext initWebApplicationContext() {
    // 查找是否有专门的根环境 先不用管
    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    // 如果不存在专用根环境 通常我们不会走到这 先不用管
    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide
                // services such as
                // setting the parent context, setting the application
                // context id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit
                    // parent -> set
                    // the root application context (if any; may be null) as
                    // the parent
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    // 如果为空
    if (wac == null) {
        // 查看是否在servlet中已经注册
        // No context instance was injected at construction time -> see if
        // one
        // has been registered in the servlet context. If one exists, it is
        // assumed
        // that the parent context (if any) has already been set and that
        // the
        // user has performed any initialization such as setting the context
        // id
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // 自己创建一个
        // No context instance is defined for this servlet -> create a local
        // one
        wac = createWebApplicationContext(rootContext);
    }
    // 判断这个环境是否支持刷新 如果不支持 下面手动刷新
    // 如果支持则前面已经刷新了
    if (!this.refreshEventReceived) {
        // Either the context is not a ConfigurableApplicationContext with
        // refresh
        // support or the context injected at construction time had already
        // been
        // refreshed -> trigger initial onRefresh manually here.
        synchronized (this.onRefreshMonitor) {
            // !!!!!!!!!!!!!!!!!!!!!重点
            // DispacherServlet 就是在这里实现的
            onRefresh(wac);
        }
    }
    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}
// DispatcherServlet 重写了该方法
@Override
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}
// 一系列的初始化工作
protected void initStrategies(ApplicationContext context) {
    // 前面一些不用管
    initMultipartResolver(context);
    // 地域
    initLocaleResolver(context);
    // 主题
    initThemeResolver(context);
    // 重点来了!!!!
    // 初始化HandlerMapping
    initHandlerMappings(context);
    // 初始化适配器
    initHandlerAdapters(context);
    // 初始化异常处理
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

我们先暂停一下,理一下思路。
HttpServletBean 中重写了 GenericServletinit() 无参方法开始初始化动作,其中HttpServletBean中先实现了 servlet 配置信息到 bean 属性信息的赋值,然后调用 initServletBean() 该方法是子类进行自定义初始化的方法。FrameworkServlet 实现了该方法并且调用了 initWebApplicationContext() 方法进行了容器和上下文的初始化工作,并且其中调用了 onRefresh(ApplicationContext context) 方法。 这里FrameworkServlet没有做任何操作而是子类 DispatcherServlet 在其中调用了 initStrategies(context) 进行初始化工作。
好了我们继续看初始化 handlerMappings方法。

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    if (this.detectAllHandlerMappings) {
        // Find all HandlerMappings in the ApplicationContext, including
        // ancestor contexts.
        // 在应用上下文中寻找 handlerMappings
        Map<String, HandlerMapping> 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 {
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        } catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }
    // Ensure we have at least one HandlerMapping, by registering
    // a default HandlerMapping if no other mappings are found.
    // 前面不用管 其实一般我们使用默认的
    if (this.handlerMappings == null) {
        // 这里是获取默认的handlerMappings
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerMappings declared for servlet '" + getServletName()
                    + "': using default strategies from DispatcherServlet.properties");
        }
    }
}
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    String key = strategyInterface.getName();
    // 获取defaultStrategies的内容
    String value = defaultStrategies.getProperty(key);
    if (value != null) {
        // 解析相应内容并初始化 handlerMappings
        // 获取内容中的类名数组
        String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
        List<T> strategies = new ArrayList<>(classNames.length);
        for (String className : classNames) {
            try {
                // 通过反射创建并加入数组中取返回给上面
                Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                Object strategy = createDefaultStrategy(context, clazz);
                strategies.add((T) strategy);
            } catch (ClassNotFoundException ex) {
                throw new BeanInitializationException("Could not find DispatcherServlet's default strategy class ["
                        + className + "] for interface [" + key + "]", ex);
            } catch (LinkageError err) {
                throw new BeanInitializationException(
                        "Unresolvable class definition for DispatcherServlet's default strategy class [" + className
                                + "] for interface [" + key + "]",
                        err);
            }
        }
        return strategies;
    } else {
        return new LinkedList<>();
    }
}

事情马上明了了,我们现在已经知道了 handlerMapping 是怎么加入队列中了(获取到 defaultStrategies 的资源内容 遍历内容获取类名 并通过反射创建对象加入队列),所以我们可以大胆猜测 defaultStrategies 中藏着秘密,它肯定已经定义好了默认的 handlerMapping 的类名。
果不其然,我们来看代码

private static final Properties defaultStrategies;
// 在静态块中已经加载了defaultStrategies
static {
    // Load default strategy implementations from properties file.
    // This is currently strictly internal and not meant to be customized
    // by application developers.
    try {
        // 通过资源初始化defaultStrategies
        // 这里的资源路径 很重要!!!!
        ClassPathResource resource = new
        ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
    catch (IOException ex) {
        throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
    }
}
// 在这里呀 DispatcherServlet.properties
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

我们去寻找一下 DispatcherServlet.properties 这个文件, 原来都给我们定义好了,我们可以看见默认的handlerMapping有两个。 BeanNameUrlHandlerMappingRequestMappingHandlerMapping

好了,我们现在终于可以总结一下了。
在我定义的简单的 DispatcherServlet 的 “同事”中,主要有 HandlerMappingHandlerHandlerMapping 会在 DispatcherServlet 初始化的时候被在加载 ,然后在 DispatcherServlet 调用到执行方法 doDispatch() 的时候,会遍历 handlerMappings 集合获取对应的 handlerhandler 是一个 Object(因为需要适配其他框架的处理器),在注解方式中是一个 HandlerMethod (里面存了Controller类的实例和method,在处理方法的时候使用反射调用该实例的method方法)。获取完 handler之后通过 处理器的处理方法返回一个视图对象并渲染页面。

再来一个组件 Adapter

其实对于“正宗”的MVC流程中,在遍历 handlerMappings 获取到相应的 handler 之后,其实并不是直接通过 handler 来执行处理方法的,而是通过 HandlerAdapter 来执行处理方法的。
这里我写了一个简单的适配器接口,源码也不复杂 你可以直接看源码

public interface HandlerAdapter {
    ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
    boolean support(Object handler);
}

handleRequest 不必说,用来执行处理的,里面传进去一个 handler 肯定最终调用的 handler的执行方法。这是典型的适配器模式。
support 判断该handler是否被该适配器支持。
其实理解上面的过程了之后再加入一个适配器就不难了,我们主要思考一下 为什么要加入适配器,我们知道 handler 是一个 Object 它的处理方法是不固定的,如果我们要在 DispatcherServlet 中通过 Handler 执行处理方法,那么就要做很多类型判断,这对于 DispatcherServlet 是非常难受的,所以需要通过适配器扩展。
这样我们可以写出一个简单的 doDispatch 方法了,有能力的可以查看源码

public void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    // 通过request在处理器映射HandlerMapping中获取相应处理器
    Object handler = getHandler(req);
    if (handler != null) {
        // 通过handler获取对应的适配器
        HandlerAdapter handlerAdapter = getHandlerAdapter(handler);
        if (handlerAdapter != null) {
            // 通过适配器调用处理器的处理方法并返回ModelAndView视图对象
            ModelAndView modelAndView = handlerAdapter.handleRequest(req, resp, handler);
            ... 处理视图并渲染
        }
    }
}

视图解析

我们知道在 doDispatch 方法调用完 HandlerAdapter 的处理方法后统一返回的是一个 ModelAndView 对象,那么这个 ModelAndView 对象是什么呢?
字面意思,模型和视图。也就是 MVC 的 Model 和 View。在 SpringMVC 中 ModelAndView 是给 框架本身支持的网页生成器使用的,它是用来连接后台和网页的类,而如今在前后端分离的趋势下,基本不怎么使用了,这里我只简单提一下。
我们知道在 HandlerAdapter 调用处理方法之后会返回一个视图对象 ModelAndView ,而在这之后,doDispatch方法会调用 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException) 去处理视图信息,这个方法又会调用一个 render() 方法进行真正的视图渲染。

非常有用的RequestResponseBodyMethodProcessor

还记不记得 @RequestBody @ResponseBody @RestController 这些注解。没错,现在我们大部分都用它们,那么它们是如何工作的呢?
奥秘要从 doDispatch() 方法中的

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

这条语句开始。 这个语句就是调用 相应的适配器的 handle 方法并返回 ModelAndView 对象。
当然通过前面的学习,我们知道最终调用到的是 RequestMappingHandlerAdapter 类的 handleInternal方法。

// 查看这个方法的源码 你会发现 除了处理一些 session 的问题
// 最终都会调用 处理器方法 invokeHandlerMethod
@Override
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response,
        HandlerMethod handlerMethod) throws Exception {
    ModelAndView mav;
    checkRequest(request);
    // Execute invokeHandlerMethod in synchronized block if required.
    // 如果配置了 session 同步
    if (this.synchronizeOnSession) {
        // 则获取 session
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        } else {
            // No HttpSession available -> no mutex necessary
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
    } else {
        // 如果没有配置 session 内同步 或者还没有创建 session 直接调用处理器方法
        // No synchronization on session demanded at all...
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }
    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        } else {
            prepareResponse(response);
        }
    }
    return mav;
}

我们来看一下 invokeHandlerMethod 中干了什么事, 看上去好密密麻麻,其实我们只要关注重点就行了,关注我注释的地方。

protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response,
        HandlerMethod handlerMethod) throws Exception {
    // 构造 Web 请求 其实就是一个代理类 封装了 请求和响应
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
        // 重点来了!!!!!
        // 将handlerMethod 封装成 ServletInvocableHandlerMethod类
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if (this.returnValueHandlers != null) {
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        // 为invocableMethod 做一些配置
        invocableMethod.setDataBinderFactory(binderFactory);
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.setTaskExecutor(this.taskExecutor);
        asyncManager.setAsyncWebRequest(asyncWebRequest);
        asyncManager.registerCallableInterceptors(this.callableInterceptors);
        asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
        if (asyncManager.hasConcurrentResult()) {
            Object result = asyncManager.getConcurrentResult();
            mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
            asyncManager.clearConcurrentResult();
            LogFormatUtils.traceDebug(logger, traceOn -> {
                String formatted = LogFormatUtils.formatValue(result, !traceOn);
                return "Resume with async result [" + formatted + "]";
            });
            invocableMethod = invocableMethod.wrapConcurrentResult(result);
        }
        // 重点来了!!!!! 调用 ServletInvocableHandlerMethod 的 invokeAndHandle 方法
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }
        return getModelAndView(mavContainer, modelFactory, webRequest);
    } finally {
        webRequest.requestCompleted();
    }
}

总结一下上面的方法就是:HandlerMethod 对象封装成 ServletInvocableHandlerMethod 然后做一些配置并调用它的 invokeAndHandle 方法

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
    // 这一步很重要 执行请求并获取返回值
    // 这里里面就涉及到了 RequestBody 注解了
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    setResponseStatus(webRequest);
    // 处理返回值
    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            disableContentCachingIfNecessary(webRequest);
            mavContainer.setRequestHandled(true);
            return;
        }
    } else if (StringUtils.hasText(getResponseStatusReason())) {
        mavContainer.setRequestHandled(true);
        return;
    }
    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer,
                webRequest);
    } catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(formatErrorForReturnValue(returnValue), ex);
        }
        throw ex;
    }
}

我们首先来解析一下 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs) 方法 ,这里涉及到了 @RequestBody 注解的使用。

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
    // 方法参数的解析 这里可以做很多关于 参数和请求 的事情
    // 这是我们需要深入查看源码的
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Arguments: " + Arrays.toString(args));
    }
    // 返回调用结果 很简单 就是通过反射调用方法 这里不做赘述
    return doInvoke(args);
}
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
    // 很简单 获取到参数
    MethodParameter[] parameters = getMethodParameters();
    if (ObjectUtils.isEmpty(parameters)) {
        return EMPTY_ARGS;
    }
    Object[] args = new Object[parameters.length];
    // 遍历参数
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        args[i] = findProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
        if (!this.resolvers.supportsParameter(parameter)) {
            throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
        }
        try {
            // 重点来了!!!!
            // 将请求中的信息通过参数解析器解析到对应的参数
            // 最终遍历完之后将参数数组返回
            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
        } catch (Exception ex) {
            // Leave stack trace for later, exception may actually be
            // resolved and handled...
            if (logger.isDebugEnabled()) {
                String exMsg = ex.getMessage();
                if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                    logger.debug(formatArgumentError(parameter, exMsg));
                }
            }
            throw ex;
        }
    }
    return args;
}
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    // 获取对应的参数解析器
    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    if (resolver == null) {
        throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName()
                + "]. supportsParameter should be called first.");
    }
    // 通过解析器解析参数 重点就在这了 因为 @RequestBody注解的存在
    // 我们会调用到 RequestResponseBodyProcessor 类的这个方法
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    parameter = parameter.nestedIfOptional();
    // 主要这里面通过 MessageConverters 消息转换器 来实现了 @RequestBody 的功能
    // 由于篇幅有限 这里不再深入分析 如果想找到答案 顺着往下查看源码就行
    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());
            }
        }
        if (mavContainer != null) {
            mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
        }
    }
    return adaptArgumentIfNecessary(arg, parameter);
}

下面我还放了一下 readWithMessageConverters 方法的代码,其实里面主要就是遍历消息转换器,然后通过转换器执行HTTP报文到参数的转换。

for (HttpMessageConverter<?> converter : this.messageConverters) {
    Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
    GenericHttpMessageConverter<?> genericConverter =
            (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
    if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
            (targetClass != null && converter.canRead(targetClass, contentType))) {
        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;
    }
}

知道了 @RequestBody 注解的原理,@ResponseBody 注解的原理也马上浮出水面了。答案就在 ServletInvocableHandlerMethod 类中的 invokeAndHandle 方法获取了 returnValue 之后的步骤

 // 答案就在这里
try {
    this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    }
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

上面两个方法你可以追踪源码,其实最终调用的还是在 RequestResponseBodyMethodProcessor 这个类中。

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
        throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    mavContainer.setRequestHandled(true);
    // 封装web请求
    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
    // 通过消息解析器解析返回的value
    // Try even with null return value. ResponseBodyAdvice could get involved.
    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
// 这里我贴出了writeWithMessageConverters方法的主要代码 因为这个方法有点长。。
// 这里遍历 Http消息转换器集合
for(
HttpMessageConverter<?> converter:this.messageConverters)
{
    GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter
            ? (GenericHttpMessageConverter<?>) converter : null);
    if (genericConverter != null
            ? ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType)
            : converter.canWrite(valueType, selectedMediaType)) {
        body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
                (Class<? extends HttpMessageConverter<?>>) converter.getClass(), inputMessage, outputMessage);
        if (body != null) {
            Object theBody = body;
            LogFormatUtils.traceDebug(logger,
                    traceOn -> "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
            addContentDispositionHeader(inputMessage, outputMessage);
            // 通过转换器来输出 重点。。。
            if (genericConverter != null) {
                genericConverter.write(body, targetType, selectedMediaType, outputMessage);
            } else {
                ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
            }
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Nothing to write: null body");
            }
        }
        return;
    }
}

我们来总结一下: 在调用 HandlerAdapter 的 处理方法的时候 会跳转调用到 RequestMappingHandlerAdapterhandleInternal 方法。这里面会将 原本的处理器 HandlerMethod 封装成 ServletInvocableHandlerMethod,然后会调用这个类中的 invokeAndHandle 方法,这个方法中主要进行了相应方法处理器的方法的调用,在调用之前,会将Http报文中的内容转换为对应的参数内容。在调用完成返回 returnValue 之后,会调用相应 HttpMessageConvert 的转换方法 然后返回。

最终变成什么样了呢?

现在,你理解 Spring MVC了么?

作者:FrancisQ

来源:https://juejin.im/post/5da18c746fb9a04e37316a9e


版权声明:文末如注明作者和来源,则表示本文系转载,版权为原作者所有 | 本文如有侵权,请及时联系,承诺在收到消息后第一时间删除 | 如转载本文,请注明原文链接。
喜欢 (2)