转载

Spring容器及其初始化

1.Spring中的几个概念

  • 在阅读Spring源码或相关文献时,会经常遇到这几个名词: WebApplicationContext --- ApplicationContext --- ServletContext --- ServletConfig .这些名词很相近但适用范围有所不同,容易造成 spring 内部实现的理解混淆,所以首先大致解释这几个名词.

    • ServletContext :这个是来自Servlet规范里的概念,它是 Servlet 用来与容器间进行交互的接口的组合.也就是,这个接口定义了一系列的方法, Servlet 通过这些方法可以很方便地与所在的容器进行一些交互.从它的定义中也可以看出在一个应用中(一个 JVM )只有一个 ServletContext .也就是说,容器中所有的 Servlet 都共享一个 ServletContext .
    • ServletConfig :它与 ServletContext 的区别在于, ServletConfig 是针对 servlet 而言的,每个 servlet 都有它独特的 ServletConfig 信息,相互之间不共享.
    • ApplicationContext :这个类是 Spring 容器功能的核心接口,它是 Spring 实现 IOC 功能最重要的接口.从它的名字可以看出,它维护了整个程序运行期所需要的上下文信息,注意这里的应用程序并不一定是 web 程序.在 Spring 中允许存在多个 ApplicationContext ,这些 ApplicationContext 相互之间形成父子,继承与被继承的关系,这也是通常我们所说的:在 Spring 中存在两个 context ,一个 Root Application Context ,一个是 Servlet Application Context ,这一点在后续会详细阐述.
    • WebApplicaitonContext :这个接口只是 ApplicationContext 接口的一个子接口,只不过它的应用形式是 web ,它在 ApplicaitonContext 的基础上,添加了对 ServletContext 的引用.

2.Spring容器的初始化

SpringMVC 配置文件中,我们通常会配置一个前端控制器 DispatcherServlet 和监听器 ContextLoaderListener 来进行 Spring 应用上下文的初始化

Spring容器及其初始化

  • 在之前的阐述中可知, ServletContext 是容器中所有 Servlet 共享的配置,它是应用于全局的.根据 Servlet 规范的规定,根据以上监听器的配置,其中 context-param 指定了配置文件的未知.在容器启动后初始化 ServletContext 时,监听器会自动加载配置文件,来初始化 Spring 的根容器 Root Application Context .
  • 同样 ServletConfig 是针对每个 Servlet 进步配置的,因此它的配置是在 servlet 的配置中,根据以上 DispatcherServlet 的配置,配置中 init-param 同样指定了在 Servlet 初始化调用 #init 方法时加载配置信息的 xml 文件,并初始化 Spring 应用容器 Servlet Application Context .

接下来我们具体分析Spring容器初始化:

  • 关于 ApplicationContext 的配置,首先,在 ServletContext 中配置 context-param 参数.通过监听器会生成所谓的 Root Application Context ,而每个 DispatcherServlet 中指定的 init-param 参数会生成 Servlet Application Context .而且它的 parent 就是 ServletContext 中生成的 Root Application Context .因此在 ServletContext 中定义的所有配置都会继承到 DispatcherServlet 中,这在之后代码中会有直观的提现.

1. Root Application Context

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    //...................
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

}

--------------------------------------------------------------------------------------------------------------------------------------------------------

public class ContextLoader {
    //...................
 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!");
        } else {
          //...................
            try {
                if(this.context == null) {
                    this.context = this.createWebApplicationContext(servletContext);
                }
                //...................
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
                ClassLoader ccl = Thread.currentThread().getContextClassLoader();
                return this.context;
            } 
        }
    }

 protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        Class<?> contextClass = this.determineContextClass(sc);
        if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        } else {
            return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
        }
    }


 protected Class<?> determineContextClass(ServletContext servletContext) {
        String contextClassName = servletContext.getInitParameter("contextClass");
        if(contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            }
        } else {
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            } 
        }
    }


    static {
        try {
            ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        } catch (IOException var1) {
            throw new IllegalStateException("Could not load 'ContextLoader.properties': " + var1.getMessage());
        }

        currentContextPerThread = new ConcurrentHashMap(1);
    }
}

