详解Spring MVC:上

 本章将分析Spring MVC自身的创建过程。首先分析Spring MVC的整体结构,然后具体分析每一层的创建过程。

9.1 整体结构介绍

 Spring MVC中核心Servlet的继承结构如下图所示。

Spring MVC核心Servlet结构图.png

 可以看到在Servlet的继承结构中有一共有5个类,GenericServlet和HttpServlet在java中,前面已经讲过,剩下的三个类HttpServletBean、FrameworkServlet和DispatcherServlet是Spring MVC中的,本章主要讲解这三个类的创建过程。
 这三个类直接实现三个接口:EnvironmentCapable、EnvironmentAware和ApplicationContextAware。XXXWare在Spring里表示对XXX可以感知,通俗点解释就是:如果在某个类里面想要使用spring的一些东西,就可以通过实现XXXWare接口告诉Spring,Spring看到后就会给你送过来,而接收的方式是通过实现接口唯一的方法set-XXX。比如,有一个类想要适应当前的ApplicationContext,那么我们只需要让它实现ApplicationContextAware接口,然后实现接口中唯一的方法void setApplicationContext (ApplicationContext applicationContext)就可以了,Spring会自动调用这个方法将applicationContext传给我们,我们只需要接受就可以了!EnvironmentCapable,顾名思义,当然就是具有Environment的能力,也就是提供Environment,所以EnvironmentCapable唯一的方法是Environment getEnvironment(),用于实现EnvironmentCapable接口的类,就是告诉Spring它可以提供Environment,当Spring需要Environment的时候就会调用其getEnvironment方法跟它要。
 了解了Aware和Capable的意思,下面再来看一个ApplicationContext和Environment。前者详细大家都很熟悉了,后者是环境的意思,具体功能与之前讲过的ServletContext有点类似。实际上在HttpServletBean中Environment使用的是Standard-Servlet-Environment(在createEnvironment方法中创建),这里确实封装了ServletContext,同时还封装了ServletConfig、JndiProperty、系统环境变量和系统属性,这些都封装到了其propertySources属性下。为了让大家理解得更深刻,在前面项目的GoController中获取Environment,然后通过调试看一下。首先让GoController实现EnvironmentAware接口,这样Spring就会将Environment传给我们,然后在处理请求的方法中加入断点,这样就可以查看Spring传进来的内容了。修改后代码如下:

/**
 * EnvironmentAware接口测试
 *
 * @version
 * @author kyle 2018年8月26日上午10:34:04
 * @since 1.8
 */
@Controller
public class GoController implements EnvironmentAware {
    private final Logger logger = LoggerFactory.getLogger(GoController.class);

    private Environment environment = null;

    @RequestMapping(value = { "/" }, method = { RequestMethod.HEAD })
    public String head() {
        return "go.jsp";
    }

    @RequestMapping(value = { "/index", "/" }, method = { RequestMethod.GET })
    public String index(Model model) throws Exception {
        logger.info("==========processed by index=========");
        // 这里设置断点
        model.addAttribute("msg", "Go Go Go!");
        return "go.jsp";
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

}

9.2 HttpServletBean

 通过前面对Sevlet的分析,我们知道Servlet创建时可以直接调用无参数的init方法。HttpServletBean的init方法如下:

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
    ...
    @Override
    public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }

        // Set bean properties from init parameters.
        try {
            //将Servlet中配置的参数封装到pvs变量中,requiredProperties为必需参数,如果没有配置将报异常
            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()));
            //模版方法,可以在子类调用,做一些初始化工作。bw代表DispatcherServlet
            initBeanWrapper(bw);
            //将配置对初始化值(如contextConfigLocation)设置DispatcherServlet
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            if (logger.isErrorEnabled()) {
                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");
        }
    }
    ...
}

 可以看到,在HttpServletBean的init中,首先将Servlet中配置对参数使用BeanWrapper设置到DispatcherServlet的相关属性,然后调用模版方法initServletBean,子类就通过这个方法初始化。

BeanWrapper是什么,怎么用

 BeanWrapper是Spring提供的一个用来操作JavaBean属性到工具,使用它可以直接修改一个对象的属性,示例如下:

public class User {
    String userName;
    public String getUserName(){
        return userName;
    }
    public void setUserName(String userName){
        this.userName = userName;
    }
}

/**
 * BeanWrapper测试
 *
 * @version
 * @author kyle 2018年8月26日上午11:45:42
 * @since 1.8
 */
