ssm-SpringMVC功能实现分析

web.xml启动流程

ssm整合

一. web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>MyBatis_06_ssm</display-name>
  
  <!--Spring配置: needed for ContextLoaderListener -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationContext.xml</param-value>
	</context-param>
 
	<!-- Bootstraps the root web application context before servlet initialization -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	
	<!-- SpringMVC配置 -->
	<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
	<servlet>
		<servlet-name>spring</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
		<!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation, springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" -->
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:spring/springmvc.xml</param-value>
		</init-param>
	</servlet>
 
	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>spring</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
  
</web-app>

二.ContextLoaderListener

我们首先分析web.xml中配置的ContextLoaderListener,他的功能有哪些?

当使用方式的时候,我们直接将Spring配置信息作为参数传入Spring容器。

ApplicationContext context=new ClassPathXmlApplicationContext("cd.xml");

①但是在web下,我们需要与Web环境相互结合,通常的办法就是将context-param的方式注册并使用ContextLoaderListener进行监听。

ContextLoaderListener的作用在启动Web容器的时候,自动装配ApplicationContext的配置信息,因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器的时候会默认执行它实现的方法,使用这个接口,开发者能够为客户端请求提供服务之前向ServletContext域对象中添加任何对象,这个对象在ServletContext在启动的时候被初始化,然后在ServletContext整个运行期间都是可见的

详细看 web.xml启动流程
在这里插入图片描述

每一个Web应用都有一个ServletContext与之关联。ServletContext对象在应用启动的时候被创建,在应用关闭的时候被销毁。ServletContext在全局范围内有效,类似于应用中的全局变量。

在ServletContextListener中核心逻辑便是初始化WebApplicationContext实例并放到ServletContext中。

2.1 ServletContextListener的使用

1.自定义ServletContextListener


public class MyServletContextListener implements ServletContextListener {
    private ServletContext context=null;
    @Override
    //该方法在ServletContext启动之后被调用,准备好处理客户端请求
    public void contextInitialized(ServletContextEvent sce) {
        //获取ServletContext
        this.context=sce.getServletContext();
        //设置自定义属性 (对应web.xml的context-param属性)      
        context.setAttribute("test","this test");
    }

    @Override
    //该方法在ServletContext关闭之后被调用
    public void contextDestroyed(ServletContextEvent sce) {
        this.context=null;
    }
}

2.在web.xml中测试

<listener>
	<listener-class>com.lv.listener.MyServletContextListener</listener-class>
</listener>

2.2 分析ContextLoaderListener

/**
	 * Initialize the root web application context.
	 * 初始化根Web应用程序上下文
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}

在这里面我们会用到一个常用类WebApplicationContext,WebApplicationContext继承ApplicationContext,在ApplicationContext的基础上有追加了一些web的操作及属性,类似我们使用ClassPathXmlApplicationContext类型提供的功能。


//使用构造时提供的应用程序上下文,或者根据“ contextClass ”和“ contextConfigLocation ”
//上下文参数创建一个新的给定Servlet上下文的Spring Web应用程序上下文
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			//web.xml中存在多次ContextLoader的定义
			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) {
				//初始化context
				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中		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;
		}
	}

initWebApplicationContext方法主要体现创建WebApplicationContext实例的一个功能框架

  1. WebApplicationContext存在性验证
    在配置中只允许声明一次ServletContextListener,多次声明会扰乱Spring的执行逻辑,所以在这首先做的就是对此验证,在Spring中如果创建 WebApplicationContext实例会记录在ServletContext中方便全局使用,使用的key为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,所以验证的方式就是查看对应的key中是否有值
  2. 创建 WebApplicationContext实例,如果通过验证,则调用createWebApplicationContext方法创建WebApplicationContext

/*
实例化此加载程序的根WebApplicationContext,可以是默认上下文类,也可以是自定义上下文类(如果已指定)。
此实现期望自定义上下文能够实现ConfigurableWebApplicationContext接口。 可以在子类中覆盖。
此外, customizeContext被前刷新的背景下,让子类来进行自定义修改的情况下调用
*/
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);
	}




//返回要使用的WebApplicationContext实现类,如果指定,则为默认XmlWebApplicationContext或自定义上下文类
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);
			}
		}
	}

在这里插入图片描述

根据静态代码块

在这里插入图片描述
在当前类ContextLoader同目录下有ContextLoader.properties属性文件

两种方式找到它
在这里插入图片描述
在这里插入图片描述

打开发现

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

综上,在初始化的过程中,程序会先读取ContextLoader.properties文件,并根据其中的配置提前将要实现的WebApplicationContext接口的实现类,并根据这个实现类通过反射的方式进行实例的创建

  1. 将实例记录在servletContext中
  2. 映射当前的类加载器与创建的实例到全局变量currentContextPerThread中

