springmvc--1--启动流程

springmvc–启动流程

文章目录

1 使用springmvc搭建一个简单的web应用

我们使用servlet3.0+以上的版本,干掉web.xml文件,不知道你们有没有和我一样的,每次新建项目还得创建一个webapp目录,就感到特别烦。servlet3.0+添加了SCI支持,通过继承一个接口替换web.xml

1.1 依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.11.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>

web应用程序只需要导入这两个包即可,其中第二个包javax.servlet-api,设定了它的scope作用域,表示不会将该依赖打包到war包中,因为tomcat中已经包含了javax.servlet-api这个包,如果这里再打包进去的话,就会造成依赖冲突。

1.2 配置一个可编程的Servlet container

public class MyWebApplicationInitializer implements WebApplicationInitializer {
    public void onStartup(javax.servlet.ServletContext servletContext) throws ServletException {

        //创建web应用的上下文
        XmlWebApplicationContext xmlWebApplicationContext = new XmlWebApplicationContext();
        //设置配置文件路径(配置文件在类路径下,即resources根目录下)
        xmlWebApplicationContext.setConfigLocation("classpath:spring-mvc.xml");
        /**
         * 创建一个DispatcherServlet
         * 了解过springmvc的都知道,它就是一个servlet,这个servlet处理所有请求
         */ 
        DispatcherServlet dispatcherServlet = new DispatcherServlet(xmlWebApplicationContext);
        //在servlet上下文中注册DispatcherServlet
        ServletRegistration.Dynamic dynamic = servletContext.addServlet(
            "dispatcherServlet", dispatcherServlet);
        //设置DispatcherServlet的顺序
        dynamic.setLoadOnStartup(1);
        //设置拦截路径
        dynamic.addMapping("/*");
    }
}

1.3 spring-mvc.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           https://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/mvc
                           https://www.springframework.org/schema/mvc/spring-mvc.xsd
                           http://www.springframework.org/schema/context
                           https://www.springframework.org/schema/context/spring-context.xsd">

    <!--只扫描controller-->
    <context:component-scan base-package="cn.lx.spring.v2.controller"></context:component-scan>

    <!--开启springmvc配置,相当于@EnableWebMvc注解-->
    <mvc:annotation-driven>
        <!--
        配置自定义的消息转换器
        需要导入jackson的包jackson-module-parameter-names和
        jackson-dataformat-xml
         -->
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper" ref="objectMapper"/>
            </bean>
            <bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
                <property name="objectMapper" ref="xmlMapper"/>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
          p:indentOutput="true"
          p:simpleDateFormat="yyyy-MM-dd"
          p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>

    <bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>

</beans>

1.4 controller控制器

@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/user")
    public User getUser(){
        User user = new User();
        user.setAge(12);
        user.setName("lixiang");
        user.setPassword("1222223");
        return user;
    }
}

浏览器访问http://localhost:8080/spring-test/test/user,得到如下结果

<User>
    <name>lixiang</name>
    <password>1222223</password>
    <age>12</age>
</User>

到此为止,一个简单的web应用程序就创建好了。但是又出现了新的问题:浏览器为什么显示的是XML格式的数据呢?我们以前不是显示json格式的数据吗?不要着急,我们接着往下看。

2 XmlWebApplicationContext创建过程

借助原生servlet3.0增加的SCI启动实现

2.1 ServletContainerInitializer接口

首先来说一下原生servlet3.0增加的SCI启动模式,不知道到直接去servlet官网找教程。

  • SCI说起来也很简单,就是web应用程序在启动的开始阶段自动回调ServletContainerInitializer接口实现类的onStartup()方法。
  • 如果你想使用这种模式,必须做到一下几点
    • 必须在META-INF/services创建一个文件javax.servlet.ServletContainerInitializer,文件内容为实现ServletContainerInitializer接口的类的完全限定名。
    • 如果在实现ServletContainerInitializer接口的类上标注@HandlesTypes注解,表示在调用onStartup()方法的时候获取到@HandlesTypes注解指定的类的clazz对象。
public interface ServletContainerInitializer {

    /**
     * 启动的时候自动回调实现类的这个方法
     * @param c 所有ServletContainerInitializer实现类@HandlesTypes注解指定的类
     * @param ctx servlet上下文
     */
    public void onStartup(Set<Class<?>> c, ServletContext ctx)
        throws ServletException; 
}

2.2 SpringServletContainerInitializer2.1接口的实现类)

springmvc定义的ServletContainerInitializer接口的实现,spring为什么要定义这个类呢?

打开spring-web包的源码,该包中存在一个META-INF/servicesjavax.servlet.ServletContainerInitializer文件,而再看它的内容org.springframework.web.SpringServletContainerInitializer,这不就是spring定义的这个类的完全限定名嘛。也就是说web应用程序在启动的开始阶段会自动回调SpringServletContainerInitializer类的onStartup()方法

我们来看一下它的源码

/**
 * 这里使用了@HandlesTypes注解,那么就会将该注解指定的类的实现类注入到onStartup()的第一个参数中
 */
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

    /**
     * 启动阶段自动回调该方法
     * webAppInitializerClasses中存放了@HandlesTypes注解指定的类的实现类的clazz对象
     * servletContext是servlet上下文
     */
    @Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
        throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<>();

        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // Be defensive: Some servlet containers provide us with invalid classes,
                // no matter what @HandlesTypes says...
                //排除掉接口和抽象类
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                    WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        //反射实例化
                        initializers.add((WebApplicationInitializer)
                                         ReflectionUtils.accessibleConstructor(waiClass).newInstance());
                    }
                    catch (Throwable ex) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            return;
        }

        servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
        //根据@Order注解排序
        AnnotationAwareOrderComparator.sort(initializers);
        //遍历,调用onStartup()方法
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }

}

在这个web程序中webAppInitializerClasses集合中有5clazz对象,它们都是WebApplicationInitializer的子类,MyWebApplicationInitializer这是我们自己定义的,另外4个类都是抽象类(AbstractContextLoaderInitializerAbstractDispatcherServletInitializerAbstractAnnotationConfigDispatcherServletInitializerAbstractReactiveWebInitializer),那么最终就只实例化了我们自己定义的MyWebApplicationInitializer,然后调用这个对象的onStartup()方法。

spring这样设计的目的是简化配置,用户不需要再配置META-INF/servicesjavax.servlet.ServletContainerInitializer文件了

3 Servlet初始化过程

下面是原生的servlet接口,我们只需要关注两个方法init()service()方法,一个用来初始化servlet,一个用来处理请求。

public interface Servlet {

    /**
     * servlet初始化方法
     * 在web应用程序启动的时候,tomcat会自动调用所有servlet的初始化方法,
     */
    public void init(ServletConfig config) throws ServletException;


    public ServletConfig getServletConfig();


    //servlet处理请求的方法
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException;


    public String getServletInfo();


    //servlet销毁方法
    public void destroy();
}

springmvc程序只向ServletContext中注册了一个Servlet,那就是DispatcherServlet,下面是它的类图

在这里插入图片描述

3.1 ServletConfigPropertyValues

这是HttpServletBean类的一个静态嵌套类,下面是它的类图

在这里插入图片描述

这个MutablePropertyValues类以前是不是见到过?

在我的spring--7--类型转换PropertyEditor这篇文章中提到:spring在属性填充阶段要填充的属性值都保存在这个结构中,所以在那篇文章的开头(2.1章节)我们就讲了MutablePropertyValues,不知道的可以去看一下,这里的重点是ServletConfigPropertyValues类。

/**
 * PropertyValues implementation created from ServletConfig init parameters.
 */
private static class ServletConfigPropertyValues extends MutablePropertyValues {

    /**
     * Create new ServletConfigPropertyValues.
     * @param config the ServletConfig we'll use to take PropertyValues from
     * @param requiredProperties set of property names we need, where
     * we can't accept default values
     * @throws ServletException if any required properties are missing
     * 构造方法
     */
    public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
        throws ServletException {

        //requiredProperties表示需要用户手动赋值的属性,不能使用默认值
        Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
                                    new HashSet<>(requiredProperties) : null);

        /**
         * 得到servlet的初始化参数
         * 其实我们在web.xml文件的servlet标签中配置的init-param标签的内容
         */
        Enumeration<String> paramNames = config.getInitParameterNames();
        while (paramNames.hasMoreElements()) {
            String property = paramNames.nextElement();
            Object value = config.getInitParameter(property);
            /**
             * 得到属性名和属性值之后,构建一个PropertyValue,保存到当前对象中
             * 一个PropertyValue对象,代表一项配置
             */
            addPropertyValue(new PropertyValue(property, value));
            //移除掉必须初始化的属性
            if (missingProps != null) {
                missingProps.remove(property);
            }
        }

        //用户没有为某个必须初始化的属性赋值,抛异常
        // Fail if we are still missing properties.
        if (!CollectionUtils.isEmpty(missingProps)) {
            throw new ServletException(
                "Initialization from ServletConfig for servlet '" + config.getServletName() +
                "' failed; the following required properties were missing: " +
                StringUtils.collectionToDelimitedString(missingProps, ", "));
        }
    }
}

总结一下这个类的作用:

  • 保存用户配置的servlet的初始化参数
  • 验证是否有必须初始化的参数用户没有手动赋值

3.2 GenericServlet类实现init()方法

/**
 * Called by the servlet container to indicate to a servlet that the
 * servlet is being placed into service.  See {@link Servlet#init}.
 *
 * <p>This implementation stores the {@link ServletConfig}
 * object it receives from the servlet container for later use.
 * When overriding this form of the method, call 
 * <code>super.init(config)</code>.
 *
 * @param config          the <code>ServletConfig</code> object
 *              that contains configutation
 *              information for this servlet
 *
 * @exception ServletException  if an exception occurs that
 *              interrupts the servlet's normal
 *              operation
 * 
 * @see              UnavailableException
 */
public void init(ServletConfig config) throws ServletException {
    //这个是固定不变的
    this.config = config;
    //进行额外的初始化,见3.3
    this.init();
}


/**
 * 这是一个空方法
 * 方便子类重写,在初始化过程中做额外的处理
 */
public void init() throws ServletException {

}

3.3 HttpServletBean类实现重载的init()方法

HttpServlet并没有实现init()方法,而是交由它的子类HttpServletBean实现

//要求用户必须手动赋值的参数名集合
private final Set<String> requiredProperties = new HashSet<>(4);

/**
 * Map config parameters onto bean properties of this servlet, and
 * invoke subclass initialization.
 * @throws ServletException if bean properties are invalid (or required
 * properties are missing), or if subclass initialization fails.
 */
@Override
public final void init() throws ServletException {

    // Set bean properties from init parameters.
    //获取用户配置的servlet的初始化配置,并封装到ServletConfigPropertyValues中,见3.1
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    //用户配置了当前servlet的初始化参数
    if (!pvs.isEmpty()) {
        try {
            //将当前DispatcherServlet对象包装为BeanWrapperImpl对象
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            //注册一个属性编辑器
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            //初始化BeanWrapperImpl
            initBeanWrapper(bw);
            /**
             * 将用户配置的servlet初始化参数填充到DispatcherServlet中,
             * 这个过程不懂的话,建议去了解一下spring的属性填充
             */
            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.
    //子类实现具体逻辑,见3.4
    initServletBean();
}


/**
 * 空方法,由子类实现
 */
protected void initServletBean() throws ServletException {
}

3.4 FrameworkServlet类实现initServletBean()方法

/**
 * Overridden method of {@link HttpServletBean}, invoked after any bean properties
 * have been set. Creates this servlet's WebApplicationContext.
 */
@Override
protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
    if (logger.isInfoEnabled()) {
        logger.info("Initializing Servlet '" + getServletName() + "'");
    }
    long startTime = System.currentTimeMillis();

    try {
        //初始化springmvc的上下文环境,见3.5
        this.webApplicationContext = initWebApplicationContext();
        //下面是一个空方法
        initFrameworkServlet();
    }
    catch (ServletException | RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        throw ex;
    }

    if (logger.isDebugEnabled()) {
        String value = this.enableLoggingRequestDetails ?
            "shown which may lead to unsafe logging of potentially sensitive data" :
        "masked to prevent unsafe logging of potentially sensitive data";
        logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
                     "': request parameters and headers will be " + value);
    }

    if (logger.isInfoEnabled()) {
        logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
    }
}

3.5 初始化springmvc的上下文环境

/**
 * Initialize and publish the WebApplicationContext for this servlet.
 * <p>Delegates to {@link #createWebApplicationContext} for actual creation
 * of the context. Can be overridden in subclasses.
 * @return the WebApplicationContext instance
 * @see #FrameworkServlet(WebApplicationContext)
 * @see #setContextClass
 * @see #setContextConfigLocation
 */
protected WebApplicationContext initWebApplicationContext() {
    //获取用户配置的父上下文,见4.1
    WebApplicationContext rootContext =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    /**
     * this.webApplicationContext肯定是不为null的
     * 还记得我们自定义的MyWebApplicationInitializer类吗,当时我们构造DispacherServlet
     * 的时候,把我们new的XmlWebApplicationContext对象传给它了
     */
    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        //现在使用我们自己创建的XmlWebApplicationContext对象
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            //实际上就是还没有调用上下文的refresh()方法
            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);
                }
                //初始化web上下文,见3.5.1
                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
        //获取注册到ServletContext中的web上下文,见3.5.2
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        //创建一个默认的web上下文,见3.5.3
        wac = createWebApplicationContext(rootContext);
    }

    //ContextRefreshListener未监听到ContextRefreshedEvent事件
    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.
        synchronized (this.onRefreshMonitor) {
            /**
             * 调用onRefresh()方法为springmvc的9大组件设置初始值
             * 该方法和ContextRefreshListener中调用的onRefresh()方法是同一个,见3.5.1.2
             */
            onRefresh(wac);
        }
    }

    //将springmvc上下文对象保存到ServletContext中
    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        /**
         * 前缀 org.springframework.web.servlet.FrameworkServlet.CONTEXT.
         * 拼接上当前Servlet的名字(我指定的是ddispatcher)
         * 所以attrName为org.springframework.web.servlet.FrameworkServlet.CONTEXT.ddispatcher
         * 所以以后可以根据该名字从ServletContext中取出来
         */
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

总结该方法主要做的事情:

  • 获取用户设置父上下文,并设置到springmvc上下文中
  • 设置一个用来监听上下文刷新事件ContextRefreshedEvent的监听器ContextRefreshListener
  • 初始化springmvc上下文
  • springmvc上下文对象保存到ServletContext
  • 如果用户未手动创建springmvc上下文对象,那么它会自动创建一个web上下文对象

3.5.1 初始化web上下文

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());
    //设置web上下文的命名空间,见3.5.1.1
    wac.setNamespace(getNamespace());
    /**
     * 添加spring的监听器ContextRefreshListener,见3.5.1.2
     * SourceFilteringListener是一个监听器的包装,它可以过滤掉非同一个容器发布事件,见3.5.1.3
     * 简单来说,就是父容器发布的事件只被由父容器的监听器监听到,子容器也是如此
     */
    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
    //获取web应用上下文的环境对象,3.5.1.4
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        //将ServletContext的配置注册到环境中,见3.5.1.5
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }

    //在执行refresh()方法前增强web上下文,见3.5.1.6
    postProcessWebApplicationContext(wac);
    /**
     * 实例化ApplicationContextInitializer接口对象,
     * 并调用接口的initialize()方法增强web上下文
     * 
     * postProcessWebApplicationContext()和ApplicationContextInitializer接口
     * 都是用来增强web上下文的
     */
    applyInitializers(wac);
    /**
     * 调用上下文的refresh()方法,
     * 这就回到了我们非常熟悉的spring环节了
     */
    wac.refresh();
}
3.5.1.1 设置 web上下文的命名空间
/**
 * Return the namespace for this servlet, falling back to default scheme if
 * no custom namespace was set: e.g. "test-servlet" for a servlet named "test".
 */
public String getNamespace() {
    return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX);
}

命名空间的名字为当前servlet的名字加上"-servlet"

3.5.1.2 ContextRefreshListener上下文刷新监听器
/**
 * ApplicationListener endpoint that receives events from this servlet's WebApplicationContext
 * only, delegating to {@code onApplicationEvent} on the FrameworkServlet instance.
 */
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        /**
         * ContextRefreshListener是FrameworkServlet的一个内部类,
         * 在内部类中通过FrameworkServlet.this的到当前FrameworkServlet的对象
         * 所以这句话的意思就是调用它外部类FrameworkServlet的onApplicationEvent方法
         */
        FrameworkServlet.this.onApplicationEvent(event);
    }
}

FrameworkServletonApplicationEvent方法

/**
 * Callback that receives refresh events from this servlet's WebApplicationContext.
 * <p>The default implementation calls {@link #onRefresh},
 * triggering a refresh of this servlet's context-dependent state.
 * @param event the incoming ApplicationContext event
 */
public void onApplicationEvent(ContextRefreshedEvent event) {
    /**
     * 表示已处理上下文刷新事件
     * 这个属性在3.5中用到了,当这个监听器没有监听到ContextRefreshedEvent事件的时候
     * DispatcherServlet就会自己手动调用onRefresh()方法为springmvc的9大组件设置初始值
     * ContextRefreshedEvent事件是在spring流程finishRefresh()方法内发布的
     */
    this.refreshEventReceived = true;
    synchronized (this.onRefreshMonitor) {
        //监听到上下文刷新事件后具体做的事
        onRefresh(event.getApplicationContext());
    }
}

onRefresh()方法被子类DispatcherServlet重写了

/**
 * 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) {
    //从方法名字也能看出来,这个方法用来初始化springmvc的9大组件
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

refresh()方法调用结束之前,会发布一个ContextRefreshedEvent事件ContextRefreshListener这个监听器监听到上下文刷新事件后,初始化springmvc上下文9大组件

3.5.1.3 SourceFilteringListener,包装监听器

下面是这个类的类图

在这里插入图片描述

public class SourceFilteringListener implements GenericApplicationListener, SmartApplicationListener {

    private final Object source;

    @Nullable
    private GenericApplicationListener delegate;


    /**
     * Create a SourceFilteringListener for the given event source.
     * @param source the event source that this listener filters for,
     * only processing events from this source
     * @param delegate the delegate listener to invoke with event
     * from the specified source
     */
    public SourceFilteringListener(Object source, ApplicationListener<?> delegate) {
        //创建包装监听器时指定源对象
        this.source = source;
        //所有的监听器都被适配为GenericApplicationListener类型
        this.delegate = (delegate instanceof GenericApplicationListener ?
                         (GenericApplicationListener) delegate : new GenericApplicationListenerAdapter(delegate));
    }

    //包装监听器的事件监听方法
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        /**
         * 比较事件的事件源对象和创建这个包装监听器时指定的source对象
         * 不相同就什么也不干
         */
        if (event.getSource() == this.source) {
            onApplicationEventInternal(event);
        }
    }

    /**
	 * Actually process the event, after having filtered according to the
	 * desired event source already.
	 * <p>The default implementation invokes the specified delegate, if any.
	 * @param event the event to process (matching the specified source)
	 */
    protected void onApplicationEventInternal(ApplicationEvent event) {
        if (this.delegate == null) {
            throw new IllegalStateException(
                "Must specify a delegate object or override the onApplicationEventInternal method");
        }
        //执行实际的监听器的监听方法
        this.delegate.onApplicationEvent(event);
    }
}

这个监听器用来包装其他监听器,当事件的事件源对象和创建这个包装监听器时指定的source对象不相同时,就不会执行监听方法。即过滤掉其他容器发布的事件

3.5.1.4 获取web应用上下文的环境对象
/**
 * Return the {@code Environment} for this application context in configurable
 * form, allowing for further customization.
 * <p>If none specified, a default environment will be initialized via
 * {@link #createEnvironment()}.
 */
@Override
public ConfigurableEnvironment getEnvironment() {
    if (this.environment == null) {
        //创建标准的web环境
        this.environment = createEnvironment();
    }
    return this.environment;
}

createEnvironment()方法在AbstractRefreshableWebApplicationContext类中重写

/**
 * Create and return a new {@link StandardServletEnvironment}. Subclasses may override
 * in order to configure the environment or specialize the environment type returned.
 */
@Override
protected ConfigurableEnvironment createEnvironment() {
    //标准的web环境
    return new StandardServletEnvironment();
}
  • 普通的spring应用程序创建的环境对象为StandardEnvironment
  • springmvc应用程序创建的环境对象为StandardServletEnvironment
3.5.1.5 将ServletContext的配置注册到环境中
@Override
public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
    /**
     * getPropertySources()得到环境中真正存储配置集合
     * 注册过程委派给了WebApplicationContextUtils.initServletPropertySources()方法完成
     */
    WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}

注册ServletContext的配置到环境中

/**
 * Replace {@code Servlet}-based {@link StubPropertySource stub property sources} with
 * actual instances populated with the given {@code servletContext} and
 * {@code servletConfig} objects.
 * <p>This method is idempotent with respect to the fact it may be called any number
 * of times but will perform replacement of stub property sources with their
 * corresponding actual property sources once and only once.
 * @param sources the {@link MutablePropertySources} to initialize (must not
 * be {@code null})
 * @param servletContext the current {@link ServletContext} (ignored if {@code null}
 * or if the {@link StandardServletEnvironment#SERVLET_CONTEXT_PROPERTY_SOURCE_NAME
 * servlet context property source} has already been initialized)
 * @param servletConfig the current {@link ServletConfig} (ignored if {@code null}
 * or if the {@link StandardServletEnvironment#SERVLET_CONFIG_PROPERTY_SOURCE_NAME
 * servlet config property source} has already been initialized)
 * @see org.springframework.core.env.PropertySource.StubPropertySource
 * @see org.springframework.core.env.ConfigurableEnvironment#getPropertySources()
 */
public static void initServletPropertySources(MutablePropertySources sources,
                                              @Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {

    Assert.notNull(sources, "'propertySources' must not be null");

    //SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams"
    String name = StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME;
    if (servletContext != null && sources.get(name) instanceof StubPropertySource) {
        sources.replace(name, new ServletContextPropertySource(name, servletContext));
    }

    //SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams"
    name = StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME;
    if (servletConfig != null && sources.get(name) instanceof StubPropertySource) {
        sources.replace(name, new ServletConfigPropertySource(name, servletConfig));
    }
}

spring标准环境向环境中注册了两个配置systemEnvironmentsystemProperties

springmvc标准环境向环境中额外注册了3个配置servletContextInitParamsservletConfigInitParamsjndiProperties

3.5.1.6 postProcessWebApplicationContext()方法,在执行refresh()方法前增强web上下文
/**
 * Post-process the given WebApplicationContext before it is refreshed
 * and activated as context for this servlet.
 * <p>The default implementation is empty. {@code refresh()} will
 * be called automatically after this method returns.
 * <p>Note that this method is designed to allow subclasses to modify the application
 * context, while {@link #initWebApplicationContext} is designed to allow
 * end-users to modify the context through the use of
 * {@link ApplicationContextInitializer ApplicationContextInitializers}.
 * @param wac the configured WebApplicationContext (not refreshed yet)
 * @see #createWebApplicationContext
 * @see #initWebApplicationContext
 * @see ConfigurableWebApplicationContext#refresh()
 */
protected void postProcessWebApplicationContext(ConfigurableWebApplicationContext wac) {
}

该方法默认为空

3.5.1.7 实例化ApplicationContextInitializer接口对象,并调用接口的initialize()方法增强web上下文
/**
 * Delegate the WebApplicationContext before it is refreshed to any
 * {@link ApplicationContextInitializer} instances specified by the
 * "contextInitializerClasses" servlet init-param.
 * <p>See also {@link #postProcessWebApplicationContext}, which is designed to allow
 * subclasses (as opposed to end-users) to modify the application context, and is
 * called immediately before this method.
 * @param wac the configured WebApplicationContext (not refreshed yet)
 * @see #createWebApplicationContext
 * @see #postProcessWebApplicationContext
 * @see ConfigurableApplicationContext#refresh()
 */
protected void applyInitializers(ConfigurableApplicationContext wac) {
    //获取用户在web.xml中配置的context-wide标签的内容
    String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM);
    /**
     * INIT_PARAM_DELIMITERS = ",; \t\n"
     * 根据分隔符分割字符串,并实例化
     */
    if (globalClassNames != null) {
        for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
            this.contextInitializers.add(loadInitializer(className, wac));
        }
    }

    /**
     * contextInitializerClasses是一个string类型属性
     * 用户可是配置DispatcherServlet的时候指定初始值
     */
    if (this.contextInitializerClasses != null) {
        for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) {
            //实例化
            this.contextInitializers.add(loadInitializer(className, wac));
        }
    }

    //根据Order接口或@Order注解排序
    AnnotationAwareOrderComparator.sort(this.contextInitializers);
    for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
        //执行接口的initialize方法增强web上下文
        initializer.initialize(wac);
    }
}

3.5.2 获取注册到ServletContext中的web上下文

/**
 * Retrieve a {@code WebApplicationContext} from the {@code ServletContext}
 * attribute with the {@link #setContextAttribute configured name}. The
 * {@code WebApplicationContext} must have already been loaded and stored in the
 * {@code ServletContext} before this servlet gets initialized (or invoked).
 * <p>Subclasses may override this method to provide a different
 * {@code WebApplicationContext} retrieval strategy.
 * @return the WebApplicationContext for this servlet, or {@code null} if not found
 * @see #getContextAttribute()
 */
@Nullable
protected WebApplicationContext findWebApplicationContext() {
    String attrName = getContextAttribute();
    if (attrName == null) {
        return null;
    }
    /**
     * 这个方法和我们获取父上下文不是同一个方法吗?
     * 也就是说找不到子上下文对象的时候,就直接使用父上下文对象作为springmvc的上下文
     */
    WebApplicationContext wac =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
    if (wac == null) {
        throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
    }
    return wac;
}

3.5.3 创建一个默认的web上下文

/**
 * Instantiate the WebApplicationContext for this servlet, either a default
 * {@link org.springframework.web.context.support.XmlWebApplicationContext}
 * or a {@link #setContextClass custom context class}, if set.
 * Delegates to #createWebApplicationContext(ApplicationContext).
 * @param parent the parent WebApplicationContext to use, or {@code null} if none
 * @return the WebApplicationContext for this servlet
 * @see org.springframework.web.context.support.XmlWebApplicationContext
 * @see #createWebApplicationContext(ApplicationContext)
 */
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
    return createWebApplicationContext((ApplicationContext) parent);
}


/**
 * Instantiate the WebApplicationContext for this servlet, either a default
 * {@link org.springframework.web.context.support.XmlWebApplicationContext}
 * or a {@link #setContextClass custom context class}, if set.
 * <p>This implementation expects custom contexts to implement the
 * {@link org.springframework.web.context.ConfigurableWebApplicationContext}
 * interface. Can be overridden in subclasses.
 * <p>Do not forget to register this servlet instance as application listener on the
 * created context (for triggering its {@link #onRefresh callback}, and to call
 * {@link org.springframework.context.ConfigurableApplicationContext#refresh()}
 * before returning the context instance.
 * @param parent the parent ApplicationContext to use, or {@code null} if none
 * @return the WebApplicationContext for this servlet
 * @see org.springframework.web.context.support.XmlWebApplicationContext
 */
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    /**
     * contextClass属性的默认值为XmlWebApplicationContext.class;
     * 这是在类中固定写死的
     */ 
    Class<?> contextClass = getContextClass();
    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");
    }
    //实例化XmlWebApplicationContext
    ConfigurableWebApplicationContext wac =
        (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    //设置环境
    wac.setEnvironment(getEnvironment());
    //设置父容器
    wac.setParent(parent);
    //获取用户设置的配置文件位置(设置在DispatcherServlet中)
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
        wac.setConfigLocation(configLocation);
    }
    //初始化刚刚创建的web上下文,见3.5.1
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

4 父子容器

4.1 获取用户配置的父上下文

WebApplicationContext rootContext =
    WebApplicationContextUtils.getWebApplicationContext(getServletContext());

spring提供了一个工具类WebApplicationContextUtils,通过工具类的getWebApplicationContext()方法,可以得到用户在ServletContext中配置的父上下文

/**
 * Find the root {@code WebApplicationContext} for this web app, typically
 * loaded via {@link org.springframework.web.context.ContextLoaderListener}.
 * <p>Will rethrow an exception that happened on root context startup,
 * to differentiate between a failed context startup and no context at all.
 * @param sc the ServletContext to find the web application context for
 * @return the root WebApplicationContext for this web app, or {@code null} if none
 * @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
 */
@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
    //从ServletContext中获取父上下文对象
    return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}


/**
 * Find a custom {@code WebApplicationContext} for this web app.
 * @param sc the ServletContext to find the web application context for
 * @param attrName the name of the ServletContext attribute to look for
 * @return the desired WebApplicationContext for this web app, or {@code null} if none
 */
@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
    Assert.notNull(sc, "ServletContext must not be null");
    //通过对应的名字取出父上下文对象
    Object attr = sc.getAttribute(attrName);
    if (attr == null) {
        return null;
    }
    if (attr instanceof RuntimeException) {
        throw (RuntimeException) attr;
    }
    if (attr instanceof Error) {
        throw (Error) attr;
    }
    if (attr instanceof Exception) {
        throw new IllegalStateException((Exception) attr);
    }
    if (!(attr instanceof WebApplicationContext)) {
        throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
    }
    return (WebApplicationContext) attr;
}

ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTEspring定义的父上下文ServletContext中的标识

//org.springframework.web.context.WebApplicationContext.ROOT
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

从上面的过程我们知道,如果我们想配置一个父子容器,那我们完全可以自己创建一个web上下文对象并初始化好(refresh()方法),然后调用servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, 自己创建的上下文对象);方法将这个上下文对象保存到ServletContext中。

4.2 配置一个父子容器

事实上spring为了简化配置,提供了一个ContextLoaderListener监听器,它可以自己创建一个上下文,或者使用用户指定的上下文,并把它们作为父上下文。可以在MyWebApplicationInitializer类这样配置

@Override
public void onStartup(ServletContext container) {
    // Create the 'root' Spring application context
    AnnotationConfigWebApplicationContext rootContext =
        new AnnotationConfigWebApplicationContext();
    rootContext.register(AppConfig.class);

    // Manage the lifecycle of the root application context
    container.addListener(new ContextLoaderListener(rootContext));

    // Create the dispatcher servlet's Spring application context
    AnnotationConfigWebApplicationContext dispatcherContext =
        new AnnotationConfigWebApplicationContext();
    dispatcherContext.register(DispatcherConfig.class);

    // Register and map the dispatcher servlet
    ServletRegistration.Dynamic dispatcher =
        container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
    dispatcher.setLoadOnStartup(1);
    dispatcher.addMapping("/");
}

4.3 ContextLoaderListener的原理

它是一个监听器,那么直接定位到contextInitialized(ServletContextEvent sce)方法

/**
 * Initialize the root web application context.
 * 该方法会在web应用开始初始化的时候被回调
 */
@Override
public void contextInitialized(ServletContextEvent event) {
    //调用父类方法
    initWebApplicationContext(event.getServletContext());
}

父类initWebApplicationContext()方法

/**
 * Initialize Spring's web application context for the given servlet context,
 * using the application context provided at construction time, or creating a new one
 * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
 * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
 * @param servletContext current servlet context
 * @return the new WebApplicationContext
 * @see #ContextLoader(WebApplicationContext)
 * @see #CONTEXT_CLASS_PARAM
 * @see #CONFIG_LOCATION_PARAM
 */
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        throw new IllegalStateException(
            "Cannot initialize context because there is already a root application context present - " +
            "check whether you have multiple ContextLoader* definitions in your web.xml!");
    }

    servletContext.log("Initializing Spring root WebApplicationContext");
    Log logger = LogFactory.getLog(ContextLoader.class);
    if (logger.isInfoEnabled()) {
        logger.info("Root WebApplicationContext: initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
        // Store context in local instance variable, to guarantee that
        // it is available on ServletContext shutdown.
        //创建监听器对象的时候没有指定web上下文
        if (this.context == null) {
            this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            //上下文还没有调用refresh()方法
            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 ->
                    // determine parent for root web application context, if any.
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                //初始化上下文,见3.5.1,虽然两个方法并不是同一个方法,但内容差不多
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        
        //父容器放到servlet上下文中,等待springmvc上下文初始化的时候取出来,见3.5.1
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = this.context;
        }
        else if (ccl != null) {
            currentContextPerThread.put(ccl, this.context);
        }

        if (logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
        }

        return this.context;
    }
    catch (RuntimeException | Error ex) {
        logger.error("Context initialization failed", ex);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
        throw ex;
    }
}

总结:

  • web应用初始化之前,会回调所有监听器的contextInitialized()方法
  • ContextLoaderListener创建了一个父上下文,并设置到Servlet上下文中
  • 自动初始化父上下文(自动调用refresh()方法)

5 springmvc默认的9大组件的初始化策略

我们知道,springmvc调用DispatcherServlet类的onRefresh()方法完成9大组件的初始化

/**
 * 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.
 * 初始化springmvc的9大组件,默认策略,组件可被覆盖
 */
protected void initStrategies(ApplicationContext context) {
    //文件上传解析器
    initMultipartResolver(context);
    //区域解析器
    initLocaleResolver(context);
    //主题解析器
    initThemeResolver(context);
    //处理器映射器
    initHandlerMappings(context);
    //处理器适配器
    initHandlerAdapters(context);
    //处理器异常解析器
    initHandlerExceptionResolvers(context);
    //视图名转换器
    initRequestToViewNameTranslator(context);
    //视图解析器
    initViewResolvers(context);
    //FlashMapManager
    initFlashMapManager(context);
}

5.1 MultipartResolver文件上传解析器

//规定的文件上传解析器的名字
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";


/**
 * Initialize the MultipartResolver used by this class.
 * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
 * no multipart handling is provided.
 */
private void initMultipartResolver(ApplicationContext context) {
    //从容器中获取名字为multipartResolver的bean,就是文件上传解析器
    try {
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
        if (logger.isTraceEnabled()) {
            logger.trace("Detected " + this.multipartResolver);
        }
        else if (logger.isDebugEnabled()) {
            logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // Default is no multipart resolver.
        //用户没有配置文件上传解析器,那么系统的就为null
        this.multipartResolver = null;
        if (logger.isTraceEnabled()) {
            logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");
        }
    }
}
  • springmvc并没有配置默认的MultipartResolver,必须用户手动配置
  • 用户手动配置的文件上传解析器的名字必须为multipartResolver

5.2 LocaleResolver区域解析器

//规定的区域解析器的名字
public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";


/**
 * 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 {
        this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
        if (logger.isTraceEnabled()) {
            logger.trace("Detected " + this.localeResolver);
        }
        else if (logger.isDebugEnabled()) {
            logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // We need to use the default.
        //使用springmvc默认的LocaleResolver,见5.10
        this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
                         "': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
        }
    }
}
  • 如果用户没有配置LocaleResolver,就使用springmvc默认的AcceptHeaderLocaleResolver
  • 用户手动配置的区域解析器的名字必须为localeResolver

5.3 ThemeResolver主题解析器

//规定的主题解析器的名字
public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";


/**
 * Initialize the ThemeResolver used by this class.
 * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
 * we default to a FixedThemeResolver.
 */
private void initThemeResolver(ApplicationContext context) {
    try {
        this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
        if (logger.isTraceEnabled()) {
            logger.trace("Detected " + this.themeResolver);
        }
        else if (logger.isDebugEnabled()) {
            logger.debug("Detected " + this.themeResolver.getClass().getSimpleName());
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // We need to use the default.
        //使用springmvc默认的ThemeResolver,见5.10
        this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No ThemeResolver '" + THEME_RESOLVER_BEAN_NAME +
                         "': using default [" + this.themeResolver.getClass().getSimpleName() + "]");
        }
    }
}
  • 如果用户没有配置ThemeResolver,就使用springmvc默认的FixedThemeResolver
  • 用户手动配置的主题解析器的名字必须为themeResolver

5.4 HandlerMapping处理器映射器

//这个属性默认为true,表明允许用户全面接管HandlerMapping
private boolean detectAllHandlerMappings = true;
//规定的处理器映射器的名字
public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";


/**
 * Initialize the HandlerMappings used by this class.
 * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
 * we default to BeanNameUrlHandlerMapping.
 */
private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;

    //允许用户全面接管HandlerMapping
    if (this.detectAllHandlerMappings) {
        // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
        //获取容器中所有类型为HandlerMapping的bean
        Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // We keep HandlerMappings in sorted order.
            //排序
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    //只允许使用名字为handlerMapping的处理器映射器
    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.
    //使用系统默认的HandlerMapping配置策略,见5.10
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                         "': using default strategies from DispatcherServlet.properties");
        }
    }
}

  • 如果用户没有配置HandlerMapping,就使用springmvc默认的BeanNameUrlHandlerMappingSimpleControllerHandlerAdapterRequestMappingHandlerMappingHandlerFunctionAdapter
  • 用户手动配置的处理器映射器的名字随意,因为是根据类型查找的。

5.5 HandlerAdapter 处理器适配器

//这个属性默认为true,表明允许用户全面接管HandlerAdapter
private boolean detectAllHandlerAdapters = true;
//规定的处理器适配器的名字
public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";

/**
 * Initialize the HandlerAdapters used by this class.
 * <p>If no HandlerAdapter beans are defined in the BeanFactory for this namespace,
 * we default to SimpleControllerHandlerAdapter.
 */
private void initHandlerAdapters(ApplicationContext context) {
    this.handlerAdapters = null;

    //允许用户全面接管HandlerAdapter
    if (this.detectAllHandlerAdapters) {
        // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
        //获取容器中所有类型为HandlerAdapter的bean
        Map<String, HandlerAdapter> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerAdapters = new ArrayList<>(matchingBeans.values());
            // We keep HandlerAdapters in sorted order.
            //排序
            AnnotationAwareOrderComparator.sort(this.handlerAdapters);
        }
    }
    //只允许使用名字为handlerAdapter的处理器适配器
    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.
    //使用系统默认的HandlerAdapter配置策略,见5.10
    if (this.handlerAdapters == null) {
        this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
                         "': using default strategies from DispatcherServlet.properties");
        }
    }
}
  • 如果用户没有配置HandlerAdapter,就使用springmvc默认的HttpRequestHandlerAdapterSimpleControllerHandlerAdapterRequestMappingHandlerAdapterHandlerFunctionAdapter
  • 用户手动配置的HandlerAdapter的名字随意,因为是根据类型查找的。

5.6 HandlerExceptionResolver处理器异常解析器

//这个属性默认为true,表明允许用户全面接管HandlerExceptionResolver
private boolean detectAllHandlerExceptionResolvers = true;
//规定的处理器异常解析器的名字
public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";


/**
 * Initialize the HandlerExceptionResolver used by this class.
 * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
 * we default to no exception resolver.
 */
private void initHandlerExceptionResolvers(ApplicationContext context) {
    this.handlerExceptionResolvers = null;

    //允许用户全面接管HandlerExceptionResolver
    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<>(matchingBeans.values());
            // We keep HandlerExceptionResolvers in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
        }
    }
    //只允许使用名字为handlerExceptionResolver的处理器异常解析器
    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) {
        //使用系统默认的HandlerExceptionResolver配置策略,见5.10
        this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
                         "': using default strategies from DispatcherServlet.properties");
        }
    }
}
  • 如果用户没有配置HandlerExceptionResolver,就使用springmvc默认的ExceptionHandlerExceptionResolverResponseStatusExceptionResolverDefaultHandlerExceptionResolver
  • 用户手动配置的HandlerExceptionResolver的名字随意,因为是根据类型查找的。

5.7 RequestToViewNameTranslator 视图名转换器

//规定的视图名转换器的名字
public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";


/**
 * Initialize the RequestToViewNameTranslator used by this servlet instance.
 * <p>If no implementation is configured then we default to DefaultRequestToViewNameTranslator.
 */
private void initRequestToViewNameTranslator(ApplicationContext context) {
    try {
        //从容器中获取
        this.viewNameTranslator =
            context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
        if (logger.isTraceEnabled()) {
            logger.trace("Detected " + this.viewNameTranslator.getClass().getSimpleName());
        }
        else if (logger.isDebugEnabled()) {
            logger.debug("Detected " + this.viewNameTranslator);
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // We need to use the default.
        //使用springmvc默认的RequestToViewNameTranslator,见5.10
        this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No RequestToViewNameTranslator '" + REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME +
                         "': using default [" + this.viewNameTranslator.getClass().getSimpleName() + "]");
        }
    }
}
  • 如果用户没有配置RequestToViewNameTranslator ,就使用springmvc默认的DefaultRequestToViewNameTranslator
  • 用户手动配置的RequestToViewNameTranslator 的名字必须为viewNameTranslator

5.8 ViewResolver视图解析器

//这个属性默认为true,表明允许用户全面接管ViewResolver
private boolean detectAllViewResolvers = true;
//规定的视图解析器的名字
public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";


/**
 * Initialize the ViewResolvers used by this class.
 * <p>If no ViewResolver beans are defined in the BeanFactory for this
 * namespace, we default to InternalResourceViewResolver.
 */
private void initViewResolvers(ApplicationContext context) {
    this.viewResolvers = null;

    //允许用户全面接管ViewResolver
    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<>(matchingBeans.values());
            // We keep ViewResolvers in sorted order.
            AnnotationAwareOrderComparator.sort(this.viewResolvers);
        }
    }
    //只允许使用名字为viewResolver的视图解析器
    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) {
        //使用系统默认的ViewResolver配置策略,见5.10
        this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No ViewResolvers declared for servlet '" + getServletName() +
                         "': using default strategies from DispatcherServlet.properties");
        }
    }
}
  • 如果用户没有配置ViewResolver,就使用springmvc默认的InternalResourceViewResolver
  • 用户手动配置的ViewResolver的名字随意,因为是根据类型查找的。

5.9 FlashMapManager

//规定的FlashMapManager的名字
public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager";

/**
 * Initialize the {@link FlashMapManager} used by this servlet instance.
 * <p>If no implementation is configured then we default to
 * {@code org.springframework.web.servlet.support.DefaultFlashMapManager}.
 */
private void initFlashMapManager(ApplicationContext context) {
    try {
        this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);
        if (logger.isTraceEnabled()) {
            logger.trace("Detected " + this.flashMapManager.getClass().getSimpleName());
        }
        else if (logger.isDebugEnabled()) {
            logger.debug("Detected " + this.flashMapManager);
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // We need to use the default.
        //使用springmvc默认的FlashMapManager,见5.10
        this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No FlashMapManager '" + FLASH_MAP_MANAGER_BEAN_NAME +
                         "': using default [" + this.flashMapManager.getClass().getSimpleName() + "]");
        }
    }
}
  • 如果用户没有配置FlashMapManager,就使用springmvc默认的SessionFlashMapManager
  • 用户手动配置的FlashMapManager的名字必须为flashMapManager

5.10 springmvc默认的组件配置策略

private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
//保存了DispatcherServlet.properties文件的内容
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 {
        //加载DispatcherServlet.properties文件的内容
        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());
    }
}

/**
 * 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();
    String value = defaultStrategies.getProperty(key);
    if (value != null) {
        //配置了多个,以,分割
        String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
        List<T> strategies = new ArrayList<>(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(
                    "Unresolvable class definition for DispatcherServlet's default strategy class [" +
                    className + "] for interface [" + key + "]", err);
            }
        }
        return strategies;
    }
    else {
        return new LinkedList<>();
    }
}

那接下来我们再来看一下这个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.method.annotation.RequestMappingHandlerMapping,\
   org.springframework.web.servlet.function.support.RouterFunctionMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
   org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
   org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
   org.springframework.web.servlet.function.support.HandlerFunctionAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
   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

springmvc默认的组件配置就是上面文件描述的这些了,一共8种(不包含文件上传解析器)

  • LocaleResolver:默认的组件类型为AcceptHeaderLocaleResolver
  • ThemeResolver:默认的组件类型为FixedThemeResolver
  • HandlerMapping:默认配置了4个,组件类型分别为为BeanNameUrlHandlerMappingSimpleControllerHandlerAdapterRequestMappingHandlerMappingHandlerFunctionAdapter
  • HandlerAdapter:默认配置了4个,组件类型分别为为HttpRequestHandlerAdapterSimpleControllerHandlerAdapterRequestMappingHandlerAdapterHandlerFunctionAdapter。很明显是和HandlerMapping一一对应的
  • HandlerExceptionResolver:默认配置了3个,组件类型分别为为ExceptionHandlerExceptionResolverResponseStatusExceptionResolverDefaultHandlerExceptionResolver
  • RequestToViewNameTranslator:默认的组件类型为DefaultRequestToViewNameTranslator
  • ViewResolver:默认的组件类型为InternalResourceViewResolver
  • FlashMapManager:默认的组件类型为SessionFlashMapManager

5.11 总结

  • MultipartResolverLocaleResolverThemeResolverRequestToViewNameTranslatorFlashMapManager5个组件必须按照规定的名字注册到容器中,才能被springmvc使用
  • HandlerMappingHandlerAdapterHandlerExceptionResolverViewResolver则不要求特定的名字,且它们可以同时在容器中配置多个,同时生效
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值