public class BeanWrapperTest {
    public static void main(String[] args) {
        User user = new User();
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(user);
        bw.setPropertyValue("userName", "张三");
        System.out.println(user.getUserName());// 输出张三
        PropertyValue value = new PropertyValue("userName", "李四");
        bw.setPropertyValue(value);
        System.out.println(user.getUserName());// 输出李四
    }
}

 这个例子首先新建一个User对象,其次使用PropertyAccessFactory封装成BeanWrapper对象,这样就可以使用BeanWrapper对象对其属性userName进行操作。BeanWrapper的使用就是这么简单,理解了这个例子,就可以理解HttpServletBean中设置属性的方法了。以下为测试补充

BeanWrapper测试.png

9.3 FrameworkServlet

 从HttpServlet中可知,FrameworkServlet的初始化入口方法应该是initServletBean,其代码如下:

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    ...
    @Override
    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");
        }
    }
    ...
}

 可以看到这里的核心代码只有两句:一句用于初始化WebApplicationContext,另一句用于初始化FrameworkServlet,而且initFrameworkServlet方法是模版方法,子类可以覆盖然后在里面做一些初始化的工作,但子类并没有使用它。这两句代码如下:

    this.webApplicationContext = initWebApplicationContext();
    initFrameworkServlet();

 可见FrameworkServlet在构建的过程中到主要作用就是初始化了WebApplication。下面来看一下initWebApplicationContext方法。

protected WebApplicationContext initWebApplicationContext() {
    //获取rootContext
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    //如果已经通过构造方法设置了WebApplicationContext
    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit parent -> set
                    // the root application context (if any; may be null) as the parent
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // 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
        //当webApplicationContext已经存在ServletContext中时,通过配置在servlet中的contextAttribute参数获取
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        //如果webApplicationContext还没有创建,则创建一个
        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.
        //当ContextRefresh事件没有触发时调用此方法,模版方法,可以在子类重写
        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;
}

 initWebApplicationContext方法做了三件事:

  • 获取spring的根容器rootContext。
  • 设置webApplication并根据情况调用onRefresh方法。
  • 将webApplicationContext设置到ServletContext中。

获取spring的根容器rootContext

 获取根容器的原理是,默认情况下spring会将自己的容器设置成ServletContext的属性默认根容器的key为org.springframework.web.context.WebApplicationContext.ROOT,定义在org.springframework.web.context.WebApplicationContext中。

public interface WebApplicationContext extends ApplicationContext {
    ...
    String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
    ...
}

 所以获取根容器只需要调用ServletContext的getAttribute就可以了。

    ServletContext#getAttribute("org.springframework.web.context.WebApplicationContext.ROOT")

设置WebApplicationContext并根据情况调用onRefresh方法

 设置webApplicationContext一共有三种方法。
 第一种方法是在构造方法中已经传递webApplicationContext参数,这时只需要对其进行一些设置即可。这种方法主要用于Servlet3.0以后的环境中,Servlet3.0之后可以在程序中使用ServletContext.addServlet方式注册Servlet,这时就可以在新建FrameworkServlet和其子类的时候通过构成方法传递已经准备好的webApplicationContext。
 第二种方法是webApplicationContext已经在ServletContext中了。这时只需要在配置Servlet的时候将ServletContext中的webApplicationContext的name配置到contextAttribute属性就可以了。比如,在ServletContext中有一个叫haha的webApplicationContext,可以这么将它配置到Spring MVC中:

<servlet>
    <servlet-name>let'sGo</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextAttribute</param-name>
        <param-value>haha</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

 第三种方法是在前面两种方法都无效的情况下自己创建一个。正常情况下就是使用的这种方式。创建过程在createWebApplicationContext方法中,createWebApplicationContext内部又调用了configureAndRefreshWebApplicationContext方法,代码如下:

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    ...
    protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
        //获取创建类型
        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");
        }
        //具体创建
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

        wac.setEnvironment(getEnvironment());
        wac.setParent(parent);
        wac.setConfigLocation(getContextConfigLocation());
        //将设置的contextConfigLocation参数传给wac,默认传入WEB-INFO/ [ServletName]-Servlet.xml
        configureAndRefreshWebApplicationContext(wac);

        return wac;
    }
    
    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...
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                        ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
            }
        }

        wac.setServletContext(getServletContext());
        wac.setServletConfig(getServletConfig());
        wac.setNamespace(getNamespace());
        //添加监听ContextRefreshEvent的监听器
        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);
        wac.refresh();
    }
    ...
}

 这里首先调用getContextClass方法获取要创建的类型,它可以通过contextClass属性设置到Servlet中,默认使用org.springframework.web.context.support.XmlWebApplicationContext然后检查属不属于ConfigurableWebApplicationContext类型,如果不属于就抛出异常。接下来通过BeanUtils.instantiateClass(contextClass)进行创建,创建后将设置到contextConfigLocation传入,如果没有设置,默认传入WEB-INFO/[ServletName]-Servlet.xml,然后进行配置。其他内容基本上都很容易理解,需要说明的是,在configureAndRefreshWebApplicationContext方法中给wac添加了监听器。

wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

 SourceFilterListener可以根据输入的参数进行选择,所以实际监听器的是ContextRefreshListener所监听的事件。ContextRefreshListener是FrameworkServlet的内部类,监听ContextRefreshedEvent事件,当接收到消息时调用FrameworkServlet的onApplicationEvent方法,在onApplicationEvent中会调用一次onRefresh方法,并将refreshEventReceived标志设置为true,表示已经refresh过,代码如下:

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    ...
    private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            FrameworkServlet.this.onApplicationEvent(event);
        }
    }
    ...
    public void onApplicationEvent(ContextRefreshedEvent event) {
        this.refreshEventReceived = true;
        onRefresh(event.getApplicationContext());
    }
    ...
}

 再回到initWebApplicationContext方法,可以看到后面会根据refreshEventReceived标志来判断是否要运行onRefresh。

if (!this.refreshEventReceived) {
    onRefresh(wac);
}

 当使用第三种方法初始化时已经refresh,不需要再调用onRefresh。同样在第一种中也调用了configureAndRefreshWebApplicationContext方法,也refresh过,所以只有使用第二种方式初始化webApplicationContext的时候才会在这里调用onRefresh方法。不过不管用哪种方式调用,onRefresh最终肯定会而且只会调用一次,而且DispatcherServlet正是通过重写这个模版来实现初始化的。

将webApplicationContext设置到ServletContext中

 最后会根据publishContext标志判断是否将创建出来的webApplicationContext设置到ServletContext的属性中,publishContext标志可以在配置Servlet时通过init-param参数进行设置,HttpServletBean初始化时会将其设置到publishContext参数。之所以将创建出来的webApplicationContext设置到ServletContext的属性中,主要是为了方便获取,在前面获取RootApplicationContext的时候已经介绍过。
 前面介绍了配置Servlet时可以设置的一些初始化参数,总结如下:

  1. contextAttribute:在ServletContext的属性中,要用作WebApplicationContext的属性名称。
  2. contextClass:创建WebApplicationContext的类型。
  3. contextConfigLocation:Spring MVC配置文件的位置。
  4. publishContext:是否将webApplicationContext设置到ServletContext的属性。

9.4 DispatcherServlet

 onRefresh方法是DispatcherServlet的入口方法。onRefresh中简单地调用了initStrategies,在initStrategies中调用了9个初始化方法:

public class DispatcherServlet extends FrameworkServlet {
    ...
    /**
     * This implementation calls {@link #initStrategies}.
     */
    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

    /**
     * Initialize the strategy objects that this servlet uses.
     * <p>May be overridden in subclasses in order to initialize further strategy objects.
     */
    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }
    ...
}

 可能有读者不理解为什么要这么写,为什么不将initStrategies的具体实现直接写到onRefresh中呢?initStrategies方法不是多余的吗?其实这主要是分层的原因,onRefresh是用来刷新容器的,initStratgies用来初始化一些策略组件。如果把initStrategies里面的代码直接写到onRefresh里面,对于程序到运行也没有影响。不过这样一来,如果在onRefresh中想再添加别到功能,就会没有将其单独写一个方法出来逻辑清晰,不过这并不是最重要的,更重要的是,如果在别的地方也需要调用initStrategies方法(如需要修改一些策略后进行热部署),但initStrategies没独立出来,就只能调用onRefresh,那样在onRefresh增加了新功能的时候就麻烦了。另外单独将initStrategies写出来还可以被子类覆盖,使用新的模式进行初始化。
 initStrategies的具体内容非常简单,就是初始化的9个组件,下面以LocalResolver为例来分析具体的初始化方式:

public class DispatcherServlet extends FrameworkServlet {
    ...
    /**
     * Initialize the LocaleResolver used by this class.
     * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
     * we default to AcceptHeaderLocaleResolver.
     */
    private void initLocaleResolver(ApplicationContext context) {
        try {
            //在context中获取
            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 + "]");
            }
        }
    }
    ...
}

 初始化方式为两步:首先通过context.getBean在容器里面按注册时的名称或类型(这里指“localeResolver”名称或者LocaleResolver.class类型)进行查找,所以在Spring MVC的配置文件中只需要配置相应类型的组件,容器就可以自动找到。如果找不到就调用getDefaultStrategy按照类型的组件。需要注意的是,这里的context指的是FrameworkServlet中创建的WebApplicationContext,而不是ServletContext。下面介绍getDefaultStrategy是怎样获取默认组件的。