在这里插入图片描述

三.DispatcherServlet

在Spring中,ContextLodaerListener只是辅助功能,用于创建WebApplicationContext类型的实例,而真正的逻辑实现其实在DispatcherServlet中进行的,DispatcherServlet是实现servlet接口的实现类。

servlet是一个Java编写的程序,此程序是基于HTTP协议的,在服务器端运行(如Tomcat),是按照servlet规范编写的一个Java类。主要是处理客户端的请求并将其结果发送到客户端。servlet生命周期是由servlet的容器来控制的,它可以分为3个阶段:初始化、运行和销毁。

  1. 初始化阶段

    • servlet容器加载servlet类,把servlet类的.class文件中的数据读取到内存
    • servlet容器创建一个ServletConfig对象。ServletConfig对象包含了servlet的初始化配置信息
    • servlet容器创建一个servlet对象
    • servlet容器调用servlet对象的init方法进行初始化。
  2. 运行阶段
    当servlet容器接收到一个请求时,servlet容器会针对这个请求创建servletRequest和servletResponse对象,然后调用service方法。并将这两个参数传递个service方法。service方法通过servletRequest对象获得请求信息。并处理该请求。在通过servletResponse对象生成这个请求的响应结果。然后销毁这两个对象。不管时get还是post,最终的请求都交给service这个方法

  3. 销毁阶段
    当Web应用被终止时,servlet容器会先调用servlet对象的destroy方法,然后在销毁servlet对象,同时也会销毁与servlet对象相关的servletConfig对象,我们可以把一些资源释放交给destroy方法处理。

3.1 DispatcherServlet的初始化

servlet初始化会调用其init方法,我们首先要检查DispatcherServlet是否重写了init方法。
在它的父类HttpServletBean中找到

/**
	 * Map config parameters onto bean properties of this servlet, and
	 * invoke subclass initialization.
	 * 将配置参数映射到该servlet的bean属性上,并调用子类初始化
	 * @throws ServletException if bean properties are invalid (or required
	 * properties are missing), or if subclass initialization fails.
	 */
	@Override
	public final void init() throws ServletException {
		
		// Set bean properties from init parameters.
		//解析init-param并封装到pvs中
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				//将当前这个servlet类转换成一个BeanWrapper,从而可以使用Spring的方式来对init-param的值进行注入
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				//注册自定义属性编辑器,一旦遇到Resource类型的属性将会使用ResourceEditor进行解析
				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();
	}

DispatcherServlet的初始化过程主要通过将当前的servlet类型实例转换为BeanWrapper类型实例,以便使用Spring中提供的注入功能进行对应的属性注入。如:contextAttribute、contextClass、nameSpace、contextConfigLocation等,都可以在web.xml文件中以初始化参数的方式配置在servlet的声明中

1、封装及验证初始化参数

ServletConfigPropertyValues除了封装属性外还有对属性的验证功能

/**
		 * Create new ServletConfigPropertyValues.
		 * @param config the ServletConfig we'll use to take PropertyValues from
		 * @param requiredProperties set of property names we need, where
		 * we can't accept default values
		 * @throws ServletException if any required properties are missing
		 */
		public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
				throws ServletException {

			Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
					new HashSet<>(requiredProperties) : null);

			Enumeration<String> paramNames = config.getInitParameterNames();
			while (paramNames.hasMoreElements()) {
				String property = paramNames.nextElement();
				Object value = config.getInitParameter(property);
				addPropertyValue(new PropertyValue(property, value));
				if (missingProps != null) {
					missingProps.remove(property);
				}
			}

			// Fail if we are still missing properties.
			if (!CollectionUtils.isEmpty(missingProps)) {
				throw new ServletException(
						"Initialization from ServletConfig for servlet '" + config.getServletName() +
						"' failed; the following required properties were missing: " +
						StringUtils.collectionToDelimitedString(missingProps, ", "));
			}
		}

封装属性主要是对初始化的参数进行封装,也就是servlet中配置的<init-param>中配置的封装。当然,用户可以通过对requireProperties参数的初始化来强制验证某些属性的必要性,这样,在属性封装的过程中,一旦检测到requireProperties中的属性没有指定初始值,就会抛出异常。

2、将当前servlet实例转化成BeanWrapper实例
PropertyAccessorFactory.forBeanPropertyAccess(this);是Spring中提供的工具方法,主要将指定实例转换成可以处理的BeanWrapper类型的实例。

3、注册相对于Resource的属性编辑器
属性编辑器,我们在上文中已经介绍并分析过其原理,这里使用属性编辑器的目的是在对当前实例(DispatcherServlet)属性注入过程中一旦遇到Resource类型的属性就会使用ResourceEditor去解析。

4、属性注入
BeanWrapper为Spring中的方法,支持Spring的自动注入。