Spring容器及其初始化

  • 为了方便阅读,这里把一些不是核心的代码过滤

    1. 根据配置文件,首先我们通过 ContextLoaderListener 对象来监听 ServletContext 初始化,在初始化方法中会调用父类 ContextLoader#initWebApplicationContext 方法来进行
    2. initWebApplication 中首先判断是否存在 Root Application Context ,如果存在则抛出异常.之后通过 #createWebApplicationContext 方法来创建容器对象,并会将容器放入 ServletContext 中.所以对于 ApplicationContextServletContext 的区别就是 ApplicationContext 其实就是 ServletContext 中的一个属性值而已.这个属性中存有程序运行的所有上下文信息,由于这个 ApplicationContext 是全局的应用上下文,所以在 Spring 中称它为 "Root Application Context" .
    3. 接下来我们具体看一下容器是如何创建的:我们进入到 #createWebApplicationContext 方法中可以看到它是通过实例化容器的class类来创建容器的,而在 #determineContextClass 方法中首先通过初始化参数来获取全路径类名,若不存在则通过配置类 #defaultStrategies 来获取容器名称.
    4. 我们可以从当前类的静态代码找到此配置类,它通过读取 ContextLoader.properties 配置文件来获取当前容器全路径类名称

2. Servlet Application Context

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
    public final void init() throws ServletException {
        //遍历获取servletConfig的所有参数
        PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
        //初始化servlet applicationContext
        this.initServletBean();
    }
}

--------------------------------------------------------------------------------------------------------------------------------------------------------

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    protected final void initServletBean() throws ServletException {
        //...................
        try {
            this.webApplicationContext = this.initWebApplicationContext();
        } 
    }

    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        WebApplicationContext wac = null;

        if(wac == null) {
            wac = this.createWebApplicationContext(rootContext);
        }

        if(this.publishContext) {
            String attrName = this.getServletContextAttributeName();
            this.getServletContext().setAttribute(attrName, wac);
            if(this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + this.getServletName() + "' as ServletContext attribute with name [" + attrName + "]");
            }
        }

        return wac;
    }

    protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
        return this.createWebApplicationContext((ApplicationContext)parent);
    }

    protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
        Class<?> contextClass = this.getContextClass();
       
        if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
        } else {
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
            wac.setEnvironment(this.getEnvironment());
            wac.setParent(parent);
            wac.setConfigLocation(this.getContextConfigLocation());
            this.configureAndRefreshWebApplicationContext(wac);
            return wac;
        }
    }
}
  • 接下来我们再看 DispatcherServlet

    1. 作为 Servlet ,根据规范它的配置信息应该是在 #init 方法中完成,我们首先进入到 DispatcherServlet#init ,其方法是继承自父类 HttpServletBean 中.在父类的 #init 方法中,首先通过遍历获取 ServletConfig 的所有参数,然后进行 Servlet Application Context 的初始化
    2. 容器初始化方法 #initServletBean 位于父类 FrameworkServlet 中,在 #initServletBean 方法中调用 #initWebApplicationContex t方法
    3. initWebApplicationContext 方法中首先通过 ServletContext 获取 Root Application Context ,然后开始初始化 Servlet Application Context ,在创建容器的过程会传入 Root Application Context 作为它的 Parent ,也就是在这里两者建立父子关系,形成之前所说的继承关系,最后同样将新创建的容器放入 ContextServlet 中.

3. 总结

以上就是关于在 Spring 中容器的大致分析,我们会在项目启动时将不同的组件实例注入到 Spring 容器中,从而实现 Spring IOC 机制.

  1. 若想要了解如何通过 Spring 注解方式自定义 DispatcherServlet 相关内容可以参考: Spring中关于的WebApplicationInitializer及其实现的分析
  2. 若想要了解关于 SpringMVC 自定义配置化相关知识可以参考:
原文  https://segmentfault.com/a/1190000020793651
正文到此结束
Loading...