02.Spring Ioc 容器 - 创建_oxf的博客-程序员宝宝

技术标签: 容器  创建  初始化  Spring  Ioc  

基本概念

Spring IoC 容器负责 Bean 创建、以及其生命周期的管理等。想要使用 IoC容器的前提是创建该容器。

创建 Spring IoC 容器大致有两种:

  • 在应用程序中创建。
  • 在 WEB 程序中创建。

实例探究

1.应用程序创建容器

这里以 Application 为例,介绍下在普通的应用程序中如何创建 Spring 的 IoC容器:

public static void main(String [ ] args) {
    // 指定配置文件
    String configLocation = ...

    // 创建容器
    ApplicationContext factory = new FileSystemXmlApplicationContext(configLocation);

    // 调用 Bean
    Animals animal = factory.getBean(Animals.class);
}

容器的创建其实与普通类的创建无异,同样需要通过 new 来完成类的实例化。

观察它的构造参数 —configLocation,该参数表示 Spring IoC容器的配置文件

在配置文件中我们定义了各种各样的 Bean,好比列了一个单子告诉 Spring ,容器中将要盛放哪些东西。


下面介绍下常见的 xml 类型的配置文件:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

    <!-- 定义一个 Bean  -->
    <bean id="animals" class="com.demo.Animals" />

</beans> 

观察代码,该配置中定义了一个名为 Animals 的 Bean(具体的配置过程这里不再阐述)。在 Spring IoC容器被创建后,其初始化过程会通过该配置文件加载里面定义的 Bean 。

在创建容器之后(这里包含容器的初始化过程),我们就可以通过 getBean 方法取得 Bean 实例,而不用再手动地去一个个通过 new 实例化 Bean,这也就是所谓的 Ioc(控制反转),由 IoC容器负责 Bean 的创建,并管理 Bean 的生命周期。


2.在 WEB 程序创建容器

想要在 Web 程序中实现 Spring IoC容器的创建,需要在项目的 web.xml 中进行相关的配置。

对于 Spring 容器的创建(启用)有以下两种方式:

// ① 利用 Servlet 启动
org.springframework.web.context.ContextLoaderServlet

// ② 利用 Listener 启动
org.springframework.web.context.ContextLoaderListener

这里以 ContextLoaderListener 为例,它在 web.xmlr 中的配置如下:

<!-- ①指定配置文件路径 -->
<!-- 也可以不指定配置文件的位置,默认为 WEB-INF 下 ApplicationContext.xml 文件-->
<!-- 多个文件路径中间用 "," 或 ";" 隔开-->
<context-param>
    <param-name>contextConfigLocation</param-name>  
         <param-value>/WEB-INF/spring-bean.xml</param-value>  
</context-param>

<!-- ②配置监听器-->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

观察代码,发现在 Web 程序想要实现容器的创建同样具备两个条件:配置文件、创建容器类。

在 web.xml 中并没有找到创建容器的方法,这里只配置了一个监听器,所以可以推断定容器的创建是在监听器启动过程中实现。


原理分析

通过上面的分析,我们已经了解到 ContextLoaderListener 是创建 Spring IoC容器的入口。 下面具体探究下 Spring IoC 容器的创建过程。


首先来看 ContextLoaderListener 类的的签名,该类实现了 ServletContextListener 接口,表明自己是一个监听器。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener

既然是监听器,那么就要从它的初始化开始分析。而监听器的初始化在 contextInitialized
方法中定义:

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

继续追踪到 initWebApplicationContext 方法,该方法通过继承 ContextLoader 类得到:

// Spring 容器
private WebApplicationContext context;

// 线程类加载器
private static volatile WebApplicationContext currentContext;

// 代表 Spring 容器
// 因为 Spring 的容器被创建后,会被保存到 ServeltContext 的属性中,方便访问。
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = 
    WebApplicationContext.class.getName() + ".ROOT";

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {

    // 1.1.判断 sc 中是否存在 Spring 容器
    if (servletContext.getAttribute(
        WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        // 抛出异常...
    }

    // 省略部分代码...

    try {
        // 1.2.判断 Srping 容器是否已经被创建
        if (this.context == null) {
            // 2.创建容器,并赋值给 context 
            this.context = createWebApplicationContext(servletContext);
        }

        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = 
                (ConfigurableWebApplicationContext) this.context;

            // 判断容器是否被激活,刚创建的容器默认 isActive 返回 false
            if (!cwac.isActive()) {

                // 3.设置父容器
                if (cwac.getParent() == null) {
                    ApplicationContext parent = 
                        loadParentContext(servletContext);
                    cwac.setParent(parent);
                }

                // 4.容器初始化,配置并刷新
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }

        // 5.添加容器到 sc
        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);
        }

        return this.context;

    }catch (RuntimeException ex) {
        // 抛出异常...
    }catch (Error err) {
        // 抛出异常...
    }
}

