本文共 27798 字,大约阅读时间需要 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类型的类 MapmatchingBeans = 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") protectedList 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. // 寻找所有的适配器并排序 MapmatchingBeans = 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. MapmatchingBeans = 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. MapmatchingBeans = 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/