我是一段不羁的公告!
记得给艿艿这 3 个项目加油,添加一个 STAR 噢。
https://github.com/YunaiV/SpringBoot-Labs
https://github.com/YunaiV/onemall
https://github.com/YunaiV/ruoyi-vue-pro

精尽 Spring MVC 源码分析 —— 容器的初始化(二)之 Servlet WebApplicationContext 容器

1. 概述

本文接 《精尽 Spring MVC 源码分析 —— 容器的初始化(一)之 Root WebApplicationContext 容器》 一文,我们来分享下 Servlet WebApplicationContext 容器的初始化的过程。

在开始之前,我们还是回过头看一眼 web.xml 的配置。代码如下:

<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 可以自定义servlet.xml配置文件的位置和名称,默认为WEB-INF目录下,名称为[<servlet-name>]-servlet.xml,如spring-servlet.xml
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-servlet.xml</param-value> // 默认
</init-param>
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
  • 即, Servlet WebApplicationContext 容器的初始化,是在 DispatcherServlet 初始化的过程中执行。

DispatcherServlet 的类图如下:

类图

  • HttpServletBean ,负责将 ServletConfig 设置到当前 Servlet 对象中。类上的简单注释如下:

    // HttpServletBean.java

    /**
    * Simple extension of {@link javax.servlet.http.HttpServlet} which treats
    * its config parameters ({@code init-param} entries within the
    * {@code servlet} tag in {@code web.xml}) as bean properties.
    */
  • FrameworkServlet ,负责初始化 Spring Servlet WebApplicationContext 容器。类上的简单注释如下:

    // FrameworkServlet.java

    /**
    * Base servlet for Spring's web framework. Provides integration with
    * a Spring application context, in a JavaBean-based overall solution.
    */
  • DispatcherServlet ,负责初始化 Spring MVC 的各个组件,以及处理客户端的请求。类上的简单注释如下:

    // DispatcherServlet.java

    /**
    * Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers
    * or HTTP-based remote service exporters. Dispatches to registered handlers for processing
    * a web request, providing convenient mapping and exception handling facilities.
    */

每一层的 Servlet 实现类,执行对应负责的逻辑。干净~下面,我们逐个类来进行解析。

2. 如何调试

执行 DispatcherServletTests#configuredDispatcherServlets() 单元测试方法,即可执行本文涉及的一些逻辑。

3. HttpServletBean

org.springframework.web.servlet.HttpServletBean ,实现 EnvironmentCapable、EnvironmentAware 接口,继承 HttpServlet 抽象类,负责将 ServletConfig 集成到 Spring 中。当然,HttpServletBean 自身也是一个抽象类。

3.1 构造方法

// HttpServletBean.java

@Nullable
private ConfigurableEnvironment environment;

/**
* 必须配置的属性的集合
*
* 在 {@link ServletConfigPropertyValues} 中,会校验是否有对应的属性
*/
private final Set<String> requiredProperties = new HashSet<>(4);
  • environment 属性,相关的方法,代码如下:

    // HttpServletBean.java

    // setting 方法
    @Override // 实现自 EnvironmentAware 接口,自动注入
    public void setEnvironment(Environment environment) {
    Assert.isInstanceOf(ConfigurableEnvironment.class, environment, "ConfigurableEnvironment required");
    this.environment = (ConfigurableEnvironment) environment;
    }

    // getting 方法
    @Override // 实现自 EnvironmentCapable 接口
    public ConfigurableEnvironment getEnvironment() {
    // 如果 environment 为空,主动创建
    if (this.environment == null) {
    this.environment = createEnvironment();
    }
    return this.environment;
    }

    protected ConfigurableEnvironment createEnvironment() {
    return new StandardServletEnvironment();
    }
  • requiredProperties 属性,必须配置的属性的集合。可通过 #addRequiredProperty(String property) 方法,添加到其中。代码如下:

    // HttpServletBean.java

    protected final void addRequiredProperty(String property) {
    this.requiredProperties.add(property);
    }

3.2 init

#init() 方法,负责将 ServletConfig 设置到当前 Servlet 对象中。代码如下:

// HttpServletBean.java

