关于spring:Spring-MVC-的实现

4次阅读

共计 35056 个字符,预计需要花费 88 分钟才能阅读完成。

在理解了 Spring IOC 和 AOP 之后,持续理解 Spring MVC 的实现。Spring MVV 是 Spring 的一个重要模块,很多 Web 利用是由 Spring 来撑持的。

在没有 Spring Boot 之前,咱们开发 Spring 的 Web 的利用,都是从以下 web.xml 配置开始的。

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

在下面的配置文件中,首先定义了一个 DispatcherServlet 的 servlet 对象,同时定义了这个 DispatcherServlet 的参数,context-param 参数的用来制订 Spring IOC 容器读区 Bean 定义的 XML 文件的门路,最初定义了一个 ContextLoaderListener 的监听器。

上述定义的 servlet 和 listener 就是 Spring MVC 在 Web 容器中的入口,这些接口通过与 Web 容器耦合,Web 容器的 ServletContext 为 Spring IOC 容器提供了一个宿主环境,这个宿主环境中,Spring 通过上述的 servlet 和 listener 建设了一个 IOC 的容器体系。这个 IOC 容器体系是通过 ContextLoaderListener 的初始化来建设的,在 IOC 容器体系建设之后,把 DispatcherServlet 作为 Spring MVC 解决 Web 申请的转发器初始化,从而实现响应 HTTP 申请的筹备。

所以整个 Spring MVC 的初始化能够概括为以下两步:

  1. ContextLoaderListener 监听 Web 容器的启动初始化一个根上下文。
  2. 初始化 DispatcherServlet,同时初始化一个用来保留控制器须要的 MVC 对象的上下文,并将第一步的上下文作为根上下文。

Spring MVC 领有两个上下文,其中一个为另一个的根上下文。

在剖析 Spring MVC 的实现之前,先理解一下 Web 容器下的上下文,即 IOC 容器,看看 Web 环境下的上下文与一般的 IOC 容器的有哪些特别之处。为了不便在 Web 环境中应用 IOC 容器,Spring 为 Web 利用提供了上下文的扩大接口 WebApplicationContext 来满足启动过程的须要,Spring 提供了 WebApplicationContext 的多个实现,咱们以 XmlWebApplicationContext 动手来理解其实现。

XmlWebApplicationContext 的继承关系如上所示。
咱们次要关注以下几个接口:

  • ApplicationContext
  • WebApplicationContext
  • ConfigurableApplicationContext
  • ConfigurableWebApplicationContext
  • AbstractRefreshableWebApplicationContext

ApplicationContext

从上述的类图中能够看到 ApplicationContext 继承自 BeanFactory,除了 BeanFactory, ApplicationContext 集成了泛滥接口,比简略的 IOC 容器领有了更多个性。
ApplicationContext 接口定义了以下办法。

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
        MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

    @Nullable
    String getId();

    String getApplicationName();

    String getDisplayName();

    long getStartupDate();

    @Nullable
    ApplicationContext getParent();


    AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;}

WebApplicationContext

WebApplicationContext 在 ApplicationContext 的根底上,定义了若干 Spring MVC 须要应用的常量,其中一个常量用于存取根上下文的 key。

String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

同时定义了 getServletContext()办法,通过这个办法获取 Web 容器的 ServletContext。

ConfigurableApplicationContext

ConfigurableApplicationContext 提供了对大部分 ApplicationContext 进行配置的办法。其中的 refresh()用于初始化一个 ApplicationContext。

ConfigurableWebApplicationContext

ConfigurableWebApplicationContext 继承自 ConfigurableApplicationContext 和 WebApplicationContext,在 ConfigurableApplicationContext 的根底上提供了更多对 ApplicationContext 进行配置的办法。

AbstractRefreshableWebApplicationContext

AbstractRefreshableWebApplicationContext 抽象类提供了对 ApplicationContext 配置文件进行配置的办法,同时定义了子类实现的 loadBeanDefintion 办法,通过 loadBeanDefintion()以及配置文件的门路加载 BeanDefinition.

XmlWebApplicationContext

