文章参考:王福强老师的《Spring揭秘》一书。
IoC:Inversion of control(控制反转),或者叫DI (依赖注入)。
为什么是控制反转?
创建java SE程序设计中,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象,而IoC是有专门的容器来创建这些对象并注入依赖对象,对象只是被动的接受依赖对昂。
DI—Dependency Injection,即“依赖注入”:
是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
Ioc容器它会以某种方式加载Configuration Metadata,然后根据这些信息绑定整个系统的对象,最终组装成一个可用的基于轻量级容器的应用系统。
两种容器类型
Spring提供了两种容器类型: BeanFactory和ApplicationContext。
BeanFactory:基础类型IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延
迟初始化策略( lazy-load)。只有当客户端对象需要访问容器中的某个受管对象的时候,才对
该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需
要的资源有限。对于资源有限,并且功能要求不是很严格的场景, BeanFactory是比较合适的
IoC容器选择。
ApplicationContext:ApplicationContext在BeanFactory的基础上构建,是相对比较高
级的容器实现,除了拥有BeanFactory的所有支持, ApplicationContext还提供了其他高级
特性,比如事件发布、国际化信息支持等。 ApplicationContext所管理
的对象,在该类型容器启动之后,默认全部初始化并绑定完成。所以,相对于BeanFactory来
说, ApplicationContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,容
器启动时间较之BeanFactory也会长一些。在那些系统资源充足,并且要求更多功能的场景中,
ApplicationContext类型的容器是比较合适的选择。
两个阶段
容器启动阶段和Bean实例化阶段。
上图摘自《spring揭秘》第4章,4.4.1
容器启动阶段
容器启动伊始,首先会通过某种途径加载Configuration MetaData。除了代码方式比较直接,在大部分情况下,容器需要依赖某些工具类( BeanDefinitionReader)对加载的Configuration MetaData进行解析和分析,并将分析后的信息编码为相应的BeanDefinition,最后把这些保存了bean定义必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器启动工作就完成了。
bean实例化阶段
经过第一阶段,现在所有的bean定义信息都通过BeanDefinition的方式注册到了BeanDefinitionRegistry中。当某个请求方通过容器的getBean方法明确地请求某个对象,或者因依赖关系容器需要隐式地调用getBean方法时,就会触发第二阶段的活动。
该阶段,容器会首先检查所请求的对象之前是否已经初始化。如果没有,则会根据注册的BeanDefinition所提供的信息实例化被请求对象,并为其注入依赖。如果该对象实现了某些回调接口,也会根据回调接口的要求来装配它。当该对象装配完毕之后,容器会立即将其返回请求方使用。如果说第一阶段只是根据图纸装配生产线的话,那么第二阶段就是使用装配好的生产线来生产具体的产品了。
容器扩展机制
BeanFactoryPostProcessor:容器扩展机制,该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做相应的修改。这就相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修改其中bean定义的某些属性,为bean定义增加其他信息等。
Spring提供了几个现成的BeanFactoryPostProcessor实现类,其中org.springframework.beans.factory.config.PropertyPlaceholderConfigurer和org.springframework.beans.factory.config.PropertyOverrideConfigurer是两个比较常用的。
PropertyPlaceholderConfigurer:通常情况下,我们不想将类似于系统管理相关的信息同业务对象相关的配置信息混杂到XML配置文件中,以免部署或者维护期间因为改动繁杂的XML配置文件而出现问题。我们会将一些数据库连接
信息、邮件服务器等相关信息单独配置到一个properties文件中,这样,如果因系统资源变动的话,只需要关注这些简单properties配置文件即可。PropertyPlaceholderConfigurer允许我们在XML配置文件中使用占位符(PlaceHolder),并将这些占位符所代表的资源单独配置到简单的properties文件中来加载。
示例1:使用了占位符的xml文件。
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>file:D:\rb\local\jdbc.properties</value>
</list>
</property>
</bean>
<bean id="rbDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" >
<property name="driverClassName" value="${jdbc.driverClass}" />
<property name="url" value="${rb.jdbcUrl}" />
<property name="username" value="${rb.user}" />
<property name="password" value="${rb.password}" />
</bean>
实例2:jdbc.properties
rb.jdbcUrl=jdbc:mysql://127.0.0.1:3006/rbtest
rb.user=test
rb.password=test4rb
机制:当BeanFactory在第一阶段加载完成所有配置信息时, BeanFactory中保存的对象的属性信息还只是以占位符的形式存在,如${rb.jdbcUrl}、 ${rb.user}。当PropertyPlaceholderConfigurer作为BeanFactoryPostProcessor被应用时,它会使用properties配置文件中的配置信息来替换相应BeanDefinition中占位符所表示的属性值。这样,当进入容器实现的第二阶段实例化bean时, bean定义中的属性值就是最终替换完成的了。PropertyPlaceholderConfigurer不单会从其配置的properties文件中加载配置项,同时还会检查Java的System类中的Properties,可以通过setSystemPropertiesMode()或者setSystemPropertiesModeName()来控制是否加载或者覆盖System相应Properties的行为。
PropertyOverrideConfigurer:可以通过PropertyOverrideConfigurer对容器中配置的任何你想处理的bean定义的property信息进行覆盖替换。如果要对容器中的某些bean定义的property信息进行覆盖,我们需要按照如下规则提供一个PropertyOverrideConfigurer使用的配置文件。
比如:pool-adjustment.properties,格式为beanName.propertyName=value
也就是说, properties文件中的键是以XML中配置的bean定义的beanName为标志开始的(通常就
是id指定的值),后面跟着相应被覆盖的property的名称。
xml中配置PropertyOverrideConfigurer
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="location" value="pool-adjustment.properties"/>
</bean>
这样,当PropertyOverrideConfigurer加载到容器之后,xml中原来配置beanName.propertyName的默认值就会被pool-adjustment.properties文件中的beanName.propertyName后的value值所覆盖。
配 置 在 properties 文 件 中 的 信 息 通 常 都 以 明 文 表 示 ,如果想要配置成密文,并且可以将加密后的字符串解密之后再覆盖到相应的bean定义中。可以通过实现自定义的BeanFactoryPostProcessor。比方说PropertyPlaceholderConfigurer,我们可以实现一个类继承自PropertyPlaceholderConfigurer,重新它的processProperties方法,在该方法中实现对加密字符串的解密。
import java.util.Properties;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
public class SafePropertyParser extends PropertyPlaceholderConfigurer {
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
Properties props)
throws BeansException {
decryptProperty("rb.password", props);
super.processProperties(beanFactoryToProcess, props);
}
private void decryptProperty(String propertyKey, Properties props) {
//decrypt property
}
}