5、servletBean初始化
在ContextLodaerListener加载的时候已经创建了WebApplicationContext实例,而在这个函数中最重要的就是对这个实例进行进一步补充初始化。父类FrameworkServlet覆盖了HttpServletBean中的initServletBean函数。
在这里插入图片描述

3.2 WebApplicationContext的初始化

/**
	 * Initialize and publish the WebApplicationContext for this servlet.
	 * <p>Delegates to {@link #createWebApplicationContext} for actual creation
	 * of the context. Can be overridden in subclasses.
	 * @return the WebApplicationContext instance
	 * @see #FrameworkServlet(WebApplicationContext)
	 * @see #setContextClass
	 * @see #setContextConfigLocation
		初始化并发布此Servlet的WebApplicationContext。
		代表createWebApplicationContext实际创建上下文。 可以在子类中覆盖
	 */
	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
			//context实例在构造函数中被注入
			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
			
			//根据contextAttribute属性加载WebApplicationContext
			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;
	}

1.寻找或创建对应的WebApplicationContext实例

  1. 通过构造函数的注入进行初始化
 当进入initWebApplicationContext函数后通过判断this.webApplicationContext != null后,
 便可以确定this.webApplicationContext 是否是通过构造器函数进行初始化。可能会有疑问,在
 initServletBean()函数中明明把创建好的实例记录在this.webApplicationContext 中
 this.webApplicationContext = initWebApplicationContext();
 怎么判断这个参数是通过构造函数初始化,而不是通过上一次函数返回值初始化?
  在Web中包含SpringWeb的核心逻辑的DispatcherServlet只可以被声明一次,在Spring中已经存在
  验证,所以这就是确保了如果this.webApplicationContext!=null,则可以直接判定this.webApplicationContext
  已经通过构造函数初始化。
 
  1. 通过contextAttribute进行初始化
通过在web.xml中配置的servlet参数contextAttribute来查找ServeltConetx中对应的属性,默认为
WebApplicationContext.class.getName()+".ROOT"

也就是在ContextLoaderListener加载时会创建WebApplicationContext实例,并将实例以WebApplicationContext.class.getName()+".ROOT"为key放入ServletContext中,可以重写初始化逻辑使用自己创建的WebApplicationContext,并在servlet的配置中通过初始化参数contextAttribute指定key。

  1. 重新创建WebApplicationContext实例

在这里插入图片描述

2.configureAndRefreshWebApplicationContext

无论是构造器注入还是单独创建,都会调用configureAndRefreshWebApplicationContext来对已经创建的WebApplicationContext实例进行配置及刷新

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);
		//加载配置文件及整合parent到wac
		wac.refresh();
	}

无论调用如何变化,只要使用ApplicationContext所提供的功能都免不了使用公共父类AbstractApplicationContext提供的refresh()进行配置文件的加载。

3.刷新

onRefresh()是FrameworkServlet类中提供的方法,在子类DispatcherServlet中进行重写,主要刷新Spring及Web功能实现中所必须的使用的全局变量。

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