XmlWebApplicationContext 继承自 AbstractRefreshableWebApplicationContext,并实现了 loadBeanDefintion 办法。同时定义了对 XmlWebApplicationContext 而言,默认的配置文件为 WEB-INF 下的 applicationContext.xml.

能够看到 XmlWebApplicationContext 定义了 Web 利用中默认的配置文件,同时提供了对 Ioc 容器的配置办法,然而必由之路,整个 ApplicationContext 的初始化仍旧是先加载 BeanDefiniton,而后依据 BeanDefiniton 实例化 IOC 容器中的 Bean。

在理解了 XmlWebApplicationContext 之后,上面咱们从以下三方面来剖析 Srping MVC 的实现。

  • ContextLoaderListener 的初始化。
  • DispatcherServlet 的初始化。
  • HTTP 申请散发。

ContextLoaderListener 的初始化

在 ContextLoaderListener 中,是实现了 ServletContextListener 的接口,ServletContextListener 定义了两个在 Web 容器启动和销毁时会回调办法,Spring MVC 两个上下文中的根上下文就是在 Web 容器启动时的回调办法 contextInitialized()中初始化的。

@Override
public void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());
}

ContextLoaderListener 继承自 ContextLoader, ContextLoaderListener 通过 ContextLoader 中的 initWebApplicationContext()初始化根上下文:

/**
 * Initialize Spring's web application context for the given servlet context,
 * using the application context provided at construction time, or creating a new one
 * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
 * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
 * @param servletContext current servlet context
 * @return the new WebApplicationContext
 * @see #ContextLoader(WebApplicationContext)
 * @see #CONTEXT_CLASS_PARAM
 * @see #CONFIG_LOCATION_PARAM
 */
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        throw new IllegalStateException(
                "Cannot initialize context because there is already a root application context present -" +
                "check whether you have multiple ContextLoader* definitions in your web.xml!");
    }

    servletContext.log("Initializing Spring root WebApplicationContext");
    Log logger = LogFactory.getLog(ContextLoader.class);
    if (logger.isInfoEnabled()) {logger.info("Root WebApplicationContext: initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
        // Store context in local instance variable, to guarantee that
        // it is available on ServletContext shutdown.
        if (this.context == null) {this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            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 ->
                    // determine parent for root web application context, if any.
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {currentContext = this.context;}
        else if (ccl != null) {currentContextPerThread.put(ccl, this.context);
        }

        if (logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;
            logger.info("Root WebApplicationContext initialized in" + elapsedTime + "ms");
        }

        return this.context;
    }
    catch (RuntimeException | Error ex) {logger.error("Context initialization failed", ex);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
        throw ex;
    }
}

根据上述的代码,整个根上下文的初始化的过程如下:

  1. 通过 createWebApplicationContext()创立根上下文。
  2. 如果有 parent 上下文,则设置 parent, 对于根上下文而言,其 parent 为 null。
  3. 通过 configureAndRefreshWebApplicationContext()对根上下文进行配置,即设置 servletContext,contextConfigLocation(后面的 web.xml 配置文件有配置)到根上下文中,以及通过 customizeContext 对上下文进行设置,最初通过 refresh()办法初始化根上下文。
  4. 将根上下文。保留到 servletContext 中。

createWebApplicationContext()的实现如下:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {Class<?> contextClass = determineContextClass(sc);
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
protected Class<?> determineContextClass(ServletContext servletContext) {String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    if (contextClassName != null) {
        try {return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", ex);
        }
    }
    else {contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        try {return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", ex);
        }
    }
}

这里通过首先通过 determineContextClass 确定应用哪个 Ioc 容器,能够通过 Context_class_param 参数设置,如果没有设置,则应用默认的 Ioc 容器,即 XmlWebApplicationContext。

到这里根上下文的初始化就实现了,即通过 ServletContext 初始化根上下文,同时对根上下文进行配置,而后保留到 ServletContext 中去,使得在全局中都能够获取到该上下文。

DispatcherServlet 的初始化

同样先看一下 DispatcherServlet 的继承体系。

能够看到 DispatcherServlet 继承自 FrameworkServlet, FrameworkServlet 继承自 HttpServletBean,HttpServletBean 继承自 HttpServlet, HttpServlet 继承自 GenericServlet, GenericServlet 继承自 Servlet。

DispatcherServlet 的初始化是通过 HttpServletBean 的 init()办法。

public final void init() throws ServletException {

    // Set bean properties from init parameters.
    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;
        }
    }

    // Let subclasses do whatever initialization they like.
    initServletBean();}

