Spring MVC基于MVC设计模式设计,其实现基于Spring IOC容器和Servlet。

Spring MVC的启动

Spring MVC通常运行在Web容器(如Tomcat)中,其启动由Web容器触发。
以下是一个常规的Web应用部署描述文件Web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    id="WebApp_ID" version="3.0">
    <display-name></display-name>
    <!-- 配置Spring MVC DispatcherServlet -->
    <servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 初始化参数 -->
        <init-param>
            <!-- 加载SpringMVC的xml到 spring的上下文容器中 -->
            <param-name>contextConfigLocation</param-name>
            <param-value>  
                classpath:spring/springmvc.xml 
            </param-value>
        </init-param>
    </servlet>
    <!-- 配置DispatcherServlet所需要拦截的 url -->
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!-- 监听spring上下文容器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 加载spring的xml配置文件到 spring的上下文容器中 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/applicationContext-*.xml</param-value>
    </context-param>

主要分为两部分:

  1. DispatcherServlet的配置
  2. ContextLoaderListener的配置

这两部分的配置为Spring MVC启动的入口,也是Web容器与Spring IOC/MVC相耦合的点(通过ServletContext耦合)。其中DispatcherServlet是一个Servlet,具备Servlet的生命周期,ContextLoaderListener实现了ServletContextListener接口,该接口提供了关于ServletContext生命周期的回调方法。在Web容器启动时,将调用Servlet生命周期的init方法,同时其作为宿主环境的上下文ServletContext将触发事件信息使得ServletContextListener监听器调用contextInitialized方法。

首先来看ContextLoaderListner:

@Override
    public void contextInitialized(ServletContextEvent event) {
        //启动了Spring IOC容器,其配置文件位置在web.xml中已经设定
        //通常这个容器中的Bean主要是web开发中的Service层和DAO层相关的类
        initWebApplicationContext(event.getServletContext());
    }

再看DispatcherServlet,在Servlet启动时将调用init方法。其继承结构如下图:
这里写图片描述
init()方法在HttpServletBean中定义,该方法调用了在FrameworkServlet中定义的initServletBean()方法,部分代码如下:

@Override
    protected final void initServletBean() throws ServletException {
        getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
        try {
        //启动另外一个IOC容器,该IOC容器配置文件在web.xml中设定过
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
        catch (ServletException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }
        catch (RuntimeException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }
    }

该IOC容器中包含的通常是web开发中的Controller层相关的Bean,启动过程中将会将ContextLoaderListener中启动的容器设置为父容器,形成IOC容器体系,在容器体系中获取Bean时,先在子容器中查找,再去父容器中查找。如Controller层中Service的注入,即需要去父容器中查找。

至此,Spring MVC启动了两个IOC容器,其中ContextLoaderListener启动的为父容器(通常负责Service层和DAO层的相关Bean管理),而DispatcherServlet启动的为子容器(通常负责Controller层的相关Bean管理),IOC容器体系建立完毕,同时两个IOC容器通过ServletContext与Web容器(Tomcat)相耦合。

HttpServletBean的init方法最终将调用DispatcherServlet的initStrategies方法,该方法主要用来初始化Spring MVC的主要支持部件:

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

重点分析HandlerMapping/HandlerAdapters/ViewResolvers这三者。

initStrategies中的主要工作就是设置DispatcherServlet中相关属性的值,对于handlerMapping/handlerAdapters/ViewResolvers这三者来说,都是先在IOC容器中查找是否已经有配置的各类实例,如果没有则启用默认类。默认值在DispatcherServlet.properties文件中有定义。至此,Spring MVC已经启动完毕。

可见,SpringMVC由tomcat以web.xml里一个Servlet一个Listener的配置触发启动,然后以这两个建立IOC容器体系,最终进行组件的初始化工作,启动完成。

Spring MVC重要组件

HandlerMapping

HandlerMapping接口只有一个方法,getHandler,但是返回的是HandlerExecutionChain,一个执行链。其主要作用是将Http请求的URL映射到对应的handler上,返回的执行链中同时包含了handler本身和对应的拦截器链。handler的类型是多样的,在源码中handler类型为Object,可以是实现了Controller接口的对象,也可以是某个方法。因此,也有多种实现类,有直接以类作为handler的,有以方法作为handler的。

在web开发中在方法上常见的RequestMapping注解handleMethod类型的handler,对应的handlerMapping为RequestMappingHandlerMapping。继承AbstractHandlerMethodMapping,持有请求URL与HandlerMethod之间的映射表。

HandlerAdapter

由于handler的多样性,需要为框架提供更好的可拓展性,使用了适配器模式,通过handlerAdapter来调用handler的handle方法。在handlerMapping中获取到HandlerExecutionChain后,从中取出handler本身,遍历已经DispatcherServlet中初始化过的handlerAdapter找到可以适配的HandlerAdapter。

以RequestMappingHandlerMapping为例,对应的Adapter为RequestMappingHandlerAdapter,该Adapter知道handler类型为HandlerMethod,最终通过反射调用完成请求的处理,返回ModelAndView结果。

ViewResolver/View

视图解析器,ViewResolver接口只有一个方法,即resolveViewName,将一个视图名解析为一个View对象。View对象只是视图在Spring MVC中的表示,并非实际意义上的视图。由于视图的多样化,一类视图解析图解析一类视图对象。以JSP视图为例,其ViewResolver为InternalResourceViewResolver,通常都会如下配置:

<bean class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

InternalResourceViewResolver持有View对象的缓存,如果缓存中不存在,则根据viewname新建View,InternalResourceViewResolver处理的视图对象类为InternalResourceView。

@Override
    protected View createView(String viewName, Locale locale) throws Exception {
        // If this resolver is not supposed to handle the given view,
        // return null to pass on to the next resolver in the chain.
        if (!canHandle(viewName, locale)) {
            return null;
        }
        // 处理视图名称中的重定向前缀
        // Check for special "redirect:" prefix.
        if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
            String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
            RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
            return applyLifecycleMethods(viewName, view);
        }
        // 处理视图名称中的转发前缀
        // Check for special "forward:" prefix.
        if (viewName.startsWith(FORWARD_URL_PREFIX)) {
            String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
            return new InternalResourceView(forwardUrl);
        }
        // Else fall back to superclass implementation: calling loadView.
        return super.createView(viewName, locale);
    }
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
//通过反射新建了View对象,并设置其URL等属性
        AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
        view.setUrl(getPrefix() + viewName + getSuffix());

        String contentType = getContentType();
        if (contentType != null) {
            view.setContentType(contentType);
        }

        view.setRequestContextAttribute(getRequestContextAttribute());
        view.setAttributesMap(getAttributesMap());

        Boolean exposePathVariables = getExposePathVariables();
        if (exposePathVariables != null) {
            view.setExposePathVariables(exposePathVariables);
        }
        Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
        if (exposeContextBeansAsAttributes != null) {
            view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
        }
        String[] exposedContextBeanNames = getExposedContextBeanNames();
        if (exposedContextBeanNames != null) {
            view.setExposedContextBeanNames(exposedContextBeanNames);
        }

        return view;
    }