@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
// <1> 解析 <init-param /> 标签,封装到 PropertyValues pvs 中
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// <2.1> 将当前的这个 Servlet 对象,转化成一个 BeanWrapper 对象。从而能够以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
// <2.2> 注册自定义属性编辑器,一旦碰到 Resource 类型的属性,将会使用 ResourceEditor 进行解析
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
// <2.3> 空实现,留给子类覆盖
initBeanWrapper(bw);
// <2.4> 以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中
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> 子类来实现,实现自定义的初始化逻辑。目前,有具体的代码实现。
initServletBean();
}
  • <1> 处,解析 Servlet 配置的 <init-param /> 标签,封装到 PropertyValues pvs 中。其中,ServletConfigPropertyValues 是 HttpServletBean 的私有静态类,继承 MutablePropertyValues 类,ServletConfig 的 PropertyValues 封装实现类。代码如下:

    // HttpServletBean.java

    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 {
    // 获得缺失的属性的集合
    Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
    new HashSet<>(requiredProperties) : null);

    // <1> 遍历 ServletConfig 的初始化参数集合,添加到 ServletConfigPropertyValues 中,并从 missingProps 移除
    Enumeration<String> paramNames = config.getInitParameterNames();
    while (paramNames.hasMoreElements()) {
    String property = paramNames.nextElement();
    Object value = config.getInitParameter(property);
    // 添加到 ServletConfigPropertyValues 中
    addPropertyValue(new PropertyValue(property, value));
    // 从 missingProps 中移除
    if (missingProps != null) {
    missingProps.remove(property);
    }
    }

    // Fail if we are still missing properties.
    // <2> 如果存在缺失的属性,抛出 ServletException 异常
    if (!CollectionUtils.isEmpty(missingProps)) {
    throw new ServletException(
    "Initialization from ServletConfig for servlet '" + config.getServletName() +
    "' failed; the following required properties were missing: " +
    StringUtils.collectionToDelimitedString(missingProps, ", "));
    }
    }

    }
    • 代码简单,实现两方面的逻辑:<1> 处,遍历 ServletConfig 的初始化参数集合,添加到 ServletConfigPropertyValues 中;<2> 处,判断要求的属性是否齐全。如果不齐全,则抛出 ServletException 异常。
  • <2.1> 处,将当前的这个 Servlet 对象,转化成一个 BeanWrapper 对象。从而能够以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中。简单来说,BeanWrapper 是 Spring 提供的一个用来操作 Java Bean 属性的工具,使用它可以直接修改一个对象的属性

  • <2.2> 处,注册自定义属性编辑器,一旦碰到 Resource 类型的属性,将会使用 ResourceEditor 进行解析。
  • <2.3> 处,空实现,留给子类覆盖。代码如下:

    // HttpServletBean.java

    /**
    * Initialize the BeanWrapper for this HttpServletBean,
    * possibly with custom editors.
    * <p>This default implementation is empty.
    * @param bw the BeanWrapper to initialize
    * @throws BeansException if thrown by BeanWrapper methods
    * @see org.springframework.beans.BeanWrapper#registerCustomEditor
    */
    protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
    }
    • 然而实际上,子类暂时木有任何实现。
  • <2.4> 处,以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中,技设置到当前 Servlet 对象中。可能比较费解,我们还是举个例子。假设如下:

    // web.xml

    <servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>spring</servlet-name>
    <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    • 此处有配置了 contextConfigLocation 属性,那么通过 <2.4> 处的逻辑,会反射设置到 FrameworkServlet.contextConfigLocation 属性。代码如下:

      // FrameworkServlet.java

      /** Explicit context config location. */
      @Nullable
      private String contextConfigLocation;

      public void setContextConfigLocation(@Nullable String contextConfigLocation) {
      this.contextConfigLocation = contextConfigLocation;
      }
      • 😈 看懂了这波骚操作了么?
  • <3> 处,调用 #initServletBean() 方法,子类来实现,实现自定义的初始化逻辑。目前,FrameworkServlet 实现类该方法。代码如下:

    // HttpServletBean.java

    protected void initServletBean() throws ServletException {
    }

4. FrameworkServlet