能够看到 HttpServletBean 的 init()办法中首先设置通过 BeanWrapper 设置在 web.xml 中设置的一些参数,而后便调用了 FrameworkServlet 的 initServletBean()办法,咱们持续看 initServletBean()的实现。

@Override
protected final void initServletBean() throws ServletException {getServletContext().log("Initializing Spring" + getClass().getSimpleName() + "'" + getServletName() + "'");
    if (logger.isInfoEnabled()) {logger.info("Initializing Servlet'" + getServletName() + "'");
    }
    long startTime = System.currentTimeMillis();

    try {this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();}
    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");
    }
}

initServletBean()中只有两个办法调用,别离是通过 initWebApplicationContext()初始化后面提到的第二个上下文,以及 initFrameworkServlet(),initFrameworkServlet()在 FrameworkServlet 是一个空实现。持续看 initFrameworkServlet()。

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) {
        // 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) {onRefresh(wac);
        }
    }

    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

首先判断上下文是否存在,如果存在就设置 parent 和通过 configureAndRefreshWebApplicationContext()进行配置,如果不存在,则通过 findWebApplicationContext()尝试寻找一个曾经存在的上下文,如果仍旧没有找到,则通过 createWebApplicationContext()创立一个上下文。最初也会将这个上下文保留到 ServletContext 中。创立上下文的过程如下:

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {Class<?> contextClass = getContextClass();
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Fatal initialization error in servlet with name'" + getServletName() +
                "': custom WebApplicationContext class [" + contextClass.getName() +
                "] is not of type ConfigurableWebApplicationContext");
    }
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    wac.setParent(parent);
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {wac.setConfigLocation(configLocation);
    }
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        // The application context id is still set to its original default value
        // -> assign a more useful id based on available information
        if (this.contextId != null) {wac.setId(this.contextId);
        }
        else {
            // Generate default id...
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
        }
    }

    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    // The wac environment's #initPropertySources will be called in any case when the context
    // is refreshed; do it eagerly here to ensure servlet property sources are in place for
    // use in any post-processing or initialization that occurs below prior to #refresh
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }

    postProcessWebApplicationContext(wac);
    applyInitializers(wac);
    wac.refresh();}

能够看到这里的上下文的创立过程与根上下文的创立相似,然而又有些不同,区别在于这里设置了 Environment,同时 configureAndRefreshWebApplicationContext 中设置了一个 ContextRefreshListener 类型的 ApplicationListener,ContextRefreshListener 对 ApplicationEvent 进行监听,如果是 ContextRefreshedEvent, 则调用 onRefresh()办法。如果 this.refreshEventReceived = false,也会调用 onRefresh()办法,避免没有对 ContextRefreshedEvent 进行监听或者不反对 ContextRefreshedEvent 监听,也能够调用 onRefresh()办法。接下来持续看 onRefresh()办法。

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

能够看到 onRefresh()办法中调用了 iniStrategies()办法。iniStrategies()办法中初始化化了 MultipartResolver, LocalResolver, ThemeResolver, HandlerMappings, HanderAdapters, HanderExceptionResolvers, RequestToViewNameTranslator, ViewResolver, FlashMapManager()等。
上面以初始化 HandlerMappings 为例,看看其具体的实现。

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

    if (this.detectAllHandlerMappings) {
        // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
        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) {this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isTraceEnabled()) {logger.trace("No HandlerMappings declared for servlet'" + getServletName() +
                    "': using default strategies from DispatcherServlet.properties");
        }
    }

    for (HandlerMapping mapping : this.handlerMappings) {if (mapping.usesPathPatterns()) {
            this.parseRequestPath = true;
            break;
        }
    }
}

HandlerMappings 的初始化的过程如下:

  1. 判断是否检测所有的 HandelrMappings, 如果须要则通过 beansOfTypeIncludingAncestors 获取所有的 HandelrMapping, 并依照 Order 排序。
  2. 如果不须要检测所有的 HandlerMapping, 则通过默认的 beanName 从 IOC 容器中获得。
  3. 如果没有拿到 HandelrMappping, 则初始化默认的 HandlerMappings(BeanNameUrlHandlerMapping, RequestMappingHandlerMapping, RouterFunctionMapping)
  4. 设置 parseRequestPath。

后面 Spring MVC 的初始化曾经实现了,在初始化实现时,在上下文环境中已定义好的所有的 HandlerMapping 都曾经加载实现了,这些 handlerMapping 保留在一个 List 中并被排序,存储中 HTTP 申请的对应的映射数据,每一个 HandlerMapping 都能够持有一些列从 URL 到 Controller 的映射,而 Spring MVC 提供了一系列 HandlerMapping 的实现,如下所示

以 SimpleUrlHandlerMapping 为例持续剖析 HandlerMapping 的设计与实现。
首先看一下 HandlerMapping 接口,HandlerMapping 中只定义的了一个办法。

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

入参是 HttpServletRequest, 返回的是 HandlerExecutionChain。HandlerExecutionChain 的实现如下:

public class HandlerExecutionChain {private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);

    private final Object handler;

    private final List<HandlerInterceptor> interceptorList = new ArrayList<>();

    private int interceptorIndex = -1;

    ...
}

能够看到 HandlerExecutionChain 保留了一个 interceptors 链和一个 handler 对象,这个 handler 对象实际上就是 HTTP 申请对应的 Controller, 在持有这个 handler 对象的同时,通过这个 interceptor 链为 handler 对象提供性能的加强。HandlerExecutionChain 中的 interceptors 链和 handler 对象须要在初始化 HandlerMapping 的时候设置好。对于 SimpleUrlHandlerMapping 而言,则是通过 Bean 的 postProcessor 来实现了的,因为 SimpleUrlHandlerMapping 是 ApplicationContextAware 的子类,在 setApplicationContext 的时候回调 WebApplicationObjectSupport 的 initApplicationContext 办法,继而回调到 SimpleUrlHandlerMapping 的 initApplicationContext 办法。

public void initApplicationContext() throws BeansException {super.initApplicationContext();
    registerHandlers(this.urlMap);
}
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {if (urlMap.isEmpty()) {logger.trace("No patterns in" + formatMappingName());
    }
    else {urlMap.forEach((url, handler) -> {
            // Prepend with slash if not already present.
            if (!url.startsWith("/")) {url = "/" + url;}
            // Remove whitespace from handler bean name.
            if (handler instanceof String) {handler = ((String) handler).trim();}
            registerHandler(url, handler);
        });
        logMappings();}
}

对于 SimpleUrlHandlerMapping 而言,则须要在初始化 SimpleUrlHandlerMapping 时指定 urlMap, 对于 BeanNameUrlHandlerMapping 而言,则会扫描 IOC 容器中的 bean,并建设 url 到 handler 的映射。同时真正的注册过程是通过父类 AbstractUrlHandlerMapping 的 registerHandler 实现的。

protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {Assert.notNull(urlPaths, "URL path array must not be null");
    for (String urlPath : urlPaths) {registerHandler(urlPath, beanName);
    }
}

protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {Assert.notNull(urlPath, "URL path must not be null");
    Assert.notNull(handler, "Handler object must not be null");
    Object resolvedHandler = handler;

    // Eagerly resolve handler if referencing singleton via name.
    if (!this.lazyInitHandlers && handler instanceof String) {String handlerName = (String) handler;
        ApplicationContext applicationContext = obtainApplicationContext();
        if (applicationContext.isSingleton(handlerName)) {resolvedHandler = applicationContext.getBean(handlerName);
        }
    }

    Object mappedHandler = this.handlerMap.get(urlPath);
    if (mappedHandler != null) {if (mappedHandler != resolvedHandler) {
            throw new IllegalStateException("Cannot map" + getHandlerDescription(handler) + "to URL path [" + urlPath +
                    "]: There is already" + getHandlerDescription(mappedHandler) + "mapped.");
        }
    }
    else {if (urlPath.equals("/")) {if (logger.isTraceEnabled()) {logger.trace("Root mapping to" + getHandlerDescription(handler));
            }
            setRootHandler(resolvedHandler);
        }
        else if (urlPath.equals("/*")) {if (logger.isTraceEnabled()) {logger.trace("Default mapping to" + getHandlerDescription(handler));
            }
            setDefaultHandler(resolvedHandler);
        }
        else {this.handlerMap.put(urlPath, resolvedHandler);
            if (getPatternParser() != null) {this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);
            }
            if (logger.isTraceEnabled()) {logger.trace("Mapped [" + urlPath + "] onto" + getHandlerDescription(handler));
            }
        }
    }
}

最终 url 到 handler 之间的映射关系保留在 AbstractUrlHandlerMapping 的 handlerMap 中,同时设置了 rootHandelr, defaultHandelr, 以及 pathPatternHandlerMap。

通过上述的初始化过程,handlerMappings 变量就曾经在获取了在 BeanDefiniton 中配置好的映射关系,其余的初始化过程与 handlerMapping 比拟相似,都是间接从 IOC 容器中读入配置,所以这里的 MVC 的初始化过程是 建设在 IOC 容器曾经初始化实现的根底上的

HTTP 申请散发

在后面的 Spring MVC 初始化的过程了初始化了 Spring MVC 须要的各个组件,最重要的是建设了 HTTP 申请到 Handler 的映射关系,上面来看看具体的申请过程。

从 Servlet 开始剖析, 后面咱们提到了 DispatcherServlet 继承自 FrameworkServlet,而 FrameworkServlet 继承自 HttpServletBean, HttpServletBean 继承自 HttpServlet。
而 HttpServletBean 重写了 HttpServlet 的 doGet, doPost, doPut 等一系列办法,咱们以 doGet()办法为例进行剖析,其余的与 doGet 相似。

protected final void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {processRequest(request, response);
}

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    initContextHolders(request, localeContext, requestAttributes);

    try {doService(request, response);
    }
    catch (ServletException | IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }

    finally {resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {requestAttributes.requestCompleted();
        }
        logResult(request, response, failureCause, asyncManager);
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

能够看到,首先做了一些筹备工作,包含获取和设置 localeContext, requestAttributes,注册 interceptor 的回调等,而后调用子类的 doService()办法,持续看 doService()办法。

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {logRequest(request);

    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {attributesSnapshot = new HashMap<>();
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {String attrName = (String) attrNames.nextElement();
            if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }

    // Make framework objects available to handlers and view objects.
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    if (this.flashMapManager != null) {FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }
        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    }

    RequestPath previousRequestPath = null;
    if (this.parseRequestPath) {previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
        ServletRequestPathUtils.parseAndCache(request);
    }

    try {doDispatch(request, response);
    }
    finally {if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
        if (this.parseRequestPath) {ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
        }
    }
}

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

            // Determine handler for the current request.
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = HttpMethod.GET.matches(method);
            if (isGet || HttpMethod.HEAD.matches(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}
            }

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

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

            if (asyncManager.isConcurrentHandlingStarted()) {return;}

            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {dispatchException = ex;}
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    }
    finally {if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {cleanupMultipart(processedRequest);
            }
        }
    }
}

doDispatch()办法中能够是 DispatcherServlet 的次要办法,这个办法中能够看到 MVC 模式的外围实现,包含筹备 ModelAndView,调用 getHandler 来响应 Http 申请,而后执行 Handle 失去返回的 ModelAndView,最初将这个 ModelAndView 对象交给相应的视图对象去出现。在这里实现了模型,视图,控制器的紧密结合。整个过程如下:

  1. getHandelr 依据 HTTP 申请拿到对应的 HandlerExecutionChain.
  2. 通过 getHandlerAdapter 失去 HandlerExecutionChain 对应的 HandlerAdapter。
  3. 通过 applyPreHandle 对 handler 进行前置加强。
  4. 通过 HandlerAdapter 的 handler 办法对 HTTP 申请进行响应,对于不同的 HandlerAdapter 有不同的解决。
  5. 通过 applyPostHandle 对 handler 进行后置加强。
  6. 通过 processDispatchResult 将这个 ModelAndView 对象交给相应的视图对象去出现。

