Spring—IOC—BeanFactory
本章内容部分取自《Spring 揭秘》
1. 什么是IOC(Inversion of Control)
IOC是一种设计理念和思路。使用一个独立的模块(IOC Service Provider)管理对象的创建和绑定等操作。代替主动获取对象的操作,改为被动的接受(即注入)。通过一些配置或者代码写好依赖关系,IOC Service Provider会自动创建和绑定对象。概括来说,IOC是一种可以帮助我们解耦各业务对象间依赖关系的对象绑定方式。
IOC的注入方式: 构造方法注入、Setter方法注入、接口注入。
2. Sping IOC
Spring IoC是一个提供IoC支持的轻量级容器,除了基本的IoC支持,Spring在IoC容器之上还提供了相应的一系列服务。
Spring提供了两种容器类型: BeanFactory 和ApplicationContext
- BeanFactory:基础类型的IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延迟初始化策略(lazy-load)。只有当客户端需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需要的系统资源少。
- ApplicationContext:ApplicationContext是在BeanFactory基础上构建的,是相对比较高级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他高级特性,比如事件发布、国际化信息支持等。ApplicationContext所管理的对象,在该容器启动后,默认全部初始化并绑定完成。
3. BeanFactory
3.1 BeanFactory的对象注册与依赖绑定方式
实际上,现在已经是SpringBoot的天下了。默认使用注解的方式注入,不再需要大量编码或者配置文件进行注册与绑定了。但大量的原有项目仍然使用原始的xml配置方式。BeanFactory的内容也适用于ApplicationContext
3.1.1 直接编码方式
|
|
BeanFactory只是一个接口,我们最终需要一个该接口的实现来进行实际的Bean的管理,DefaultListableBeanFactory就是这么一个比较通用的BeanFactory实现类。DefaultListableBeanFactory除了间接地实现了BeanFactory接口,还实现了BeanDefinitionRegistry接口,该接口才是在BeanFactory的实现中担当Bean注册管理的角色。基本上,BeanFactory接口只定义如何访问容器内管理的Bean的方法,各个BeanFactory的具体实现类负责具体Bean的注册以及管理工作。BeanDefinitionRegistory接口定义抽象了Bean的注册逻辑。通常情况下,具体的BeanFactory实现类会实现这个接口来管理Bean的注册。
3.1.2 配置文件方式
Spring的IOC容器支持两种配置文件格式:Properties文件格式和XML文件格式。当然也可以引入自己的文件格式,自行实现文件解析,如果有必要的话。
(1)Properties配置格式的加载
BeanDefinitionRegistry beanRegistry = new DefaultListableBeanFactory();
BeanDefinitionReader beanDefinitionReader = new BeanDefinitionReaderImpl(beanRegistry);
beanDefinitionReader.loadBeanDefinitions("配置文件路径");
Spring提供了org.springframework.beans.factory.support.PropertiesBeanDefinitionReader类用于Properties格式配置文件的加载。所以我们不用自己去实现BeanDefinitionReader,只要根据该类的读取规则,提供相应的配置文件即可。
properties格式
myBean.(class) = MyBean
#--------通过构造方法注入------------#
myBean.$0(ref) = dependent_1
#--------通过Setter注入----------#
myBean.dependentOne = dependent_1
dependent_1.(class) = DependentOne
(2) Xml配置格式的加载
Xml配置格式是Spring支持最完整,功能最强大的表达方式。
加载xml:不过现在XmlBeanFactory已经是Deprecated,弃用了
(3)注解方式
这是我们现在最常用的方式,@Autowired 标注变量,@Component、@Service等等标注类。
3.2 Xml配置规则
- <Beans>: Xml配置文件中最顶层的元素,可以包含0或者1个<description>和多个<bean>以及<import>或者<alias>.
- <description>: 描述信息
- <import>: 通常情况下,可以根据模块功能或者层次关系,将配置信息分门别类地放到多个配置文件中。可以在A.xml中引入B.xml。类似于<import resource="B.xml">
- <alias>: 可以通过alias标签为<bean>起别名。
- <bean>: <bean id= "myBean" name="别名" class="filePath/MyBean"></bean>
3.2.1 构造方法注入
<bean id= "myBean" name="别名" class="filePath/MyBean">
<constructor-arg index="0">
<ref bean="dependentOne"/>
</constructor-arg >
<constructor-arg index="1" ref="dependentTwo"/>
<constructor-arg index="2" type="int" value="123"/>
</bean>
属性:
- type:指定参数类型
- index:指定参数序号
3.2.2 Setter方法注入
<bean id= "myBean" name="别名" class="filePath/MyBean">
<property name="dependentOne" >
<ref bean = "denpendent_1"/>
</property>
<property name="dependentTwo" ref="denpendent_2"/>
</bean>
<property>有一个name属性,用来指定该property将会注入到对象的哪个属性中。之后通过value或者ref指定具体的依赖对象引用或者值。
Setter和constructor两种方法可以组合使用。如果单独使用的话,要确保提供了默认的构造方法。
3.2.3 <property> 和<constructor-arg>中的配置项
- <value>: 可以为主体对象注入简单的数据类型,不但可以指定String类型,也可以指定Java语言中的基本类型以及它们的包装器类型,如int、Integer等。
- <ref>: 使用ref来引用容器中其他的对象实例,可以通过ref的local、parent和bean属性来指定引用的对下给你的beanName是什么。
-
- local:只能指定与当前配置对象在同一个配置文件的对下给你定义的名称
- parent:只能指定位与当期容器的父容器中定义的对象引用。
- bean: 能搜索到的都可以,一般直接使用bean就行。
-
- <idref>: 写法跟<ref>相同,不同的是<idref>注入的是其他Bean的名称。可以理解为注入的就是一个String,而不是这个对象实例。而且会检查这个BeanName是否存在。
<bean id="bean1" class="Bean1"/>
<bean id="bean2" class="Bean2">
<constructor-arg>
<ref bean="bean1"/>
</constructor-arg>
<constructor-arg>
<idref bean="bean3"/>
</constructor-arg>
<constructor-arg>
<value>bean1</value>
</constructor-arg>
</bean>
构造器有三个参数: 第一个传入bean1的对象实例。第二个传入“bean3”这个字符串,但是会检查是否有Bean的名字叫bean3,这里没有就会报错。第三个传入“bean2”这个字符串。
- 内部<bean>: 可以在<property> 和<constructor-arg>的内部使用<bean>标签定义只有当前对象可以访问的内部对象。
- <list>: 对应注入对象类型为java.util.List及其子类或者数组类型的依赖对象。list内部可以嵌套其他的标签填充。
- <set>:对应注入Java Collection中类型为java.util.Set或者其子类的依赖对象。使用与<list>类似
- <map>:对应注入java.util.Map或者其子类类型的依赖对象
- <props>: 对应java.util.Properties的对象。类似于Map,但只能String类型的键值。
- <null/>: ........null
3.2.4 继承和模板
两个<bean>之间可以通过parent属性指定继承关系。除了特殊指定的意外,childBean会继承parentBean的属性。
<bean id="fatherBean" class="FatherClass">
<property/>
</bean>
<bean id="childBean" class="ChildClass">
<property/>
</bean>
模板:继承了模板的Bean会包含模板中的属性
3.2.5 Bean的Scope属性
scope属性用来声明容器中的对象所应该处于的限定场景或者说该对象的存活时间。共五种singleton、prototype、request、session、global session,其中request、session、global session只存在于Web应用中。
<bean id="..." class="..." scope="singleton/prototype/request/session/global session">
- singleton:scope的默认属性,singleton类型的bean,在一个容器中只存在一个共享实例,所有对该对象的引用将共享这个实例。该实例从容器启动,并初始化该对象后,将一直存活到容器推出。
- prototype:容器在接到该类型对象的请求时,会每次都重新生成一个新的对现象实例给请求方。包括依赖注入、显式的getBean()等。
request、session、global session只存在于Web应用中,也就是Web容器中,例如:WebApplicationContext
- request:request类型的Bean,容器会为每个HTTP请求创建一个全新的实例,每个请求也是只有一个共享的。请求结束,生命周期也即告结束。
- session:容器会为每个会话(session)创建一个实例。
- global session: 只有应用于portlet的Web应用程序中才有意义,它映射到portlet的global范围的session。
3.2.6 自定义的Scope类型
Scope是一个接口,除了singleton和propotype是硬编码外,request,session,global session 实际也是实现了org.springframework.beans.factory.config.Scope接口。要实现自己的scope类,首先要给出一个Scope接口的实现类,然后把这个Scope接口的实现类注册到容器中。可以使用ConfigurableBeanFactory的 void registerScope(String var1, Scope var2);
方法注册。
Scope myScope = new MyScope();
mybeanFactory.registerScope("scopeName", myScope);
//这样就可以在配置文件中使用
<bean id="" class="" scope="scopeName">
除了直接编码方式外,Spring还提供一个专门用于统一注册scope的beanFactoryPostProcessor实现,即org.springframework.beans.factory.config.CustomScopeConfigurer
类。对于ApplicationContext来说,因为它可以自动加载BeanFactoryPostProcessor,直接可以在配置文件中注册。
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="myScope" value="src/../../MyScope"/>
</map>
</property>
</bean>
<bean id="myBean" class="..." scope="myScope">
<aop:scoped-proxy/>
</bean>
3.2.7 工厂方法注入
1. 静态工厂方法
public class Foo{
private BarInterface barInterface;
}
public class StaticBarInterfaceFactory{
public static BarInterface getInstance(Param param){
return new BarInterfaceImpl(param);
}
}
//配置如下,会为foo注入 BarInterfaceImpl 实现类的对象。注意: constructor-arg 传入的参数是工厂方法的参数,而不是构造方法的参数。
<bean id= "foo" class="...Foo">
<property name = "barInterface" ref="bar"/>
</property>
</bean>
<bean id="bar" class="...StaticBarInterfaceFactory" factory-method="getInstance">
<constructor-arg>
<ref bean="param"/>
<constructor-arg>
</bean>
<bean id="param" class="...Param"/>
2. 非静态工厂方法
public class Foo{
private BarInterface barInterface;
}
public class NonStaticBarInterfaceFactory{
public BarInterface getInstance(Param param){
return new BarInterfaceImpl(param);
}
}
//配置如下,NonStaticBarInterfaceFactory是作为正常的Bean注册到容器的,而bar使用factory-bean指定工厂类实例。注意: constructor-arg 传入的参数是方法的参数,而不是构造方法的参数。
<bean id= "foo" class="...Foo">
<property name = "barInterface" ref="bar"/>
</property>
</bean>
<bean id="barFactory" class="...NonStaticBarInterfaceFactory">
<bean id="bar" factory-bean="barFactory" factory-method="getInstance">
<constructor-arg>
<ref bean="param"/>
<constructor-arg>
</bean>
<bean id="param" class="...Param"/>
3.FactoryBean
FactoryBean是Spring提供的一种可以扩展容器对象实例化逻辑的接口。当实例化过程过于繁琐,Xml配置过于复杂或者某些第三方库不能直接注册到Spring容器的时候,就可以实现org.springframework.beans.factory.FactoryBean接口。
|
|
|
3.2.8 方法注入、BeanFactoryAware 和方法替换
- 方法注入: <look-up>。 像下方图一这样使用的时候,只是从容器中获取了一次对象。每次调用getNewsBean()返回的是同一个实例。运用<look-up>标签进行方法注入,每次getNewsBean()获得独立的Bean实例
- BeanFactoryAware
当然其他方法也可以实现方法注入的功能。比如BeanFactoryAware接口。源码如下。实现该接口,将BeanFactory注入到实例中,这样就可以在实例方法中调用getBean()来获取独立的Bean实例。如下图
public interface BeanFactoryAware extends Aware {
void setBeanFactory(BeanFactory var1) throws BeansException;
}
- ObjectFactoryCreatingFactoryBean
ObjectFactoryCreatingFactoryBean是Spring提供的一个Factorybean实现,并且实现了BeanFactoryAware,使用ObjectFactoryCreatingFactoryBean的好处就是隔离了客户端对象对BeanFactory的直接引用。
- 方法替换:<replaced-method>