public class DispatcherServlet extends FrameworkServlet {
    ...
    /**
     * Return the default strategy object for the given strategy interface.
     * <p>The default implementation delegates to {@link #getDefaultStrategies},
     * expecting a single object in the list.
     * @param context the current WebApplicationContext
     * @param strategyInterface the strategy interface
     * @return the corresponding strategy object
     * @see #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);
    }

    /**
     * Create a List of default strategy objects for the given strategy interface.
     * <p>The default implementation uses the "DispatcherServlet.properties" file (in the same
     * package as the DispatcherServlet class) to determine the class names. It instantiates
     * the strategy objects through the context's BeanFactory.
     * @param context the current WebApplicationContext
     * @param strategyInterface the strategy interface
     * @return the List of corresponding strategy objects
     */
    @SuppressWarnings("unchecked")
    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
        String key = strategyInterface.getName();
        //从defaultStrategies获取所需策略的类型
        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>();
        }
    }
    ...
}

 可以看到getDefaultStrategy中调用了getDefaultStrategies,后者返回的是List,这是因为HandlerMapping等组件可以有多个,所以定义了getDefaultStrategies方法,getDefaultStrategy直接调用了getDefaultStrategies中实际执行创建的方法是ClassUtils.forName,它需要的参数是className,所以最重要的是看classNames,classNames又来自value,而value来自defaultStrategies.getProperty(key)。所以关键点就在defaultStrategies中,defaultStrategies是一个静态属性,在static块中进行初始化的。

public class DispatcherServlet extends FrameworkServlet {
    ...
    private static final Properties defaultStrategies;

    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 '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
        }
    }
    ...
}

 我们看到defaultStrategies是DispatcherServlet类所在包下的DEFAULT_STRATEGIES_PREFIX文件里定义的属性,DEFAULT_STRATEGIES_PATH的值是DispatcherServlet.properties。所以defaultStrategies里面存放的是DispatcherServlet.properties里面定义的键值对,代码如下:

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

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

 可以看到,这里确定定义了不同组件的类型,一共定义了8个组件,处理上传组件MultipartResolver是没有默认配置的,这也很容易理解,并不是每个应用都需要上传功能,即使需要上传也不一定就要使用MultipartResolver,所以MultipartResolver不需要默认配置。另外HandlerMapping、Handler和HandlerExceptionResolver都配置了多个,其实ViewResolver也可以有多个,只是默认的配置只有一个。
 这里需要注意两个问题:首先默认配置并不是最优配置,也不是spring的推荐配置,只是在没有配置的时候可以有个默认值,不至于空着。里面的有些默认配置甚至已经被标注为@Deprecated,表示已弃用,如DefaultAnnotationHandlerMapping、AnnotationMethodHandlerAdapter以及AnnotationMethodHandlerExceptionResolver。另外需要注意的一点是,默认配置是在相应类型没有配置的时候才会使用,如当使用<mvc:annoraruon-driven/>后,并不会全部使用默认配置。因为它配置了HandlerMapping、HandlerAdapter和HandlerExceptionResolver,而且还做了很多别的工作,更详细的内容可以查看org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser。
 DispatcherServlet的创建过程主要是对9大组件进行初始化,具体每个组件的作用后面具体讲解。

9.5 小结

 本章主要分析了Spring MVC自身的创建过程,Spring MVC中Servlet一共三个层次,分别是HttpServletBean、FrameworkServlet和DispatcherServlet。HttpServletBean直接继承自Java的HttpServlet,其作用是将Servlet中配置的参数设置到相应的属性:FrameworkServlet初始化了WebApplicationContext,DispatcherServlet初始化了自身的9个组件。
 FrameworkServlet初始化WebApplicationContext一共有三种方式,过程中使用了Servlet中配置的一些参数。
 整体结构非常简单---分三个层次做了三件事,但具体实现过程还是有点复杂的,这其实也是spring的特点:结构简单,实现复杂。结构简单主要是顶层设计好,实现复杂的主要是提供的功能比较多,可配置的地方也非常多。当然,正是因为实现复杂,才让Spring MVC使用起来更加灵活,这一点在后面会有深刻多体会。

写博原因

 spring mvc是面试官经常问的point,不管初级、中级、高级程序员折在这上的不少,希望大家面试顺利,在读完《详解Spring MVC 上下》之后能震慑住面试官。《下》我会在9月初完成,感谢观看.....

《详解Spring MVC:下》

如果需要給我修改意见的发送邮箱:erghjmncq6643981@163.com
资料参考:《看透Spring MVC-源代码分析与实践》
转发博客,请注明,谢谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容