org.springframework.web.servlet.FrameworkServlet ,实现 ApplicationContextAware 接口,继承 HttpServletBean 抽象类,负责初始化 Spring Servlet WebApplicationContext 容器。同时,FrameworkServlet 自身也是一个抽象类。

4.1 构造方法

FrameworkServlet 的属性还是非常多,我们还是只看部分的关键属性。代码如下:

// FrameworkServlet.java

/**
* WebApplicationContext implementation class to create.
*
* 创建的 WebApplicationContext 类型
*/
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;

/**
* Explicit context config location.
*
* 配置文件的地址
*/
@Nullable
private String contextConfigLocation;

/**
* WebApplicationContext for this servlet.
*
* WebApplicationContext 对象
*/
@Nullable
private WebApplicationContext webApplicationContext;
  • contextClass 属性,创建的 WebApplicationContext 类型,默认为 DEFAULT_CONTEXT_CLASS 。代码如下:

    /**
    * Default context class for FrameworkServlet.
    * @see org.springframework.web.context.support.XmlWebApplicationContext
    */
    public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
    • 😈 又是我们熟悉的 XmlWebApplicationContext 类。在上一篇文章的 ContextLoader.properties 配置文件中,我们已经看到咯。
  • contextConfigLocation 属性,配置文件的地址。例如:/WEB-INF/spring-servlet.xml

  • webApplicationContext 属性,WebApplicationContext 对象,即本文的关键,Servlet WebApplicationContext 容器。它有四种方式进行“创建”。

    • 方式一:通过构造方法,代码如下:

      // FrameworkServlet.java

      public FrameworkServlet(WebApplicationContext webApplicationContext) {
      this.webApplicationContext = webApplicationContext;
      }
      • 通过方法参数 webApplicationContext
    • 方式二:因为实现 ApplicationContextAware 接口,也可以 Spring 注入。代码如下:

      // FrameworkServlet.java

      /**
      * If the WebApplicationContext was injected via {@link #setApplicationContext}.
      *
      * 标记 {@link #webApplicationContext} 属性,是否通过 {@link #setApplicationContext(ApplicationContext)} 方法进行注入
      */
      private boolean webApplicationContextInjected = false;

      @Override
      public void setApplicationContext(ApplicationContext applicationContext) {
      if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
      this.webApplicationContext = (WebApplicationContext) applicationContext;
      this.webApplicationContextInjected = true;
      }
      }
      • 和方式一,是有几分类似的。
    • 方式三:见 #findWebApplicationContext() 方法。
    • 方式四:见 #createWebApplicationContext(WebApplicationContext parent) 方法。

4.2 initServletBean

#initServletBean() 方法,进一步初始化当前 Servlet 对象。实际上,重心在初始化 Servlet WebApplicationContext 容器。代码如下:

// FrameworkServlet.java

@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 {
// 初始化 WebApplicationContext 对象
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");
}
}
  • <1> 处,调用 #initWebApplicationContext() 方法,初始化 Servlet WebApplicationContext 对象。详细解析,见 「4.3 initWebApplicationContext」
  • <2> 处,调用 #initFrameworkServlet() 方法,空实现。子类有需要,可以实现该方法,实现自定义逻辑。代码如下:

    // FrameworkServlet.java

    /**
    * This method will be invoked after any bean properties have been set and
    * the WebApplicationContext has been loaded. The default implementation is empty;
    * subclasses may override this method to perform any initialization they require.
    * @throws ServletException in case of an initialization exception
    */
    protected void initFrameworkServlet() throws ServletException {
    }
    • 😈 然而实际上,并没有子类,对该方法重新实现。

4.3 initWebApplicationContext

#initWebApplicationContext() 方法,初始化 Servlet WebApplicationContext 对象。代码如下:

艿艿提示:这个方法的逻辑并不复杂,但是涉及调用的方法的逻辑比较多。同时,也是本文最最最核心的方法了。

// FrameworkServlet.java

protected WebApplicationContext initWebApplicationContext() {
// <1> 获得根 WebApplicationContext 对象
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());