/**
	 * Initialize the strategy objects that this servlet uses.
	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
	 * 初始化此servlet使用的策略对象。
		可以在子类中重写以初始化其他策略对象
	 */
	protected void initStrategies(ApplicationContext context) {1.
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

1.初始化MultipartResolve

在Spring中,MultipartResolve主要用来处理文件上传。默认情况下,Spring是没有multipart处理的,
如果使用Spring的multipart则需要在Web应用上下文中添加multipart解析器。这样每个请求就会被检查
是否包含multipart,如果请求中包含multipart,那么上下文中定义的MultipartResolve就会解析它,
这样的请求中的multipart属性就会像其他属性一样被处理

使用方式:

  <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 设置文件上传的最大字节数       -->
        <property name="maxInMemorySize" value="10000"></property>
    </bean>

2.初始化LocaleResolver

在Spring的国际化配置中有3种使用方式

  1. 基于URL参数的配置

    通过URL参数来控制国际化,比如在页面上加入<a href ="?locale=zh_CN">简体中文</a>
    来控制使用国际化参数。而这个功能就是AcceptHeaderLocalResolver,默认的参数名为locale,
    注意大小写。里面放的就是你提交的参数,比如en_US、zh_CN之类的
    

配置如下:

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver">
</bean>
  1. 基于session的配置

    它通过检验用户会话中预置性来解析区域。最常用的是根据用户本次会话过程中的语言设定
    决定语言种类(例如:登录时选择语言种类,则此次登录周期内统一使用此语言),如果会话属性不存在,
    它会根据accept-languageHTTP头部确定默认区域。	
    

配置如下:

<bean id="localeResolver"  class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
</bean>
  1. 基于cookie的配置

    从CookieLocaleResolver用于通过浏览器的cookie设置取得Locale对象。
    这种策略在应用程序不支持会话或状态必须保存在客户端时有用
    

配置如下:

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
</bean>

对于这三种情况,提取配置文件中设置的LocaleResolver来初始化DispatcherServlet中的localeResolver属性
在这里插入图片描述

3.初始化ThemeResolver

在Web开发中经常会遇到通过主题Theme来控制网页风格,这将进一步改善用户体验,一个主题就是一组静态资源(比如样式和图片),它们可以影响程序的视觉效果

  1. 主题资源
    org.springframework.ui.context.ThemeSource是Spring中主题资源的接口,Spring的主题需要通过ThemeSource接口实现方主题信息的资源。
    org.springframework.ui.context.support.ResourceBundleThemeSource 是ThemeSource接口的默认实现类
   <bean id="themeSource" class="org.springframework.ui.context.support.ResourceBundleThemeSource">
        <property name="basenamePrefix" value="com.lv"></property>
    </bean>

默认情况在类路径的根目录下查找相应的资源文件,也可以通过basenamePrefix设置

  1. 主题解析器
    不同用户怎么使用不同主题资源呢?
    org.springframework.web.servlet.ThemeResolver是主题解析器的接口
    它常用的子类有三个。
    FixedThemeResolver:用于选择一个固定的主题
    <bean id=“themeResolver” class=“org.springframework.web.servlet.theme.FixedThemeResolver”>
    <property name=“defaultThemeName>” value=“summer”></property>
    </bean>
    以上作用设置主题文件summer.properties,整个项目不变
    CookieThemeResolver:以Cookie的形式存放在客户端
    <bean id=“themeResolver” class=“org.springframework.web.servlet.theme.CookieThemeResolver”>
    <property name=“defaultThemeName>” value=“summer”></property>
    </bean>
    SessionThemeResolver:将主题保存在用户放的HTTPSession中
    <bean id=“themeResolver” class=“org.springframework.web.servlet.theme.SessionThemeResolver”>
    <property name=“defaultThemeName>” value=“summer”></property>
    </bean>
    AbstractThemeResolver:是一个抽象类被SessionThemeResolver和FixedThemeResolver继承,用户也可以继承它自定义主题解析器

  2. 拦截器
    如果需要根据用户请求来改变主题,那么Spring实现了一个拦截器----

 <bean id="interceptor" class="org.springframework.web.servlet.theme.ThemeChangeInterceptor">
        <property name="paramName" value="themeName"></property>
    </bean>

设置用户请求参数为themeName,即URL为?themeName=具体的主题名称。此外,还需要在handlerMapping中设置拦截器,必须在HandleMapping中添加拦截器

提取配置文件中设置的ThemeResolver来初始化DispatcherServlet中的themeResolver属性
在这里插入图片描述

4.初始化HandlerMappings(重点)

当客户端发出Request是DispatcherServlet会将Request提交给HandlerMapping,然后HandlerMapping根据WebApplicationContext的配置来回传给DispatcherServlet相应的Controller。

基于SpringMVC的Web应用程序中,我们可以为DispatcherServlet提供多个HadlerMapping供其使用。DispathcerServlet在选择HandlerMapping的过程中,将根据我们指定的一系列HandlerMapping的优先级进行排序,然后优先选择优先级在前的HandlerMapping。

如果找到可用的则使用,否则继续按照优先级寻找,知道找到一个可用的Handler为止。

/**
	 * Initialize the HandlerMappings used by this class.
	 * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
	 * we default to BeanNameUrlHandlerMapping.
	 * 初始化此类使用的HandlerMappings。
		如果在BeanFactory中没有为此名称空间定义HandlerMapping Bean,则默认为			BeanNameUrlHandlerMapping
	 */
	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;
			}
		}
	}

默认情况下,SpringMVC将加载当前系统中所有实现了HandlerMapping接口的bean,如果只希望加载指定的,可以修改web.xml中DispathcerServlet的初始参数<init-param>,将detectAllHandlerMappings的值设置为false

此时将查找名为"handlerMapping"的bean,并作为作为系统中唯一的hadlerMapping。如果没有定义的话,SpringMVC将在DispatcherServlet所在目录下的DispatcherServlet.properties中定义的HandlerMapping的内容加载默认的hadlerMapping(用户没有自定义的)

5.初始化HandlerAdapters

在计算机编程中,适配器模式将一个类的接口适配成用户所期待的。使用适配器可以将接口不兼容而无法在一起工作的类协同工作,做法就是将类自己的接口包裹在一个已存在的类中。

