在理解了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()中初始化的。

@Overridepublic 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()的实现。

@Overrideprotected 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()办法对视图进行出现。