// <2> 获得 WebApplicationContext wac 变量
WebApplicationContext wac = null;
// 第一种情况,如果构造方法已经传入 webApplicationContext 属性,则直接使用
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
// 赋值给 wac 变量
wac = this.webApplicationContext;
// 如果是 ConfigurableWebApplicationContext 类型,并且未激活,则进行初始化
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
// 设置 wac 的父 context 为 rootContext 对象
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);
}
// 配置和初始化 wac
configureAndRefreshWebApplicationContext(cwac);
}
}
}
// 第二种情况,从 ServletContext 获取对应的 WebApplicationContext 对象
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);
}

// <3> 如果未触发刷新事件,则主动触发刷新事件
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.
onRefresh(wac);
}

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

return wac;
}
  • <1> 处,调用 WebApplicationContextUtils#getWebApplicationContext((ServletContext sc) 方法,获得 Root WebApplicationContext 对象,这就是在 《精尽 Spring MVC 源码分析 —— 容器的初始化(一)之 Root WebApplicationContext 容器》 中初始化的呀。代码如下:

    // WebApplicationContextUtils.java

    @Nullable
    public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
    return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
    }

    @Nullable
    public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
    Assert.notNull(sc, "ServletContext must not be null");
    Object attr = sc.getAttribute(attrName);
    // ... 省略各种校验的代码
    return (WebApplicationContext) attr;
    }
    • 熟不熟悉,惊喜不惊喜。
  • <2> 处,获得 WebApplicationContext wac 变量。下面,会分成三种情况。

  • ========== 第一种情况 ==========
  • 如果构造方法已经传入 webApplicationContext 属性,则直接使用。实际上,就是我们在 「4.1 构造方法」 提到的 Servlet WebApplicationContext 容器的第一、二种方式。
  • 实际上,这块代码和 ContextLoader#initWebApplicationContext(ServletContext servletContext)中间段 是一样的。除了 #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 的具体实现代码不同。详细解析,见 「4.4 configureAndRefreshWebApplicationContext」
  • ========== 第二种情况 ==========
  • 这种情况,就是我们在 「4.1 构造方法」 提到的 Servlet WebApplicationContext 容器的第三种方式。
  • 如果此处 wac 还是为空,则调用 #findWebApplicationContext() 方法,从 ServletContext 获取对应的 WebApplicationContext 对象。代码如下:

    // FrameworkServlet.java

    /** ServletContext attribute to find the WebApplicationContext in. */
    @Nullable
    private String contextAttribute;
    @Nullable
    public String getContextAttribute() {
    return this.contextAttribute;
    }

    @Nullable
    protected WebApplicationContext findWebApplicationContext() {
    String attrName = getContextAttribute();
    // 需要配置了 contextAttribute 属性下,才会去查找
    if (attrName == null) {
    return null;
    }
    // 从 ServletContext 中,获得属性名对应的 WebApplicationContext 对象
    WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
    // 如果不存在,则抛出 IllegalStateException 异常
    if (wac == null) {
    throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
    }
    return wac;
    }
    • 一般情况下,我们不会配置 contextAttribute 属性。所以,这段逻辑暂时无视。
  • ========== 第三种情况 ==========
  • 这种情况,就是我们在 「4.1 构造方法」 提到的 Servlet WebApplicationContext 容器的第四种方式。
  • 如果此处 wac 还是为空,则调用 #createWebApplicationContext(WebApplicationContext parent) 方法,创建一个 WebApplicationContext 对象。代码如下:

    // FrameworkServlet.java

    /**
    * WebApplicationContext implementation class to create.
    *
    * 创建的 WebApplicationContext 类型
    */
    private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
    public Class<?> getContextClass() {
    return this.contextClass;
    }

    protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    // <a> 获得 context 的类
    Class<?> contextClass = getContextClass();
    // 如果非 ConfigurableWebApplicationContext 类型,抛出 ApplicationContextException 异常
    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");
    }
    // <b> 创建 context 类的对象
    ConfigurableWebApplicationContext wac =
    (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    // <c> 设置 environment、parent、configLocation 属性
    wac.setEnvironment(getEnvironment());
    wac.setParent(parent);
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
    wac.setConfigLocation(configLocation);
    }

    // <d> 配置和初始化 wac
    configureAndRefreshWebApplicationContext(wac);

    return wac;
    }
    • <a> 处,获得 context 的类,即 contextClass 属性。并且,如果非 ConfigurableWebApplicationContext 类型,抛出 ApplicationContextException 异常。
    • <b> 处,创建 context 类的对象。
    • <c> 处,设置 environmentparentconfigLocation 属性。其中,configLocation 是个重要属性。
    • <d> 处,调用 #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,配置和初始化 wac 。详细解析,见 「4.4 configureAndRefreshWebApplicationContext」
  • ========== END ==========
  • <3> 处,如果未触发刷新事件,则调用 #onRefresh(ApplicationContext context) 主动触发刷新事件。详细解析,见 「4.5 onRefresh」 中。另外,refreshEventReceived 属性,定义如下:

    // FrameworkServlet.java

    /**
    * Flag used to detect whether onRefresh has already been called.
    *
    * 标记是否接收到 ContextRefreshedEvent 事件。即 {@link #onApplicationEvent(ContextRefreshedEvent)}
    */
    private boolean refreshEventReceived = false;
  • <4> 处,如果 publishContexttrue 时,则将 context 设置到 ServletContext 中。涉及到的变量和方法如下:

    // FrameworkServlet.java

    /**
    * Should we publish the context as a ServletContext attribute?.
    *
    * 是否将 {@link #webApplicationContext} 设置到 {@link ServletContext} 的属性种
    */
    private boolean publishContext = true;

    /**
    * Prefix for the ServletContext attribute for the WebApplicationContext.
    * The completion is the servlet name.
    */
    public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";

    public String getServletContextAttributeName() {
    return SERVLET_CONTEXT_PREFIX + getServletName();
    }

    // HttpServletBean.java

    @Override
    @Nullable
    public String getServletName() {
    return (getServletConfig() != null ? getServletConfig().getServletName() : null);
    }