上面看一下 getHandelr(), getHandlerAdapter()和 processDispatchResult()办法。
getHandler()的实现如下:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {return handler;}
        }
    }
    return null;
}

在所有的 handlerMapping 中找到到一个与以后 request 匹配的 handler, 找到就返回。

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {Object handler = getHandlerInternal(request);
    if (handler == null) {handler = getDefaultHandler();
    }
    if (handler == null) {return null;}
    // Bean name or resolved handler?
    if (handler instanceof String) {String handlerName = (String) handler;
        handler = obtainApplicationContext().getBean(handlerName);
    }

    // Ensure presence of cached lookupPath for interceptors and others
    if (!ServletRequestPathUtils.hasCachedPath(request)) {initLookupPath(request);
    }

    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

    if (logger.isTraceEnabled()) {logger.trace("Mapped to" + handler);
    }
    else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {logger.debug("Mapped to" + executionChain.getHandler());
    }

    if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {CorsConfiguration config = getCorsConfiguration(handler, request);
        if (getCorsConfigurationSource() != null) {CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
            config = (globalConfig != null ? globalConfig.combine(config) : config);
        }
        if (config != null) {config.validateAllowCredentials();
        }
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }

    return executionChain;
}

获得 handler 的具体过程在 getHandlerInternal() 办法中,这个办法承受 HTTP 申请作为参数。失去 handler 之后封装成 HandlerExecutionChain 返回。

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {String lookupPath = initLookupPath(request);
    Object handler;
    if (usesPathPatterns()) {RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request);
        handler = lookupHandler(path, lookupPath, request);
    }
    else {handler = lookupHandler(lookupPath, request);
    }
    if (handler == null) {
        // We need to care for the default handler directly, since we need to
        // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
        Object rawHandler = null;
        if (StringUtils.matchesCharacter(lookupPath, '/')) {rawHandler = getRootHandler();
        }
        if (rawHandler == null) {rawHandler = getDefaultHandler();
        }
        if (rawHandler != null) {
            // Bean name or resolved handler?
            if (rawHandler instanceof String) {String handlerName = (String) rawHandler;
                rawHandler = obtainApplicationContext().getBean(handlerName);
            }
            validateHandler(rawHandler, request);
            handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
        }
    }
    return handler;
}

在 getHandlerInternal 中能够看到具体的 url 的匹配过程,即首先判断是否有设置模式解析器,如果有,则对 url 的模式进行匹配,如果没有则间接查找,如果后面没有找到适合的 handler,则顺次尝试 rootHandelr ,defaultHandler 是否匹配,如果找到则返回对应的 handler, 否则返回 null。