private void initHandlerAdapters(ApplicationContext context) {
		this.handlerAdapters = null;

		if (this.detectAllHandlerAdapters) {
			// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerAdapter> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerAdapters = new ArrayList<>(matchingBeans.values());
				// We keep HandlerAdapters in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerAdapters);
			}
		}
		else {
			try {
				HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
				this.handlerAdapters = Collections.singletonList(ha);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerAdapter later.
			}
		}

		// Ensure we have at least some HandlerAdapters, by registering
		// default HandlerAdapters if no other adapters are found.
		if (this.handlerAdapters == null) {
			this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
			if (logger.isTraceEnabled()) {
				logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}
	}

同样在初始化的过程中涉及了一个变量detctAllHandlerAdapters,它的作用和detctAllHandlerMappings类似,只不过作用对象为handlerAdapter。亦可以通过<init-param>强制系统只加载bean name为"handlerAdapter"的handlerAdapter。

如果没找到对应的bean,会尝试加载默认的适配器
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

会根据当前路径的DispatcherServlet.properties来初始化本身
。
作为总控制器的派遣器servlet通过处理器映射得到处理后,会轮询处理器适配器模块,查找能够处理当前HTTP请求的处理器适配器的实现,处理器适配器模块根据处理器映射返回的处理器类型。

  1. HTTP请求处理器适配器(HttpRequestHandlerAdapter)
    HTTP请求处理器适配器仅仅支持对HTTP请求处理器的适配。它简单的将HTTP请求对象和响应对象传递给HTTP请求处理器的实现,不需要返回值。主要应用在基于HTTP的远程调用的实现上。

  2. 简单控制器处理器适配器(SimpleControllerHandlerAdapter)
    这个实现类将HTTP请求适配到一个控制器实现进行处理。这里控制器的实现是一个简单的控制器接口的实现。这个适配器被设计成一个框架类的实现,不需要被改写,客户化的业务逻辑通常是在控制器接口的实现类中实现的

  3. Request注解处理器适配器(RequestMappingHandlerAdapter)
    RequestMappingHandlerAdapter实现了HandlerAdapter接口,顾名思义,表示handler的adapter,这里的handler指的是Spring处理具体请求的某个Controller的方法,也就是说HandlerAdapter指的是将当前请求适配到某个Handler的处理器。RequestMappingHandlerAdapter是HandlerAdapter的一个具体实现,主要用于将某个请求适配给@RequestMapping类型的Handler处理

  4. HandlerFunctionAdapter
    这块是webflux的知识

在这里插入图片描述

6.初始化HandlerExceptionResolvers

基于HandlerExceptionResolver接口的异常处理,使用这种方式只需要实现resolveException方法,该方法返回一个ModelAndView对象,在方法内部对异常的类型判断,然后尝试生成对应的ModelAndView对象,如果该方法返回null,则Spring会继续寻找其他的是实现了HandlerExceptionResolver接口的bean,会搜索注册在其他环境中的实现了HandlerExceptionResolver接口的bean,逐个执行,直到返回一个ModelAndView对象。

7.初始化RequestToViewNameTranslator

当Controller处理器方法没有一个View对象或逻辑视图名称,并且该方法中没有直接往response的输出流里面写数据的时候,Spring就会采用约定好的方式提供一个逻辑视图名。通过RequestToViewNameTranslator接口的getViewName方法来实现的,Spring为我们提供了一个默认的实现DefaultRequestToViewNameTranslator。

  • prefix:前缀,表示约定好的视图名称需要加上的前缀,默认是空串
  • suffix:后缀,表示约定好的视图名称需要加上的后缀,默认是空串
  • separator:分隔符,默认是斜杠"/"
  • stripLeadingSlash:如果首字符是分隔符,是否要去除,默认是true
  • stripTrailingSlash:如果最后一个字符是分隔符,是否要去除,默认是true
  • stripExtension: 如果请求路径包含扩展名是否要去除,默认是true
  • urlDecode:是否需要对URL解码,默认是true

当Controller处理器方法没有返回逻辑视图名称时,DefaultRequestToViewNameTranslator会获取收到请求的URI,然后根据提供的属性做做一些改造,把改造之后的结果作为视图名称返回。

以请求/test/index.html为例,

  • prefix和suffix都存在,其他为默认值,返回的逻辑视图为suffixtest/indexsuffix。
  • stripLeadingSlash和stripTrailingSlash都为false,其他默认,这时候对应的逻辑视图名称是/product/index.html。
  • 都是默认值,返回product/index

8.初始化ViewResolvers

在SpringMVC中,当Controller将请求处理结果放到ModelAndView中以后,DispatcherServlet会根据ModelAndView选择合适的View视图进行渲染,ModelAndView中定义了resolveViewName方法,根据viewName创建合适类型的View实现。

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix"></property>
        <property name="suffix>"></property>
    </bean>

9.初始化FlashMapManager