4.4 configureAndRefreshWebApplicationContext

#configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,配置和初始化 wac 。代码如下:

// FrameworkServlet.java

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
// <1> 如果 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());
}
}

// <2> 设置 wac 的 servletContext、servletConfig、namespace 属性
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());

// <3> 添加监听器 SourceFilteringListener 到 wac 中
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

// <4> TODO 芋艿,暂时忽略
// 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());
}

// <5> 执行处理完 WebApplicationContext 后的逻辑。目前是个空方法,暂无任何实现
postProcessWebApplicationContext(wac);

// <6> 执行自定义初始化 context TODO 芋艿,暂时忽略
applyInitializers(wac);

// <7> 刷新 wac ,从而初始化 wac
wac.refresh();
}
  • 【相同】<6> 处,执行自定义初始化 context TODO 芋艿,暂时忽略。
  • 【相同】<7> 处,刷新 wac ,从而初始化 wac

4.5 onRefresh

#onRefresh(ApplicationContext context) 方法,当 Servlet WebApplicationContext 刷新完成后,触发 Spring MVC 组件的初始化。代码如下:

// FrameworkServlet.java

/**
* Template method which can be overridden to add servlet-specific refresh work.
* Called after successful context refresh.
* <p>This implementation is empty.
* @param context the current WebApplicationContext
* @see #refresh()
*/
protected void onRefresh(ApplicationContext context) {
// For subclasses: do nothing by default.
}
  • 这是一个空方法,具体的实现,在子类 DispatcherServlet 中。代码如下:

    // DispatcherServlet.java

    /**
    * 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) {
    // 初始化 MultipartResolver
    initMultipartResolver(context);
    // 初始化 LocaleResolver
    initLocaleResolver(context);
    // 初始化 ThemeResolver
    initThemeResolver(context);
    // 初始化 HandlerMappings
    initHandlerMappings(context);
    // 初始化 HandlerAdapters
    initHandlerAdapters(context);
    // 初始化 HandlerExceptionResolvers
    initHandlerExceptionResolvers(context);
    // 初始化 RequestToViewNameTranslator
    initRequestToViewNameTranslator(context);
    // 初始化 ViewResolvers
    initViewResolvers(context);
    // 初始化 FlashMapManager
    initFlashMapManager(context);
    }
    • 这里,我们先不深究,在 DispatcherServlet 的初始化的文章中,详细解析。

#onRefresh() 方法,有两种方式被触发:

  • 方式一,在 「4.3 initWebApplicationContext」 中,有两种情形,会触发。
    • 情形一:情况一 + wac 已激活。
    • 情形二:情况二。
    • 这两种情形,此时 refreshEventReceivedfalse ,所以会顺着 #initWebApplicationContext() 方法的 <3> 的逻辑,调用 #onRefresh() 方法。😈 貌似说的有点绕,大家自己顺顺。
  • 方式二,在 「4.3 initWebApplicationContext」 中,也有两种情况,会触发。不过相比方式一来说,过程会“曲折”一点。
    • 情形一:情况一 + wac 未激活。
    • 情形二:情况三。
    • 这两种情形,都会调用 #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,在 wac 执行刷新完成后,会回调在该方法中,注册的 SourceFilteringListener 监听器。详细解析,见 「5. SourceFilteringListener」

5. SourceFilteringListener

org.springframework.context.event.SourceFilteringListener ,实现 GenericApplicationListener、SmartApplicationListener 监听器,实现将原始对象触发的事件,转发给指定监听器。代码如下:

// SourceFilteringListener.java

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;
this.delegate = (delegate instanceof GenericApplicationListener ?
(GenericApplicationListener) delegate : new GenericApplicationListenerAdapter(delegate));
}

/**
* Create a SourceFilteringListener for the given event source,
* expecting subclasses to override the {@link #onApplicationEventInternal}
* method (instead of specifying a delegate listener).
* @param source the event source that this listener filters for,
* only processing events from this source
*/
protected SourceFilteringListener(Object source) {
this.source = source;
}