视图解析完成之后,就是最后的视图渲染工作了,渲染工作由View对象来完成。
InternalResourceView的render方法实现在其基类AbstractView中定义:

@Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    //整合Model对象
        Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
        //设置response响应头
        prepareResponse(request, response);
        //最终渲染
        renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
    }

最终渲染部分代码:

@Override
    protected void renderMergedOutputModel(
            Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

        // 将各种Model值都设置到Http请求的请求属性中去,这里的Model已经
        //是经过应用处理过的值,将其巧妙地加入Request的属性中继续传递给视图
        exposeModelAsRequestAttributes(model, request);

        // Expose helpers as request attributes, if any.
        exposeHelpers(request);

        // 通过View对象的URL即可获取到真正的视图路径
        String dispatcherPath = prepareForRendering(request, response);

        // 这里获取RequestDispatcher,即JSP对应的Servlet对象,并调用service方法响应请求
        RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
        if (rd == null) {
            throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
                    "]: Check that the corresponding file exists within your web application archive!");
        }

        // If already included or response already committed, perform include, else forward.
        if (useInclude(request, response)) {
            response.setContentType(getContentType());
            if (logger.isDebugEnabled()) {
                logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
            }
            rd.include(request, response);
        }

        else {
            // Note: The forwarded resource is supposed to determine the content type itself.
            if (logger.isDebugEnabled()) {
                logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
            }
            rd.forward(request, response);
        }
    }

View对象并不是真正的视图对象,只是视图对象在SpringMVC中的表示,持有了真正视图对象的路径,渲染时通过RequestDispatcher将Model数据传给真正的视图。

Spring MVC对Http请求的处理

DispatcherServlet是spring mvc的核心Servlet,承担了请求转发处理的工作。它的本质是Servlet,其可以处理的请求在web.xml中定义,处理方法即service方法,该方法在父类HttpServlet中实现,并提供了钩子给子类。在DispatcherServlet中,主要处理逻辑在doDispatch方法中,部分代码如下:

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
            ModelAndView mv = null;
            Exception dispatchException = null;
            try {
                // 根据请求获取handler
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // 获取相应的handlerAdapter
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                //调用handler的拦截器链的preHandle方法,不符合则返回
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                // 将请求交给handler处理
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                applyDefaultViewName(request, mv);
                //调用handler的拦截器链的postHandler方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            //处理最终结果,主要是将ModelAndView渲染至相应视图
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
    }

processDispatchResult部分代码如下:

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
            HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
        // 渲染视图
        if (mv != null && !mv.wasCleared()) {
            render(mv, request, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        }
    }

总结

Spring MVC启动了两个IOC容器用于构建Spring应用,通过DispatcherServlet拦截Http请求,通过handlerMapping和handlerAdapter获取handler处理得到ModelAndView后,再通过View对象将Model包装在Http请求中,通过RequestDispatcher完成请求的进一步转发给真正的视图进行处理并返回http响应。

https://www.javatpoint.com/requestdispatcher-in-servlet
spring技术内幕

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