SpringMVC Flash attributes 提供了一个请求存储属性,可供其他请求使用。在使用重定向的时候非常必要,例如 Post/Redirect/Get模式。Flash attributes在重定向之前暂存,以便重定向之后还能使用,并立即删除。

SpringMVC有两个重要的抽象来支持flash attributes。 FlashMap用于保持flash attributes,而FlashMapManager用于存储、检索、管理FlashMap实例。

flash attribute支持默认开启(on)并不需要显示启用,它永远不会导致HTTP Session的创建。这两个实例可以通过静态方法RequestContextUtils从Spring MVC的任何位置访问。

四、DispatcherServlet的逻辑处理

首先查看父类FrameworkServlet的doGet和doPost方法

在这里插入图片描述
在这里插入图片描述

  1. 为了保证当前线程的LocaleContext以及RequestAttributes可以在当前请求后还能恢复,提取当前线程的两个属性
  2. 根据当前request创建对应的LocaleContext以及RequestAttributes,并绑定到当前线程。
  3. 委托doService方法进一步处理
  4. 请求处理结束后恢复线程到原始状态
  5. 请求处理结束后无论成功与否发布事件通知。

在这里插入图片描述
做了一些准备工作,比如将localeResolver、themeResolver等设置在request属性中

/*
处理实际的分派给处理程序。
该处理程序将通过依次应用servlet的HandlerMappings来获得。 通过查询Servlet的已安装HandlerAdapter来查找支持该处理程序类的第一个HandlerAdapter,从而获得HandlerAdapter。
所有HTTP方法都由该方法处理。 由HandlerAdapters或处理程序本身来决定可接受的方法*/
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 {
				//如果是MultipartContext类型的request则转换request为MultipartHttpServletRequest类型的request
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				//根据request信息寻找对应的Handler
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					//没有找到通过response反馈错误信息
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				//根据当前的handler寻找对应的HandlerAdapter
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				//当前handler支持last-modified头处理
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(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.
				//激活handler并返回视图
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				//视图名称转换应用于需要添加的前缀后缀情况
				applyDefaultViewName(processedRequest, mv);
				//应用所有拦截器的postHandle方法
				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);
				}
			}
		}
	}

4.1 MultipartContext类型的request处理

对于请求的处理,Spring首先考虑的是对于Multipart的处理,如果是MultipartContext类型的request,则转换request为MultipartHttpServletRequest类型的request。

/**
	 * Convert the request into a multipart request, and make multipart resolver available.
	 * <p>If no multipart resolver is set, simply use the existing request.
	 * 将请求转换为多部分请求,并使多部分解析器可用。
		如果未设置多部分解析器,则只需使用现有请求
	 */
	protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
		if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
			if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
				if (DispatcherType.REQUEST.equals(request.getDispatcherType())) {
					logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
				}
			}
			else if (hasMultipartException(request)) {
				logger.debug("Multipart resolution previously failed for current request - " +
						"skipping re-resolution for undisturbed error rendering");
			}
			else {
				try {
					return this.multipartResolver.resolveMultipart(request);
				}
				catch (MultipartException ex) {
					if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
						logger.debug("Multipart resolution failed for error dispatch", ex);
						// Keep processing error dispatch with regular request handle below
					}
					else {
						throw ex;
					}
				}
			}
		}
		// If not returned before: return original request.
		return request;
	}
4.2 根据request信息寻找对应的Handler
    <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/userlist.html">userController</prop>
            </props>
        </property>
    </bean>

在Spring加载的过程中,Spring会将类型为SimpleUrlHandlerMapping的实例加载到this.handlerMappings中,根据request提取对应的Handler,无非就是提取当前实例中的userController,但是userController继承自AbstractController类型的实例,与HandlerExecutionChain无关联,那么是怎么封装的呢?
在这里插入图片描述
在系统启动时Spring会将所有的映射类型的bean注册到this.handlerMappings变量中,所有此函数就是遍历所有的HandlerMapping,并调用getHandler进行封装处理,以SimpleUrlHandlerMapping
为例
在这里插入图片描述
首先使用getHandlerInternal方法根据request信息获取对应的Handler,如果以SimpleUrlHandlerMapping为例分析,我们推断根据URL找到匹配的Controller并返回,没有找到对应的Controller则会尝试找默认处理器,当查找的controller为String类型时,那就意味着返回的是配置的bean名称,需要根据bean名称查找对应的bean,最后,还要通过getHandlerExecutionChain方法对返回的Handler进行封装,以保证满足返回类型的匹配。