@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event.getSource() == this.source) { // 判断来源
onApplicationEventInternal(event);
}
}

@Override
public boolean supportsEventType(ResolvableType eventType) {
return (this.delegate == null || this.delegate.supportsEventType(eventType));
}

@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return supportsEventType(ResolvableType.forType(eventType));
}

@Override
public boolean supportsSourceType(@Nullable Class<?> sourceType) {
return (sourceType != null && sourceType.isInstance(this.source));
}

@Override
public int getOrder() {
return (this.delegate != null ? this.delegate.getOrder() : Ordered.LOWEST_PRECEDENCE);
}


/**
* 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);
}

}
  • 这个类的核心代码,就是 #onApplicationEvent(ApplicationEvent event) 方法中,判断事件的来源,就是原始类 source 。如果是,则调用 #onApplicationEventInternal(ApplicationEvent event) 方法,将事件转发给 delegate 监听器。

我们在回看下 #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,创建 SourceFilteringListener 对象时,传入的两个参数:

  • source 属性,就是 wac 对象。
  • delegate 属性,就是 ContextRefreshListener 对象。

下面,让我们来看看 ContextRefreshListener 具体的代码实现,代码如下:

// FrameworkServlet.java

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

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

}
  • ContextRefreshListener 是 FrameworkServlet 的内部类。
  • #onApplicationEvent(ContextRefreshedEvent event) 方法中,会回调 FrameworkServlet#onApplicationEvent(event) 方法,代码如下:

    // FrameworkServlet.java

    public void onApplicationEvent(ContextRefreshedEvent event) {
    // <1> 标记 refreshEventReceived 为 true
    this.refreshEventReceived = true;
    // <2> 处理事件中的 ApplicationContext 对象。这个方法,目前是空实现,由子类 DispatcherServlet 来实现。
    onRefresh(event.getApplicationContext());
    }
    • <1> 处,标记 refreshEventReceivedtrue 。这样,在 #initWebApplicationContext() 方法的 <3> 的逻辑,就不会调用 #onRefresh() 方法。
    • <2> 处,调用 #onRefresh(ApplicationContext context) 方法,也就回到了 「4.5 onRefresh」 的逻辑了。

666. 彩蛋

舒服,真舒服。哈哈哈哈~写舒服了。

参考和推荐如下文章:

总访客数 && 总访问量