(三)SpringMVC原理解析之启动源码分析

目录

一、Web.xml文件读取流程

二、UML类图

三、源码解析之DispatcherServlet类

1.GenericServlet类

2.HttpServletBean类

3.FrameworkServlet类

4.DispatcherServlet类

4.1 DispatcherServlet.properties文件

4.2 initMultipartResolver方法

4.3 initLocaleResolver方法

4.4 getDefaultStrategy方法和getDefaultStrategies方法

4.5 initHandlerMappings方法

4.6 类似其它几个方法


一、Web.xml文件读取流程

在进入SpringMVC的源码分析之前,我们根据Web.xml文件配置来了解一下Tomcat的启动流程:

  1. 根据context-param节点初始化ServletContext,当初始化ServletContext之后将会调用Listener;
  2. 读取listener节点值,Spring的监听便是在此流程完成;
  3. 读取filter节点值,常见的struts便是基于拦截器初始化完成的;
  4. 读取servlet节点值,读取完该节点的值后调用serlvet的init方法,初始化serlvet,SpringMVC便是指定serlvet的子类DispatcherServlet,在init方法中切入完成SpringMVC的初始化。

二、UML类图

从图中我们可以清晰的一下看见几条脉络:

  1. ServletConfig-DispatcherServlet这一条继承线,同时从图中也可以看到DispatcherServlet包含了图中很多类的实例,很多关系和它相关,因此从图中可以大致推断该类是SpringMVC的核心类,当然从web.xml配置文件也可以看得出该类确实是SpringMVC实现的关键;
  2. Model、View和ModelAndView体系,可以看出View和Model自成体系,SpringMVC是通过ModelAndView类将Model和View两个体系关联起来的,并且很多Resolver都是使用的该类,而不是直接使用Model和View;
  3. HandlerMapping体系,该体系是为了解决@Controller、@RequestMapping等类似serlvet-name标签以及serlvet-mapping标签,标明具体解决路径的类(即controller);
  4. HandlerAdapter接口是SpringMVC框架为了和其他框架和本框架集成以及使用注解驱动开发而创建的,原理是使用适配器模式将其他的handler对象经过方法handle后转变成ModelAndView对象,从而完成注解驱动以及和其它框架的集成;
  5. HandlerExceptionResolver接口,用来统一处理SpringMVC处理过程中所抛出的异常和error jsp现象;
  6. 其它各类的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方法的流程:

  1. 获得tomcat解析得到的SerlvetConfig,并将其用ServletConfigPropertValues封装成PropertyValues,封装流程如下:
    1. 先从HttpServletBean获得requiredProperties集合属性,该集合是用来判断必须存在的属性是否存在,如果不存在会抛出异常;
    2. 调用ServletConfig的getInitParameterNames方法,获得init-param标签中子标签param-name的值当作key,随后使用该key调用方法getInitParameter,获得具体的value,并将key和value封装成PropertyValue对象并添加进propertyValueList集合,在随后流程中被用到;
  2. 使用BeanWrapper类封装HttpSerlvetBean的实现类,调用setPropertyValues将ServletConfig封装过后的PropertyValues对象中的值赋值给实现类相应的字段,如实例中有contextConfigLocation值,将会对应实现类的该字段,并把其对应的value赋给该字段;
  3. 进行完上两步,接下来将会调用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>();
   }
}

可以看到该方法的流程如下:

  1. 从defaultStrategies对象中拿取strategyInterface策略接口所对应的的key,即文件中左侧key;
  2. 根据key获得文件中对应的value;
  3. 根据value获得其对应的className类名称数组;
  4. 根据数组一个一个的创建实例化对象并添加进对象strategies;
  5. 返回对象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方法的流程便已经全部走完。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值