目录
4.1 DispatcherServlet.properties文件
4.4 getDefaultStrategy方法和getDefaultStrategies方法
一、Web.xml文件读取流程
在进入SpringMVC的源码分析之前,我们根据Web.xml文件配置来了解一下Tomcat的启动流程:
- 根据context-param节点初始化ServletContext,当初始化ServletContext之后将会调用Listener;
- 读取listener节点值,Spring的监听便是在此流程完成;
- 读取filter节点值,常见的struts便是基于拦截器初始化完成的;
- 读取servlet节点值,读取完该节点的值后调用serlvet的init方法,初始化serlvet,SpringMVC便是指定serlvet的子类DispatcherServlet,在init方法中切入完成SpringMVC的初始化。
二、UML类图
从图中我们可以清晰的一下看见几条脉络:
- ServletConfig-DispatcherServlet这一条继承线,同时从图中也可以看到DispatcherServlet包含了图中很多类的实例,很多关系和它相关,因此从图中可以大致推断该类是SpringMVC的核心类,当然从web.xml配置文件也可以看得出该类确实是SpringMVC实现的关键;
- Model、View和ModelAndView体系,可以看出View和Model自成体系,SpringMVC是通过ModelAndView类将Model和View两个体系关联起来的,并且很多Resolver都是使用的该类,而不是直接使用Model和View;
- HandlerMapping体系,该体系是为了解决@Controller、@RequestMapping等类似serlvet-name标签以及serlvet-mapping标签,标明具体解决路径的类(即controller);
- HandlerAdapter接口是SpringMVC框架为了和其他框架和本框架集成以及使用注解驱动开发而创建的,原理是使用适配器模式将其他的handler对象经过方法handle后转变成ModelAndView对象,从而完成注解驱动以及和其它框架的集成;
- HandlerExceptionResolver接口,用来统一处理SpringMVC处理过程中所抛出的异常和error jsp现象;
- 其它各类的Resolver,从名字也可以看出来其大致针对的目标,如ViewResolver就是针对View对象来解析的,其它的Resolver都是类似的。
三、源码解析之DispatcherServlet类
从我们可以直观看到的web.xml配置开始分析,切入点便是DispatcherServlet,从上一篇的分析可以知道读取完serlvet-name标签的配置后将会调用serlvet的init方法,从UML类图和该类的名字可以看到,DispatcherServlet也是一个Serlvet。
1.GenericServlet类
首先看到GenericServlet这个实现了Serlvet接口和SerlvetConfig接口的超实现子类,源码如下:
public abstract class GenericServlet
implements Servlet, ServletConfig, java.io.Serializable {
private static final String LSTRING_FILE =
"javax.servlet.LocalStrings";
private static ResourceBundle lStrings =
ResourceBundle.getBundle(LSTRING_FILE);
private transient ServletConfig config;
public GenericServlet() { }
public void destroy() {
}
public String getInitParameter(String name) {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getInitParameter(name);
}
public Enumeration<String> getInitParameterNames() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getInitParameterNames();
}
public ServletConfig getServletConfig() {
return config;
}
public ServletContext getServletContext() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getServletContext();
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletName() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getServletName();
}
}
可以从该超类看到,tomcat读取完servlet-name标签后,会首先调用init(ServletConfig)方法,随将SerlvetConfig赋值之后再调用init()方法来完成实质性的初始化,SerlvetConfig此类会是Tomcat读取分析完web.xml而得到的配置类,随后的初始化将会用到其中的配置。
2.HttpServletBean类
接下来再看到DispatcherServlet的父类HttpServletBean,该类继承了JDK的HttpSerlvet,并重写了init方法,该类的源码如下:
public abstract class HttpServletBean extends HttpServlet
implements EnvironmentCapable, EnvironmentAware {
protected final Log logger = LogFactory.getLog(getClass());
private final Set<String> requiredProperties = new HashSet<String>();
private ConfigurableEnvironment environment;
protected final void addRequiredProperty(String property) {
this.requiredProperties.add(property);
}
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
try {
// ServletConfig保存了DispatcherServlet的一些配置项,通过pvs对象和bw
// 对象将可以完成配置项的绑定,诸如init-param这些
PropertyValues pvs = new ServletConfigPropertyValues(
getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory
.forBeanPropertyAccess(this);
ResourceLoader resourceLoader =
new ServletContextResourceLoader(getServletContext());
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;
}
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() +
"' configured successfully");
}
}
protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
// 交给子类实现
}
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = this.createEnvironment();
}
return this.environment;
}
protected ConfigurableEnvironment createEnvironment() {
return new StandardServletEnvironment();
}
private static class ServletConfigPropertyValues
extends MutablePropertyValues {
public ServletConfigPropertyValues(ServletConfig config,
Set<String> requiredProperties)
throws ServletException {
Set<String> missingProps = (requiredProperties != null &&
!requiredProperties.isEmpty()) ?
new HashSet<String>(requiredProperties) : null;
Enumeration<String> en = config.getInitParameterNames();
while (en.hasMoreElements()) {
String property = en.nextElement();
// 获得init-param配置项
Object value = config.getInitParameter(property);
// 进行绑定
addPropertyValue(new PropertyValue(property, value));
if (missingProps != null) {
missingProps.remove(property);
}
}
if (missingProps != null && missingProps.size() > 0) {
throw new ServletException("..."));
}
}
}
}
可以看到在该类中init方法的流程:
- 获得tomcat解析得到的SerlvetConfig,并将其用ServletConfigPropertValues封装成PropertyValues,封装流程如下:
- 先从HttpServletBean获得requiredProperties集合属性,该集合是用来判断必须存在的属性是否存在,如果不存在会抛出异常;
- 调用ServletConfig的getInitParameterNames方法,获得init-param标签中子标签param-name的值当作key,随后使用该key调用方法getInitParameter,获得具体的value,并将key和value封装成PropertyValue对象并添加进propertyValueList集合,在随后流程中被用到;
- 使用BeanWrapper类封装HttpSerlvetBean的实现类,调用setPropertyValues将ServletConfig封装过后的PropertyValues对象中的值赋值给实现类相应的字段,如实例中有contextConfigLocation值,将会对应实现类的该字段,并把其对应的value赋给该字段;
- 进行完上两步,接下来将会调用initServletBean方法,在HttpServletBean中,该方法为空,需要实现子类去重写。
至此,HttpServletBean的功能就已经完成了,此类的主要功能便是将ServletConfig对象解析读取web.xml的serlvet-name标签的init-param初始化值给赋值到实现类的字段上。
3.FrameworkServlet类
接下来看到HttpServletBean的子类FrameworkServlet,其部分关键源码如下:
public abstract class FrameworkServlet extends HttpServletBean
implements ApplicationContextAware {
/**
* WebApplicationContext名称空间的后缀。
* 如果这个类的servlet在上下文中被命名为“test”
* servlet使用的命名空间将解析为“test-servlet”。
*/
public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";
/* FrameworkServlet默认的程序上下文class类型 */
public static final Class<?> DEFAULT_CONTEXT_CLASS =
XmlWebApplicationContext.class;
/* WebApplicationContext的ServletContext属性的前缀。完整是servlet名称。 */
public static final String SERVLET_CONTEXT_PREFIX =
FrameworkServlet.class.getName() + ".CONTEXT.";
/* init-param标签多个值分割符号 */
private static final String INIT_PARAM_DELIMITERS = ",; \t\n";
/* 检查serlvet3.0+的HttpServletResponse.getStatus方法 */
private static final boolean responseGetStatusAvailable =
ClassUtils.hasMethod(HttpServletResponse.class, "getStatus");
private String contextAttribute;
/* 要创建的WebApplicationContext实现类 */
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
/* 要分配的WebApplicationContext id */
private String contextId;
/* 要分配的WebApplicationContext namespace */
private String namespace;
/* 显式上下文配置位置 */
private String contextConfigLocation;
/* 实例应用于上下文的ApplicationContextInitializer */
private final
List<ApplicationContextInitializer<ConfigurableApplicationContext>>
contextInitializers =
new ArrayList
<ApplicationContextInitializer
<ConfigurableApplicationContext>>();
/* 通过init-param标签指定的ApplicationContextInitializer类 */
private String contextInitializerClasses;
/* 是否应该将上下文作为ServletContext属性发布 */
private boolean publishContext = true;
/* 是否应该在每个请求结束时发布一个ServletRequestHandledEvent */
private boolean publishEvents = true;
/* 是否将LocaleContext和RequestAttributes作为可继承的子线程 */
private boolean threadContextInheritable = false;
/* 是否向doService发送一个HTTP option请求 */
private boolean dispatchOptionsRequest = false;
/* 是否向doService发送一个HTTP trace请求 */
private boolean dispatchTraceRequest = false;
/* servlet中用到的web程序上下文 */
private WebApplicationContext webApplicationContext;
/* WebApplicationContext是否通过setApplicationContext方法注入的 */
private boolean webApplicationContextInjected = false;
/* 检测本类中onRefresh方法是否被调用 */
private boolean refreshEventReceived = false;
public FrameworkServlet() {
}
public FrameworkServlet(WebApplicationContext webApplicationContext) {
this.webApplicationContext = webApplicationContext;
}
}
该类的代码比较多,执行的操作也比较多,让我们一步一步来分析,首先可以看到initServletBean方法,该方法是在HttpServletBean中的init方法调用的,可以看到该方法实际上除了各种日志打印和时间记录,重要的只有两行代码,一个是调用initWebApplicationContext来初始化本类中的webApplicationContext成员属性,实现Spring容器初始化;一个是调用initFrameworkServlet方法来对FrameworkServlet来实现初始化,该方法用了模板设计模式,提供给程序进行扩展。
那么现在来看到initWebApplicationContext方法的具体实现流程,方法代码如下:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils
.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 一般webApplicationContext属性不为空大概率是构造函数传入的
// 因此就可以直接将webApplicationContext赋给wac
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac =
(ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// 如果cwac已经初始化过,isActive一定为true,进入到该语句中则代表
// cwac还没有初始化过,因此需要调用初始化方法
if (cwac.getParent() == null) {
// 如果cwac父程序上下文不存在则尝试用rootContext
// 作为它的父程序上下文
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 先尝试使用已经初始化过的上下文来赋值,如果有则使用已经初始化过的上下文
// 如果没有则返回null,继续下面的流程
wac = findWebApplicationContext();
}
if (wac == null) {
// 没有上下文实例则创建web上下文实例
wac = createWebApplicationContext(rootContext);
}
// refreshEventReceived标识是判断onRefresh方法是否被调用,如果被调用为true
// 注意,该onRefresh是FrameworkServlet或者其子类的方法, 而不是程序上下文
if (!this.refreshEventReceived) {
// 如果onRefresh方法没有被调用,则调用onRefresh方法
// 而在初始化的时候该方法一定会被调用,因为refreshEventReceived变量
// 默认值是false
onRefresh(wac);
}
// publishContext为true则代表将程序上下文作为ServletContext的属性发布出去
if (this.publishContext) {
// 将上下文发布在ServletContext的属性中
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
在代码中已经将流程写的很清楚了,但在第一篇中我们的配置而言,webApplicationContext属性是为null的,因此会调用findWebApplicationContext方法,但显然该方法会返回null,因此后面会调用createWebApplicationContext方法来初始化程序上下文,而后面调用完该方法refreshEventReceived属性肯定为true,因此后面只会将上下文发布到ServletContext中的属性中。
接下来仔细看createWebApplicationContext方法,其源码如下:
protected WebApplicationContext createWebApplicationContext(
ApplicationContext parent) {
// getContextClass将会使用DEFAULT_CONTEXT_CLASS成员属性
// 该成员属性从源码可以看到是XmlWebApplicationContext类型的
Class<?> contextClass = getContextClass();
// 判断如果不是ConfigurableWebApplicationContext类则抛出异常
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");
}
// 根据class来具体使用反射实例化class对象
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils
.instantiateClass(contextClass);
// 设置wac必须的属性,getContextConfigLocation方法调用的成员属性
// 就是在HttpServletBean中完成赋值的
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
// 调用真正的准备初始化方法
configureAndRefreshWebApplicationContext(wac);
return wac;
}
其关键性代码基本上只有四行,getContextClass方法确定了ApplicationContext的类型,wac.setEnvironment(getEnvironment())此方法设置了环境变量,而wac.setConfigLocation方法则是将web.xml中servlet-name标签的contextConfigLocation值赋值给applicationContext,确定读取xml文件的位置,最后调用configureAndRefreshWebApplicationContext方法,进行实质性的初始化程序上下文操作。
接下来看到方法configureAndRefreshWebApplicationContext,其源码如下:
protected void configureAndRefreshWebApplicationContext
(ConfigurableWebApplicationContext wac) {
// 最开始id会被默认初始化为identityToString方法的值
// 因此在该配置下肯定会相等进入判断条件执行语句
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 如果contextId成员属性被赋值了则用contextId
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// 否则使用默认生成id
wac.setId(ConfigurableWebApplicationContext
.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext()
.getContextPath()) + '/' + getServletName());
}
}
// 设置相关ServletContext、ServletConfig、以及Listener
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac,
new ContextRefreshListener()));
// 在任何情况下,当上下文被刷新时,都会调用wac环境的#initPropertySources;
// 确保servlet属性源在#refresh之前的任何postProcessor或初始化中都处于适当
// 的位置,getEnvironment会创建StandardEnvironment,该类实现了
// ConfigurableWebEnvironment接口
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env)
.initPropertySources(getServletContext(), getServletConfig());
}
// 模板方法,提供程序扩展性
postProcessWebApplicationContext(wac);
// 调用实现了ApplicationContextInitializer类
// 一般在SpringMVC中被配置在globalInitializerClasses属性中
applyInitializers(wac);
// 调用Spring初始化容器核心方法,开始初始化Spring容器
wac.refresh();
}
在该方法中值得注意的有一个方法applyInitializers,该方法会读取ServletContext中InitParameter属性的globalInitializerClasses值,并将其实现了ApplicationContextInitializer接口的类实例化并一一添加进contextInitializers集合,最后依次调用其initialize。
而ConfigurableWebApplicationContext对象的refresh方法不做过多描述,在以往的篇幅已经讲过。
ApplicationContextInitializer接口是Spring容器在进行onRefresh方法执行的,在普通的Web项目启动Spring时读取的值是contextInitializerClasses。同时如果该类被@Order注解了,会根据其优先级来按不同顺序执行。
调用完createWebApplicationContext方法后,程序上下文已经被初始化了,接下来将会调用onRefresh。
4.DispatcherServlet类
接下来方法调用栈终于跑到了DispatcherServlet类中了,原来都是在该类的父类中调用来调用去的,现在终于轮到它登场了。部分关键代码源码如下:
public class DispatcherServlet extends FrameworkServlet {
/* multipartResolver命名空间 */
public static final String MULTIPART_RESOLVER_BEAN_NAME =
"multipartResolver";
/* localeResolver命名空间 */
public static final String LOCALE_RESOLVER_BEAN_NAME =
"localeResolver";
/* themeResolver命名空间 */
public static final String THEME_RESOLVER_BEAN_NAME =
"themeResolver";
/* handlerMapping命名空间 */
public static final String HANDLER_MAPPING_BEAN_NAME =
"handlerMapping";
/* handlerAdapter命名空间 */
public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
/* handlerExceptionResolver命名空间 */
public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME =
"handlerExceptionResolver";
/* viewNameTranslator命名空间 */
public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME =
"viewNameTranslator";
/* viewResolver命名空间 */
public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
/* flashMapManager命名空间 */
public static final String FLASH_MAP_MANAGER_BEAN_NAME =
"flashMapManager";
/* 在请求属性中用来保存当前程序上下文 */
public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE =
DispatcherServlet.class.getName() + ".CONTEXT";
/* 在请求属性中用来保存当前LOCALE_RESOLVER */
public static final String LOCALE_RESOLVER_ATTRIBUTE =
DispatcherServlet.class.getName() + ".LOCALE_RESOLVER";
/* 在请求属性中用来保存当前THEME_RESOLVER */
public static final String THEME_RESOLVER_ATTRIBUTE =
DispatcherServlet.class.getName() + ".THEME_RESOLVER";
/* 在请求属性中用来保存当前THEME_SOURCE */
public static final String THEME_SOURCE_ATTRIBUTE =
DispatcherServlet.class.getName() + ".THEME_SOURCE";
/**
* 保存只读映射的请求属性的名称,该映射包含前一个请求保存的
* “input”flash属性(如果有的话)。
*/
public static final String INPUT_FLASH_MAP_ATTRIBUTE =
DispatcherServlet.class.getName() + ".INPUT_FLASH_MAP";
/* 保存带有属性的“输出”FlashMap以供后续请求保存的请求属性的名称。 */
public static final String OUTPUT_FLASH_MAP_ATTRIBUTE =
DispatcherServlet.class.getName() + ".OUTPUT_FLASH_MAP";
/* 在请求属性中用来保存当前FLASH_MAP_MANAGER */
public static final String FLASH_MAP_MANAGER_ATTRIBUTE =
DispatcherServlet.class.getName() + ".FLASH_MAP_MANAGER";
/* 在请求属性中用来保存当前EXCEPTION */
public static final String EXCEPTION_ATTRIBUTE =
DispatcherServlet.class.getName() + ".EXCEPTION";
/* 当未找到请求的映射处理程序时使用的日志类别。 */
public static final String PAGE_NOT_FOUND_LOG_CATEGORY =
"org.springframework.web.servlet.PageNotFound";
/**
* 相对于DispatcherServlet类路径资源
* 它定义了DispatcherServlet的默认策略名。
*/
private static final String DEFAULT_STRATEGIES_PATH =
"DispatcherServlet.properties";
protected static final Log pageNotFoundLogger =
LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
private static final Properties defaultStrategies;
static {
try {
// 从属性文件加载默认策略实现。该文件在webmvc包和DispatcherServlet
// 同一层文件夹下,里面包含了各个属性的默认实现。
// DispatcherServlet.properties文件是官方定的默认策略
// 并不意味着适用于每个项目。
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());
}
}
/* 检测所有的handlerMapping还是仅仅指明的handlerMapping bean */
private boolean detectAllHandlerMappings = true;
/* 检测所有的handlerAdapter还是仅仅指明的handlerAdapter bean */
private boolean detectAllHandlerAdapters = true;
/* 检测所有的ExceptionResolvers还是仅仅指明的ExceptionResolvers bean */
private boolean detectAllHandlerExceptionResolvers = true;
/* 检测所有的ViewResolvers还是仅仅指明的ViewResolvers bean */
private boolean detectAllViewResolvers = true;
/* 如果没有找到处理程序来处理此请求是否抛出NoHandlerFoundException */
private boolean throwExceptionIfNoHandlerFound = false;
/* 在包含请求之后执行请求属性的清理 */
private boolean cleanupAfterInclude = true;
/* 此servlet使用的MultipartResolver */
private MultipartResolver multipartResolver;
/* 此servlet使用的LocaleResolver */
private LocaleResolver localeResolver;
/* 此servlet使用的ThemeResolver */
private ThemeResolver themeResolver;
/* 此servlet使用的HandlerMapping*/
private List<HandlerMapping> handlerMappings;
/* 此servlet使用的HandlerAdapter*/
private List<HandlerAdapter> handlerAdapters;
/* 此servlet使用的HandlerExceptionResolver */
private List<HandlerExceptionResolver> handlerExceptionResolvers;
/* 此servlet使用的RequestToViewNameTranslator */
private RequestToViewNameTranslator viewNameTranslator;
/* 此servlet使用的FlashMapManager */
private FlashMapManager flashMapManager;
/* 此servlet使用的ViewResolver */
private List<ViewResolver> viewResolvers;
/* 在方法setContextConfigLocation时被调用 */
public DispatcherServlet() {
super();
setDispatchOptionsRequest(true);
}
public DispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
setDispatchOptionsRequest(true);
}
}
在分析FrameworkServlet时我们已经分析到onRefresh方法来了,该方法被DispatcherServlet重写了,具体方法源码如下:
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
可以看到onRefresh调用了initStrategies方法,initStrategies方法中是一个门面方法,只记录了各个方法的具体调用顺序,没有其它的操作。
4.1 DispatcherServlet.properties文件
在分析initStrategies方法前,我们需要先了解一个文件,该文件便是在DispatcherServlet类中的成员属性值DEFAULT_STRATEGIES_PATH,在该类中的静态代码块中,读取了该名称的文件,文件内容如下所示:
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
读取该文件的静态代码块如下:
static {
try {
// 从属性文件加载默认策略实现。该文件在webmvc包和DispatcherServlet
// 同一层文件夹下,里面包含了各个属性的默认实现。
// DispatcherServlet.properties文件是官方定的默认策略
// 并不意味着适用于每个项目。
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());
}
}
如代码所示,读取完该文件后会调用PropertiesLoaderUtils.loadProperties方法将其具体对应的值放到defaultStrategies对象中,该对象在后续的初始化将会被一直用到。
4.2 initMultipartResolver方法
方法是用来初始化成员对象multipartResolver的,而multipartResolver对象是用来解析封装文件的,该方法过程很简单,源码如下:
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");
}
}
}
可以看到该方法的流程是先是调用context上下文的getBean方法,查找上下文是否含有成员属性MULTIPART_RESOLVER_BEAN_NAME(即multipartResolver)名称的类,如果有则将其拿出来赋给multipartResolver,若没有则让其等于空。其它几个init方法基本上都是如此,先从上下文中获取对应的类,如果有就使用上下文中的类,如果没有则使用默认的或为空。
4.3 initLocaleResolver方法
该方法是用来初始化localeResolver成员对象的,其对象的主要作用是根据request的属性来提供某种本地策略修改request和response,其源码如下:
private void initLocaleResolver(ApplicationContext context) {
try {
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME,
LocaleResolver.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 + "]");
}
}
}
可以看到该方法的大致流程其实和方法initMultipartResolver差不多,只是在catch块中localeResolver对象的值为getDefaultStrategy方法的返回值,不直接为空。
4.4 getDefaultStrategy方法和getDefaultStrategies方法
该方法顾名思义是根据传进去的接口获得相应的默认策略,其源码如下:
protected <T> T getDefaultStrategy(ApplicationContext context,
Class<T> strategyInterface) {
List<T> strategies = getDefaultStrategies(context, strategyInterface);
if (strategies.size() != 1) {
throw new BeanInitializationException(
"DispatcherServlet needs exactly 1 strategy for interface [" +
strategyInterface.getName() + "]");
}
return strategies.get(0);
}
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>();
}
}
可以看到该方法的流程如下:
- 从defaultStrategies对象中拿取strategyInterface策略接口所对应的的key,即文件中左侧key;
- 根据key获得文件中对应的value;
- 根据value获得其对应的className类名称数组;
- 根据数组一个一个的创建实例化对象并添加进对象strategies;
- 返回对象strategies。
getDefaultStrategy方法实际调用的是getDefaultStrategies方法,只是getDefaultStrategy方法返回的是单独的一个对象,而getDefaultStrategies方法返回的是对象数组,从方法名称上也能看出来。
4.5 initHandlerMappings方法
该方法是用来初始化成员对象handlerMappings的,该成员对象的作用是在请求和处理对象之间建立映射关系,该对象的类型是一个数组。方法源码如下:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
// 如果detectAllHandlerMappings为true则检测出所有的,false则只用单独一个
if (this.detectAllHandlerMappings) {
// 先根据HandlerMapping接口找到上下文中已存在的对象
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context,
HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans
.values());
// 根据Order接口排序
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME,
HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// 忽略,默认的handlerMapping在后面会处理
}
}
// 设置默认的handlerMapping
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context,
HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" +
getServletName() + "': using default");
}
}
}
4.6 类似其它几个方法
在initStrategies方法中还剩下六个方法未贴源码,但是这些方法的大致流程和已经贴过的三个方法类似,如下:
- initThemeResolver方法,用来初始化themeResolver成员对象,其作用为根据request和response解析主题和修改主题,方法流程类似于initLocaleResolver方法;
- initHandlerAdapters方法,用来初始化handlerAdapters数组对象,其作用为MVC框架SPI,允许参数化的核心MVC工作流,方法流程类似于initHandlerMappings方法;
- initHandlerExceptionResolvers方法,用来初始化handlerExceptionResolvers数组对象的,其作用为处理在handler mapping或者执行过程中抛出来的异常i,方法流程类似于nitHandlerMappings方法;
- initRequestToViewNameTranslator方法,用来初始化viewNameTranslator对象,其作用为如果相应的视图没有找到,则跳转到request转成的逻辑视图,流程类似于initLocaleResolver方法;
- initViewResolvers方法,用来初始化viewResolvers数组对象,其作用为可根据名称解析成对应的视图,方法流程类似于initHandlerMappings方法;
- initFlashMapManager方法,用来初始化flashMapManager成员对象,其作用为保存和检索FlashMap的实例对象,FlashMap的作用便是在重定向前保存Post/Redirect/Get方法的request属性,方法流程类似于initLocaleResolver方法。
至此,initStrategies方法的流程便已经全部走完。