基本概念

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 容器的一种。具体的继承关系如下:

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

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 容器的创建过程:

02.Spring Ioc 容器 - 创建的更多相关文章

  1. Spring IOC容器创建bean过程浅析

    1. 背景 Spring框架本身非常庞大,源码阅读可以从Spring IOC容器的实现开始一点点了解.然而即便是IOC容器,代码仍然是非常多,短时间内全部精读完并不现实 本文分析比较浅,而完整的IOC ...

  2. Spring源码分析:Spring IOC容器初始化

    概述: Spring 对于Java 开发来说,以及算得上非常基础并且核心的框架了,在有一定开发经验后,阅读源码能更好的提高我们的编码能力并且让我们对其更加理解.俗话说知己知彼,百战不殆.当你对Spri ...

  3. Spring IOC容器分析(4) -- bean创建获取完整流程

    上节探讨了Spring IOC容器中getBean方法,下面我们将自行编写测试用例,深入跟踪分析bean对象创建过程. 测试环境创建 测试示例代码如下: package org.springframe ...

  4. Spring IOC 容器源码分析 - 创建原始 bean 对象

    1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续.在上一篇文章中,我们从战略层面上领略了doCreateBean方法的全过程.本篇文章,我们就从战术的层面上,详细分析doCreat ...

  5. Spring IOC 容器源码分析 - 创建单例 bean 的过程

    1. 简介 在上一篇文章中,我比较详细的分析了获取 bean 的方法,也就是getBean(String)的实现逻辑.对于已实例化好的单例 bean,getBean(String) 方法并不会再一次去 ...

  6. Spring源码之IOC容器创建、BeanDefinition加载和注册和IOC容器依赖注入

    总结 在SpringApplication#createApplicationContext()执行时创建IOC容器,默认DefaultListableBeanFactory 在AbstractApp ...

  7. 自定义模拟一个Spring IOC容器

    一.模拟一个IOC容器: 介绍:现在,我们准备使用一个java project来模拟一个spring的IOC容器创建对象的方法,也就是不使用spring的jar自动帮助我们创建对象,而是通过自己手动书 ...

  8. Spring IoC容器的初始化过程

    Spring IoC容器的初始化包括 BeanDefinition的Resource定位.载入和注册 这三个基本的过程.IoC容器的初始化过程不包含Bean依赖注入的实现.Bean依赖的注入一般会发生 ...

  9. 学习Spring(一) 实例化Spring IoC容器

    实例化Spring IoC容器 1,读取其配置来创建bean实例 2,然后从Spring IoC容器中得到可用的bean实例 Spring提供两种IoC容器实现类型 a,一种为bean工厂 b,应用程 ...

随机推荐

  1. npm in macbook

    打开终端,试了很多次 npm install anywhere -g,结果还是报错,大概就说没权限. 所以,才想起之前看过的博客中,提到用sudo去执行. 终于,没问题了! 如果npm install ...

  2. BZOJ1720:[Usaco2006 Jan]Corral the Cows 奶牛围栏

    我对二分的理解:https://www.cnblogs.com/AKMer/p/9737477.html 题目传送门:https://www.lydsy.com/JudgeOnline/problem ...

  3. bzoj 4816 数字表格 —— 反演

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4816 推导过程同:http://www.cnblogs.com/zhouzhendong/p ...

  4. Wireshark抓包常见问题解析

    1.   tcp out-of-order(tcp有问题) 解答: 1).    应该有很多原因.但是多半是网络拥塞,导致顺序包抵达时间不同,延时太长,或者包丢失,需要重新组合数据单元 因为他们可能是 ...

  5. JAVA 1.5 并发之 Executor框架 (二)execute VS submit

    http://www.cnblogs.com/rockman12352/p/3788688.html 上一篇对于整体框架讲了很多东西,但是具体在使用时有一些细节并没有说出来 首先是执行任务 execu ...

  6. CUDA V9.2 sample编译问题

    这个哥们也遇到一样的问题 CUDA 9.1/9.2 与 Visual Studio 2017 (VS2017 15.6.4) 的不兼容问题 错误有显示 #if _MSC_VER < 1600 | ...

  7. Devexpress GridControl

    1.隐藏“Drag a column header here to group by that column”如下: 选择gridview->属性 选择OptionView->ShowGr ...

  8. 《Spring实战》系列之Bean的装配-Days02

    2.1 回顾 对于我第一天在bean的装配中写的,是一些基本的语法或者是Spring本身的一些规定,但是我没有对此进行深究.接下来就让我们仔细的讨论一下细节问题.和传统的类的定义和方法的调用做一些比较 ...

  9. Linux中发布项目的一些命令笔记

    记一下,Linux中发布项目的一些难记的命令: .安装jdk a.检测是否安装了jdk 运行java -version b.若有需要将其卸载 c.查看安装那些jdk rpm -qa | grep ja ...

  10. UE mac版16.10.0.22破解

    http://bbs.feng.com/read-htm-tid-10828753.html 去官网下载原载,先运行一次,再在终端里执行下面代码就可以破解完成! printf '\x31\xC0\xF ...