本文共 27699 字,大约阅读时间需要 92 分钟。
本文是笔者阅读Spring源码的记录文章,由于本人技术水平有限,在文章中难免出现错误,如有发现,感谢各位指正。在阅读过程中也创建了一些衍生文章,衍生文章的意义是因为自己在看源码的过程中,部分知识点并不了解或者对某些知识点产生了兴趣,所以为了更好的阅读源码,所以开设了衍生篇的文章来更好的对这些知识点进行进一步的学习。
Spring全集目录:
本系列目录如下:
衍生篇目录如下:
在 一文中我们搭建了 SpringMVC 框架,简单分析了一下 ContextLoaderListener。下面我们来看看 DispatcherServlet 的初始化过程。
在web.xml 中,我们还配置了一个Servlet :
springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:applicationContext.xml springmvc / 
SpringMVC 的实现原理是通过 servlet 拦截所有的URL来达到控制的目的。而所使用的Servlet 即是 DispatcherServlet。
简单来说,就是所有的请求(这里说的比较绝对,仅为了表述用,具体拦截多少请求是通过 <url-pattern> 标签配置)都是首先请求到 DispatcherServlet,由 DispatcherServlet 来根据路径等信息转发到不同的处理器,再将结果返回。
我们来看一下 DispatcherServlet 的结构,如下:
 
   可以看到,DispatcherServlet本质上是一个Servlet。在上面讲述 Servlet 生命周期的时候我们说过,Servlet 仅初始化一次,并且调用init 方法进行初始化。Dispatcher 调用的是 HttpServletBean#init 方法,下面我们来具体看看
