- 前言
- 第一部分 核心实现
- 第 1 章 Spring 整体架构和环境搭建
- 第 2 章 容器的基本实现
- 第 3 章 默认标签的解析
- 第 4 章 自定义标签的解析
- 第 5 章 bean 的加载
- 第 6 章 容器的功能扩展
- 第 7 章 AOP
- 第二部分 企业应用
- 第 8 章 数据库连接 JDBC
- 第 9 章 整合 MyBatis
- 第 10 章 事务
- 第 11 章 SpringMVC
- 第 12 章 远程服务
- 第 13 章 Spring 消息
11.3.3 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 = (ConfigurableWebApplication
Context) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
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.
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;
}
对于本函数中的初始化主要包含几个部分。
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 =
attrName);
WebApplicationContextUtils.getWebApplicationContext(getServletContext(),
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer
not registered?");
}
return wac;
}
(3)重新创建 WebApplicationContext 实例。
如果通过以上两种方式并没有找到任何突破,那就没办法了,只能在这里重新创建新的实例了。
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
//获取 servlet 的初始化参数 contextClass,如果没有配置默认为 XmlWebApplicationContext.class
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
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);
//parent 为在 ContextLoaderListener 中创建的实例
//在 ContextLoaderListener 加载的时候初始化的 WebApplicationContext 类型实例
wac.setParent(parent);
//获取 contextConfigLocation 属性,配置在 servlet 初始化参数中
wac.setConfigLocation(getContextConfigLocation());
//初始化 Spring 环境包括加载配置文件等
configureAndRefreshWebApplicationContext(wac);
return wac;
}
2.configureAndRefreshWebApplicationContext
无论是通过构造函数注入还是单独创建,都免不了会调用 configureAndRefreshWeb Application Context 方法来对已经创建的 WebApplicationContext 实例进行配置及刷新,那么这个步骤又做了哪些工作呢?
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplication
Context 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...
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
// Servlet <= 2.4: resort to name specified in web.xml, if any.
wac.setId(ConfigurableWebApplicationContext.APPLICATION_ CONTEXT_
ID_PREFIX +
ObjectUtils.getDisplayString(sc.getServletContextName()));
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_
ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
}
// Determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(sc);
wac.setParent(parent);
wac.setServletContext(sc);
String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (initParameter != null) {
wac.setConfigLocation(initParameter);
}
customizeContext(sc, wac);
//加载配置文件及整合 parent 到 wac
wac.refresh();
}
无论调用方式如何变化,只要是使用 AlicationContext 所提供的功能最后都免不了使用公共父类 AbstractApplicationContext 提供的 refresh() 进行配置文件加载。
3.刷新
onRefresh 是 FrameworkServlet 类中提供的模板方法,在其子类 DispatcherServlet 中进行了重写,主要用于刷新 Spring 在 Web 功能实现中所必须使用的全局变量。下面我们会介绍它们的初始化过程以及使用场景,而至于具体的使用细节会在稍后的章节中再做详细介绍。
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
//(1) 初始化 MultipartResolver
initMultipartResolver(context);
//(2) 初始化 LocaleResolver
initLocaleResolver(context);
//(3) 初始化 ThemeResolver
initThemeResolver(context);
//(4) 初始化 HandlerMappings
initHandlerMappings(context);
//(5) 初始化 HandlerAdapters
initHandlerAdapters(context);
//(6) 初始化 HandlerExceptionResolvers
initHandlerExceptionResolvers(context);
//(7) 初始化 RequestToViewNameTranslator
initRequestToViewNameTranslator(context);
//(8) 初始化 ViewResolvers
initViewResolvers(context);
//(9) 初始化 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. Commons
MultipartResolver">
<!--该属性用来配置可上传文件的最大 byte 数 -->
<property name="maximumFileSize"><value>100000</value></property>
</bean>
当然,CommonsMultipartResolver 还提供了其他功能用于帮助用户完成上传功能,有兴趣的读者可以进一步查看。
那么 MultipartResolver 就是在 initMultipartResolver 中被加入到 DispatcherServlet 中的。
private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME,
MultipartResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// Default is no multipart resolver.
this.multipartResolver = null;
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate MultipartResolver with name '" +
MULTIPART_RESOLVER_BEAN_NAME +
"': no multipart request handling provided");
}
}
}
因为之前的步骤已经完成了 Spring 中配置文件的解析,所以在这里只要在配置文件注册过都可以通过 ApplicationContext 提供的 getBean 方法来直接获取对应 bean ,进而初始化 MultipartResolver 中的 multipartResolver 变量。
(2)初始化 LocaleResolver。
在 Spring 的国际化配置中一共有 3 种使用方式。
基于 URL 参数的配置。
通过 URL 参数来控制国际化,比如你在页面上加一句<a href="?locale=zh_CN">简体中文</a>来控制项目中使用的国际化参数。而提供这个功能的就是 AcceptHeaderLocaleResolver,默认的参数名为 locale,注意大小写。里面放的就是你的提交参数,比如 en_US、zh_CN 之类的,具体配置如下;
<bean id="localeResolver" class="org.Springframework.web.servlet.i18n. AcceptHeader
LocaleResolver"/>
基于 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 种方式都可以解决国际化的问题,但是,对于 LocalResolver 的使用基础是在 DispatcherServlet 中的初始化。
private void initLocaleResolver(ApplicationContext context) {
try {
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, Locale 确
良 Resolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate LocaleResolver with name '" +
LOCALE_RESOLVER_BEAN_NAME +
"': using default [" + this.localeResolver + "]");
}
}
}
提取配置文件中设置的 LocaleResolver 来初始化 DispatcherServlet 中的 localeResolver 属性。
(3)初始化 ThemeResolver。
在 Web 开发中经常会遇到通过主题 Theme 来控制网页风格,这将进一步改善用户体验。简单地说,一个主题就是一组静态资源(比如样式表和图片),它们可以影响应用程序的视觉效果。Spring 中的主题功能和国际化功能非常类似。构成 Spring 主题功能主要包括如下内容。
主题资源。
org.Springframework.ui.context.ThemeSource 是 Spring 中主题资源的接口,Spring 的主题需要通过 ThemeSource 接口来实现存放主题信息的资源。
org.Springframework.ui.context.support.ResourceBundleThemeSource 是 ThemeSource 接口默认实现类(也就是通过 ResourceBundle 资源的方式定义主题),在 Spring 中的配置如下:
<bean id="themeSource" class="org.Springframework.ui.context.support.ResourceBundle
ThemeSource">
<property name="basenamePrefix" value="com.test. "></property>
</bean>
默认状态下是在类路径根目录下查找相应的资源文件,也可以通过 basenamePrefix 来制定。这样,DispatcherServlet 就会在 com.test 包下查找资源文件。
主题解析器。
ThemeSource 定义了一些主题资源,那么不同的用户使用什么主题资源由谁定义呢?org.Springframework.web.servlet.ThemeResolver 是主题解析器的接口,主题解析的工作便是由它的子类来完成。
对于主题解析器的子类主要有 3 个比较常用的实现。以主题文件 summer.properties 为例。
n FixedThemeResolver 用于选择一个固定的主题。
<bean id="themeResolver" class="org.Springframework.web.servlet.theme.FixedTheme Resolver">
<property name="defaultThemeName" value="summer"/>
</bean>
以上配置的作用是设置主题文件为 summer.properties,在整个项目内固定不变。
o CookieThemeResolver 用于实现用户所选的主题,以 cookie 的形式存放在客户端的机器上,配置如下:
<bean id="themeResolver" class="org.Springframework.web.servlet.theme.CookieThemeResolver">
<property name="defaultThemeName" value="summer"/>
</bean>
pSessionThemeResolver 用于主题保存在用户的 HTTP Session 中。
<bean id="themeResolver" class="org.Springframework.web.servlet.theme.SessionThemeResolver">
<property name="defaultThemeName" value="summer"/>
</bean>
以上配置用于设置主题名称,并且将该名称保存在用户的 HttpSession 中。
q AbstractThemeResolver 是一个抽象类被 SessionThemeResolver 和 FixedThemeResolver 继承,用户也可以继承它来自定义主题解析器。
拦截器。
如果需要根据用户请求来改变主题,那么 Spring 提供了一个已经实现的拦截器 Theme ChangeInterceptor 拦截器了,配置如下:
<bean id="themeChangeInterceptor" class="org.Springframework.web.servlet.theme. Theme
ChangeInterceptor">
<property name="paramName" value="themeName"></property>
</bean>
其中设置用户请求参数名为 themeName,即 URL 为?themeName=具体的主题名称。此外,还需要在 handlerMapping 中配置拦截器。当然需要在 HandleMapping 中添加拦截器。
<property name="interceptors">
<list>
<ref local="themeChangeInterceptor" />
</list>
</property>
了解了主题文件的简单使用方式后,再来查看解析器的初始化工作,与其他变量的初始化工作相同,主题文件解析器的初始化工作并没有任何特别需要说明的地方。
private void initThemeResolver(ApplicationContext context) {
try {
this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.
class);
if (logger.isDebugEnabled()) {
logger.debug("Using ThemeResolver [" + this.themeResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
if (logger.isDebugEnabled()) {
logger.debug(
"Unable to locate ThemeResolver with name '" + THEME_
RESOLVER_BEAN_NAME + "': using default [" +
this.themeResolver + "]");
}
}
}
(4)初始化 HandlerMappings。
当客户端发出 Request 时 DispatcherServlet 会将 Request 提交给 HandlerMapping,然后 HanlerMapping 根据 Web Application Context 的配置来回传给 DispatcherServlet 相应的 Controller。
在基于 SpringMVC 的 Web 应用程序中,我们可以为 DispatcherServlet 提供多个 Handler Mapping 供其使用。DispatcherServlet 在选用 HandlerMapping 的过程中,将根据我们所指定的一系列 HandlerMapping 的优先级进行排序,然后优先使用优先级在前的 HandlerMapping。如果当前的 HandlerMapping 能够返回可用的 Handler,DispatcherServlet 则使用当前返回的 Handler 进行 Web 请求的处理,而不再继续询问其他的 HandlerMapping。否则,DispatcherServlet 将继续按照各个 HandlerMapping 的优先级进行询问,直到获取一个可用的 Handler 为止。初始化配置如下:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
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.Springframework. web.servlet.DispatcherServlet 所 在 目 录 下 的 DispatcherServlet.properties 中 所 定 义 的 org.Springframework.web.servlet.HandlerMapping 的内容来加载默认的 handlerMapping(用户没有自定义 Strategies 的情况下)。
(5)初始化 HandlerAdapters。
从名字也能联想到这是一个典型的适配器模式的使用,在计算机编程中,适配器模式将一个类的接口适配成用户所期待的。使用适配器,可以使接口不兼容而无法在一起工作的类协同工作,做法是将类自己的接口包裹在一个已存在的类中。那么在处理 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。亦可通过如下配置来强制系统只加载 bean name 为“handlerAdapter”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 {
try {
// DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH,
DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'Dispatcher Servlet.
properties': " + ex.getMessage());
}
}
在系统加载的时候,defaultStrategies 根据当前路径 DispatcherServlet.properties 来初始化本身,查看 DispatcherServlet.properties 中对应于 HandlerAdapter 的属性:
org.Springframework.web.servlet.HandlerAdapter=org.Springframework.web.servlet.mvc.Htt
pRequestHandlerAdapter,\
org.Springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.Springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
由此得知,如果程序开发人员没有在配置文件中定义自己的适配器,那么 Spring 会默认加载配置文件中的 3 个适配器。
作为总控制器的派遣器 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 对象。
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.Springframework.stereotype.Component;
import org.Springframework.web.servlet.HandlerExceptionResolver;
import org.Springframework.web.servlet.ModelAndView;
@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");
}
}
这个类必须声明到 Spring 中去,让 Spring 管理它,在 Spring 的配置文件 applicationContext.xml 中增加以下内容:
<bean id="exceptionHandler" class="com.test.exception.MyExceptionHandler"/>
初始化代码如下:
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including
ancestor contexts.
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.
class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<Handler Exception
Resolver>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
OrderComparator.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, Handler
ExceptionResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerExceptionResolvers found in servlet '" +
getServletName() + "': using default");
}
}
}
(7)初始化 RequestToViewNameTranslator。
当 Controller 处理器方法没有返回一个 View 对象或逻辑视图名称,并且在该方法中没有直接往 response 的输出流里面写数据的时候,Spring 就会采用约定好的方式提供一个逻辑视图名称。这个逻辑视图名称是通过 Spring 定义的 org.Springframework.web.servlet.RequestToView NameTranslator 接口的 getViewName 方法来实现的,我们可以实现自己的 Request ToViewName Translator 接口来约定好没有返回视图名称的时候如何确定视图名称。Spring 已经给我们提供了一个它自己的实现,那就是 org.Springframework.web.servlet.view.DefaultRequest ToViewName Translator。
在介绍 DefaultRequestToViewNameTranslator 是如何约定视图名称之前,先来看一下它支持用户定义的属性。
prefix:前缀,表示约定好的视图名称需要加上的前缀,默认是空串。
suffix:后缀,表示约定好的视图名称需要加上的后缀,默认是空串。
separator:分隔符,默认是斜杠“/”。
stripLeadingSlash:如果首字符是分隔符,是否要去除,默认是 true。
stripTrailingSlash:如果最后一个字符是分隔符,是否要去除,默认是 true。
stripExtension:如果请求路径包含扩展名是否要去除,默认是 true。
urlDecode:是否需要对 URL 解码,默认是 true。它会采用 request 指定的编码或者 ISO-8859-1 编码对 URL 进行解码。
当我们没有在 SpringMVC 的配置文件中手动的定义一个名为 viewNameTranlator 的 Bean 的时候,Spring 就会为我们提供一个默认的 viewNameTranslator,即 DefaultRequest ToViewName Translator。
接下来看一下,当 Controller 处理器方法没有返回逻辑视图名称时,DefaultRequestToView NameTranslator 是如何约定视图名称的。DefaultRequestToViewNameTranslator 会获取到请求的 URI,然后根据提供的属性做一些改造,把改造之后的结果作为视图名称返回。这里以请求路径 http://localhost/app/test/index.html 为例,来说明一下 DefaultRequestToViewNameTranslator 是如何工作的。该请求路径对应的请求 URI 为/test/index.html,我们来看以下几种情况,它分别对应的逻辑视图名称是什么。
prefix 和 suffix 如果都存在,其他为默认值,那么对应返回的逻辑视图名称应该是 prefixtest/indexsuffix。
stripLeadingSlash 和 stripExtension 都为 false,其他默认,这时候对应的逻辑视图名称是/product/index.html。
都采用默认配置时,返回的逻辑视图名称应该是 product/index。
如果逻辑视图名称跟请求路径相同或者相关关系都是一样的,那么我们就可以采用 Spring 为我们事先约定好的逻辑视图名称返回,这可以大大简化我们的开发工作,而以上功能实现的关键属性 viewNameTranslator,则是在 initRequestToViewNameTranslator 中完成。
private void initRequestToViewNameTranslator(ApplicationContext context) {
try {
this.viewNameTranslator =
context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME,
RequestToViewNameTranslator.class);
if (logger.isDebugEnabled()) {
logger.debug("Using RequestToViewNameTranslator [" + this.viewName
Translator + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.viewNameTranslator = getDefaultStrategy(context, RequestToViewName
Translator.class);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate RequestToViewNameTranslator with name '" +
REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME + "': using default
[" + this.viewNameTranslator +
"]");
}
}
}
(8) 初始化 ViewResolvers。
在 SpringMVC 中,当 Controller 将请求处理结果放入到 ModelAndView 中以后, DispatcherServlet 会根据 ModelAndView 选择合适的视图进行渲染。那么在 SpringMVC 中是如何选择合适的 View 呢?View 对象是是如何创建的呢?答案就在 ViewResolver 中。ViewResolver 接口定义了 resolverViewName 方法,根据 viewName 创建合适类型的 View 实现。
那么如何配置 ViewResolver 呢?在 Spring 中,ViewResolver 作为 Spring Bean 存在,可以在 Spring 配置文件中进行配置,例如下面的代码,配置了 JSP 相关的 viewResolver。
<bean class="org.Springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
viewResolvers 属性的初始化工作在 initViewResolvers 中完成。
private void initViewResolvers(ApplicationContext context) {
this.viewResolvers = null;
if (this.detectAllViewResolvers) {
// Find all ViewResolvers in the ApplicationContext, including ancestor
contexts.
Map<String, ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context,
ViewResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.
values());
// We keep ViewResolvers in sorted order.
OrderComparator.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.isDebugEnabled()) {
logger.debug("No ViewResolvers found in servlet '" + getServletName()
+ "': using default");
}
}
}
(9)初始化 FlashMapManager。
SpringMVC Flash attributes 提供了一个请求存储属性,可供其他请求使用。在使用重定向时候非常必要,例如 Post/Redirect/Get 模式。Flash attributes 在重定向之前暂存(就像存在 session 中)以便重定向之后还能使用,并立即删除。
SpringMVC 有两个主要的抽象来支持 flash attributes。FlashMap 用于保持 flash attributes,而 FlashMapManager 用于存储、检索、管理 FlashMap 实例。
flash attribute 支持默认开启("on")并不需要显式启用,它永远不会导致 HTTP Session 的创建。这两个 FlashMap 实例都可以通过静态方法 RequestContextUtils 从 Spring MVC 的任何位置访问。
flashMapManager 的初始化在 initFlashMapManager 中完成。
private void initFlashMapManager(ApplicationContext context) {
try {
this.flashMapManager =
context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);
if (logger.isDebugEnabled()) {
logger.debug("Using FlashMapManager [" + this.flashMapManager + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.flashMapManager = getDefaultStrategy(context, FlashMapManager.
class);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate FlashMapManager with name '" +
FLASH_MAP_MANAGER_BEAN_NAME + "': using default [" +
this.flashMapManager + "]");
}
}
}
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论