Spring源码分析——SpringMVC实现

Spring MVC概述

Spring MVC是Spring的一个重要模块,在Web应用中MVC的设计模式已经广为人知,MVC的设计概念如下图所示
Spring源码分析——SpringMVC实现_第1张图片

MVC模式在UI设计中使用的非常普遍,在Gof的设计模式的经典著作中,开篇就是这个模式。这个模式的额主要特点是分离了模型,视图与控制器三种角色,将业务处理从UI设计中独立出来,封装到模型与控制器设计中去。使得它们相互解耦可以独立扩展。
使用Spring MVC的时候,需要在web.xml中配置DispatcherServlet,这个DispatcherServlet可以看做一个前端控制器的具体实现。还需要在Bean定义配置请求与控制器的对应关系,以及各种视图的展现方式。

应用上下文(ApplicationContext)在web容器中的启动

了解Spring MVC需要首先了解Spring IOC是如何在IOC容器中起效果的。如果要在Web环境适应IOC容器需要为Spring IOC设计一个启动过程,而这个过程是和web容器的启动集成在一起的。下面以Tomcat这个web容器为例分析IOC容器的启动过程。
web.xml



    
        Spring character encoding filter
        
            org.springframework.web.filter.CharacterEncodingFilter
        
        
            encoding
            utf-8
        
    
    
        Spring character encoding filter
        /*
    
    
        contextConfigLocation
        classpath:applicationContext.xml
    
    
        org.springframework.web.context.ContextLoaderListener
    
    
        org.springframework.web.util.IntrospectorCleanupListener
    
    
        SpringMVC
        org.springframework.web.servlet.DispatcherServlet
        
            contextConfigLocation
            classpath:spring-mvc.xml
        
        1
        true
    
    
        SpringMVC
        /
    
    
        /index.jsp
    

在这个部署描述文件中首先定义了一个Servlet对象,这个DispatcherServlet起着分发请求的作用,对它的分析是后面几节的重点但我们现在的关注点在ContextLoaderListener这个监听器。(节点中的内容用来指定Spring IOC容器读取Bean定义的xml文件路径)。ContextLoaderListener监听器负责完成ioc容器在web环境中的启动工作。
DispatcherServlet与ContextLoaderListener提供了web容器对Spring的接口,也就是说这些接口与web容器的耦合是通过ServletContext实现的。ServletContext为Spring的IOC容器提供了一个宿主环境。

IOC容器启动的基本过程

ioc容器的启动过程就是上下文建立的过程,该上下文与ServletContext相伴相生,由ContextLoaderListener启动的上下文为根上下文,在根上下文的基础上还有一个与Web MVC相关的上下文,构成一个层次化的上下文体系,具体过程如下
Spring源码分析——SpringMVC实现_第2张图片

之前已经说了ioc容器的初始化的入口在ContextLoaderListener,这是一个Spring提供的Servlet容器的监听类,它的继承关系如下
Spring源码分析——SpringMVC实现_第3张图片

WebApplicationContext的设计

为了方便在web环境中使用ioc容器,Spring提供了上下文的扩展接口WebApplicationContext来满足启动过程的需要,它的继承关系如下
Spring源码分析——SpringMVC实现_第4张图片

我们看看WebApplicationContext接口的定义,只有一个抽象方法属于该接口

	/**
	 * Return the standard Servlet API ServletContext for this application.
	 */
	@Nullable
	ServletContext getServletContext();

通过这个方法可以获得当前Web容器的ServletContext,相当于提供了一个Web容器级别的全局环境。

在启动时Spring会使用默认的XmlWebApplicationContext作为IOC容器,在Web环境中对定位BeanDefination元数据的Resource有特殊的要求,这个要求体现在getConfigLoacation这个方法中。
XmlWebApplicationContext:loadBeanDefinations

	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			for (String configLocation : configLocations) {
				reader.loadBeanDefinitions(configLocation);
			}
		}
	}

AbstractRefreshableConfigApplicationContext:getConfigLocations

	@Nullable
	protected String[] getConfigLocations() {
		return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
	}

可以看到当我们不定义时会通过getDefaultConfigLocations方法加载默认的配置文件。
XmlWebApplicationContext:getDefaultConfigLocations

	/** Default config location for the root context */
	public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
	
	@Override
	protected String[] getDefaultConfigLocations() {
		if (getNamespace() != null) {
			return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
		}
		else {
			return new String[] {DEFAULT_CONFIG_LOCATION};
		}
	}

这下明白为什么spring的入门教程都叫你把配置文件取成applicationContext.xml了吧。(笑)
事实上XmlWebApplicationContext中基本的上下文功能都已经通过继承获得,而这个类在此基础上扩展的就是去何处获取定义BeanDefination的元信息,在获得这些信息后后面的过程就和我们系列文章的第一篇那样使用XmlBeanDefinationReader载入Bean定义信息,最后完成整个上下文的初始化过程的。

ContextLoaderListenre的设计与实现

ContextLoaderListener通过基类ContextLoader完成对WebApplicationContext的初始化,通过实现ServletContextListener完成ServletContext生命周期的回调。之前已经说了ioc容器会随着ServletContext的创建而创建,那么相应的回调方法就是contextInitialized
ContextLoaderListener

	/**
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}

可以看到将初始化工作交给了基类ContextLoader去做
ContextLoader:initWebApplicationContext

	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	    // 判断在Servlet上下文中是否已经有根ioc容器存在
		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!");
		}

		Log logger = LogFactory.getLog(ContextLoader.class);
		servletContext.log("Initializing Spring root WebApplicationContext");
		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) {
			    // 传入ServletContext,创建WebApplicationContext
				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.isDebugEnabled()) {
				logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
						WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
			}
			if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
			}

			return this.context;
		}
		catch (RuntimeException ex) {
			logger.error("Context initialization failed", ex);
	     	//将创建好的ioc容器与ServletContext的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性绑定
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
		catch (Error err) {
			logger.error("Context initialization failed", err);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
			throw err;
		}
	}

具体的根容器的创建在createWebApplicationContext方法中
ContextLoader: 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);
	}

创建完成调用configureAndRefreshWebApplicationContext完成初始化工作
ContextLoader:configureAndRefreshWebApplicationContext

	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		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
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				wac.setId(idParam);
			}
			else {
				// Generate default id...
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}
        //将ServletContext设置进ioc容器中
		wac.setServletContext(sc);
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}

		// 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(sc, null);
		}

		customizeContext(sc, wac);
		wac.refresh();
	}

熟悉的refresh方法~ioc容器具体的启动过程可以参考博主系列博客的第一篇。

我们之前说过web环境下使用的默认的ioc容器是XmlWebApplicationContext,事实上使用什么容器是由determineContextClass方法决定的

	protected Class determineContextClass(ServletContext 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);
			}
		}
	}

这就是ioc容器在Web容器中的启动过程,与在应用中启动方式类似,这里需要考虑的是Web容器的环境特点以及ioc容器与web容器的结合等。在ioc容器初始化完成后该上下文会被存储到全局上下文ServletContext中,接下来我们分析DispatcherServlet的启动初始化,提前剧透这个ioc上下文会被设置为DispactherServlet独立的ioc上下文的双亲上下文哦

DispactherServlet的启动与初始化

在完成对ContextLoaderListener的初始化后,Web容器开始初始化DispatcherServlet,DispatcherServlet会自己创建一个独立的ioc容器用于获取Spring MVC需要的Bean对象(这也是我们为Spring MVC独立配置一个xml文件的原因)。在建立这个上下文后还会从ServletContext中获取根上下文作为这个上下文作为双亲上下文(为什么要这样做的原因之后将会说明),并且在自身的上下文创建完毕后同样要设置到ServletContext中供全局获取与使用。

Dispatcher 类继承关系

想要了解这个过程先来看看Dispatcher的类继承关系
Spring源码分析——SpringMVC实现_第5张图片

DispatcherServlet通过继承FrameworkServelet与HttpServletBean而继承了HttpServlet(因此它对于抽象方法doService的实现会是源码分析的重点)。通过Servlet API对Http请求进行响应,成为Spring MVC的前端处理器。

Dispatcher Servlet的工作主要分为两部分,一部分为自身的初始化工作。另一个就是对Http请求的分发。下面是这两部分功能的一张简单的时序图
Spring源码分析——SpringMVC实现_第6张图片

DispatcherServlet中WebApplicationContext的初始化

作为Servlet,DispatcherServlet的启动过程与Servlet的生命周期是相联系的,那么我们来看看Servlet启动会调用的init方法
DispatcherServlet:init

@Override
	public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}

		// Set bean properties from init parameters.
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		// 获取Servlet的初始化参数,对Bean进行配置
		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方法中完成具体的初始化
		initServletBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}

那我们看看父类FrameworkServlet中的initServletBean的实现
FrameworkServlet:initServletBean

	@Override
	protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
		if (this.logger.isInfoEnabled()) {
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException | RuntimeException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (this.logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
					elapsedTime + " ms");
		}
	}

可以看到调用了两个方法,一个初始化ioc容器,一个初始化FrameworkServlet,我们先看ioc容器的初始化
FrameworkServlet:initWebApplicationContext

	protected WebApplicationContext initWebApplicationContext() {
	    // 获取ServletContext中的根容器
		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);
					}
					// ioc容器的初始化,看来又要调用refresh方法了
					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
			// 如果ioc容器还未创建则直接调用createWebApplicationContext方法创建并直接传入rootContext参数作为父容器
			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.
			onRefresh(wac);
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			}
		}

		return wac;
	}

WebApplicationContext已创建情况下的逻辑已经很清楚,而不存在的情况下会直接调用createWebApplicationContext这个方法
FrameworkServlet:createWebApplicationContext

	protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
		Class contextClass = getContextClass();
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Servlet with name '" + getServletName() +
					"' will try to create custom WebApplicationContext context of class '" +
					contextClass.getName() + "'" + ", using parent context [" + parent + "]");
		}
		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;
	}

可以看到直接创建的过程同样将传入的rootContext作为了自己的父容器,验证了我们上面的说法,那么为什么要做这样的工作呢?
这样设置是因为,对于一个具体的Bean的查找过程来说,系统会先去根容器中去寻找(和ioc容器的实现有关,具体代码请自行阅读getBean方法),这样在Spring MVC中就可以使用定义在根容器中的Bean了。

Spring MVC相关组件的初始化

我们看到在ioc容器创建完成后都调用了onRefresh这个方法:
DispatcherServlet:onRefresh

	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

DispatcherServlet:initStrategies

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

至此,我们看到了Spring MVC相关组件的初始化入口,而这里我们只对HandlerMapping组件进行分析,对其他组件感兴趣的同学从入口调用接下去读源码即可。

在HandlerMapping的初始化过程中,将在Bean配置文件中配置好的HandlerMapping从ioc容器中取出
DispatcherServlet:initHandlerMappings

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

		if (this.detectAllHandlerMappings) {
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			// 导入所有的HandlerMapping
			Map matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
			    // 直接从ioc容器中取
				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) {
		    // 如果没有找到则设置为默认的HandlerMapping,默认的定义在DispatcherServlet.properties文件中
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}
	}

DispatcherServlet.properties和DispatcherServlet在一个包下,有所有Spring MVC相关组件的默认配置

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

MVC对HTTP请求的分发

之前我们分析了DispatcherServlet中HandlerMapping的初始化过程,而这个HandlerMapping是做什么的我们还不知道。而实际上它就是承担HTTP请求分发的MVC组件。
每一个HandlerMapping都持有一系列从URL请求到Controller的映射,而Spring MVC提供了一系列HandlerMapping的实现
Spring源码分析——SpringMVC实现_第7张图片

我们以SimpleHandlerMapping这个实现分析,在SimpleHandlerMapping中定义了一个map持有关系的也设,通过这些URL请求与控制器的对应关系,使Spring MVC应用可以根据Http请求来确定对应的控制器(Controller)。
获得这个映射关系的方法由顶层接口HandlerMapping定义

	@Nullable
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

可以看到返回的是一个HandlerExecutionChain而不是Handler,为什么要这样设计呢?还记得我们在做web应用时经常使用的拦截器(HandlerInterceptor)吗?事实上请求很少直接到达对应的控制器,在这之前需要经过一些前置操作。离开控制器后往往也有一系列后置操作,而HandlerExecutionChain为这中模式提供了封装实现
HandlerExecutionChain
Spring源码分析——SpringMVC实现_第8张图片

在HandlerExecutionChain中持有一个过滤器链,以及最终的控制器。
HandlerExecutionChain中的这两个元素需要在定义HandlerMapping时配置好,例如对于SimpleURLHandleMapping就是根据URL映射的方式注册Handler和Interceptor,从而维护一个反映这种映射关系(URL->HandlerExecutionChain)的handlerMap。这些信息是什么时候注册好的呢?事实上这个注册过程咋容器对Bean进行依赖注入时发生,通过一个postProcessor完成,注意SimpleUrlHandlerMapping通过继承ApplicationObjectSupport实现了对ApplicationContextAware的实现,而这个接口的setApplicationContext将会在依赖注入的时候调用setApplicationContext这个方法
ApplicationObjectSupport:setApplicationContext

	@Override
	public final void setApplicationContext(@Nullable ApplicationContext context) throws BeansException {
		if (context == null && !isContextRequired()) {
			// Reset internal context state.
			this.applicationContext = null;
			this.messageSourceAccessor = null;
		}
		else if (this.applicationContext == null) {
			// Initialize with passed-in context.
			if (!requiredContextClass().isInstance(context)) {
				throw new ApplicationContextException(
						"Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]");
			}
			this.applicationContext = context;
			this.messageSourceAccessor = new MessageSourceAccessor(context);
			initApplicationContext(context);
		}
		else {
			// Ignore reinitialization if same context passed in.
			if (this.applicationContext != context) {
				throw new ApplicationContextException(
						"Cannot reinitialize with different application context: current one is [" +
						this.applicationContext + "], passed-in one is [" + context + "]");
			}
		}
	}

可以看到在获取到了ioc容器后直接调用了initApplicationContext方法,而这个方法在SimpleURLHandlerMapping中的实现是这样的
SimpleURLHandlerMapping:initApplicationContext

	@Override
	public void initApplicationContext() throws BeansException {
		super.initApplicationContext();
		registerHandlers(this.urlMap);
	}

看来registerHandlers就是我们要寻找的handlerMap的初始化入口了
SimpleURLHandlerMapping:registerHandlers

	protected void registerHandlers(Map urlMap) throws BeansException {
		if (urlMap.isEmpty()) {
			logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
		}
		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);
			});
		}
	}

SimpleURLHandlerMapping:registerHandler

	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.
		// 如果直接使用Bean名进行映射,就直接从容器中取对应的Handler
		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.");
			}
		}
		// 处理url是/的映射,将这个映射对应的controller设置到rootHandler中
		else {
			if (urlPath.equals("/")) {
				if (logger.isInfoEnabled()) {
					logger.info("Root mapping to " + getHandlerDescription(handler));
				}
				setRootHandler(resolvedHandler);
			}
			// 处理url是/*的映射,把这个controller设置到defaultHandler中
			else if (urlPath.equals("/*")) {
				if (logger.isInfoEnabled()) {
					logger.info("Default mapping to " + getHandlerDescription(handler));
				}
				setDefaultHandler(resolvedHandler);
			}
			else {
			    // 处理正常的映射,直接根据key-value设置到handlerMap中即可
				this.handlerMap.put(urlPath, resolvedHandler);
				if (logger.isInfoEnabled()) {
					logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
				}
			}
		}
	}

至此,这个配置好的handlerMap为SpringMvc响应HTTP请求准备好了基础映射数据,根据这个基础映射数据可以方便的找到每个url对应的controller。

请求的映射过程

获取请求对应的controller的入口方法是HandlerMapping接口定义的getHandler方法
AbstractHandlerMapping:getHandler

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);
		}

		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		if (CorsUtils.isCorsRequest(request)) {
			CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}
		return executionChain;
	}

AbstractHandlerMapping:getHandlerExecutionChain

	protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

        // 为HTTP请求添加匹配的过滤器
		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
			if (interceptor instanceof MappedInterceptor) {
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
				if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				}
			}
			else {
				chain.addInterceptor(interceptor);
			}
		}
		return chain;
	}

取得Handler的具体过程在getHandlerIternal方法中,包括从HTTP请求中获取URL,然后去urlMapping中取得Handler
AbstractURLHandlerMapping:getHandlerIternal

	@Override
	@Nullable
	protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		Object 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 ("/".equals(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);
			}
		}
		if (handler != null && logger.isDebugEnabled()) {
			logger.debug("Mapping [" + lookupPath + "] to " + handler);
		}
		else if (handler == null && logger.isTraceEnabled()) {
			logger.trace("No handler mapping found for [" + lookupPath + "]");
		}
		return handler;
	}

AbstractURLHandlerMapping:lookupHandler

	@Nullable
	protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
		// Direct match?
		Object handler = this.handlerMap.get(urlPath);
		if (handler != null) {
			// Bean name or resolved handler?
			if (handler instanceof String) {
				String handlerName = (String) handler;
				handler = obtainApplicationContext().getBean(handlerName);
			}
			validateHandler(handler, request);
			return buildPathExposingHandler(handler, urlPath, urlPath, null);
		}

		// Pattern match?
		List matchingPatterns = new ArrayList<>();
		for (String registeredPattern : this.handlerMap.keySet()) {
			if (getPathMatcher().match(registeredPattern, urlPath)) {
				matchingPatterns.add(registeredPattern);
			}
			else if (useTrailingSlashMatch()) {
				if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
					matchingPatterns.add(registeredPattern +"/");
				}
			}
		}

		String bestMatch = null;
		Comparator patternComparator = getPathMatcher().getPatternComparator(urlPath);
		if (!matchingPatterns.isEmpty()) {
			matchingPatterns.sort(patternComparator);
			if (logger.isDebugEnabled()) {
				logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
			}
			bestMatch = matchingPatterns.get(0);
		}
		if (bestMatch != null) {
			handler = this.handlerMap.get(bestMatch);
			if (handler == null) {
				if (bestMatch.endsWith("/")) {
					handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
				}
				if (handler == null) {
					throw new IllegalStateException(
							"Could not find handler for best pattern match [" + bestMatch + "]");
				}
			}
			// Bean name or resolved handler?
			if (handler instanceof String) {
				String handlerName = (String) handler;
				handler = obtainApplicationContext().getBean(handlerName);
			}
			validateHandler(handler, request);
			String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);

			// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
			// for all of them
			Map uriTemplateVariables = new LinkedHashMap<>();
			for (String matchingPattern : matchingPatterns) {
				if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
					Map vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
					Map decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
					uriTemplateVariables.putAll(decodedVars);
				}
			}
			if (logger.isDebugEnabled()) {
				logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
			}
			return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
		}

		// No handler found...
		return null;
	}

代码有点长我们只需要关心这一句即可Object handler = this.handlerMap.get(urlPath);。下面的代码是无法直接根据URL找到handler时所需要做的处理。

接下来要处理的最后一个问题就是请求怎么在DispatcherServlet完成分发的

Spring MVC对于请求的分发

DispatcherServlet作为一个servlet对于HTTP请求的处理一定在它的doService方法中,我们分析一下这个方法
DispatcherServlet:doService

	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (logger.isDebugEnabled()) {
			String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
			logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
					" processing " + request.getMethod() + " request for [" + getRequestUri(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 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);
		}

		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);
				}
			}
		}
	}

doService中对于请求做了一些预处理之后调用了doDispatch,听名字也觉得是我们要找的方法(滑稽)
DispatcherServlet: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 = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					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);
				}
			}
		}
	}

这个方法的功能很强大,涉及了从接受请求到渲染成视图返回的全部过程,我们只看我们关心的部分:mappedHandler = getHandler(processedRequest);
DispatcherServlet:getHandler

	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping hm : this.handlerMappings) {
				if (logger.isTraceEnabled()) {
					logger.trace(
							"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
				}
				HandlerExecutionChain handler = hm.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

大致的逻辑就是遍历DispatcherServlet维护的HandlerMappings集合,调用它们的getHandler方法,最后将第一个获得的HandlerExecutionChain返回。接下来就是以doDispatch为入口先执行HandlerExecutionChain中的过滤器链的preHandler方法,接着执行Handler方法(这里使用了适配器模式),最终再依次执行过滤器链中每一个过滤器的postHandler方法,将得到的结果交给视图解析器渲染。

HandlerAdapter

HandlerAdapter是Spring MVC定义的一个接口,它定义了这样一个抽象方法

	@Nullable
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

我们以我们最熟悉的RequestMappingHandlerAdapter为例子说明一些这一系列类的设计思路。

对于RequestMapping这个注解我们都不陌生,而RequestMappingHandlerAdapter正是对这个注解注解的控制器方法的一个适配。我们看看RequestMappingHandlerAdapter的继承关系
Spring源码分析——SpringMVC实现_第9张图片

RequestMappingHandlerAdapter继承了AbstractHandlerMethodAdapter,而对于handler方法的实现也继承于它
AbstractHandlerMethodAdapter:handler

	@Override
	@Nullable
	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return handleInternal(request, response, (HandlerMethod) handler);
	}

而handleInternal这个抽象方法是由RequestMappingHandlerAdapter自身实现的
RequestMappingHandlerAdapter:handleInternal

	@Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ModelAndView mav;
		checkRequest(request);

		// Execute invokeHandlerMethod in synchronized block if required.
		if (this.synchronizeOnSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				Object mutex = WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					mav = invokeHandlerMethod(request, response, handlerMethod);
				}
			}
			else {
				// No HttpSession available -> no mutex necessary
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
			// No synchronization on session demanded at all...
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}

		if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
			if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
				applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
			}
			else {
				prepareResponse(response);
			}
		}

		return mav;
	}

mav = invokeHandlerMethod(request, response, handlerMethod);这句代码说明了最终对于controller方法的调用是通过反射实现的。

以上只是分析了HandlerAdapter系列类的基本设计思路,事实上光是RequestMappingHandlerAdapter这一个类的设计就有许多细节。今后有空了将会针对这一块进行详细的分析。

那么到这里对于Spring MVC对于请求分发的过程就分析完毕了。事实上在doDispatch中做了许多事情,我们下一节要说的视图的呈现也是在在这个方法中完成的。

MVC视图的呈现

在doDispatch中获取到了请求的结果后调用了这个方法
DispatcherServlet: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.isDebugEnabled()) {
				logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
						"': assuming HandlerAdapter completed request handling");
			}
		}

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

		if (mappedHandler != null) {
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}

其中对于render方法的调用就是视图渲染的如口render(mv, request, response);
DispatcherServlet:Render

	/**
	 * Render the given ModelAndView.
	 * 

This is the last stage in handling a request. It may involve resolving the view by name. * @param mv the ModelAndView to render * @param request current HTTP servlet request * @param response current HTTP servlet response * @throws ServletException if view is missing or cannot be resolved * @throws Exception if there's a problem rendering the view */ 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.isDebugEnabled()) { logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'"); } 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 + "] in DispatcherServlet with name '" + getServletName() + "'", ex); } throw ex; } }

从方法的javadoc上也很容易知道方法的实现目的,至于视图的具体渲染过程就交给各位亲爱的读者自己分析了(我才不会说劳资写不动了呢)

总结

没有总结(雾)。想了一下也没有什么好说的,只能说又一次被源码作者的精妙设计给惊艳了吧233。空闲的时间想来也不多了,接下的几天争取把数据库与事务的相关源码分析写完完结这个系列,然而懒是原罪(逃)

你可能感兴趣的:(spring,源码分析)