观察上面的代码,可以发现该方法的工作流程可以分为:

  • 1.判断容器是否存在
  • 2.创建容器
  • 3.设置父容器
  • 4.容器初始化
  • 5.添加到 ServletContext

流程详解

1.判断容器是否存在

判断容器是否存在的步骤如下:

  • 程序先从 ServletContext 的属性中去寻找 Spring 容器,存在说明容器已经被创建并完成一系列的初始化工作。因为容器在初始化后被添加到 ServletContext 的属性。

  • 若 ServletContext 中不存在,容器再从成员变量 context 中去寻找,不为空,说明容器已经被创建,那么直接进入初始化的相关工作。因为容器在被创建后会赋值给 context 。


2.创建容器

创建容器,这里创建的容器实现了 WebApplicationContext 接口,该接口属于 Spring 容器的一种。具体的继承关系如下:

Alt text

再来探究具体的创建过程:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {

    // 1.取得容器类型
    Class<?> contextClass = determineContextClass(sc);

    // 检验容器类型是否实现 ConfigurableWebApplicationContext 接口
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        // 抛出异常...
    }

    // 2.利用反射创建容器
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

  • 取得容器类型,默认为容器类行为 XmlWebApplicationContext

    // 容器类型
    // 指代 web.xml 中定义的 <context-param> 标签
    public static final String CONTEXT_CLASS_PARAM = "contextClass";
    
    protected Class<?> determineContextClass(ServletContext servletContext) {
    
        // 从 sc 的初始化参数中获取,即取得 <context-param> 标签的内容 
        String contextClassName = 
            servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    
        if (contextClassName != null) {
            try {
                // 取得类对象
                return ClassUtils.forName(contextClassName,
                     ClassUtils.getDefaultClassLoader());
            }catch (ClassNotFoundException ex) {
                // 抛出异常...
            }
        }else {
    
            // 从 Spring 默认策略(属性文件)中取得容器类型
            XmlWebApplicationContext contextClassName = 
                defaultStrategies.getProperty(WebApplicationContext.class.getName());
    
            try {
                return ClassUtils.forName(contextClassName, 
                    ContextLoader.class.getClassLoader());
            }catch (ClassNotFoundException ex) {
                // 抛出异常...
            }
        }
    }
    
    // 关于默认策略,即属性文件的内容定义如下
    org.springframework.web.context.WebApplicationContext = 
        org.springframework.web.context.support.XmlWebApplicationContext

  • 利用反射创建容器

    public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
        // 省略代码...
    
        if (clazz.isInterface()) {
            // 抛出异常...
        }
        try {
            // 通过构造方法实例化该类
            return instantiateClass(clazz.getDeclaredConstructor());
        }catch (NoSuchMethodException ex) {
            //抛出异常...
        }
    }
    
    public static <T> T instantiateClass(Constructor<T> ctor, Object... args)
        throws BeanInstantiationException {
        // 省略代码...
        try {
            // 设置访问权限
            ReflectionUtils.makeAccessible(ctor);
            // 利用构造函数完成实例化
            return ctor.newInstance(args);
    
        }catch (InstantiationException ex) {
            //抛出异常...
        }catch (IllegalAccessException ex) {
            //抛出异常...
        }catch (IllegalArgumentException ex) {
            //抛出异常...
        }catch (InvocationTargetException ex) {
            //抛出异常...
        }
    }

分析代码,容器的创建过程:

  • 决定容器类型。
  • 通过反射利用类的构造器实例化对象。

3.设置父容器

设置父容器,分为加载、设置两个步骤,重点来看加载父容器的过程,即 loadParentContext 方法的具体实现:

protected ApplicationContext loadParentContext(ServletContext servletContext) {
    ApplicationContext parentContext = null;

    // 取得 web.xml 中 <context-param> 属性名为 
    // locatorFactorySelector、parentContextKey 的值

    String locatorFactorySelector = 
        servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
    String parentContextKey = 
        servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);

    // 取得父容器
    if (parentContextKey != null) {

        BeanFactoryLocator locator = 
            ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);

        // 省略代码...

        this.parentContextRef = locator.useBeanFactory(parentContextKey);

        parentContext = (ApplicationContext) this.parentContextRef.getFactory();
    }

    return parentContext;
}

4.容器初始化

容器的初始化指的是容器的配置刷新过程,该过程包含了两个动作:配置、刷新。

