乐趣区

关于java:Spring-MVC-九Context层级基于配置

Context 层级的问题,后面文章应该曾经说分明了。

只不过,后面文章是以注解形式举例说明的,通过配置形式怎么体现 Context 层级呢?有必要也说一下,毕竟当初很多我的项目都是基于 xml 配置实现的。

web.xml

基于配置的 Spring MVC 的入口就是 web.xml 文件,毕竟 web.xml 是基于 web 的利用的先人入口 ……

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         id="WebApp_ID" version="3.1">
  <!--1、启动 Spring 的容器 -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>

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

肯定要搞清楚的是,context-param 配置是用来指定的 Spring 容器的配置文件所在门路的。必须是和 ContextLoaderListener 一起配合起作用的。ContextLoaderListener 读取 context-param 的配置来实现 Spring IoC 容器的初始化的。

Spring IoC 容器和 Spring MVC 的 Servlet Web ApplicationContext 容器不是必须要离开的,也能够配置为同一个容器。

比方以上配置不指定(不配置 Spring IoC 容器),也就是去掉 contextConfigLocation 以及 ContextLoaderListener,让 Spring MVC 的容器担负起 Spring IoC 容器的职责,也是能够的。

web.xml 上面持续配置 spring MVC 的 xml 文件所在门路,DispathcerServlet 初始化的时候会读取。


  <!--2、springmvc 的前端控制器,拦挡所有申请 -->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

Spring-mvc.xml

名字是能够在 web.xml 文件中指定的,不指定的话默认就是 dispatcherServlet 名 -dispatcher.xml(须要读一下 SpringMVC 源码确认)。

如果你想要 Spring IoC 容器和 Spring MvC 的 web applicationContext 容器离开的话,就在 Spring-mvc.xml 文件中指定包扫描门路仅扫描 controller,否则,全扫描即可。反之,Spring Ioc 容器存在的话,配置文件的扫描门路也要排除掉 Controller 的扫描:

    <context:component-scan base-package="org.example.service">
        <context:exclude-filter type="annotation"
                                expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

Servlet 和根容器的关系

下图高深莫测的阐明了两者之间的关系:

Servlet 容器寄存 Controller、VIewResolver、HanderMapping 等 DispatcherServlet 的相干对象,根容器能够寄存其余 Service、Repositories 等对象。

一个 DispatcherServlet 能够对应的有一个 Servlet WebApplicationContext 容器,一个 Web 利用能够有多个 DispatcherServlet(这种利用其实比拟少见),所以一个利用能够有多个 Servlet WebApplicationContext 容器。然而个别状况下,即便有多个 Servlet 容器,一个利用也心愿只有一个根容器,以便在不同的 Servlet 容器之间共享根容器的对象。

根容器初始化过程

xml 配置文件的状况下,根容器要依附 ContextLoaderListener 来初始化。ContextLoaderListener 是 Spring MVC 实现的 ServletContextListener 接口的实现类,ServletContextListener 是 Java Servlet 的接口。Servlet 规范约定,在 Servlet 容器初始化的过程中,会回调 ServletContextListener 接口的 contextInitialized 办法。

所以如果咱们在 web.xml 文件中配置了 ContextLoaderListener,那么,Tomcat 在 Servlet 容器初始化的过程中就会回调 ContextLoaderListener 的 contextInitialized 办法:

    @Override
    public void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());
    }

该办法会调用 initWebApplicationContext 办法,这个办法咱们在后面文章中其实曾经剖析过了,咱们再来看一下:

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.
            if (this.context == null) {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);
                }
            }
      ... 省略 n 行代码 

最终会调用到 configureAndRefreshWebApplicationContext 办法:

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);
        }
      ...

configureAndRefreshWebApplicationContext 办法会读取 web.xml 中 contextConfigLocation 配置,Spring IoC 容器的初始化配置文件 applicationContext.xml 文件名、曾经所在位置就是在这儿被读入的。

读入配置文件信息之后,调用 refresh 办法:

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

refresh 办法是 Spring framework 的办法,具体就不在这儿深入研究了,咱们只是简略跟中一下配置文件的加载过程:

    public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            ...

在 refresh 办法的 obtainFreshBeanFactory() 办法中:

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {refreshBeanFactory();
        return getBeanFactory();}

持续跟踪 refreshBeanFactory(); 办法:

@Override
    protected final void refreshBeanFactory() throws BeansException {if (hasBeanFactory()) {destroyBeans();
            closeBeanFactory();}
        try {DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            loadBeanDefinitions(beanFactory);
            this.beanFactory = beanFactory;
        }
        catch (IOException ex) {throw new ApplicationContextException("I/O error parsing bean definition source for" + getDisplayName(), ex);
        }
    }

持续跟踪,loadBeanDefinitions(beanFactory); 办法,会调用到 XmlWebApplicationContext 类中:

@Override
    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);
    }

最初一个办法调用,loadBeanDefinitions 办法:

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

终于发现了依据 configLocation 通过 loadBeanDefinitions 办法读取配置文件、调用 beandefinition 的代码逻辑。

能够发现代码是能够反对多个配置文件的!

从代码的角度,咱们也实现了 xml 配置形式下,SpringMVC 与 spring framework 集成,实现 spring Ioc 容器的初始化过程!

与后面两篇文:Spring MVC 四:Context 层级 /Spring MVC 五 – Spring MVC 的配置和 DispatcherServlet 初始化过程:Context 层级联合, 咱们就实现了基于注解的、以及基于 xml 配置文件两种形式下的 Spring MVC 框架下,Spring IoC 容器的初始化代码的剖析!

上一篇 Spring MVC 八 – 内置过滤器

退出移动版