4.2.1 根据request查找对应的Handler

	/**
	 * Look up a handler for the URL path of the given request.
	 * @param request current HTTP request
	 * @return the handler instance, or {@code null} if none found
	 * 查找给定请求的URL路径的处理程序
	 */
	@Override
	@Nullable
	protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
		//解析用于匹配的url有效路径
		String lookupPath = initLookupPath(request);
		Object handler;
		if (usesPathPatterns()) {
			//根据路径寻找Handler
			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, '/')) {
				//如果请求路径仅仅是“/”,那么使用RootHandler进行处理
				rawHandler = getRootHandler();
			}
			if (rawHandler == null) {
				//无法找到对应的handler
				rawHandler = getDefaultHandler();
			}
			if (rawHandler != null) {
				// Bean name or resolved handler?
				//根据beanName获取对应的bean
				if (rawHandler instanceof String) {
					String handlerName = (String) rawHandler;
					rawHandler = obtainApplicationContext().getBean(handlerName);
				}
				//模板方法
				validateHandler(rawHandler, request);
				handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
			}
		}
		return handler;
	}

在这里插入图片描述
在这里插入图片描述

链处理机制,是Spring中非常常用的处理方式,是Aop中重要的组成部分,可以更方便的对目标对象进行扩展及拦截。

4.2.2 加入拦截器到执行链

这个方法主要目的是将配置中的对应拦截器加入到执行链中以保证这个拦截器可以有效的作用于目标对象。
在这里插入图片描述

/* 
	 * 为给定的处理程序(包括适用的拦截器)构建一个HandlerExecutionChain 。
	 * 默认实现使用给定的处理程序,处理程序映射的公共拦截器以及与当前请求URL匹配的任何MappedInterceptors来构建标准的HandlerExecutionChain 。 拦截器按照注册时的顺序添加。 子类可以重写此方法,以扩展/重新排列拦截器的列表。
	 * 注意:传入的处理程序对象可以是原始处理程序,也可以是预建的HandlerExecutionChain 。 此方法应显式处理这两种情况,要么构建新的HandlerExecutionChain ,要么扩展现有链。
	 * 为了仅在自定义子类中添加拦截器,请考虑调用super.getHandlerExecutionChain(handler, request)并在返回的链对象上调用HandlerExecutionChain.addInterceptor 。
	 */
	protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
			if (interceptor instanceof MappedInterceptor) {
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
				if (mappedInterceptor.matches(request)) {
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				}
			}
			else {
				chain.addInterceptor(interceptor);
			}
		}
		return chain;
	}

4.3 没找到对应的Handler的错误处理

如果没有找到对应的Handler(默认情况下开发人员设置默认的Handler),通过response向用户返回错误信息。
在这里插入图片描述

4.4 根据当前Handler寻找对应的HandlerAdapter

在这里插入图片描述

获取适配器就是遍历所有的适配器选择合适的适配器返回,而某个适配器是否适合用于当前Handler逻辑封装在具体适配器中,

在这里插入图片描述
SimpleServletHandlerAdapter就是处理普通的Web请求的,而且对于SpringMVC来说,我们会把逻辑封装到Controller子类中

4.5缓存处理

Last-Modified缓存机制。

1.客户端第一次输入URL时,服务器会返回内容和状态码200,表示成功,同时会添加一个"Last-Modified"响应头,表示此文件在服务器上最后更新时间。

2.客户端第二次请求这个URL时,客户端会向服务器发送请求"If-Modified-Since",咨询服务器该时间之后当前请求内容是否被修改,如果没有变化,则自动返回HTTP304状态码(只要响应头,内容为空,这样就节省了宽带)。

4.6 HandlerInterceptor

Servlet API定义的servlet过滤器可以在servlet处理每个Web请求前后分别对它进行前置处理和后置处理。此外,有些时候,你可能只想处理由某些SpringMVC处理程序处理的Web请求,并在这些处理程序返回的模型被传递做到视图前做一些处理。

SpringMVC允许你通过处理拦截Web请求,进行前置处理和后置处理。处理拦截是在Spring的Web应用程序上下文中配置的,因为它们可以利用各种容器特性,并引用容器中声明的任何bean。处理拦截都必须实现HandlerInterceptor接口,有三个方法preHandle,postHandle,afterCompletion,第一个和第二个分别是在处理程求请求之前和之后被调用的。
第二个还可以返回ModelAndView对象,因此可以在它里面操作模型属性,最后一个方法在所有请求处理完成之后被调用(如视图呈现之后)

4.7 逻辑处理

在这里插入图片描述
对于逻辑处理其实就是通过适配器中转调用Handler并返回视图的

对SimpleControllerHandlerAdapter分析
在这里插入图片描述
在这里插入图片描述

4.8 异常视图处理

在这里插入图片描述

在这里插入图片描述

4.9 根据视图跳转页面