具体流程下一篇再来详细分析。


总结

最后来看下 Spring 容器的创建过程:

这里写图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u012420654/article/details/52684909

智能推荐

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is ..._weixin_30257433的博客-程序员宝宝

之前做的项目是resteasy的上传,代码没有问题,断点都不进来呢。我以为可以直接移植到SpringMVC,但是SpringMVC不支持MultipartFormDataInput ,用MultipartFile就可以了。老的无法兼容新的。正确代码如下@RequestMapping(value = "/importExcelForEduQuestion",produces = ...

Web_PHP_Curl浅说;_cyb_23的博客-程序员宝宝

<?php/** * curl会话 * @author 2WR3_cyb */class CurlClass { /** * Curl使用示例 * @param string $url 请求路径,如'http://x.x.x'; * @param array $fields 请求参数,如array('var' => 'value'), or can

黑马程序员--面对对象3_捌年的博客-程序员宝宝

------- android培训、java培训、期待与您交流! ----------一、继承定义:  在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并可以加入若干新的内容,或修改原来的方法使之更适合特殊的需要,这就是继承。继承是子类自动共享父类数据和方法的机制,这是类之间的一种关系,提高了软件的可重用性和可

基础的重要性(程序员之路)_张冰庭的博客-程序员宝宝

学习编程有几年了,感觉走了不少弯路,而不少的学弟学妹又在重蹈我当初的覆辙,不免有些痛心。最近在网上也看了许多前辈们的经验建议,再结合自己的学习经历在这里谈谈基础的重要性,希望帮助大家少走些弯路。    什么是基础呢?就是要把我们大学所学的离散数学,算法与数据结构,操作系统,计算机体系结构,编译原理等课程学好,对计算机的体系,CPU本身,操作系统内核,系统平台,面向对象编程,程序的性能等要有

0003C语言--数据类型及运算符_北冥有鱼zsp的博客-程序员宝宝

本文主要讲解的知识点是进制数,数据类型及其转换,运算符。1.进制数及其转换进制是一种计数机制,它可以使用有限的数字符号代表所有的数值。对于任何一种进制——X 进制, 就表示某一位置上的数运算时逢 X 进一位。对于计算机系统来说,常见的进制数有二进制,八进制,十进制及十六进制。(1)二进制对于绝大多数计算机系统来说,数据都是通过二进制的形式存在的。二进制是一种“逢二进一”的机制, 它...

随便推点

Redis zSet命令_qq_19557947的博客-程序员宝宝

zAdd增加一个或多个元素,如果该元素已经存在,更新它的socre值虽然有序集合有序,但它也是集合,不能重复元素,添加重复元素只会更新原有元素的score值$redis->zAdd('key', 1, 'val1');$redis->zAdd('key', 0, 'val0');$redis->zAdd('key', 5, 'val5');$redis->zRange('

3D图形学(6):正向渲染和延迟渲染_鹅厂程序小哥的博客-程序员宝宝

内容引自《Real Time Rendering 3rd》Forward Rendering(正向渲染)发生在渲染管线的顶点处理阶段,会计算所有的顶点的光照。全平台支持。规则一:最亮的几个光源会被实现为像素光照 规则二:然后最多4个光源会被实现为顶点光照 规则三:剩下的光源会被实现为效率较高的球面调谐光照(Spherical Hamanic),这是一种模拟光照规则一补充说明...

(原创)咱们公司遇到一个想开发和抖音一样的app的客户?_qq18723817197的博客-程序员宝宝

如果客户说想开发一款和抖音一模一样的app,结果会?——链环科技

我和你们不同--和谐就是和而不同--就是多样性的统一_LLKJDLLKJD的博客-程序员宝宝

     软件开发我从来没有研究过,我是寻求一个帮助偶然来到这个空间,希望哪位朋友能够帮助我编写一个这样简单的软件---将标志某商品的品名、产地生产商、经销商、规格型号、产品编号、特殊字符的信息编写成为另外一组可以自由设定的信息,当输入其中某一段信息后,就可以自动导出这些自由设定的信息,就好象户口管理,输入名字,就可以导出身份证号码一样;用途是:好查找库房放置的商品位置。    我的手机号码是

每个程序员都必须遵守的编程原则_马兆娟的博客-程序员宝宝

恰巧看到这篇文章,学过面向对象语言的人会很容易理解里面涉及的编程原则,这些原则恰恰是面向对象语言中常说的。感觉这篇文章写得不错,共享出来,共同学习……原文地址:http://www.aqee.net/principles-of-good-programming/      ============================分割线,请看下文=====================

推荐文章

热门文章

相关标签