美文网首页
Spring MVC - 1

Spring MVC - 1

作者: 程序员札记 | 来源:发表于2023-04-10 18:36 被阅读0次

概述 ContextLoaderListener

ContextLoaderListener 实现了 ServletContextListener 接口,ServletContextListener 是 Java EE 标准接口之一,类似 Jetty,Tomcat,JBoss 等 java 容器启动时便会触发该接口的 contextInitialized。

image.png

ContextLoaderListener 是由 Spring 公司 提供的一个类,您需要做的就是在 J2EE Servlet 标准的 web.xml 中 <web-app/> 声明一个 <listener/>

<listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

ContextLoaderListener 初始化过程

image.png

网上关于 ContextLoaderListener 加载过程的文章已经很多很全面了,所以我就结合排名第一的文章,画了一张时序图。如果你是电脑查看的,你可以右击图片,然后“在新标签页中打开图片” 查看大图。

总的来看有几个点很重要:

  1. contextInitialized 通知 Spring Web 应用程序 Servlet 上下文发生变更。

  2. 我们需要为 Spring Web 应用程序创建一个 WebApplicationContext,一般来说,这个实现类是 XmlWebApplicationContext

  3. 我们需要为 Spring 应用程序上下文创建一个 ConfigurableListableBeanFactory,一般来说,这个实现类是 DefaultListableBeanFactory

  4. 最后,在 Spring 容器加载 Bean 定义时,会调用 loadBeanDefinitions 从 configLocations 即配置文件路径下,读取和解析配置文件。

1. contextInitialized

首先,ContextLoaderListener 是 ContextLoader 的子类,并且实现了 ServletContextListener 接口。

第三方 Servlet 容器主动调用 contextInitialized(ServletContextEvent event)。参数 ServletContextEvent 是一个事件类,用于通知 web 应用程序的 Servlet 上下文的更改。

2.1 实例化 Spring Web 应用程序上下文对象

紧接着调用 initWebApplicationContext 并且把 ServletContext 作为参数。接口 WebApplicationContext 是 ApplicationContext 的子类,是 Spring Web 应用程序的上下文。

createWebApplicationContext(ServletContextEvent event) 方法会 new 一个 WebApplicationContext 实例。具体的做法是先通过 determineContextClass 来决定应用上下文的类对象 contextClass,然后通过 BeanUtils.instantiateClass 来实例化对象。

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
      Class<?> contextClass = determineContextClass(sc);
      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
优先从 web.xml 中获取上下文参数,这里的<context-param /> 是 Servlet 的上下文参数,不是Spring 应用的上下文参数!但是这段代码实际效果是,在 Servlet 的上下文参数中,指定了接下来要创建的 Spring 应用的 ApplicationContext 的实现类名称。

<context-param>
      <param-name>contextClass</param-name>
      <param-value>org.springframework.web.context.support.XmlWebApplicationContext</param-value>
</context-param>

<context-param /> 标签内的参数可以通过 Java 代码 ServletContext # getInitParameter 方法得到,具体的代码是由 Servlet 容器来实现的。

如果没有指定 contextClass 的名称,那么就会去获取默认的 contextClass 类名。默认 contextClass 类名需要从 defaultStrategies 中去获得。这个静态成员变量的初始化代码如下:

defaultStrategies 初始化
知道这个默认配置,也就是知道有这么一回事儿,学个思路。实际上这个配置是在打包的 jar 文件中的,我们开发者也没办法自定义。
ContextLoader.properties
一般来说我们使用 Spring MVC 框架时,用到的都是 XmlWebApplicationContext,即解析 xml 文件生成 WebApplicationContext


image.png

2.2 配置和刷新 Spring Web 应用程序上下文

经过上一步,我们已经创建出一个 XmlWebApplicationContext,但是光有对象还是不够的,我们还需要configureAndRefreshWebApplicationContext来配置和刷新上下文。

第一段代码:设定 XmlWebApplicationContext 的 id

第二段代码:设定 XmlWebApplicationContext 的 configLocation

contextConfigLocation

// 这是 configureAndRefreshWebApplicationContext 的第二块代码
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
      wac.setConfigLocation(configLocationParam);
}

从这段代码可以知道,我们可以用 <context-param /> 设置 contextConfigLocation 参数,为 WebApplicationContext 自定义配置文件的路径。

/**
 * Name of servlet context parameter (i.e., {@value}) that can specify the
 * config location for the root context, falling back to the implementation's
 * default otherwise.
 * @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION
 */
 public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";

如果不自定义的话,默认会去加载 /WEB-INF/applicationContext.xml

/** Default config location for the root context. */
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";

但是,这个 /WEB-INF/applicationContext.xml 还是需要你自己创建的,不然会抛出异常。

java.io.FileNotFoundException: Could not open ServletContext resource [/WEB-INF/applicationContext.xml]

最后一行:wac.refresh()

中间有配置环境变量的,有点复杂,等下次有机会遇到与之相关的问题再做分析。所以本次跳过了。

3. 创建 Bean 工厂

“破后而立” 之 refreshBeanFactory

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
    // 刷新 Bean 工厂
    protected final void refreshBeanFactory() throws BeansException {
        // “破”:如果刷新时,Spring上下文实例中已经有一个Bean工厂了,那么就先销毁它
        if (this.hasBeanFactory()) {
            this.destroyBeans();
            this.closeBeanFactory();
        }

        try {
            // “立”:重新创建一个新的 Bean 工厂实例
            DefaultListableBeanFactory beanFactory = this.createBeanFactory();
            beanFactory.setSerializationId(this.getId());
            this.customizeBeanFactory(beanFactory);
            // 为 Bean 工厂加载 Bean 定义
            this.loadBeanDefinitions(beanFactory);
            // 为 Bean 工厂赋值新创建的工厂对象
            synchronized(this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        } catch (IOException var5) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
        }
    }      
}

4. 从 xml 配置文件中加载 Bean 定义

为 Bean 工厂加载 Bean 定义,本文使用的 xml 配置方式。XmlWebApplicationContext 用的是 XmlBeanDefinitionReader 来读取文件中的 Bean 定义。

public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
      // 接着上面的
      protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // Create a new XmlBeanDefinitionReader for the given BeanFactory.
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // Configure the bean definition reader with this context's
        // resource loading environment.
        beanDefinitionReader.setEnvironment(getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        // Allow a subclass to provide custom initialization of the reader,
        // then proceed with actually loading the bean definitions.
        initBeanDefinitionReader(beanDefinitionReader);
        loadBeanDefinitions(beanDefinitionReader);
    }
}

还记得上文中 ContextLoader # configureAndRefreshWebApplicationContext 中的那段代码吗?

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

现在,<context-param/> 中配置的 contextConfigLocatin 就是在这里被使用的。

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
      String[] configLocations = getConfigLocations();
      if (configLocations != null) {
            for (String configLocation : configLocations) {
                  reader.loadBeanDefinitions(configLocation);
            }
      }
}

加载 Bean 定义的具体过程可以去查询 XmlWebApplicationContext # loadBeanDefinitions 方法。

相关文章

网友评论

      本文标题:Spring MVC - 1

      本文链接:https://www.haomeiwen.com/subject/yfjwddtx.html