在spring中,ContextLoaderListener只是辅助功能,用于创建WebApplicationContext类型实例,而真正的逻辑实现其实是在DispatcherServlet中进行的,DispatcherServlet是实现servlet接口的实现类。
servlet是一个Java编写的程序,此程序是基于HTTP协议的,在服务器端运行的(如Tomcat),是按照servlet规范编写的一个java类。主要是处理客户端的请求并将其结果发送到客户端。servlet的生命周期是由servlet的容器来控制的,它可以分为3个阶段:初始化,运行和销毁。
(1)初始化阶段。
(2)运行阶段
当servlet容器接收到一个请求时,servlet容器会针对这个请求创建serlvetRequest和servletResponse对象,然后调用service方法。并把这两个参数传递给service方法。service方法通过servletRequest对象获得请求的信息。并处理该请求。再通过servletResponse对象生成这个请求的相应结果。然后销毁servletResponse和servletRequest对象。
(3)销毁阶段
当web应用终止时,servlet容器会先调用servlet对象的destory方法,然后再销毁servlet对象,同时销毁servlet关联的ServletConfig对象。我们可以在destroy方法的实现中,释放servlet所占用的资源,如关闭数据库连接,关闭输入输出流等。
回到顶部
servlet初始化阶段会调用其init方法,所以我们看下DispatcherServle的init方法。在其父类HttpServletBean中找到了该方法。
public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // Set bean properties from init parameters. try { //解析init-param并封装在pvs中 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); //将当前的这个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) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); throw ex; } // Let subclasses do whatever initialization they like. //留给子类扩展 initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } }
DispatcherServlet的初始化过程主要是通过将当前的servlet类型实例转换为BeanWrapper类型实例 ,以便使用Srping中提供的注入功能进行对应属性的注入。这些属性如contextAttribute,contxtClass,nameSpace,contextConfigLocation等,都可以在web.xml文件中以初始化参数的方式配置在servlet声明中。DispatcherServlet继承自FramworkServlet,FrameworkServlet类上包含对应的同名属性,属性注入主要包含如下步骤:
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties) throws ServletException { Set<String> missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ? new HashSet<String>(requiredProperties) : null; Enumeration en = config.getInitParameterNames(); while (en.hasMoreElements()) { String property = (String) en.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 (missingProps != null && missingProps.size() > 0) { throw new ServletException( "Initialization from ServletConfig for servlet '" + config.getServletName() + "' failed; the following required properties were missing: " + StringUtils.collectionToDelimitedString(missingProps, ", ")); } }
在ContextLoaderListener加载的时候已经创建了WebApplicationContext实例,而在这个函数最重要的就是对这个实例进行进一步的补充初始化。
继续查看initServletBean()。父类FrameworkServlet覆盖了HttpServletBean中的initServletBean,函数如下:
protected final void initServletBean() throws ServletException { getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'"); if (this.logger.isInfoEnabled()) { this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started"); } long startTime = System.currentTimeMillis(); try { this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } catch (ServletException ex) { this.logger.error("Context initialization failed", ex); throw ex; } catch (RuntimeException ex) { this.logger.error("Context initialization failed", ex); throw ex; } if (this.logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " + elapsedTime + " ms"); } }
上面的函数设计了计时器来统计初始化的执行时间,而且提供了一个扩展方法initFrameworkServelt()用于子类的覆盖操作,而作为关键的初始化逻辑实现委托给了initWebApplicationContext(). initWebApplicationContext函数的主要工作就是 创建或者刷新 WebApplicationContext实例 并对servlet功能所使用的变量进行初始化 。
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); } //刷新上下文环境,初始化Spring环境包括加载配置文件等 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. onRefresh(wac); } if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); if (this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } } return wac; }
上面代码前面都是在获取webApplicationContext,后面有这样一行代码,我们看下: configureAndRefreshWebApplicationContext
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... ServletContext sc = getServletContext(); if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) { // Servlet <= 2.4: resort to name specified in web.xml, if any. String servletContextName = sc.getServletContextName(); if (servletContextName != null) { wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName + "." + getServletName()); } else { wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName()); } } else { // Servlet 2.5's getContextPath available! wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.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()进行配置文件加载。对于本函数中的初始化主要包含几个部分。
1.寻找或创建对应的WebApplicationContext实例
2.configureAndRefreshWebApplicationContext。无论是通过构造函数注入还是单独创建,都免不了会调用configureAndRefreshWebApplicationContext方法来对已经创建的WebApplicationContext实例进行配置及刷新。
3.DispatcherServlet的刷新
回到顶部
onRefresh是FrameworkServletl类中提供的模板方法,在其子类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); //初始化HandlerAdapter initHandlerAdapters(context); //初始化HandlerExcpetionResolvers initHandlerExceptionResolvers(context); //初始化RequestToViewNameTranslator initRequestToViewNameTranslator(context); //初始化ViewResolvers initViewResolvers(context); //初始化FlashMapManager initFlashMapManager(context); }
在Spring中,MultipartResolver主要用来处理文件上传。默认情况下,Spring是没有multipart处理的,因为一些开发者想要自己处理它们。如果想使用Spring的multipart,则需要在Web应用的上下文中添加multipart解析器。这样,每个请求都被检查是否包含multipart。如果请求中包含multipart,那么上下文中定义的MultipartResolver就会解析它,这样请求中的multipart属性就会象其他属性一样被处理。常用配置如下:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 设置上传文件大小的参数--> <property name="resolveLazily" value="true"/> <!--btye为单位,5M--> <property name="maxUploadSize" value="5242880"/> </bean>
因为之前的步骤已经完成了Spring中配置文件的解析,所以在这里只要配置文件注册过都可以通过ApplicationContext提供了getBean方法获取对应的bean,进而初始化MultipartResolver中的multipartResolver变量。
在Spring的国际化配置中一共有3种使用方式。
(1)基于URL参数的配置。通过URL参数来控制国际化,比如你在页面上加一句简体中文来控制项目中使用的国际化参数。
< a href= "myTest.jsp?locale=zh_CN">简体中文 </ a>
而提供这个功能的就是AcceptHeaderLocaleResolver,默认的参数名为locale,注意大小写。里面放的就是你的提交参数,比如en_US、zh_CN之类的,具体配置如下;
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver"></bean>
(2)基于session的配置。它通过检验用户会话中预置的属性来解析区域。最常用的是根据用户本次会话过程中的语言设定决定语言种类(例如,用户登录时选择语言种类,则此次登录周期内统一使用此语言设定),如果该会话属性不存在,它会根据accept-language HTTP头部确定默认区域。
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean>
(3)基于Cookie的国际化配置。CookieLocaleResolver用于通过浏览器的cookie设置取得Locale对象。这种策略在应用程序不支持会话或者状态必须保存在客户端时有用,配置如下:
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>
这三种方式都可以解决国际化的问题,对应的拦截方式:
<bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
在mapping url映射的时候,可以调用这个属性来跳转的时候,进行国际化的拦截。
在Web开发中经常会遇到主题Theme来控制网页风格,这将进一步改善用户体验,简单说就是一个主题就是一组静态资源,可以影响应用程序的视觉效果。Spring中的主题功能和国际化功能非常相似,构成Spring主题功能主要包括如下内容。
(1)主题资源。Spring中的ThemeSource是Spring中主题资源的接口,Spring的主题需要通过ThemeSource接口来实现存放主题信息的资源。 ResourceBundleThemeSource是ThemeSource接口默认实现类,在Spring中的配置如下:
<bean id="themsSource" class="org.Springframework.ui.context.support.ResourceBundleThemeSource"> </bean>
默认状态下是在类路径根目录下查找相应的资源文件,也可以通过beasenamePrefix指定查找资源文件的包路径。
(2)主题解析器。
ThemeSource定义了一些主题资源,ThemeResolver是主题解析器的接口,主题解析的工作则是其子类完成的。
对于主题解析器,主要有三个比较常见的实现,以主题summer.properties为例。
FixedThemeResolver:用于选择一个固定的主题。
<bean id="themeResolver" class="org.Springframework.web.servlet.theme.FixedThemeResolver"> <property name="defaultThemeName" value="主题名" /> </bean>
CookieThemeResolver:用于实现用户所选的主题,以Cookie的方式存放在客户端的机器上。
<bean id="themeResolver" class="org.Springframework.web.servlet.theme.CookieThemeResolver"> <property name="defaultThemeName" value="主题名" /> </bean>
SessionThemeResolver:用户主题保存在用户的HTTP Session中。
<bean id="themeResolver" class="org.Springframework.web.servlet.theme.SessionThemeResolver"> <property name="defaultThemeName" value="主题名" /> </bean>
以上配置用于设置主题名称,并将该名称保存在用户的HttpSession中。
另外,对于FixedThemeResolver和SessionThemeResolver,共同继承了AbstractThemeResolver类。用户也可以自己实现自己的解析器继承AbstractThemeResolver类。
(3)拦截器。
如果需要根据用户请求来改变主题,那么Spring提供了一个已经实现的拦截器ThemeChangeInterceptor拦截器,具体的配置如下:
<bean id="themeChangeInterceptor" class="org.Springframework.web.servlet.theme.ThemeChangeInterceptor"> <property name="paramName" value="themeName"></property> </bean>
其中设置用户请求参数名为themeName,即URL为?themeName=具体的主题名称。此外还需要在handlerMapping中配置拦截器。
<property name="interceptors"> <list> <ref local="themeChangeInterceptor" /> </list> </property>
当客户端发出Request时DispatcherServlet会将Request提交给HandlerMaping,然后HandlerMapping根据WebApplicationContext的配置来回传给DispatcherServlet相应的Controller.
在基于SpringMVC的web应用程序中,我们可以为DispatcherServlet提供多个HandlerMapping供其使用 。 DispatcherServlet在选用HandlerMapping的过程中,将根据我们所制定的一系列HandlerMapping的优先级进行排序,然后优先使用优先级在前面的handlerMapping.如果当前的HandlerMapping能够返回可用的Handler,DispatcherSevlet则使用当前返回的Handler进行Web请求的处理。而不再继续询问其他的HandlerMapping。否则,DispatcherServlet将继续按照各个handlerMapping的优先级进行询问,直到获得一个可用的Handler为止。初始化配置如下:
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<HandlerMapping>(matchingBeans.values()); // We keep HandlerMappings in sorted order. OrderComparator.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.isDebugEnabled()) { logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default"); } } }
默认情况下,SpringMVC将加载当前系统中所有实现了HandlerMapping接口的bean。如果只期望SpringMVC加载指定的handlermapping时,可以修改web.xml中的DispatcherServlet的初始化参数,将detectAllHandlerMappings的值设置为false:
<init-param> <param-name>detectAllHandlerMappings</param-name> <param-value>false</param-value> </init-param>
此时,SpringMVC将查找名为“handlerMapping”的bean,并作为当前系统中唯一的handlermapping.如果没有没有定义handlerMapping的话,则SpringMVC将按照org.Springframeword.web.servlet.DispatcherServlet所在目录下的 DispatcherServlet.properties 中所定义的org.Springframeword.web.servlet.HandlerMapping的内容来加载默认的handlerMapping(用户没有自定义Strategies的情况下)
DispatcherServlet.properties
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,/ org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,/ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,/ org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,/ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,/ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
从名字也能联想到这个一个典型的适配器模式的使用,在计算机编程中,适配器模式将一个类的接口适配成用户所期待的。使用适配器,可以使接口而无法一起工作的的类协同工作,做法是将类自己的接口包裹在一个已经存在的类中。那么在处理handler中为什么会使用适配模式呢?我们看下他的初始化逻辑。
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<HandlerAdapter>(matchingBeans.values()); // We keep HandlerAdapters in sorted order. OrderComparator.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.isDebugEnabled()) { logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default"); } } }
同样在初始化的过程中涉及了一个变量detectAllHandlerAdapters,detectAllhandlerAdapters作用和detectAllHandlerMappings类似,只不过作用对象为handlerAdapter。也可以通过如下配置强制系统只加载beanname为“handlerAdapteer”的handlerAdapter。
<init-param> <param-name>detectAllhandlerAdapters</param-name> <param-value>false</param-value> </init-param>
如果无法找得到对应的bean,那么系统会尝试加载默认的适配器。
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { String key = strategyInterface.getName(); String value = defaultStrategies.getProperty(key); if (value != null) { String[] classNames = StringUtils.commaDelimitedListToStringArray(value); List<T> strategies = new ArrayList<T>(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( "Error loading DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]: problem with class file or dependent class", err); } } return strategies; } else { return new LinkedList<T>(); } }
在getDefaultStrategies函数中,Spring会尝试从defaultStrategies中加载对应的HandlerAdapter属性,那么defaultStrategies是如何初始化的呢?
在 当前类DispatcherServlet 中存在这样一段初始化代码块:
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 'DispatcherServlet.properties': " + ex.getMessage()); } }
在系统加载的时候,defaultStrategies根据当前路径DispatcherServlet.properties来初始化本身,查看DispatcherServlet.properties中对应于HandlerAdapter的属性:
org.Springframework.web.servlet.HandlerAdapter=org.Springframework.web.servlet.mvc.HttpRequestHandlerAdapter,/
org.Springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,/
org.Springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
由此得知,如果程序开发人员没有在配置文件中定义自己的适配器,那么Spring会默认加载配置文件中的3个适配器。
作为总控制器的派遣器servlet通过处理器映射得到处理器后,会 轮询 处理器适配器模块,查找能够处理当前HTTP请求的处理器适配器的实现,处理器适配器模块根据处理器映射返回的处理器类型,例如简单的控制器类型、注解控制器类型或者远程调用处理器类型,来选择某一个适当的处理器适配器的实现,从而适配当前的HTTP请求。
Spring中所使用的Handler并没有任何特殊的联系,但是为了统一处理,Spring提供了不同情况下的适配器。
@Component public class ExceptionHandler implements HandlerExceptionResolver { private static final Log logs = LogFactory.getLog(ExceptionHandler.class); @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object obj, Exception exception) { request.setAttribute("exception", exception.toString()); request.setAttribute("exceptionStack", exception); logs.error(exception.toString(), exception); return new ModelAndView("error/exception"); } }
使用这种异常处理方式,需要实现resolveException方法,该方法返回一个ModelAndView对象,在方法内部对异常的类型进行判断,然后尝试生成对应的ModelAndView对象,如果该方法返回了null,则Spring会继续寻找其他的实现了HandlerExceptionResolver接口的bean,直到返回一个ModelAndView对象。
配置如下:
<bean id="exceptionHandler" class="com.test.exception.ExceptionHandler" />
当Controller处理器方法没有返回一个View对象或逻辑视图名称,并且在该方法中没有直接往response的输出流里面写数据的时候,Spring就会采用约定好的方式提供一个逻辑视图名称。这个逻辑视图名称是通过Spring定义的org.Springframework.web.servlet.RequestToView NameTranslator接口的getViewName方法来实现的,我们可以实现自己的Request ToViewName Translator接口来约定好没有返回视图名称的时候如何确定视图名称。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就会为我们提供一个默认的viewNameTranslator,即DefaultRequestToViewName Translator。
接下来看一下,当Controller处理器方法没有返回逻辑视图名称时,DefaultRequestToView NameTranslator是如何约定视图名称的。DefaultRequestToViewNameTranslator会获取到请求的URI,然后根据提供的属性做一些改造,把改造之后的结果作为视图名称返回。这里以请求路径http://localhost/app/test/index.html为例,来说明一下DefaultRequestToViewNameTranslator是如何工作的。该请求路径对应的请求URI为/test/index.html,我们来看以下几种情况,它分别对应的逻辑视图名称是什么。
如果逻辑视图名称跟请求路径相同或者相关关系都是一样的,那么我们就可以采用Spring为我们事先约定好的逻辑视图名称返回,这可以大大简化我们的开发工作。
在SpringMVC中,当Controller将请求处理结果放入到ModelAndView中以后,DispatcherServlet会根据ModelAndView选择合适的视图进行渲染。那么在SpringMVC中是如何选择合适的View呢?View对象是如何创建的呢?答案在ViewResolver中。ViewResolver接口定义了resolverViewName方法,根据viewName创建合适类型的View实现。
那么如何配置ViewResolver呢?在Spring中,ViewResolver作为SpringBean存在,可以在配置文件中进行配置,例如下面的代码,配置了JSP相关的veiwResolver.
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/view/" /> <property name="suffix" value=".jsp" /> </bean>
SpringMVC Flash attributs提供了一个请求存储属性,可供其他请求使用。在使用重定向的时候非常必要,例如Post/Redirect/Get模式。Flash attributes在重定向之前暂存(就像存在session中)以便重定向之后还能使用,并立即删除。
SpringMVC有两个主要的抽象来支持flasth attributes。 FlashMap 用于保持flash attributes,而 FlashMapManager 用于存储,检索,管理FlashMap实例。
flash attribute支持默认开始(“on”)并不需要显示启用,它永远不会导致HTTPSession的创建。这两个FlashMap实例都可以通过静态方法RequestContextUtils从SpringMVC的任何位置访问。
如有疑问或同行交流欢迎加群讨论: 151258054