无论成功与否都要给用户返回信息
在这里插入图片描述
在这里插入图片描述

  1. 解析视图名resolveViewName
    DispatcherServlet会根据ModelAndView选择合适的视图名进行渲染,这个功能是在resolveViewName方法中实现的
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
			Locale locale, HttpServletRequest request) throws Exception {

		if (this.viewResolvers != null) {
			for (ViewResolver viewResolver : this.viewResolvers) {
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
					return view;
				}
			}
		}
		return null;
	}

我们以InternalResourceViewResolver为例,其中resolverViewName的实现在其父类AbstractCachingViewResolver

public View resolveViewName(String viewName, Locale locale) throws Exception {
		if (!isCache()) {
			//不在缓存中的情况下创建
			return createView(viewName, locale);
		}
		else {
			//否则直接在缓存中提取
			Object cacheKey = getCacheKey(viewName, locale);
			View view = this.viewAccessCache.get(cacheKey);
			if (view == null) {
				synchronized (this.viewCreationCache) {
					view = this.viewCreationCache.get(cacheKey);
					if (view == null) {
						// Ask the subclass to create the View object.
						view = createView(viewName, locale);
						if (view == null && this.cacheUnresolved) {
							view = UNRESOLVED_VIEW;
						}
						if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
							this.viewAccessCache.put(cacheKey, view);
							this.viewCreationCache.put(cacheKey, view);
						}
					}
				}
			}
			else {
				if (logger.isTraceEnabled()) {
					logger.trace(formatKey(cacheKey) + "served from cache");
				}
			}
			return (view != UNRESOLVED_VIEW ? view : null);
		}
	}

在父类 UrlBasedViewResolver重写了createView


	/**
	 * Overridden to implement check for "redirect:" prefix.
	 * <p>Not possible in {@code loadView}, since overridden
	 * {@code loadView} versions in subclasses might rely on the
	 * superclass always creating instances of the required view class.
	 * @see #loadView
	 * @see #requiredViewClass
	 * 重写以实现对“ redirect:”前缀的检查。
	 * 在loadView不可能的,因为子类中重写的loadView版本可能依赖于超类始终创建所需视图类的实例。
	 */
	@Override
	protected View createView(String viewName, Locale locale) throws Exception {
		// If this resolver is not supposed to handle the given view,
		// return null to pass on to the next resolver in the chain.
		//如果当前解析器不支持当前解析器如ViewName为空的情况
		if (!canHandle(viewName, locale)) {
			return null;
		}

		// Check for special "redirect:" prefix.
		//处理前缀为redirect:xx的情况
		if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
			String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
			RedirectView view = new RedirectView(redirectUrl,
					isRedirectContextRelative(), isRedirectHttp10Compatible());
			String[] hosts = getRedirectHosts();
			if (hosts != null) {
				view.setHosts(hosts);
			}
			return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
		}

		// Check for special "forward:" prefix.
		//处理前缀为forward:xx的情况
		if (viewName.startsWith(FORWARD_URL_PREFIX)) {
			String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
			InternalResourceView view = new InternalResourceView(forwardUrl);
			return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
		}

		// Else fall back to superclass implementation: calling loadView.
		return super.createView(viewName, locale);
	}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 页面跳转
    在这里插入图片描述

在这里插入图片描述
当通过viewName解析到对应的View后,就可以进一步地处理跳逻辑了

/**
	 * Prepares the view given the specified model, merging it with static
	 * attributes and a RequestContext attribute, if necessary.
	 * Delegates to renderMergedOutputModel for the actual rendering.
	 * 根据给定的模型准备视图,并在必要时将其与静态属性和RequestContext属性合并。 
	 * 委托renderMergedOutputModel进行实际渲染
	 * @see #renderMergedOutputModel
	 */
	@Override
	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);
	}

可以一些属性直接放到ModelAndView中,然后可以通过JSTL语法或request获取,下面的方法就是解析这些属性


	/**
	 * Creates a combined output Map (never {@code null}) that includes dynamic values and static attributes.
	 * Dynamic values take precedence over static attributes.
	 * 创建一个包含动态值和静态属性的组合输出Map(绝不为null )。 动态值优先于静态属性
	 */
	protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model,
			HttpServletRequest request, HttpServletResponse response) {

		@SuppressWarnings("unchecked")
		Map<String, Object> pathVars = (this.exposePathVariables ?
				(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);

		// Consolidate static and dynamic model attributes.
		int size = this.staticAttributes.size();
		size += (model != null ? model.size() : 0);
		size += (pathVars != null ? pathVars.size() : 0);

		Map<String, Object> mergedModel = CollectionUtils.newLinkedHashMap(size);
		mergedModel.putAll(this.staticAttributes);
		if (pathVars != null) {
			mergedModel.putAll(pathVars);
		}
		if (model != null) {
			mergedModel.putAll(model);
		}

		// Expose RequestContext?
		if (this.requestContextAttribute != null) {
			mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
		}

		return mergedModel;
	}

处理页面跳转
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值