接下来看 getHandlerAdapter()。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {for (HandlerAdapter adapter : this.handlerAdapters) {if (adapter.supports(handler)) {return adapter;}
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

getHandlerAdapter()通过 supports()办法找到一个适合的 HandlerAdapter。

对 HandlerAdapter 的 handler 办法则有不同实现,因为这个 Handler 可能是一个独立的 Bean,也可能使 Bean 中的某个办法,HandlerAdapter 就是不同的 Handler 的实现做一个适配,具体能够到具体的 HandlerAdapter 实现。

持续看一下 processDispatchResult()。

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
        @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
        @Nullable Exception exception) throws Exception {

    boolean errorView = false;

    if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();}
        else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    // Did the handler return a view to render?
    if (mv != null && !mv.wasCleared()) {render(mv, request, response);
        if (errorView) {WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {if (logger.isTraceEnabled()) {logger.trace("No view rendering, null ModelAndView returned.");
        }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    if (mappedHandler != null) {// Exception (if any) is already handled..
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

失去 ModelAndView 之后就能够通过不同的视图对象去出现视图,processDispatchResult()先获得 ModelAndView, 而后通过调用视图对象的 render()办法实现特定视图的出现工作。

视图出现

后面失去 ModelAndView 对象之后交给具体的视图对象去实现相应的视图出现。
在看 render()办法之前先看一下 Spring 中的视图对象。

能够看到,在 View 接口下,实现了一系列具体的 View 对象,而这些 View 对象,又依据其不同的个性归类到不同的抽象类中,比方 AbstractView 类细分为 AbstractFeedView, AbstractPdfView, AbstractXlsView, AbstractJackson2View, AbtractUrlBaseView。通过对不同的视图类实现形式进行归类,便于利用的应用和扩大,同时 View 接口的设计也很简略,只须要实现 render()。

持续看后面提到 render()办法。

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Determine locale for request and apply it to the response.
    Locale locale =
            (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
    response.setLocale(locale);

    View view;
    String viewName = mv.getViewName();
    if (viewName != null) {
        // We need to resolve the view name.
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        if (view == null) {throw new ServletException("Could not resolve view with name'" + mv.getViewName() +
                    "'in servlet with name'" + getServletName() + "'");
        }
    }
    else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        view = mv.getView();
        if (view == null) {throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a" +
                    "View object in servlet with name'" + getServletName() + "'");
        }
    }

    // Delegate to the View object for rendering.
    if (logger.isTraceEnabled()) {logger.trace("Rendering view [" + view + "]");
    }
    try {if (mv.getStatus() != null) {response.setStatus(mv.getStatus().value());
        }
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {if (logger.isDebugEnabled()) {logger.debug("Error rendering view [" + view + "]", ex);
        }
        throw ex;
    }
}

从下面能够看到,首先在 ModelAndView 对象中失去 View 对象,如果 ModelAndView 对象中曾经有了最终实现的视图出现的对象,就间接调用视图对象的 render()办法,如果没有就看是否设置了视图对象的名称,并通过视图对象的名称进行解析,从而失去须要应用的视图对象。

看一下视图对象的 render()办法。这里以罕用的 JSP 页面对应的 JstlView 对象为例来剖析视图的出现。JstlView 没有实现 render()办法,应用的 render()办法是它的基类 AbstractView 中的实现的。

public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
        HttpServletResponse response) throws Exception {if (logger.isDebugEnabled()) {logger.debug("View" + formatViewName() +
                ", model" + (model != null ? model : Collections.emptyMap()) +
                (this.staticAttributes.isEmpty() ? "":", static attributes " + this.staticAttributes));
    }

    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    prepareResponse(request, response);
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

这个 render 办法次要实现一些数据筹备工作,createMergedOutputModel()将所有的数据属性整合到一个 mergedModel 外面。prepareResponse()则设置响应头的属性,renderMergedOutputModel()的实现在 InternalResourceView 中。

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

    // Expose the model object as request attributes.
    exposeModelAsRequestAttributes(model, request);

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

    // Determine the path for the request dispatcher.
    String dispatcherPath = prepareForRendering(request, response);

    // Obtain a RequestDispatcher for the target resource (typically a JSP).
    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 [" + getUrl() + "]");
        }
        rd.include(request, response);
    }

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

renderMergedOutputModel()次要实现的工作如下:

  1. exposeModelAsRequestAttributes()将模型对象寄存到 HttpServletRequest 外面。
  2. exposeHelpers()设置 LocalizationContext。
  3. getRequestDispatcher()获取 RequestDispatcher。
  4. 通过 RequestDispatcher 转发申请到对应的视图资源上,实现 JSP 页面的出现。

总结

整个 Spring MVC 的原理以 DispatcherServlet 为外围,总的来说,Spring MVC 的实现大抵由以下几个局部组成。

  1. 建设 HTTP 申请到 Controller 的映射。
  2. HTTP 申请达到之后,通过 getHandler 失去 handlerExecutionChain 对申请进行响应。
  3. 失去相应的 ModelAndView,由视图对象的 render()办法对视图进行出现。
正文完
 0