DispatcherServlet 的初始化过程主要是通过将当前的Servlet 类型实例转换为 BeanWrapper 类型实例,以便于使用Spring中提供注入功能进行对应属性的注入。
同时上面也提到了,在这里面会完成 WebApplicationContext 的具体初始化。
@Override	public final void init() throws ServletException {   		// Set bean properties from init parameters.		// 1. 封装及验证初始化参数		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);		if (!pvs.isEmpty()) {   			try {   				// 2. 将当前 Servlet 实例转化为 BeanWrapper 实例				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);				// 3. 注册于相对于 Resource 的属性编辑器				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));				// 4. 属性注入				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.		// 5. servletBean的初始化		initServletBean();	}   上面的流程大致梳理如下:
封装及验证初始化参数
ServletConfigPropertyValues 除了封装属性外还有对属性验证的功能。 public ServletConfigPropertyValues(ServletConfig config, SetrequiredProperties) throws ServletException { Set missingProps = (!CollectionUtils.isEmpty(requiredProperties) ? new HashSet<>(requiredProperties) : null); // 获取 web.xml 中 DispatcherServlet 配置的 init-params 属性内容 Enumeration paramNames = config.getInitParameterNames(); while (paramNames.hasMoreElements()) { String property = paramNames.nextElement(); Object value = config.getInitParameter(property); // 保存 init-params 属性 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 实例转化为 BeanWrapper 实例
PropertyAccessorFactory.forBeanPropertyAccess 是Spring提供的工具方法,主要用于将指定实例转化为 Spring 中可以处理的 BeanWrapper 类型的实例 注册于相对于 Resource 的属性编辑器
在 对当前实例属性注入过程中一旦遇到 Resource 类型的属性就会使用ResourceEditor 去解析 属性注入
BeanWrapper 为Spring中的方法,支持Spring的自动注入 servletBean的初始化
实际上,在ContextLoaderListener 加载的时候就已经创建了 WebApplicationContext实例,在这里则是对 WebApplicationContext 的进一步的初始化补充。 @Override	protected final void initServletBean() throws ServletException {   		....		long startTime = System.currentTimeMillis();		try {   			// 委托给 initWebApplicationContext 方法来完成进一步的初始化			this.webApplicationContext = initWebApplicationContext();			// 留给子类覆盖操作			initFrameworkServlet();		}		catch (ServletException | RuntimeException ex) {   			logger.error("Context initialization failed", ex);			throw ex;		}		...	}   关于 initWebApplicationContext 的实现是在 FrameworkServlet#initWebApplicationContext 中完成,下面我们就来看看其实现过程。
下面我们看看 FrameworkServlet#initWebApplicationContext 的代码如下:
protected WebApplicationContext initWebApplicationContext() {   		WebApplicationContext rootContext =				WebApplicationContextUtils.getWebApplicationContext(getServletContext());		WebApplicationContext wac = null;		// 1. 如果 webApplicationContext 在构造函数的时候被注入,则 wac != null, 则可以直接使用		if (this.webApplicationContext != null) {   			wac = this.webApplicationContext;			if (wac instanceof ConfigurableWebApplicationContext) {   				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;				if (!cwac.isActive()) {   					if (cwac.getParent() == null) {   						cwac.setParent(rootContext);					}					// 2.刷新上下文环境					configureAndRefreshWebApplicationContext(cwac);				}			}		}		// 3. 如果构造函数并没有注入,则wac为null,根据 contextAttribute 属性加载 WebApplicationContext		if (wac == null) {   			// 根据 contextAttribute 属性加载 WebApplicationContext			wac = findWebApplicationContext();		}		// 4. 如果上面两个方式都没有加载到 WebApplicationContext,则尝试自己加载		if (wac == null) {   			// 自己尝试创建WebApplicationContext			wac = createWebApplicationContext(rootContext);		}		if (!this.refreshEventReceived) {   			synchronized (this.onRefreshMonitor) {   				// 5.刷新				onRefresh(wac);			}		}		if (this.publishContext) {   			// Publish the context as a servlet context attribute.			String attrName = getServletContextAttributeName();			getServletContext().setAttribute(attrName, wac);		}		return wac;	}   其实我们可以看到,第1,3,4步都是在找 WebApplicationContext的实例。第2,5步才是真正的初始化。
结合上面的代码我们可以看到 寻找 WebApplicationContext的实例可以分为三步:
WebApplicationContext。若注入直接使用WebApplicationContext。WebApplicationContext。下面我们来一步一步分析
在调用 initWebApplicationContext 方法时 第一步的判断条件就是 this.webApplicationContext != null。如果this.webApplicationContext != null。则说明 WebApplicationContext 是通过构造注入的方式注入进来,可以直接使用。
this.webApplicationContext 不会是之前初始化留下的值。   即通过 web.xml 文件中配置的servlet 参数 contextAttribute 来查找 ServerContext 中对应的属性。这里可以回忆一下,在ContextLoaderListener 中,已经将创建好的WebApplicationContext 实例保存到了ServletContext 中,其key值默认为 WebApplicationContext.class.getName() + ".ROOT"。不过这个key值是可以更改的。这里即是通过 contextAttribute 作为 key 来尝试去ServletContext中获取WebApplicationContext。contextAttribute 在初始化 DispatcherServlet 的时候可以通过 setContextAttribute 进行设置。默认的contextAttribute 为null。
protected WebApplicationContext findWebApplicationContext() {   		String attrName = getContextAttribute();		if (attrName == null) {   			return null;		}		WebApplicationContext wac =				WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);		if (wac == null) {   			throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");		}		return wac;	}...	public String getContextAttribute() {   		return this.contextAttribute;	}   上面两种方式都没有找到 WebApplicationContext,则就只能重新创建 WebApplicationContext 实例了。
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {   		// 获取 Servlet 初始化参数 contextClass,即获取WebApplicationContext 的具体类型,如果没有配置默认是 XmlWebApplicationContext 类型。		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);		// 获取web.xml 配置的 DispatcherServlet init-params contextConfigLocation 属性		String configLocation = getContextConfigLocation();		if (configLocation != null) {   			wac.setConfigLocation(configLocation);		}		// 配置 WebApplicationContext 实例并刷新		configureAndRefreshWebApplicationContext(wac);		return wac;	}   我们这里额外注意一个方法:configureAndRefreshWebApplicationContext。
无论是通过构造注入还是单独创建,都会调用 configureAndRefreshWebApplicationContext 方法来对已经创建的 WebApplicationContext 实例进行配置及刷新。其实就是刷新Spring
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());			}		}		// 设置一些属性,ServletContext、ServletConfig等		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);		// 这里调用了 AbstractApplicationContext#refresh		wac.refresh();	}   其实关键就再最后一步,调用了bstractApplicationContext#refresh方法,而这个方法正是整个Spring初始化的开始。关于该方法,这里可以简单的理解是完成了Spring容器的功能。
具体的分析(该方法的实现非常复杂,并且本文Spring mvc的分析并没有太大关系,所以如果仅仅了解 mvc 的话,可以暂时不看该方法的具体实现) :
综上之后,WebApplicationContext 已经获取完毕,下面开始进行进一步的初始化。
onRefresh 是 FrameworkServlet 提供的模板方法,供子类实现。我们来看看 DispatcherServlet#onRefresh 的实现内容,主要用于 刷新Spring 在Web功能实现中所必须使用的全局变量。
/**	 * Name of the class path resource (relative to the DispatcherServlet class)	 * that defines DispatcherServlet's default strategy names.	 */	private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";	// 默认策略配置文件	private static final Properties defaultStrategies;			static {   		// Load default strategy implementations from properties file.		// This is currently strictly internal and not meant to be customized		// by application developers.		try {   			// 初始化资源配置文件			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);		}		catch (IOException ex) {   			throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());		}	}	@Override	protected void onRefresh(ApplicationContext context) {   		// 初始化策略		initStrategies(context);	}	/**	 * Initialize the strategy objects that this servlet uses.	 * May be overridden in subclasses in order to initialize further strategy objects.	 */	protected void initStrategies(ApplicationContext context) {   		// 初始化多文件上传解析器		initMultipartResolver(context);		// 初始化国际化解析器		initLocaleResolver(context);		// 初始化主题解析器		initThemeResolver(context);		// 初始化 HandlerMappering		initHandlerMappings(context);		// 初始化 HandlerAdapter		initHandlerAdapters(context);		// 初始化 Handler异常解析器		initHandlerExceptionResolvers(context);		// 初始化RequestToViewNameTranslator		initRequestToViewNameTranslator(context);		// 初始化 视图解析器		initViewResolvers(context);		// 初始化 FlashMapManager 		initFlashMapManager(context);	}
   需要提一句,这里的 defaultStrategies 属性是加载了默认的配置文件 DispatcherServlet.properties。这个文件和 DispatcherServlet 同级,里面记录了各种默认的加载策略。在下面的代码讲解中会知道。
上面初始化了一大堆的东西,下面我们来一句一句分析。
这一步顾名思义,就是配置多文件解析器。在 Spring 中,MultipartResolver 主要用来处理文件上传。默认情况下,Spring是没有 Multipart 处理的。如果想使用 Spring 的 Multipart ,则需要手动注入 Multipart 解析器,即MultipartResolver。这样Spring 会检查每个请求是否包含 Multipart,如果包含,那么 MultipartResolver 就会解析它。一般我们可以注入 CommonsMultipartResolver。
其实现代码也很简单,从容器中获取beanName 为 multipartResolver 解析器,并保存到 DispatcherServlet 中。
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";	private void initMultipartResolver(ApplicationContext context) {   		try {   			this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);			if (logger.isTraceEnabled()) {   				logger.trace("Detected " + this.multipartResolver);			}			else if (logger.isDebugEnabled()) {   				logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());			}		}		catch (NoSuchBeanDefinitionException ex) {   			// Default is no multipart resolver.			this.multipartResolver = null;			if (logger.isTraceEnabled()) {   				logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");			}		}	}   这里是初始化国际化解析器。逻辑和上面基本相同,代码就不再展示不同的是,这里获取的beanName 为 localeResolver。
一般情况下, localeResolver 有三种注入实例
AcceptHeaderLocaleResolver : 基于URL 参数的配置 。他会根据请求的URL后缀来判断国际化场景。比如 : “http://xxx?local=zh_CN”。local参数也可以是en_US。CookieLocaleResolver : 基于Cookie的国际化配置。他是通过浏览器的 Cookies 设置取得Local对象。SessionLocaleResolver :基于 Session 的配置,他通过验证用户会话中预置的属性来解析区域。常用的是根据用户本次会话过程中语言来决定语言种类。如果该会话属性不存在,则会根据 http的 accept-language 请求头确认国际化场景。在Web开发中经常会遇到通过主题Theme 来控制网页风格,这将进一步改善用户体验。简单的说,一个主题就是一组静态资源(比如样式表和图片),他们可以影响应用程序的视觉效果。Spring中的主题功能和国际化功能非常类似。这里就不再过多赘述。
简单说一下三个常用的主题解析器FixedThemeResolver :用于选择一个固定的主题SessionThemeResolver :用于主题保存在用户的http session中CookieThemeResolver :实现用户所选的主题,以cookie的形式存放在客户端的机器上当客户端发出 Request 时, DispatcherServlet 会将 Request 提交给 HandlerMapping,然后HandlerMapping 根据WebApplicationContext的配置来回传给DispatcherServlet相应的Controller。
在基于Spring mvc 的web应用程序中,我们可以为DispatcherServlet 提供多个 HandlerMapping 供其使用。DispatcherServlet 在选用 HandlerMapping 的过程中,将会根据我们所指定的一系列Handler 的优先级进行排序,然后优先使用优先级在前的HandlerMapping。如果当前HandlerMapping能够返回可用的Handler,DispatcherServlet 则是使用当前返回的Handler 进行Web请求的处理,而不再询问其他HandlerMapping,否则DispatcherServlet将按照各个HandlerMapping 的优先级进行询问,知道获取到一个可用的Handler 为止。
其代码如下:
private void initHandlerMappings(ApplicationContext context) {   		this.handlerMappings = null;		// 如果启用所有的HandlerMapping。可以通过这个参数来控制是使用指定的HandlerMapping,还是检索所有的		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 {   			// 如果使用指定的参数,从容器中获取beanName 为 handlerMapping 的HandlerMapping			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.			}		}		// 如果handlerMappings  为null。则使用默认策略指定的HandlerMapping		if (this.handlerMappings == null) {   			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);		}	}       这里看一下 getDefaultStrategies 的实现。
@SuppressWarnings("unchecked")	protected         List          getDefaultStrategies(ApplicationContext context, Class            strategyInterface) {   		// 获取Name		String key = strategyInterface.getName();		// 从配置文件中获取value		String value = defaultStrategies.getProperty(key);		// 获取到value 之后就是对value的处理,添加返回。		if (value != null) {   			String[] classNames = StringUtils.commaDelimitedListToStringArray(value);			List              strategies = new ArrayList<>(classNames.length);			for (String className : classNames) {   				try {   					Class        clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());					Object strategy = createDefaultStrategy(context, clazz);					strategies.add((T) strategy);				}				catch (ClassNotFoundException ex) {   					throw new BeanInitializationException(							"Could not find DispatcherServlet's default strategy class [" + className +							"] for interface [" + key + "]", ex);				}				catch (LinkageError err) {   					throw new BeanInitializationException(							"Unresolvable class definition for DispatcherServlet's default strategy class [" +							className + "] for interface [" + key + "]", err);				}			}			return strategies;		}		else {   			return new LinkedList<>();		}	}                         这里注意:
detectAllHandlerMappings 参数用来 判断是否启用所有的HandlerMapping。可以通过这个参数来控制是使用指定的HandlerMapping,还是检索所有的HandlerMapping。
默认策略指定的 HandlerMapping 是从 DispatcherServlet.properties 配置文件中取出的值,以 org.springframework.web.servlet.HandlerMapping (HandlerMapping 的全路径类名) 作为key所取出的value。
关于几种HandlerMapping,我们这里来简单看看。具体后续开设衍生篇来看
BeanNameUrlHandlerMapping :以beanName 作为key值
RequestMappingHandlerMapping :完成@Controller和@RequestMapping 的解析,并将解析保存。请求发送时与请求路径进行匹配对应找到合适的Handler。RequestMappingHandlerMapping 实现了 InitializingBean 接口,会在afterPropertiesSet 方法中
调用时机:解析@Controller和@RequestMapping注解是在 afterPropertiesSet方法中进行的。匹配调用则是在 DispatcherServlet doDispatch方法中的getHandler中调用了HandlerMapper中的getHandler中的getHandlerInternal方法。
SimpleUrlHandlerMapping :基本逻辑是通过注入SimpleurlHandlerMapping 的mapping属性,mapping key为url, value为handler(beanName)。这里需要注意Controller必须要实现Controller接 口。具体讲解请看衍生篇:
初始化处理器适配器。这里使用了适配器模式来设计。
private void initHandlerAdapters(ApplicationContext context) {   		this.handlerAdapters = null;		// 如果启用所有的HandlerAdapter。可以通过这个参数来控制是使用指定的HandlerMapping,还是检索所有的		if (this.detectAllHandlerAdapters) {   			// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.			// 寻找所有的适配器并排序			Map        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 {   			// 没有启用则从Spring 容器中获取 beanName = handlerAdapter 并且类型是 HandlerAdapter 类型的bean。并			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.		// 如果还没有获取到适配器,则使用默认策略的适配器。从 DispatcherServlet.properties 中获取 org.springframework.web.servlet.HandlerAdapter 为key值的value加载到容器中。		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");			}		}	}       可以看到逻辑和上面的HandlerMapping 基本相同。
这里我们简单介绍一下三个 HandlerAdapter
基于 HandlerExceptionResolver接口的异常处理,使用这种方式只需要实现 org.springframework.web.servlet.HandlerExceptionResolver#resolveException 方法,该方法返回一个 ModelAndView 对象,在方法内部对异常的类型进行判断,然后尝试生成对应的 ModelAndView对象,如果该方法返回了null。则Spring会继续寻找其他的实现了HandlerExceptionResolver 接口的bean,直至找到一个可以返回ModelAndView 的 HandlerExceptionResolver 。
至于代码逻辑,和上面基本相同,这里不再赘述。
private void initHandlerExceptionResolvers(ApplicationContext context) {   		this.handlerExceptionResolvers = null;		if (this.detectAllHandlerExceptionResolvers) {   			// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.			Map        matchingBeans = BeanFactoryUtils					.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);			if (!matchingBeans.isEmpty()) {   				this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());				// We keep HandlerExceptionResolvers in sorted order.				AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);			}		}		else {   			try {   				HandlerExceptionResolver her =						context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);				this.handlerExceptionResolvers = Collections.singletonList(her);			}			catch (NoSuchBeanDefinitionException ex) {   				// Ignore, no HandlerExceptionResolver is fine too.			}		}		// Ensure we have at least some HandlerExceptionResolvers, by registering		// default HandlerExceptionResolvers if no other resolvers are found.		if (this.handlerExceptionResolvers == null) {   			this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);			if (logger.isTraceEnabled()) {   				logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +						"': using default strategies from DispatcherServlet.properties");			}		}	}       当Controller处理器方法没有返回一个View对象或者逻辑视图名称,并且在该方法中没有直接放Response 的输出流中写数据的时候,Spring就会采用约定好的方式提供一个逻辑视图名称。这个逻辑视图名称是通过 Spring 定义的 org.springframework.web.servlet.RequestToViewNameTranslator 接口的 getViewName 方法实现的。Spring默认提供了一个实现 org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator 可以看一下其支持的属性
public class DefaultRequestToViewNameTranslator implements RequestToViewNameTranslator {   	// 分隔符	private static final String SLASH = "/";	// 视图前缀	private String prefix = "";	// 视图后缀	private String suffix = "";	// 默认的分隔符	private String separator = SLASH;	// 如果首字符是分隔符是否需要去除,默认true	private boolean stripLeadingSlash = true;	// 如果尾字符是分隔符是否需要去除,默认true	private boolean stripTrailingSlash = true;	// 如果请求路径包含扩展名是否需要去除,默认true	private boolean stripExtension = true;	// 通过这个属性可以设置多种属性	private UrlPathHelper urlPathHelper = new UrlPathHelper();		/**	 * Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.	 * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath	 */	 // URL查找是否应始终使用当前路径中的完整路径	public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {   		this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);	}	/**	 * Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.	 * @see org.springframework.web.util.UrlPathHelper#setUrlDecode	 */	 // 是否要对 URL 解码,默认 true。它会采用request 指定的编码或者 ISO-8859-1 编码对 URL 进行解码	public void setUrlDecode(boolean urlDecode) {   		this.urlPathHelper.setUrlDecode(urlDecode);	}	/**	 * Set if ";" (semicolon) content should be stripped from the request URI.	 * @see org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent(boolean)	 */	 // 是否删除 ; 内容	public void setRemoveSemicolonContent(boolean removeSemicolonContent) {   		this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent);	}	....}   这里可以知道,我们日常使用的视图转换器就是 DefaultRequestToViewNameTranslator 。
代码逻辑比较简单,和上面的类似,不赘述。
/** * Initialize the RequestToViewNameTranslator used by this servlet instance. *If no implementation is configured then we default to DefaultRequestToViewNameTranslator. */ private void initRequestToViewNameTranslator(ApplicationContext context) { try { this.viewNameTranslator = context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class); if (logger.isTraceEnabled()) { logger.trace("Detected " + this.viewNameTranslator.getClass().getSimpleName()); } else if (logger.isDebugEnabled()) { logger.debug("Detected " + this.viewNameTranslator); } } catch (NoSuchBeanDefinitionException ex) { // We need to use the default. this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class); if (logger.isTraceEnabled()) { logger.trace("No RequestToViewNameTranslator '" + REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME + "': using default [" + this.viewNameTranslator.getClass().getSimpleName() + "]"); } } }
在 Spring mvc 中。当 Controller 将请求处理结果放入到 ModelAndView 中以后,DispatcherServlet 会根据 ModelAndView 选择合适的视图进行渲染。在org.springframework.web.servlet.ViewResolver#resolveViewName 方法中,通过 viewName 创建合适类型的View。
代码逻辑相同,不再赘述。
private void initViewResolvers(ApplicationContext context) {   		this.viewResolvers = null;		if (this.detectAllViewResolvers) {   			// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.			Map        matchingBeans =					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);			if (!matchingBeans.isEmpty()) {   				this.viewResolvers = new ArrayList<>(matchingBeans.values());				// We keep ViewResolvers in sorted order.				AnnotationAwareOrderComparator.sort(this.viewResolvers);			}		}		else {   			try {   				ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);				this.viewResolvers = Collections.singletonList(vr);			}			catch (NoSuchBeanDefinitionException ex) {   				// Ignore, we'll add a default ViewResolver later.			}		}		// Ensure we have at least one ViewResolver, by registering		// a default ViewResolver if no other resolvers are found.		if (this.viewResolvers == null) {   			this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);			if (logger.isTraceEnabled()) {   				logger.trace("No ViewResolvers declared for servlet '" + getServletName() +						"': using default strategies from DispatcherServlet.properties");			}		}	}       Spring MVC Flash attributes 提供了一个请求存储属性,可供其他请求使用。在使用重定向的时候非常必要。 Flash attributes 在重定向之前暂存以便重定向之后还能使用,并立即删除。
Spring MVC 有两个主要的抽象类来支持 Flash attributes。FlashMap 用于保存 Flash attributes。而FlashMapManager 用于存储、检索、管理 FlashMap 实例。
Flash attributes 支持默认开启(“on”),并不需要显式启用,它永远不会导致Http Session 的创建,这两个方法都可以通过 静态方法 RequestContextUtils 从 Spring mvc 的任何位置访问。
初始化代码如下,不再赘述。
private void initFlashMapManager(ApplicationContext context) {   		try {   			this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);			if (logger.isTraceEnabled()) {   				logger.trace("Detected " + this.flashMapManager.getClass().getSimpleName());			}			else if (logger.isDebugEnabled()) {   				logger.debug("Detected " + this.flashMapManager);			}		}		catch (NoSuchBeanDefinitionException ex) {   			// We need to use the default.			this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);			if (logger.isTraceEnabled()) {   				logger.trace("No FlashMapManager '" + FLASH_MAP_MANAGER_BEAN_NAME +						"': using default [" + this.flashMapManager.getClass().getSimpleName() + "]");			}		}	}   以上:内容部分参考 《Spring源码深度解析》 如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正
转载地址:http://uykcz.baihongyu.com/