1996年,Michael Mattson在一篇有关探讨面向对象框架的文章中,首先提出了IOC 这个概念。IOC是Inversion of Control的缩写,多数书籍翻译成“控制反转”,还有些书籍翻译成为“控制反向”或者“控制倒置”。IOC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦。
2004年,Martin Fowler探讨了同一个问题,既然IOC是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现IOC的方法:注入。所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。
IOC中最基本的技术就是“反射(Reflection)”编程,通俗来讲就是根据给出的类名(字符串方式)来动态地生成对象。这种编程方式可以让对象在生成时才决定到底是哪一种对象。
举个简单例子:
Person(人)每天都要吃早餐(食物)。我们可以用如下程序表示
public class Person {
public void eat() {
Food food = new food();
System.out.println("I eat food:{}", food.toString());
}
}
在我们吃饭之前必须先new food()(做饭),要不然就吃不上。
Ioc 会怎么样做呢
public class Person {
private Food food;
public void eat() {
System.out.println("I eat food:{}", food.toString());
}
}
我们可以把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言的的反射编程,根据配置文件中给出的类名生成相应的对象。从实现来看,IOC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。
接下来的问题是如何将依赖的对象准备好呢(依赖注入),常用的有两种方式:构造方法注入和setter注入
构造器注入
public Person(Food food) {
this.food = food;
}
setter注入
public void setFood(Food food) {
this.food = food;
}
Spring IOC的初始化过程:
IOC要实现却并不那么容易。它需要一系列技术要实现。首先它需要知道服务的对象是谁,以及需要为服务对象提供什么样的服务。提供的服务指:要完成对象的构建(即把饭做好),将其送到服务对象即完成对象的绑定(即把饭端到我面前)。
IOC需要实现两个技术:
对于这两个方面技术的实现具有很多的方式:硬编码(Ioc 框架都支持),配置文件(重点),注解(简洁)。但无论哪种方式都是在Ioc容器里面实现的(我们可以理解为一个大池子,里面躺着各种各样的对象,并能通过一定的方式将它们联系起来)spring提供了两种类型的容器,一个是BeanFactory,一个是ApplicationContext(可以认为是BeanFactory的扩展),下面我们将介绍这两种容器如何实现对对象的管理。
如果没有特殊指定,默认采用延迟初始化策略(lazy-load)。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需要的资源有限。对于资源有限,并且功能要求不是很严格的场景,BeanFactory是比较合适的 IoC容器选择。
我们先来看一下BeanFactory类的关系图(如下所示)
有三个很重要的部分:
BeanDefinition
实现Bean的定义(即对象的定义),且完成了对依赖的定义
BeanDefinitionRegistry
,将定义好的bean,注册到容器中(此时会生成一个注册码)
BeanFactory
是一个bean工厂类,从中可以取到任意定义过的bean
最重要的部分就是BeanDefinition
,它完成了Bean的生成过程。一般情况下我们都是通过配置文件(xml,properties)的方式对bean进行配置,每种文件都需要实现BeanDefinitionReader
,因此是reader本身现了配置文字到bean对象的转换过程。
Bean的生成大致可以分为两个阶段:容器启动阶段和bean实例化阶段
容器启动阶段:
加载配置文件(通常是xml文件)
通过reader生成beandefinition
beanDefinition注册到beanDefinitionRegistry
bean实例化阶段:
当某个bean 被 getBean()调用时, bean需要完成初时化,以及其依赖对象的初始化如果bean本身有回调,还需要调用其相应的回调函数。从上面我们也可以知道,beanDefinition
(容器启动阶段)只完成bean的定义,并未完成初始化。初始是通过beanFactory的getBean()时才进行的。
Spring IOC在初始化完成之后,给了我们提供一些方法,让我们来改变一些bean的定义
org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
:使我们可能通过配置文件的形式,配置一些参数PropertyOverrideConfigurer
:则可以覆盖原本的bean参数CustomEditorConfigurer
:则提供类型转换支持(配置文件都是string,它需要知道转换成何种类型)Bean的初始化过程:
如果你认为实例化的对象就是通过我们定义的类new出来的就错了,其实这里用到了AOP机制,生成了其代理对象(通过反射机制生成接口对象,或者是通过CGLIB生成子对象),具体可以参见在上一篇博客。
bean的具体装载过程是由beanWrapper
实现的,它继承了PropertyAccessor
(可以对属性进行访问)、PropertyEditorRegistry
和TypeConverter
接口 (实现类型转换,就上前面说的)。
完成设置对象属性之后,则会检查是否实现了Aware
类型的接口,如ApplicationContextAware
;如果实现了,则主动加载。
BeanPostprocessor
可以帮助完成在初始化bean之前或之后 帮我们完成一些必要工作,比如我们在连接数据库之前将密码存放在一个加密文件,当我们连接数据库之前,需要将密码进行加载解密。只要实现相应的接口即可, 下面是BeanPostprocessor
接口定义:
public interface BeanPostProcessor {
/**
* Apply this BeanPostProcessor to the given new bean instance before any bean
* initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
* or a custom init-method). The bean will already be populated with property values.
* The returned bean instance may be a wrapper around the original.
* @param bean the new bean instance
* @param beanName the name of the bean
* @return the bean instance to use, either the original or a wrapped one; if
* {@code null}, no subsequent BeanPostProcessors will be invoked
* @throws org.springframework.beans.BeansException in case of errors
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
*/
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
/**
* Apply this BeanPostProcessor to the given new bean instance after any bean
* initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
* or a custom init-method). The bean will already be populated with property values.
* The returned bean instance may be a wrapper around the original.
* In case of a FactoryBean, this callback will be invoked for both the FactoryBean
* instance and the objects created by the FactoryBean (as of Spring 2.0). The
* post-processor can decide whether to apply to either the FactoryBean or created
* objects or both through corresponding {@code bean instanceof FactoryBean} checks.
*
This callback will also be invoked after a short-circuiting triggered by a
* {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method,
* in contrast to all other BeanPostProcessor callbacks.
* @param bean the new bean instance
* @param beanName the name of the bean
* @return the bean instance to use, either the original or a wrapped one; if
* {@code null}, no subsequent BeanPostProcessors will be invoked
* @throws org.springframework.beans.BeansException in case of errors
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
* @see org.springframework.beans.factory.FactoryBean
*/
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
在完成postProcessor
之后,则会看对象是否定义了InitializingBean
接口,如果是,则会调用其afterProper
-tiesSet()方法进一步调整对象实例的状态,这种方式并不常见。spring还提供了另外一种指定初始化的方式,即在bean定义中指定init-method 。
当这一切完成之后,还可以指定对象销毁的一些回调,比如数据库的连接池的配置,则销毁前需要关闭连接等。相应的可以实现DisposableBean
接口或指定destroy-method
ApplicationContext
在 BeanFactory
的基础上构建,是相对比较高级的容器实现,除了拥有 BeanFactory
的所有支持,ApplicationContext
还提供了其他高级特性,比如事件发布、国际化信息支持等。
ApplicationContext
所管理 的对象,在该类型容器启动之后,默认全部初始化并绑定完成。所以,相对于 BeanFactory
来说,ApplicationContext
要求更多的系统资源,同时,因为在启动时就完成所有初始化,容器启动时间较之 BeanFactory
也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext
类型的容器是比较合适的选择。
具体差异:
bean的加载方式
BeanFactory
提供BeanReader
来从配置文件中读取bean配置。相应的ApplicationContext
也提供几个读取配置文件的方式:
FileSystemXmlApplicationContext
:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径
ClassPathXmlApplicationContext
:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
WebXmlApplicationContext
:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。
AnnotationConfigApplicationContext
ConfigurableWebApplicationContext
ApplicationContext
采用的非懒加载方式。
它会在启动阶段完成所有的初始化,并不会等到getBean()才执行。所以,相对于BeanFactory
来说,ApplicationContext
要求更多的系统资源,同时,因为在启动时就完成所有初始化,容器启动时间较之BeanFactory
也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext
类型的容器是比较合适的选择。
ApplicationContext
还额外增加了三个功能:ApplicationEventPublisher
, ResourceLoader
, MessageResource
,以下为具体解释:ResourceLoader
并不能将其看成是Spring独有的功能,spring IOC只是借助于ResourceLoader
来实现资源加载。也提供了各种各样的资源加载方式:
DefaultResourceLoader
首先检查资源路径是否以classpath:前缀打头,如果是,则尝试构造ClassPathResource类 型资源并返回。否则, 尝试通过URL,根据资源路径来定位资源
FileSystemResourceLoader
它继承自Default-ResourceLoader,但覆写了getResourceByPath(String)方法,使之从文件系统加载资源并以 FileSystemResource类型返回
ResourcePatternResolver
批量查找的ResourceLoader
spring与ResourceLoader之间的关系
所有ApplicationContext
的具体实现类都会直接或者间接地实现AbstractApplicationContext
, AbstactApplicationContext
依赖了DeffaultResourceLoader
, ApplicationContext
继承了ResourcePatternResolver
,所到头来ApplicationContext
的具体实现类都会具有DefaultResourceLoader
和PathMatchingResourcePatterResolver
的功能。这也就是会什么ApplicationContext
可以实现统一资源定位。
Spring的文档对Event的支持翻译之后描述如下:
ApplicationContext
通过ApplicationEvent
类和ApplicationListener
接口进行事件处理。 如果将实现ApplicationListener
接口的bean注入到上下文中,则每次使用ApplicationContext
发布ApplicationEvent
时,都会通知该bean。 本质上,这是标准的观察者设计模式。
ApplicationEvent
:继承自EventObject,同时是spring的application中事件的父类,需要被自定义的事件继承。
ApplicationListener
:继承自EventListener,spring的application中的监听器必须实现的接口,需要被自定义的监听器实现其onApplicationEvent方法
ApplicationEventPublisherAware
:在spring的context中希望能发布事件的类必须实现的接口,该接口中定义了设置ApplicationEventPublisher的方法,由ApplicationContext调用并设置。在自己实现的ApplicationEventPublisherAware子类中,需要有ApplicationEventPublisher属性的定义。
ApplicationEventPublisher
:spring的事件发布者接口,定义了发布事件的接口方法publishEvent。因为ApplicationContext实现了该接口,因此spring的ApplicationContext实例具有发布事件的功能(publishEvent方法在AbstractApplicationContext中有实现)。在使用的时候,只需要把ApplicationEventPublisher的引用定义到ApplicationEventPublisherAware的实现中,spring容器会完成对ApplicationEventPublisher的注入。
ApplicationContext
接口扩展了MessageSource
接口,因而提供了消息处理的功能(i18n或者国际化)。与HierarchicalMessageSource
一起使用,它还能够处理嵌套的消息。
当一个ApplicationContext
被加载时,它会自动在context中查找已定义为MessageSource
类型的bean。此bean的名称须为messageSource。如果找到,那么所有对上述方法的调用将被委托给该bean。否则ApplicationContext
会在其父类中查找是否含有同名的bean。如果有,就把它作为MessageSource
。如果它最终没有找到任何的消息源,一个空的StaticMessageSource
将会被实例化,使它能够接受上述方法的调用。
Spring目前提供了两个MessageSource
的实现:ResourceBundleMessageSource
和StaticMessageSource
。它们都继承NestingMessageSource以便能够处理嵌套的消息。StaticMessageSource
很少被使用,但能以编程的方式向消息源添加消息。ResourceBundleMessageSource
会用得更多一些.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="org.spring21"/>
beans>
@Component
public class Person {
@Resource
private Food food;
public void setFood(Food food) {
this.food = food;
}
}
@Component
public class Person {
@Resource
private Food food;
public setFood(Food food) {
this.food = food;
}
@PostConstruct
public void wash() {
System.out.println("饭前洗手");
}
@PreDestroy
public void brush() {
System.out.println("饭后刷牙");
}
}