(二) Spring IOC 容器
(二) Spring IOC 容器
1、IoC 容器简介
在我们使用传统的编码方式编写代码时,如果在类中需要引用另外一个类的对象,我们一般会直接在类中使用new
关键字,创建这样一个对象。此时,使用这个对象的类,就与这个对象所对应的类产生了耦合性。IoC
中文名称为控制反转,它就是用来解决上述问题的一种设计思想,它能指导我们设计出耦合性更低,更易于管理的代码。传统的程序,都是在需要使用的地方主动地创建对象,而IoC
的思想却是将创建对象的工作交给IoC
容器去进行。我们告诉IoC
容器,它需要创建那些对象,IoC
容器创建好这些对象,然后主动地将它们注入到需要使用的地方。此时,需要使用对象的那些类,并不主动地获取对象,而且由IoC
容器为它们分配,控制权在IoC
容器手上,这就是控制反转。当然,IoC
容器并不只是控制对象,可以理解为控制外部资源的获取。
IoC 容器是 Spring 的核心,也可以称为 Spring 容器。Spring 通过 IoC 容器来管理对象的实例化和初始化,以及对象从创建到销毁的整个生命周期。
Spring 中使用的对象都由 IoC 容器管理,不需要我们手动使用 new 运算符创建对象。由 IoC 容器管理的对象称为 Spring Bean,Spring Bean 就是 Java 对象,和使用 new 运算符创建的对象没有区别。
Spring 通过读取 XML 或 Java 注解中的信息来获取哪些对象需要实例化。
2、Spring 提供 2 种不同类型的 IoC 容器,即 BeanFactory 和 ApplicationContext 容器。
1.BeanFactory 容器
BeanFactory 是最简单的容器,由 org.springframework.beans.factory.BeanFactory 接口定义,采用懒加载(lazy-load),所以容器启动比较快。BeanFactory 提供了容器最基本的功能。
为了能够兼容 Spring 集成的第三方框架(如 BeanFactoryAware、InitializingBean、DisposableBean),所以目前仍然保留了该接口。
简单来说,BeanFactory 就是一个管理 Bean 的工厂,它主要负责初始化各种 Bean,并调用它们的生命周期方法。
BeanFactory 接口有多个实现类,最常见的是 org.springframework.beans.factory.xml.XmlBeanFactory。使用 BeanFactory 需要创建 XmlBeanFactory 类的实例,通过 XmlBeanFactory 类的构造函数来传递 Resource 对象。如下所示。
Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory factory = new XmlBeanFactory(resource);
HelloWorld obj = (HelloWorld) factory.getBean("helloWorld");
2. ApplicationContext 容器
ApplicationContext 继承了 BeanFactory 接口,由 org.springframework.context.ApplicationContext 接口定义,对象在启动容器时加载。ApplicationContext 在 BeanFactory 的基础上增加了很多企业级功能,例如 AOP、国际化、事件支持等。ApplicationContext 容器包括 BeanFactory 容器的所有功能,所以通常不建议使用BeanFactory。
ApplicationContext 接口有两个常用的实现类,具体如下。
(1)ClassPathXmlApplicationContext
该类从类路径 ClassPath 中寻找指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作,具体如下所示。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);
在上述代码中,configLocation 参数用于指定 Spring 配置文件的名称和位置,如 Beans.xml。
(2)FileSystemXmlApplicationContext
该类从指定的文件系统路径中寻找指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作,具体如下所示。
ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);
它与 ClassPathXmlApplicationContext 的区别是:在读取 Spring 的配置文件时,FileSystemXmlApplicationContext 不会从类路径中读取配置文件,而是通过参数指定配置文件的位置。即 FileSystemXmlApplicationContext 可以获取类路径之外的资源,如“F:/workspaces/Beans.xml”
通常在 Java 项目中,会采用 ClassPathXmlApplicationContext 类实例化 ApplicationContext 容器的方式,而在 Web 项目中,ApplicationContext 容器的实例化工作会交由 Web 服务器完成。Web 服务器实例化 ApplicationContext 容器通常使用基于 ContextLoaderListener 实现的方式,它只需要在 web.xml 中添加如下代码:
<!--指定Spring配置文件的位置,有多个配置文件时,以逗号分隔-->
<context-param>
<param-name>contextConfigLocation</param-name>
<!--spring将加载spring目录下的applicationContext.xml文件-->
<param-value>
classpath:spring/applicationContext.xml
</param-value>
</context-param>
<!--指定以ContextLoaderListener方式启动Spring容器-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
需要注意的是: BeanFactory 和 ApplicationContext 都是通过 XML 配置文件加载 Bean 的。
二者的主要区别在于,如果 Bean 的某一个属性没有注入,使用 BeanFacotry 加载后,第一次调用 getBean() 方法时会抛出异常,而 ApplicationContext 则会在初始化时自检,这样有利于检查所依赖的属性是否注入
因此,在实际开发中,通常都选择使用 ApplicationContext,只有在系统资源较少时,才考虑使用 BeanFactory。
3、Spring容器如何实现IOC
这一块,我就简单地说一说我今天下午在研究Spring
的IoC
源码的过程中,了解到的一些内容。首先,Spring
的IoC
容器,可以简单地理解为就是BeanFactory
接口的一个实现类对象,比如Spring
的应用上下文接口ApplicationContext
就是继承自BeanFactory
,而我们使用较多的ClassPathXmlApplicationContext
就是ApplicationContext
的一个实现类。它们都可以理解为是Spring
的IoC
容器,而BeanFactory
就是这个继承体系中的最高层。下面我就以单例bean的创建,简单地说一说Spring
的IoC
实现的一个过程,假设使用到的是ClassPathXmlApplicationContext
这个容器,解析的是xml
配置文件:
- 容器解析
xml
配置文件,将声明在xml
文件中的bean
的配置信息提取出来,每一个bean
的配置信息被封装成一个BeanDefinitionHolder
对象。BeanDefinitionHolder
主要包含三个成员——BeanDefinition
对象,bean
的名称(id
),以及bean
的别名。BeanDefinition
对象就是用来保存一个bean
的配置信息,比如bean
的作用域,bean
的类型,bean
的依赖,bean
的属性...... - 容器创建一个
ConcurrentHashMap
对象,这个map
的key
是bean
的名称,而value
则是BeanDefinition
对象的引用。容器将第一步中解析出的每一个BeanDefinitionHolder
对象,它对应的bean
名称,以及拥有的BeanDefinition
引用放入这个map
中。所以,这个map
保存了我们在程序中声明的所有的bean
的配置信息。 - 容器初始化完成后,开始创建
bean
,遍历第二步中的map
集合,获取到bean
的名称以及对应的BeanDefinition
对象,通过BeanDefinition
对象中的信息,判断这个bean
有没有定义为延迟加载,若没有,则需要现在就进行创建。在创建前,先通过BeanDefinition
中的配置信息,判断此bean
有没有depend-on
(依赖)其他bean
,若有,则先创建当前bean
依赖的bean
(此处的depend-on
不是bean
的属性,而是需要通过配置项进行配置的)。之后,则创建当前遍历到的bean
。 bean
在创建完成后,通过bean
的BeanDefinition
对象,获取bean
需要注入值的属性,然后为属性赋值,若属性的值类型是其他的bean
,则以上面相同的步骤,创建属性对应的bean
;- 容器创建一个
ConcurrentHashMap
,将创建好的单例bean
保存在其中。我们在代码中可以通过容器的getBean
方法,传入bean
的名称或类型获取单例bean
。容器会先去这个map
中查找,若map
中不存在,且这个bean
的配置在第2
步中存储配置信息的map
中能够找到,则创建这个bean
,放入存储bean
的map
中(因为bean
可以配置延迟加载,即第一次获取时加载);
Spring
中,bean
最基本的两种作用域就是singleton
(单例)和prototype
(多例),默认为单例。以上过程是单例bean
的创建过程,若作用域为prototype
,则每一次调用getBean
方法,都会创建一个新的bean
。顺带一提,创建bean
的方式是通过反射机制,这个大部分人应该都知道。