servlet的生命周期
在Spring中,ContextLoaderListener只是辅助功能,用于创建WebApplicationContext类型实例,而真正的逻辑实现其实是在DispatcherServlet中进行的,DispatcherServlet是实现servlet接口的实现类。
servlet是一个Java编写的程序,此程序是基于HTTP协议的,在服务器端运行的(如Tomcat),是按照servlet规范编写的一个Java类。主要是处理客户端的请求并将其结果发送到客户端。servlet的生命周期是由servlet的容器来控制的,它可以分为三个阶段:初始化、运行和销毁。
(1)初始化阶段。
❤ servlet容器加载servlet类,把servlet类的.class文件中的数据读到内存中。
❤ servlet容器创建一个ServletConfig对象。ServletConfig对象包含了servlet的初始化配置信息。
❤ servlet容器创建一个servlet对象。
❤ servlet容器调用servlet对象的init方法进行初始化。
(2)运行阶段。
当servlet容器接收到一个请求时,servlet容器会针对这个请求创建servletRequest和servletResponse对象,然后调用service方法。并把这两个参数传递给service方法。service方法通过servletRequest对象获得请求的信息,并处理该请求。再通过servletResponse对象生成这个请求的响应结果。然后销毁servletRequest和servletResponse对象,我们不管这个请求是post提交的还是get提交的,最终这个请求都会由service方法来处理。
(3)销毁阶段。
当Web应用被终止时,servlet容器会先调用servlet对象的destroy方法,然后再销毁servlet对象,同时也会销毁与servlet对象相关联的servletConfig对象。我们可以在destroy方法的实现中,释放servlet所占用的资源,如关闭数据库连接,关闭文件输入输出流等。
servlet的框架是由两个Java包组成:javax.servlet和javax.servlet.http。在java.servlet包中定义了所有的servlet类都必须实现或扩展的通用接口和类,在javax.servlet.http包中定义了采用HTTP通信协议的HttpServlet类。
servlet被设计成请求驱动,servlet的请求可能包含多个数据项,当Web容器接收到某个servlet请求时,servlet把请求封装成一个HttpServletRequest对象,然后把对象传给servlet的对应的服务方法。
HTTP的请求方式包括delete、get、options、post、put和trace,在HttpServlet类中分别提供了对应的服务方法,它们是doDelete、doGet、doOptions、doPost、doPut和doTrace。
DispatcherServlet的初始化
在Servlet的初始化阶段会调用其init方法,所以我们首先要查看在DispatcherServlet中是否重写了init方法。我们在其父类HttpServletBean中找到了该方法。
public final void init() throws ServletException { // 解析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; } } //留给子类扩展 initServletBean(); }
DispatcherServlet的初始化过程主要是通过将当前的servlet类型实例转换为BeanWrapper类型实例,以便使用Spring中提供的注入功能进行对应属性的注入。这些属性如contextAttribute、contextClass、nameSpace、contextConfigLocation等,都可以在web.xml文件中以初始化参数的方式配置在servlet的声明中。DispatcherServlet继承自FrameworkServlet,FrameworkServlet类上包含对应的同名属性,Spring会保证这些参数被注入到对应的值中。属性注入主要包含以下几个步骤:
(1)封装及验证初始化参数
ServletConfigPropertyValues除了封装属性外还有对属性验证的功能。
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>中配置的封装。当然,用户可以通过对requiredProperties参数的初始化来强制验证某些属性的必要性,这样,在属性封装的过程中,一旦检测到requiredProperties中的属性没有指定初始值,就会抛出异常。
(2)将当前servlet实例转化成BeanWrapper实例
PropertyAccessorFactory.forBeanPropertyAccess是Spring中提供的工具方法,主要用于将指定实例转化为Spring中可以处理的BeanWrapper类型的实例。
(3)注册相对于Resource的属性编辑器
属性编辑器,我们在上下文中已经介绍并且分析过其原理,这里使用属性编辑器的目的是在对当前实例(DispatcherServlet)属性注入过程中一旦遇到Resource类型的属性就会使用ResourceEditor去解析。
(4)属性注入
BeanWrapper为Spring中的方法,支持Spring的自动注入。其实我们最常用的属性注入无非是contextAttribute、contextClass、nameSpace、contextConfigLocation等属性。
(5)servletBean的初始化
在contextLoaderListener加载的时候已经创建了WebApplicationContext实例,而在这个函数中最重要的就是对这个实例进行进一步的补充初始化。
继续查看initServletBean。父类FrameworkServlet覆盖了HttpServletBean中的initServletBean函数,如下:
protected final void initServletBean() throws ServletException { getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'"); if (logger.isInfoEnabled()) { logger.info("Initializing Servlet '" + getServletName() + "'"); } long startTime = System.currentTimeMillis(); try { this.webApplicationContext = initWebApplicationContext(); //设计为子类覆盖 initFrameworkServlet(); } catch (ServletException | RuntimeException ex) { logger.error("Context initialization failed", ex); throw ex; } if (logger.isDebugEnabled()) { String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data"; logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value); } if (logger.isInfoEnabled()) { logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms"); } }
上面的函数设计了计时器来统计初始化的执行时间,而且提供了一个扩展方法initFrameworkServlet用于子类的覆盖操作,而作为关键的初始化逻辑实现委托给了initWebApplicationContext方法。
WebApplicationContext的初始化
initWebApplicationContext函数的主要工作就是创建或刷新WebApplicationContext实例并对Servlet功能所使用的变量进行初始化。
protected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { //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) { // 根据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实例
WebApplicationContext的寻找及创建包括以下几个步骤:
(1)通过构造函数的注入进行初始化
当进入initWebApplicationContext函数后通过判断this.webApplicationcontext != null 后,便可以确定this.webApplicationcontext是否是通过构造函数来进行初始化的。可是有的可能会有疑问,在initServletBean函数中明明是把创建好的实例记录在了this.webApplicationContext中:
this.webApplicationContext = initWebApplicationContext();
何以判断这个参数是通过构造函数来初始化,而不是通过上一次的函数返回值初始化呢?如果存在这个问题,那就是你忽略了一个问题:在Web中包含了SpringWeb的核心逻辑的DispatcherServlet只可以被声明一次,在Spring中已经存在验证,所以这就确保了如果this.webApplicationcontext != null,则可以直接判定this.webApplicationcontext 已经通过构造函数初始化。
(2)通过contextAttribute进行初始化
通过在web.xml文件中配置的servlet参数contextAttribute来查找ServletContext中对应的属性,默认为WebApplicationContext.class.getName()+".ROOT",也就是在ContextLoaderListener加载时会创建WebApplicationContext实例,并将实例以WebApplicationContext.class.getName()+".ROOT"为key放入ServletContext中,当然也可以自定义重写初始化逻辑来使用自己的WebApplicationContext,并在Servlet的配置中通过初始化参数contextAttribute指定key。
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; }
(3)重新创建WebApplicationContext实例
如果通过以上两种方式并没有找到任何突破,那就没有办法了,只能在这里重新创建新的实例了。
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { //获取Servlet的初始化参数ContextClass,如果没有配置默认为xmlWebApplicationContext.class 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"); } //通过反射方式实例化contextclass ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); //parent为在ContextLoaderListener中创建的实例,在ContextLoaderListener加载的时候初始化的WebApplicationContext类型实例 wac.setParent(parent); String configLocation = getContextConfigLocation(); if (configLocation != null) { //获取contextConfigLocation属性,配置在Servlet初始化参数中 wac.setConfigLocation(configLocation); } //初始化Spring环境包括加载配置文件等 configureAndRefreshWebApplicationContext(wac); return wac; }
2.configureAndRefreshWebApplicationContext
无论是通过构造函数注入还是单独创建,都免不了会调用configureAndRefreshWebApplicationContext方法来对已经创建的WebApplicationContext实例进行配置及刷新,那么这个步骤又做了哪些工作呢?
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // The application context id is still set to its original default value // -> assign a more useful id based on available information String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { wac.setId(idParam); } else { // Generate default id... wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); } } wac.setServletContext(sc); String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocationParam != null) { wac.setConfigLocation(configLocationParam); } // The wac environment's #initPropertySources will be called in any case when the context // is refreshed; do it eagerly here to ensure servlet property sources are in place for // use in any post-processing or initialization that occurs below prior to #refresh ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(sc, null); } customizeContext(sc, wac); //加载配置文件及整合parent到wac wac.refresh(); }
无论调用方式如何变化,只要是使用ApplicationContext所提供的功能最后都免不了使用公共父类AbstractApplicationContext提供的refresh方法进行配置文件加载。
3.刷新
onRefresh是FrameworkServlet类中提供的模板方法,在其子类DispatcherServlet中进行了重写,主要用于刷新Spring在web功能实现中所必须使用的全局变量。下面来介绍它们的初始化过程以及使用场景。
protected void onRefresh(ApplicationContext context) { initStrategies(context); }
protected void initStrategies(ApplicationContext context) { //初始化MultipartResolver initMultipartResolver(context); //初始化LocaleResolver initLocaleResolver(context); //初始化ThemeResolver initThemeResolver(context); //初始化HandlerMappings initHandlerMappings(context); //初始化HandlerAdapters initHandlerAdapters(context); //初始化HandlerExceptionResolvers initHandlerExceptionResolvers(context); //初始化RequestToViewNameTranslator initRequestToViewNameTranslator(context); //初始化ViewResolvers initViewResolvers(context); //初始化FlashMapManager initFlashMapManager(context); }
(1)初始化MultipartResolver。
在Spring中MultipartResolver主要用来处理文件上传。默认情况下,Spring是没有multipart处理的,因为一些开发者想要自己处理它们。如果想使用Spring的multipart,则需要在Web应用的上下文中添加multipart解析器。这样,每个请求就会被检查是否包含multipart。然而,如果请求中包含multipart,那么上下文中定义的MultipartResolver就会解析它,这样请求中的multipart属性就会像其他属性一样被处理。常用配置如下:
<bean id="multipartResolver" class="org.Springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 该属性用来配置可上传文件的最大byte数 --> <property name="maximumFileSize"><value>10000</value></property> </bean>
(2)初始化LocaleResolver。
在Spring的国际化配置中一共有三种使用方式。
❤ 基于URL参数的配置;
通过URL参数来控制国际化,比如你在页面上加一句<a href="?locale=zh_CN">简体中文</a>来控制项目中使用的国际化参数。而提供这个功能的就是AcceptHeaderLocaleResolver,默认的参数名为locale,注意大小写。里面放的就是你的提交参数,比如en_US、zh_CN之类的,具体配置如下:
<bean id="localeResolver" class="org.Springframework.web.servlet.i18n.AcceptHeaderLocaleResolver"/>
❤ 基于session的配置;
它通过检验用户会话中预置的属性来解析区域。最常用的是根据用户本次会话过程中的语言设定决定语言种类(例如:用户登录时选择语言种类,则此次登录周期内统一使用此语言设定),如果该会话属性不存在,它会根据accept-language HTTP头部确定默认区域。
<bean id="localeResolver" class="org.Springframework.web.servlet.i18n.SessionLocaleResolver"/>
❤ 基于Cookie的国际化配置;
CookieLocaleResolver用于通过浏览器的cookie设置取得Locale对象。这种策略在应用程序不支持会话或者状态必须存在客户端时有用,配置如下:
<bean id="localeResolver" class="org.Springframework.web.servlet.i18n.CookieLocaleResolver"/>
(3)初始化ThemeResolver。
在Web开发中经常会遇到通过主题Theme来控制网页风格,这将进一步改善用户体验。简单的说,一个主题就是一组静态资源(比如样式表和图片),它们可以影响应用程序的视觉效果。Spring中的主题功能和国际化功能非常相似。构成Spring主题功能主要包括如下内容:
❤ 主题资源
Spring的主题需要通过ThemeSource接口来实现存放主题信息的资源。在Spring的配置如下:
<bean id="themeSource" class="org.Springframework.ui.context.support.ResourceBundleThemeSource"> <property name="basenamePrefix" value="com.test. "></property> </bean>
默认状态下是在类路径根目录下查找相应的资源文件,也可以通过basenamePrefix来制定。
❤ 主题解析器
ThemeSource定义了一些主题资源,那么不同的用户使用什么主题资源由谁定义呢?主题解析的工作便是由ThemeResolver的子类来完成。
❤ 拦截器
如果需要根据用户请求来改变主题,那么Spring提供了一个已经实现的拦截器ThemeChangeInterceptor拦截器了,配置如下:
<bean id="themeChangeInterceptor" class="org.Springframework.web.servlet.theme.ThemeChangeInterceptor> <property name="paramName" value="themeName"></property> </bean>
其中设置用户请求参数名为paramName,即URL为?themeName=具体的主题名称。此外,还需要在handlerMapping中配置拦截器。当然需要在HandleMapping中添加拦截器。
<property name="interceptors"> <list> <ref local="themeChangeInterceptor"/> </list> </property>
(4)初始化HandlerMapping。
当客户端发出Request时DispatcherServlet会将Request提交给HandlerMapping,然后HandlerMapping根据WebApplicationContext的配置来回传给DispatcherServlet相应的Controller。
在基于SpringMVC的Web应用程序中,我们可以为DispatcherServlet提供多个HandlerMapping供其使用。DispatcherServlet在选用HandlerMapping的过程中,将根据我们所指定的一系列HandlerMapping的优先级进行排序,然后使用优先级在前的HandlerMapping,如果当前的HandlerMapping能够返回可用的Handler,DispatcherServlet则使用当前返回的Handler进行Web请求的处理,而不再继续询问其他的HandlerMapping。否则,DispatcherServlet将继续按照各个HandlerMapping的优先级进行询问,直到获取一个可用的Handler为止。
默认情况下,SpringMVC将加载当前系统中所有实现了HandlerMapping接口的bean。如果只期望SpringMVC加载指定的HandlerMapping时,可以修改web.xml中的DispatcherServlet的初始参数,将delectAllHandlerMappings的值设置为false:
<init-param> <param-name>detectAllHandlerMappings</param-name> <param-value>false</param-value> </init-param>
此时,SpringMVC将查找名为detectAllHandlerMappings的bean,并作为当前系统中唯一的HandlerMapping。如果没有定义HandlerMapping的话,SpringMVC将按照DispatcherServlet.properties中所定义的HandlerMapping的内容来加载默认的HandlerMapping。
(5)初始化HandlerAdapters。
在系统加载的时候,根据当前路径DispatcherServlet.properties来初始化本身,查看DispatcherServlet.properties中对应于HandlerAdapter的属性。
如果程序开发人员没有在配置文件中定义自己的适配器,那么Spring会默认加载配置文件中的三个适配器。
作为总控制器的派遣器servlet通过处理器映射得到处理器后,会轮询处理器适配器模块,查找能够处理当前HTTP请求的处理器适配器的实现,处理器适配器模块根据处理器映射返回的处理器类型,例如简单的控制器类型、注解控制器类型或远程调用处理器类型,来选择某一个适当的处理器适配器的实现,从而适配当前的HTTP请求。
❤ HTTP请求处理器适配器(HttpRequestHandlerAdapter)
Http请求处理器适配器仅仅支持对HTTP请求处理器的适配。它简单的将HTTP请求对象和响应对象传递给HTTP请求处理器的实现,它并不需要返回值。它主要应用在基于HTTP的远程调用的实现上。
❤ 简单控制器处理器适配器(SimpleControllerHandlerAdapter)
这个实现类将HTTP请求适配到一个控制器的实现进行处理。这里控制器的实现是一个简单的控制器接口的实现。简单控制器处理器适配器被设计成一个框架类的实现,不需要被改写,客户化的业务逻辑通常是在控制器接口的实现类中实现的。
❤ 注解方法处理器适配器(AnnotationMethodHandlerAdapter)
这个类的实现是基于注解的实现,它需要结合注解方法映射和注解方法处理器协同工作。它通过解析声明在注解控制器的请求映射信息来解析相应的处理器方法来处理当前的HTTP请求。在处理的过程中,它通过反射来发现探测处理器方法的参数,调用处理器方法,并且映射返回值到模型和控制器对象,最后返回模型和控制对象作为主控制器的派遣器Servlet。
Spring中所使用的Handler并没有任何特殊的联系,但是为了统一处理,Spring提供了不同情况下的适配器。
(6)初始化HandlerExceptionResolvers
基于HandlerExceptionResolver接口的异常处理,使用这种方式只需要实现resolveException方法,该方法返回一个ModelAndView对象,在方法内部对异常的类型进行判断,然后尝试生成对应的ModelAndView对象,如果该方法返回了null,则Spring会继续寻找其他的实现了HandlerExceptionResolver接口的bean,也就是说,Spring会搜索所有注册在其环境中的实现了HandlerExceptionResolver接口的bean,逐个执行,直到返回了一个ModelAndView对象。
(7)初始化RequestToViewNameTranslator
当Controller处理器方法没有返回一个View对象或者逻辑视图名称,并且在该方法中没有直接往response的输出流里面写数据的时候,Spring就会采用约定好的方式提供一个逻辑视图名称。这个逻辑视图名称是通过Spring定义的org.Springframework.web.servlet.RequestToViewNameTranslator接口的getViewName方法来实现的,我们可以实现自己的RequestToViewNameTranslator接口来约定好没有返回视图名称的时候如何确定视图名称。Spring已经给我们提供了一个它自己的实现,那就是org.Springframework.web.servlet.view.DefaultRequestToViewNameTranslator。
在介绍DefaultRequestToViewNameTranslator是如何约定视图名称之前,先来看一下它支持用户定义的属性:
❤ prefix:前缀,表示约定好的视图名称需要加上的前缀,默认是空串;
❤ suffix:后缀,表示约定好的视图名称需要加上的后缀,默认是空串;
❤ separator:分隔符,默认是“/”,斜杠;
❤ stripLeadingSlash:如果首字母是分隔符,是否要去除,默认是true;
❤ stripTrailingSlash:如果最后一个字符是分隔符,是否要去除,默认是true;
❤ stripExtension:如果请求路径包含扩展名是否要去除,默认是true;
❤ urlDecode:是否需要对URL解码,默认是true。它会采用request指定的编码或者ISO-8859-1编码对URL进行解码;
当我们没有在SpringMVC的配置文件中手动的定义一个名为viewNameTranlator的bean的时候,Spring就会为我们提供一个默认的viewNameTranlator,即DefaultRequestToViewNameTranslator。
接下来看一下,当Controller处理器方法没有返回逻辑视图名称时,DefaultRequestToViewNameTranslator是如何约定视图名称的。DefaultRequestToViewNameTranslator会获取到请求的URL,然后根据提供的属性做一些改造,把改造之后的结果作为视图名称返回。这里以请求的路径:http://localhost/app/test/index.html为例,来说明一下DefaultRequestToViewNameTranslator是如何工作的。该路径对应的请求URI为/test.index.html,我们看以下几种情况,它分别对应的逻辑视图名称是什么。
❤ prefix和suffix如果都存在,其他为默认值,那么对应的返回的逻辑视图名称应该是prefixtest/index.suffix。
❤ stripLeadingSlash和stripExtension都为false,其他默认,这时候对应的逻辑视图名称是/product/index.html。
❤ 都采用默认设置时,返回的逻辑视图名称应该是product/index。
如果逻辑视图名称跟请求路径相同或者相关关系都是一样的,那么我们就可以采用Spring为我们事先约定好的逻辑视图并返回,这可以大大简化我们的开发工作,而以上功能实现的关键属性viewNameTranlator,则是在initRequestToViewNameTranslator中完成的。
(8)初始化ViewResolvers
在SpringMVC中,当Controller将请求处理结果放入到ModelAndView中以后,DispatcherServlet会根据ModelAndView选择合适的视图进行渲染。那么在SpringMVC中是如何选择合适的View呢?View对象是如何创建的呢?答案就在ViewResolver中。ViewResolver接口定义了resolverViewName方法,根据viewName创建合适类型的View实现。
<!-- 配置jsp 显示ViewResolver --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean>
(9)初始化FlashMapManager
SpringMVC Flash Attributes提供了一个请求存储属性,可供其他请求使用。在使用重定向时候非常必要,例如Post/Redirect/Get模式。Flash Attributes在重定向之前暂存(就像存在session中)以便重定向之后还能使用,并立即删除。
SpringMVC有两个主要的抽象来支持Flash Attributes,FlashMap用于保持Flash Attributes,而FlashMapManager用于存储、检索、管理FlashMap实例。
Flash Attributes支持默认开启并不需要显式启用,它永远不会导致HTTP session的创建。这两个FlashMap实例都可以通过静态方法RequestContextUtils从SpringMVC的任何位置访问。
FlashMapManager的初始化在initFlashMapManager中完成。
参考:《Spring源码深度解析》 郝佳 编著: