001-SpringMVC和Spring的结合

SpringMVC 全体系图

SpringMVC配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-config.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>mvc-test</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--SpringMVC配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>

    <servlet-mapping>
        <servlet-name>mvc-test</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

整体启动流程

在启动Spring容器之前,需要启动Tomcat等Web服务器,由Tomcat等Web服务器根据配置文件web.xml,中配置的ContextLoaderListener监听器来启动Spring容器,启动Spring 容器之后,再由Tomcat等Web服务器根据配置文件web.xml 中配置的 DispatcherServlet 来启动 SpringMVC 容器,然后以Spring容器做为父容器将两个容器关联起来,最后由事件通知机制来初始化 SpringMVC 的九大内置组件。

启动 Spring 容器

如上述所说,由 Web 服务器根据配置文件中 ContextLoaderListener 来启动 Spring 容器,contextInitialized(ServletContextEvent event)。

initWebApplicationContext(ServletContext servletContext)

初始化context,获取到一个 Root webApplicationContext,同时刷新容器,最后将其记录到servlet上下文中,方便后续在SpringMVC容器中获取,将其关联到一块。

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        // web.xml中存在多次ContextLoader定义
        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.
        if (this.context == null) {
            // 初始化context,第一次执行的时候获取到一个root webApplicationcontext
            this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            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);
                }
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        // 将创建的context对象记录在servletContext中,创建并且准备好了spring容器
        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;
    }
}

createWebApplicationContext(servletContext)

获取上下文类对象,实例化上下文对象

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    // 获取contextClass的Class对象
    Class<?> contextClass = determineContextClass(sc);
    // 如果是自定义的contextClass对象,那么必须要实现ConfigurableWebApplicationContext此接口,否则无法直接运行
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                                              "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

determineContextClass(sc)

从上下文中获取上下文类名称,如果没有获取到,从默认的配置文件 ContextLoader.properties 中获取

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

configureAndRefreshWebApplicationContext(cwac, servletContext)

设置一些基本属性,加载全局配置文件中 contextConfigLocation 的值,将其设置 Spring 容器上下文中,方便其后续进行上下文刷新,加载和实例化 Bean。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        // The application context id is still set to its original default value
        // -> assign a more useful id based on available information
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        if (idParam != null) {
            wac.setId(idParam);
        }
        else {
            // Generate default id...
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                      ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }

    wac.setServletContext(sc);
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }

    // 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(sc, null);
    }

    customizeContext(sc, wac);
    wac.refresh();
}

启动 SpringMVC 容器

如上述所说,由 Web 容器根据配置文件,来启动 Servlet,由 Servlet 的生命周期来控制,init、service、destroy,在 init 方法中进行各种参数的初始化,同时启动 SpringMVC 容器等。

init()

将 servlet 中配置的 init-param 参数封装到 PropertyValues 变量中,方便后续将其填充到 DispatcherServlet 中。

public final void init() throws ServletException {

    // Set bean properties from init parameters.
    // 将servlet中配置的init-param参数封装到pvs变量中
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            // 将当前的servlet对象转化成BeanWrapper对象,从而能够以spring的方法来将pvs注入到该beanWrapper中
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            // 注册自定义属性编辑器,一旦有Resource类型的属性,将会使用ResourceEditor进行解析
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            // 模板方法,可以在子类调用,做一些初始化工作,bw代表的是DispatcherServlet
            initBeanWrapper(bw);
            // 以spring的方式来将pvs注入到该beanWrapper对象中,将配置的初始化值(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.
    // 模板方法,子类初始化的入口方法,查看FrameworkServlet#initServletBean方法
    initServletBean();
}

initServletBean()

主要用于创建并刷新 applicationContext,同时对其用到的变量进行初始化,包括九大组件。

protected final void initServletBean() throws ServletException {
    // 记录开启时间
    long startTime = System.currentTimeMillis();

    try {
        // 创建或刷新WebApplicationContext实例并对servlet功能所使用的变量进行初始化
        this.webApplicationContext = initWebApplicationContext();
        // 模板方法,空实现,留给子类扩展
        initFrameworkServlet();
    }
    catch (ServletException | RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        throw ex;
    }
}

initWebApplicationContext()

获取根 WebApplicationContext,然后创建一个 webApplicationContext,

protected WebApplicationContext initWebApplicationContext() {
    // 获得根webApplicationContext对象
    WebApplicationContext rootContext =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    // 获得webApplicationContext wac对象
    WebApplicationContext wac = null;

    // 如果构造方法中已经传入webApplicationContext属性,则直接使用
    // 此方式主要用于servlet3.0之后的环境,也就是说可以通过ServletContext.addServlet的方法注册servlet,此时就可以在创建FrameworkServlet和
    // 其子类的时候通过构造方法传递已经准备好的webApplicationContext
    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        // 如果是ConfigurationWebApplicationContext类型,并且未激活,则进行初始化
        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);
            }
        }
    }
    // 从servletContext获取对应的webApplicationContext对象
    // 此方式需要在配置Servlet的时候将servletContext中的webApplicationContext的name配置到contextAttribute属性就可以
    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
        wac = findWebApplicationContext();
    }
    // 当前面两种方式都无效的情况下会创建一个webApplicationContext对象,一般情况下都是使用这样的方式
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        wac = createWebApplicationContext(rootContext);
    }

    // 将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(wac);
        }
    }

    // 将applicationContext设置到servletContext中
    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

createWebApplicationContext(rootContext)

获取 servlet 的初始化参数值 contextClass,默认值为 XmlWebApplicationContext,通过反射的方式实例化 contextClass,设置各种初始化参数。

createWebApplicationContext((ApplicationContext) parent)

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    // 获取servlet的初始化参数contextClass,如果没有配置默认为XmlWebApplicationContext.class
    Class<?> contextClass = getContextClass();
    // 如果非ConfigurableWebApplicationContext类型,抛出ConfigurableWebApplicationContext异常
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException(
            "Fatal initialization error in servlet with name '" + getServletName() +
            "': custom WebApplicationContext class [" + contextClass.getName() +
            "] is not of type ConfigurableWebApplicationContext");
    }
    // 通过反射方式实例化contextClass
    ConfigurableWebApplicationContext wac =
        (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    // 设置environment
    wac.setEnvironment(getEnvironment());
    // parent为在ContextLoaderListener中创建的实例,在ContextLoaderListener加载的时候初始化的WebApplicationContext类型实例
    wac.setParent(parent);
    // 获取contextConfigLocation属性,配置在servlet初始化参数中
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
        // 将设置的contextConfigLocation参数传给wac,默认传入WEB-INFO/servletName-servlet.xml
        wac.setConfigLocation(configLocation);
    }
    // 配置和初始化wac
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

configureAndRefreshWebApplicationContext(wac)

配置和初始化 SpringMVC 容器,设置 容器的 servletContext、servletConfig、namespace 等属性,添加监听器,监听 ContextRefreshedEvent 事件,当接收到消息的时候会调用 onApplicationEvent 方法。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    // 如果wac使用了默认编号,则重新设置id属性
    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
        // 使用contextId属性
        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的servletContext、servletConfig、namespace属性
    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    // 添加监听器sourceFilteringListener到wac中,实际监听的是ContextRefreshListener所监听的事件,监听ContextRefreshedEvent事件,
    // 当接收到消息之后会调用onApplicationEvent方法,调用onRefresh方法,并将refreshEventReceived标志设置为true,表示已经refresh过
    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());
    }

    // 执行处理完WebApplicationContext后的逻辑,此处为空方法,不做任何实现
    postProcessWebApplicationContext(wac);
    // 执行自定义初始化context
    applyInitializers(wac);
    // 刷新wac,从而初始化wac
    wac.refresh();
}

ContextRefreshListener

wac.refresh() 刷新容器的时候会在最后发出上下文刷新完成 ContextRefreshedEvent 事件,最后会调用 onApplicationEvent 来进行九大内置组件的初始化。

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

   @Override
   public void onApplicationEvent(ContextRefreshedEvent event) {
      FrameworkServlet.this.onApplicationEvent(event);
   }
}

FrameworkServlet.this.onApplicationEvent(event)

将收到刷新事件的标志置为 true,防止后续进行重复刷新。

public void onApplicationEvent(ContextRefreshedEvent event) {
   // 标记 refreshEventReceived 为true
   this.refreshEventReceived = true;
   synchronized (this.onRefreshMonitor) {
      // 处理事件中的 ApplicationContext 对象,空实现,子类DispatcherServlet会实现
      onRefresh(event.getApplicationContext());
   }
}

onRefresh(event.getApplicationContext())

初始化九大内置组件。

protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {
    // 初始化 MultipartResolver:主要用来处理文件上传.如果定义过当前类型的bean对象,那么直接获取,如果没有的话,可以为null
    initMultipartResolver(context);
    // 初始化 LocaleResolver:主要用来处理国际化配置,基于URL参数的配置(AcceptHeaderLocaleResolver),基于session的配置(SessionLocaleResolver),基于cookie的配置(CookieLocaleResolver)
    initLocaleResolver(context);
    // 初始化 ThemeResolver:主要用来设置主题Theme
    initThemeResolver(context);
    // 初始化 HandlerMapping:映射器,用来将对应的request跟controller进行对应
    initHandlerMappings(context);
    // 初始化 HandlerAdapter:处理适配器,主要包含Http请求处理器适配器,简单控制器处理器适配器,注解方法处理器适配器
    initHandlerAdapters(context);
    // 初始化 HandlerExceptionResolver:基于HandlerExceptionResolver接口的异常处理
    initHandlerExceptionResolvers(context);
    // 初始化 RequestToViewNameTranslator:当controller处理器方法没有返回一个View对象或逻辑视图名称,并且在该方法中没有直接往response的输出流里面写数据的时候,spring将会采用约定好的方式提供一个逻辑视图名称
    initRequestToViewNameTranslator(context);
    // 初始化 ViewResolver: 将ModelAndView选择合适的视图进行渲染的处理器
    initViewResolvers(context);
    // 初始化 FlashMapManager: 提供请求存储属性,可供其他请求使用
    initFlashMapManager(context);
}

initHandlerMappings(context);

初始化处理器映射器,看是否开启了探测功能,如果开启,则扫描所有已注册的 HandlerMapping 的 Bean,添加到 handlerMappings 中,如果没有开启,则获取名称为 handlerMapping 类型的Bean,将其添加到handlerMappings中;最后如果上述未获取到,则获取默认配置的 handlerMapping 类。

private void initHandlerMappings(ApplicationContext context) {
    // 将handlerMappings置空
    this.handlerMappings = null;

    // 如果开启探测功能,则扫描已注册的HandlerMapping的bean,添加到handlerMappings中
    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);
        // 添加到handlerMappings中,并进行排序
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // We keep HandlerMappings in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    // 如果关闭探测功能,则获取Bean名称为handlerMapping对应的bean,将其添加到handlerMappings
    else {
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }

    // Ensure we have at least one HandlerMapping, by registering
    // a default HandlerMapping if no other mappings are found.
    // 如果未获得到,则获得默认配置的handlerMapping类
    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_test.properties");
        }
    }
}

getDefaultStrategies(context, HandlerMapping.class);

从默认的 DispatcherServlet.properties 中加载配置,文件位置位于 org.springframework.web.servlet 下,

private static final String DEFAULT_STRATEGIES_PATH = "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 {
        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());
    }
}

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    // 获得strategyInterface对应的value值
    String key = strategyInterface.getName();
    // 创建value对应的对象们,并返回
    String value = defaultStrategies.getProperty(key);
    if (value != null) {
        // 基于","分隔,创建classNames数组
        String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
        // 创建strategyInterface集合
        List<T> strategies = new ArrayList<>(classNames.length);
        // 遍历classNames数组,创建对应的类,添加到strategyInterface中
        for (String className : classNames) {
            try {
                // 获得className类
                Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                // 创建className对应的类,并添加到strategies中
                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<>();
    }
}
posted @ 2021-12-29 14:31  ice_image  阅读(69)  评论(0编辑  收藏  举报