学习链接地址:http://www.iteye.com/blogs/subjects/spring3
第一部分 DI的配置使用
依赖和依赖注入
Spring IoC容器的依赖有两层含义:Bean依赖容器和容器注入Bean的依赖资源:
- Bean依赖容器:也就是说Bean要依赖于容器,这里的依赖是指容器负责创建Bean并管理Bean的生命周期,正是由于由容器来控制创建Bean并注入依赖,也就是控制权被反转了,这也正是IoC名字的由来,此处的有依赖是指Bean和容器之间的依赖关系。
- 容器注入Bean的依赖资源:容器负责注入Bean的依赖资源,依赖资源可以是Bean、外部文件、常量数据等,在Java中都反映为对象,并且由容器负责组装Bean之间的依赖关系,此处的依赖是指Bean之间的依赖关系,可以认为是传统类与类之间的“关联”、“聚合”、“组合”关系。
Spring IoC容器注入依赖资源主要有以下两种基本实现方式:
- 构造器注入:就是容器实例化Bean时注入那些依赖,通过在在Bean定义中指定构造器参数进行注入依赖,包括实例工厂方法参数注入依赖,但静态工厂方法参数不允许注入依赖;
- setter注入:通过setter方法进行注入依赖;
- 方法注入:能通过配置方式替换掉Bean方法,也就是通过配置改变Bean方法功能。
构造器注入
构造器注入可以根据参数索引注入、参数类型注入或Spring3支持的参数名注入。但是参数名注入是有限制的,需要使用在编译程序时打开调试模式(即在编译时使用“javac –g:vars”在class文件中生成变量调试信息,默认是不包含变量调试信息的,从而能获取参数名字,否则获取不到参数名字)或在构造器上使用@ConstructorProperties(java.beans.ConstructorProperties)注解来指定参数名。
public class HelloImpl3 implements HelloApi {
private String message;
private int index;
// @java.beans.ConstructorProperties({"message", "index"})
public HelloImpl3(String message, int index) {
this.message = message;
this.index = index;
}
@Override
public void sayHello() {
System.out.println(index + ":" + message);
}
}
- 根据参数索引注入,使用标签“ < constructor-arg index=”1” value=”1”/>”来指定注入的依赖,其中“index”表示索引,从0开始,即第一个参数索引为0,“value”来指定注入的常量值,配置方式如下
根据参数类型进行注入,使用标签“< constructor-arg type=”java.lang.String” value=”Hello World!”/>”来指定注入的依赖,其中“type”表示需要匹配的参数类型,可以是基本类型也可以是其他类型,如“int”、“java.lang.String”,“value”来指定注入的常量值,配置方式如下:
根据参数名进行注入,使用标签“< constructor-arg name=”message” value=”Hello World!”/>”来指定注入的依赖,其中“name”表示需要匹配的参数名字,“value”来指定注入的常量值,配置方式如下:
构造器注入
<!-- 通过构造器参数索引方式依赖注入 -->
<bean id="byIndex" class="cn.javass.spring.chapter3.HelloImpl3">
<constructor-arg index="0" value="Hello World!"/>
<constructor-arg index="1" value="1"/>
</bean>
<!-- 通过构造器参数类型方式依赖注入 -->
<bean id="byType" class="cn.javass.spring.chapter3.HelloImpl3">
<constructor-arg type="java.lang.String" value="Hello World!"/>
<constructor-arg type="int" value="2"/>
</bean>
<!-- 通过构造器参数名称方式依赖注入 -->
<bean id="byName" class="cn.javass.spring.chapter3.HelloImpl3">
<constructor-arg name="message" value="Hello World!"/>
<constructor-arg name="index" value="3"/>
</bean>
@Test
public void testConstructorDependencyInjectTest() {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("chapter3/constructorDependencyInject.xml");
//获取根据参数索引依赖注入的Bean
HelloApi byIndex = beanFactory.getBean("byIndex", HelloApi.class);
byIndex.sayHello();
//获取根据参数类型依赖注入的Bean
HelloApi byType = beanFactory.getBean("byType", HelloApi.class);
byType.sayHello();
//获取根据参数名字依赖注入的Bean
HelloApi byName = beanFactory.getBean("byName", HelloApi.class);
byName.sayHello();
}
静态工厂方法注入
//静态工厂类
public class DependencyInjectByStaticFactory {
public static HelloApi newInstance(String message, int index) {
return new HelloImpl3(message, index);
}
}
<bean id="byIndex" class="cn.javass.spring.chapter3.DependencyInjectByStaticFactory" factory-method="newInstance">
<constructor-arg index="0" value="Hello World!"/>
<constructor-arg index="1" value="1"/>
</bean>
<bean id="byType" class="cn.javass.spring.chapter3.DependencyInjectByStaticFactory" factory-method="newInstance">
<constructor-arg type="java.lang.String" value="Hello World!"/>
<constructor-arg type="int" value="2"/>
</bean>
<bean id="byName" class="cn.javass.spring.chapter3.DependencyInjectByStaticFactory" factory-method="newInstance">
<constructor-arg name="message" value="Hello World!"/>
<constructor-arg name="index" value="3"/>
</bean>
实例工厂类
//实例工厂类
public class DependencyInjectByInstanceFactory {
public HelloApi newInstance(String message, int index) {
return new HelloImpl3(message, index);
}
}
<bean id="instanceFactory" class="cn.javass.spring.chapter3.DependencyInjectByInstanceFactory" />
<bean id="byIndex" factory-bean="instanceFactory" factory-method="newInstance">
<constructor-arg index="0" value="Hello World!"/>
<constructor-arg index="1" value="1"/>
</bean>
<bean id="byType" factory-bean="instanceFactory" factory-method="newInstance">
<constructor-arg type="java.lang.String" value="Hello World!"/>
<constructor-arg type="int" value="2"/>
</bean>
<bean id="byName" factory-bean="instanceFactory" factory-method="newInstance">
<constructor-arg name="message" value="Hello World!"/>
<constructor-arg name="index" value="3"/>
</bean>
setter注入
setter注入,是通过在通过构造器、静态工厂或实例工厂实例好Bean后,通过调用Bean类的setter方法进行注入依赖。
setter注入方式只有一种根据setter名字进行注入:
public class HelloImpl4 implements HelloApi {
private String message;
private int index;
//setter方法
public void setMessage(String message) {
this.message = message;
}
public void setIndex(int index) {
this.index = index;
}
@Override
public void sayHello() {
System.out.println(index + ":" + message);
}
}
<!-- 通过setter方式进行依赖注入 -->
<bean id="bean" class="cn.javass.spring.chapter3.HelloImpl4">
<property name="message" value="Hello World!"/>
<property name="index">
<value>1</value>
</property>
</bean>
@Test
public void testSetterDependencyInject() {
BeanFactory beanFactory =
new ClassPathXmlApplicationContext("chapter3/setterDependencyInject.xml");
HelloApi bean = beanFactory.getBean("bean", HelloApi.class);
bean.sayHello();
}
注入常量
<property name="message" value="Hello World!"/>
或
<property name="index"><value>1</value></property>
Spring容器目前能对各种基本类型把配置的String参数转换为需要的类型。
Spring类型转换系统对于boolean类型进行了容错处理,除了可以使用“true/false”标准的Java值进行注入,还能使用“yes/no”、“on/off”、“1/0”来代表“真/假”,所以大家在学习或工作中遇到这种类似问题不要觉得是人家配置错了,而是Spring容错做的非常好。
public class BooleanTestBean {
private boolean success;
public void setSuccess(boolean success) {
this.success = success;
}
public boolean isSuccess() {
return success;
}
}
<!-- boolean参数值可以用on/off -->
<bean id="bean2" class="cn.javass.spring.chapter3.bean.BooleanTestBean">
<property name="success" value="on"/>
</bean>
<!-- boolean参数值可以用yes/no -->
<bean id="bean3" class="cn.javass.spring.chapter3.bean.BooleanTestBean">
<property name="success" value="yes"/>
</bean>
<!-- boolean参数值可以用1/0 -->
<bean id="bean4" class="cn.javass.spring.chapter3.bean.BooleanTestBean">
<property name="success" value="1"/>
</bean>
注入Bean ID
用于注入Bean的ID,ID是一个常量不是引用,且类似于注入常量,但提供错误验证功能,配置方式如下所示:
<!-- 方式一 -->
<property name="id"><idref bean="bean1"/></property>
<!-- 方式二 -->
<property name="id"><idref local="bean2"/></property>
第一种方式可以在容器初始化时校验被引用的Bean是否存在,如果不存在将抛出异常,而第二种方式只有在Bean实际使用时才能发现传入的Bean的ID是否正确,可能发生不可预料的错误。因此如果想注入Bean的ID,推荐使用第一种方式。
public class IdRefTestBean {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
<bean id="bean1" class="java.lang.String">
<constructor-arg index="0" value="test"/>
</bean>
<bean id="bean2" class="java.lang.String">
<constructor-arg index="0" value="test"/>
</bean>
<bean id="idrefBean1" class="cn.javass.spring.chapter3.bean.IdRefTestBean">
<property name="id"><idref bean="bean1"/></property>
</bean>
<bean id="idrefBean2" class="cn.javass.spring.chapter3.bean.IdRefTestBean">
<property name="id"><idref local="bean2"/></property>
</bean>
< idref bean=”……”/>将在容器初始化时校验注入的ID对于的Bean是否存在,如果不存在将抛出异常。
< idref local=”……”/>将在XML解析时校验注入的ID对于的Bean在当前配置文件中是否存在,如果不存在将抛出异常,它不同于< idref bean=”……”/>是校验发生在XML解析式而非容器初始化时,且只检查当前配置文件中是否存在相应的Bean。
注入集合、数组和字典
Spring不仅能注入简单类型数据,还能注入集合(Collection、无序集合Set、有序集合List)类型、数组(Array)类型、字典(Map)类型数据、Properties类型数据。
注入集合类型
包括Collection类型、Set类型、List类型数据
- List类型:需要使用标签来配置注入
public class ListTestBean {
private List<String> values;
public List<String> getValues() {
return values;
}
public void setValues(List<String> values) {
this.values = values;
}
}
<bean id="listBean" class="cn.javass.spring.chapter3.bean.ListTestBean">
<property name="values">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
</bean>
@Test
public void testListInject() {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("chapter3/listInject.xml");
ListTestBean listBean = beanFactory.getBean("listBean", ListTestBean.class);
System.out.println(listBean.getValues().size());
Assert.assertEquals(3, listBean.getValues().size());
}
- Set类型:需要使用标签来配置注入
public class CollectionTestBean {
private Collection<String> values;
public void setValues(Collection<String> values) {
this.values = values;
}
public Collection<String> getValues() {
return values;
}
}
<bean id="setBean" class="cn.javass.spring.chapter3.bean.SetTestBean">
<property name="values">
<set>
<value>1</value>
<value>2</value>
<value>3</value>
</set>
</property>
</bean>
- Collection类型:因为Collection类型是Set和List类型的基类型,所以使用< set>或< list>标签都可以进行注入,配置方式完全和以上配置方式一样,只是将测试类属性改成“Collection”类型。
<bean id="collectionBean1" class="cn.javass.spring.chapter3.bean.CollectionTestBean">
<property name="values">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
</bean>
<bean id="collectionBean2" class="cn.javass.spring.chapter3.bean.CollectionTestBean">
<property name="values">
<set>
<value>1</value>
<value>2</value>
<value>3</value>
</set>
</property>
</bean>
@Test
public void testCollectionInject() {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("chapter3/collectionInject.xml");
//本质是List类型
CollectionTestBean collectionBean1 = beanFactory.getBean("collectionBean1", CollectionTestBean.class);
System.out.println("Type:" + collectionBean1.getValues().getClass().getName());
System.out.println(collectionBean1.getValues().size());
Assert.assertEquals(3, collectionBean1.getValues().size());
//本质是Set类型
CollectionTestBean collectionBean2 = beanFactory.getBean("collectionBean2", CollectionTestBean.class);
System.out.println("Type:" + collectionBean2.getValues().getClass().getName());
System.out.println(collectionBean2.getValues().size());
Assert.assertEquals(3, collectionBean2.getValues().size());
}
注入数组类型
需要使用< array>标签来配置注入
<bean id="arrayBean" class="cn.javass.spring.chapter3.bean.ArrayTestBean">
<property name="array">
<array value-type="java.lang.String" merge="default">
<value>1</value>
<value>2</value>
<value>3</value>
</array>
</property>
<property name="array2">
<array>
<array>
<value>1</value>
<value>2</value>
<value>3</value>
</array>
<array>
<value>4</value>
<value>5</value>
<value>6</value>
</array>
</array>
</property>
</bean>
@Test
public void testArrayInject() {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("chapter3/arrayInject.xml");
ArrayTestBean arrayBean = beanFactory.getBean("arrayBean", ArrayTestBean.class);
System.out.println(arrayBean.getArray().length);
System.out.println(arrayBean.getArray2().length);
System.out.println(arrayBean.getArray2()[0].length);
System.out.println(arrayBean.getArray2()[1].length);
}
注入字典(Map)类型
字典类型是包含键值对数据的数据结构,需要使用< map>标签来配置注入,其属性“key-type”和“value-type”分别指定“键”和“值”的数据类型,并使用子标签来指定键数据,< value>子标签来指定键对应的值数据。
<bean id="mapBean" class="cn.javass.spring.chapter3.bean.MapTestBean">
<property name="values">
<map key-type="java.lang.String" value-type="java.lang.String">
<entry>
<key><value>1</value></key>
<value>11</value>
</entry>
<entry key="2" value="22"/>
</map>
</property>
</bean>
Properties注入
Spring能注入java.util.Properties类型数据,需要使用< props>标签来配置注入,键和值类型必须是String,不能变,子标签< prop key=”键”>值< /prop>来指定键值对。
<bean id="propertiesBean" class="cn.javass.spring.chapter3.bean.PropertiesTestBean">
<property name="values">
<props value-type="int" merge="default">
<prop key="1">12sw</prop>
<prop key="2">2</prop>
</props>
</property>
</bean>
<bean id="propertiesBean2" class="cn.javass.spring.chapter3.bean.PropertiesTestBean">
<property name="values">
<value>
1=11
2=22,
3=33;
4=44
</value>
</property>
</bean>
@Test
public void testPropertiesInject() {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("chapter3/propertiesInject.xml");
PropertiesTestBean propertiesBean = beanFactory.getBean("propertiesBean", PropertiesTestBean.class);
System.out.println(propertiesBean.getValues().size());
System.out.println(propertiesBean.getValues().containsValue("22"));
PropertiesTestBean propertiesBean2 = beanFactory.getBean("propertiesBean2", PropertiesTestBean.class);
System.out.println(propertiesBean2.getValues().size());
System.out.println(propertiesBean2.getValues().containsKey("1"));
System.out.println(propertiesBean2.getValues().containsKey("2"));
System.out.println(propertiesBean2.getValues().containsKey("3"));
System.out.println(propertiesBean2.getValues().containsKey("4"));
System.out.println(propertiesBean2.getValues().containsKey("5"));
System.out.println(propertiesBean2.getValues().containsValue("11"));
}
引用其它Bean
构造器注入方式
- 通过” < constructor-arg>”标签的ref属性来引用其他Bean
- 通过” < constructor-arg>”标签的子< ref>标签来引用其他Bean,使用bean属性来指定引用的Bean
setter注入方式
- 通过” < property>”标签的ref属性来引用其他Bean
- 通过” < property>”标签的子< ref>标签来引用其他Bean,使用bean属性来指定引用的Bean
public class HelloApiDecorator implements HelloApi {
private HelloApi helloApi;
//空参构造器
public HelloApiDecorator() {
}
//有参构造器
public HelloApiDecorator(HelloApi helloApi) {
this.helloApi = helloApi;
}
public void setHelloApi(HelloApi helloApi) {
this.helloApi = helloApi;
}
@Override
public void sayHello() {
System.out.println("==========装饰一下===========");
helloApi.sayHello();
System.out.println("==========装饰一下===========");
}
}
<!-- 定义依赖Bean -->
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<!-- 通过构造器注入 -->
<bean id="bean1" class="cn.javass.spring.chapter3.bean.HelloApiDecorator">
<constructor-arg index="0" ref="helloApi"/>
</bean>
<!-- 通过构造器注入 -->
<bean id="bean2" class="cn.javass.spring.chapter3.bean.HelloApiDecorator">
<property name="helloApi"><ref bean=" helloApi"/></property>
</bean>
其他引用方式
除了最基本配置方式以外,Spring还提供了另外两种更高级的配置方式,< ref local=””/>和< ref parent=””/>
- < ref local=””/>配置方式:用于引用通过< bean id=”beanName”>方式中通过id属性指定的Bean,它能利用XML解析器的验证功能在读取配置文件时来验证引用的Bean是否存在。因此如果在当前配置文件中有相互引用的Bean可以采用< ref local>方式从而如果配置错误能在开发调试时就发现错误。
- < ref parent=””/>配置方式:用于引用父容器中的Bean,不会引用当前容器中的Bean,当然父容器中的Bean和当前容器的Bean是可以重名的,获取顺序是先查找当前容器中的Bean,如果找不到再从父容器找。
<!-- sources/chapter3/parentBeanInject.xml表示父容器配置-->
<!--注意此处可能子容器也定义一个该Bean-->
<bean id="helloApi" class="cn.javass.spring.chapter3.HelloImpl4">
<property name="index" value="1"/>
<property name="message" value="Hello Parent!"/>
</bean>
<!-- sources/chapter3/localBeanInject.xml表示当前容器配置-->
<!-- 注意父容器中也定义了id 为 helloApi的Bean -->
<bean id="helloApi" class="cn.javass.spring.chapter3.HelloImpl4">
<property name="index" value="1"/>
<property name="message" value="Hello Local!"/>
</bean>
<!-- 通过local注入 -->
<bean id="bean1" class="cn.javass.spring.chapter3.bean.HelloApiDecorator">
<constructor-arg index="0"><ref local="helloApi"/></constructor-arg>
</bean>
<!-- 通过parent注入 -->
<bean id="bean2" class="cn.javass.spring.chapter3.bean.HelloApiDecorator">
<property name="helloApi"><ref parent="helloApi"/></property>
</bean>
@Test
public void testLocalAndparentBeanInject() {
//初始化父容器
ApplicationContext parentBeanContext =
new ClassPathXmlApplicationContext("chapter3/parentBeanInject.xml");
//初始化当前容器
ApplicationContext beanContext = new ClassPathXmlApplicationContext(
new String[] {"chapter3/localBeanInject.xml"}, parentBeanContext);
HelloApi bean1 = beanContext.getBean("bean1", HelloApi.class);
bean1.sayHello();//该Bean引用local bean
HelloApi bean2 = beanContext.getBean("bean2", HelloApi.class);
bean2.sayHello();//该Bean引用parent bean
}
内部Bean定义
内部Bean就是在< property>或< constructor-arg>内通过标签定义的Bean,该Bean不管是否指定id或name,该Bean都会有唯一的匿名标识符,而且不能指定别名,该内部Bean对其他外部Bean不可见。
<bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator">
<property name="helloApi">
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
</property>
</bean>
@Test
public void testInnerBeanInject() {
ApplicationContext context = new ClassPathXmlApplicationContext("chapter3/innerBeanInject.xml");
HelloApi bean = context.getBean("bean", HelloApi.class);
bean.sayHello();
}
处理null值
Spring通过< value>标签或value属性注入常量值,所有注入的数据都是字符串,那如何注入null值呢?通过“null”值吗?当然不是因为如果注入“null”则认为是字符串。Spring通过< null/>标签注入null值。
对象图导航注入支持
所谓对象图导航是指类似a.b.c这种点缀访问形式的访问或修改值。Spring支持对象图导航方式依赖注入。对象图导航依赖注入有一个限制就是比如a.b.c对象导航图注入中a和b必须为非null值才能注入c,否则将抛出空指针异常。
Spring不仅支持对象的导航,还支持数组、列表、字典、Properties数据类型的导航,对Set数据类型无法支持,因为无法导航。
数组和列表数据类型可以用array[0]、list[1]导航,注意”[]”里的必须是数字,因为是按照索引进行导航,对于数组类型注意不要数组越界错误。
字典Map数据类型可以使用map[1]、map[str]进行导航,其中“[]”里的是基本类型,无法放置引用类型。
public class NavigationC {
public void sayNavigation() {
System.out.println("===navigation c");
}
}
public class NavigationB {
private NavigationC navigationC;
private List<NavigationC> list;
private Properties properties;
private NavigationC[] array = new NavigationC[1];
private Map<String, NavigationC> map;
//由于setter和getter方法占用太多空间,故省略,大家自己实现吧
}
public class NavigationA {
private NavigationB navigationB;
public void setNavigationB(NavigationB navigationB) {
this.navigationB = navigationB;
}
public NavigationB getNavigationB() {
return navigationB;
}
}
<bean id="c" class="cn.javass.spring.chapter3.bean.NavigationC"/>
<bean id="b" class="cn.javass.spring.chapter3.bean.NavigationB">
<property name="list"><list></list></property>
<property name="map"><map></map></property>
<property name="properties"><props></props></property>
</bean>
<bean id="a" class="cn.javass.spring.chapter3.bean.NavigationA">
<!-- 首先注入navigatiionB 确保它非空 -->
<property name="navigationB" ref="b"/>
<!-- 对象图导航注入 -->
<property name="navigationB.navigationC" ref="c"/>
<!-- 注入列表数据类型数据 -->
<property name="navigationB.list[0]" ref="c"/>
<!-- 注入map类型数据 -->
<property name="navigationB.map[key]" ref="c"/>
<!-- 注入properties类型数据 -->
<property name="navigationB.properties[0]" ref="c"/>
<!-- 注入properties类型数据 -->
<property name="navigationB.properties[1]" ref="c"/>
<!-- 注入数组类型数据 ,注意不要越界-->
<property name="navigationB.array[0]" ref="c"/>
</bean>
@Test
public void testNavigationBeanInject() {
ApplicationContext context = new ClassPathXmlApplicationContext("chapter3/navigationBeanInject.xml");
NavigationA navigationA = context.getBean("a", NavigationA.class);
navigationA.getNavigationB().getNavigationC().sayNavigation();
navigationA.getNavigationB().getList().get(0).sayNavigation();
navigationA.getNavigationB().getMap().get("key").sayNavigation();
navigationA.getNavigationB().getArray()[0].sayNavigation();
((NavigationC)navigationA.getNavigationB().getProperties().get("1")).sayNavigation();
}
配置简写
构造器注入
- 常量值
<!-- 简写 -->
<constructor-arg index="0" value="常量"/>
<!-- 全写 -->
<constructor-arg index="0"><value>常量</value></constructor-arg>
- 引用
<!-- 简写 -->
<constructor-arg index="0" ref="引用"/>
<!-- 全写 -->
<constructor-arg index="0"><ref bean="引用"/></constructor-arg>
setter注入
- 常量值
<!-- 简写 -->
<property name="message" value="常量"/>
<!-- 全写 -->
<property name="message"><value>常量</value></ property>
- 引用
<!-- 简写 -->
<property name="message" ref="引用"/>
<!-- 全写 -->
<property name="message"><ref bean="引用"/></ property>
- 数组:< array>没有简写形式
- 列表:< list>没有简写形式
- 集合:< set>没有简写形式
- 字典
<!-- 简写 -->
<map>
<entry key="键常量" value="值常量"/>
<entry key-ref="键引用" value-ref="值引用"/>
</map>
<!-- 全写 -->
<map>
<entry><key><value>键常量</value></key><value>值常量</value></entry>
<entry><key><ref bean="键引用"/></key><ref bean="值引用"/></entry>
</map>
- Properties:没有简写形式
使用p命名空间简化setter注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="bean1" class="java.lang.String">
<constructor-arg index="0" value="test"/>
</bean>
<bean id="idrefBean1" class="cn.javass.spring.chapter3.bean.IdRefTestBean" p:id="value"/>
<bean id="idrefBean2" class="cn.javass.spring.chapter3.bean.IdRefTestBean" p:id-ref="bean1"/>
</beans>
- xmlns:p=”http://www.springframework.org/schema/p” :首先指定p命名空间;
- < bean id=”……” class=”……” p:id=”value”/> :常量setter注入方式,其等价于< property name=”id” value=”value”/>;
- < bean id=”……” class=”……” p:id-ref=”bean1”/> :引用setter注入方式,其等价于< property name=”id” ref=”bean1”/>。
第二部分 循环依赖
什么是循环依赖
循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环。此处不是循环调用,循环调用是方法之间的环调用。
public class CircleA {
private CircleB circleB;
public CircleA() {
}
public CircleA(CircleB circleB) {
this.circleB = circleB;
}
public void setCircleB(CircleB circleB) {
this.circleB = circleB;
}
public void a() {
circleB.b();
}
}
public class CircleB {
private CircleC circleC;
public CircleB() {
}
public CircleB(CircleC circleC) {
this.circleC = circleC;
}
public void setCircleC(CircleC circleC) {
this.circleC = circleC;
}
public void b() {
circleC.c();
}
}
public class CircleC {
private CircleC circleC;
public CircleB() {
}
public CircleB(CircleC circleC) {
this.circleC = circleC;
}
public void setCircleC(CircleC circleC) {
this.circleC = circleC;
}
public void b() {
circleC.c();
}
}
Spring如何解决循环依赖
构造器循环依赖
表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。
Spring容器将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException**异常表示循环依赖**;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。
<bean id="circleA" class="cn.javass.spring.chapter3.bean.CircleA">
<constructor-arg index="0" ref="circleB"/>
</bean>
<bean id="circleB" class="cn.javass.spring.chapter3.bean.CircleB">
<constructor-arg index="0" ref="circleC"/>
</bean>
<bean id="circleC" class="cn.javass.spring.chapter3.bean.CircleC">
<constructor-arg index="0" ref="circleA"/>
</bean>
@Test(expected = BeanCurrentlyInCreationException.class)
public void testCircleByConstructor() throws Throwable {
try {
new ClassPathXmlApplicationContext("chapter3/circleInjectByConstructor.xml");
}catch (Exception e) {
//因为要在创建circle3时抛出;
Throwable e1 = e.getCause().getCause().getCause();
throw e1;
}
}
- Spring容器创建“circleA” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleB”,并将“circleA”标识符放到“当前创建Bean池”;
- Spring容器创建“circleB” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleC”,并将“circleB”标识符放到“当前创建Bean池”;
- Spring容器创建“circleC” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleA”,并将“circleC”标识符放到“当前创建Bean池”;
- 到此为止Spring容器要去创建“circleA”Bean,发现该Bean标识符在“当前创建Bean池”中,因为表示循环依赖,抛出BeanCurrentlyInCreationException。
setter循环依赖
对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的Bean来完成的,而且只能解决单例作用域的Bean循环依赖。
addSingletonFactory(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
- Spring容器创建单例“circleA” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的Bean,并将“circleA”标识符放到“当前创建Bean池”;然后进行setter注入“circleB”;
- Spring容器创建单例“circleB” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的Bean,并将“circleB”标识符放到“当前创建Bean池”,然后进行setter注入“circleC”;
- Spring容器创建单例“circleC” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的Bean,并将“circleC”标识符放到“当前创建Bean池”,然后进行setter注入“circleA”;进行注入“circleA”时由于提前暴露了“ObjectFactory”工厂从而使用它返回提前暴露一个创建中的Bean;
- 最后在依赖注入“circleB”和“circleA”,完成setter注入。
对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。
<!-- 定义Bean配置文件,注意scope都是“prototype”-->
<bean id="circleA" class="cn.javass.spring.chapter3.bean.CircleA" scope="prototype">
<property name="circleB" ref="circleB"/>
</bean>
<bean id="circleB" class="cn.javass.spring.chapter3.bean.CircleB" scope="prototype">
<property name="circleC" ref="circleC"/>
</bean>
<bean id="circleC" class="cn.javass.spring.chapter3.bean.CircleC" scope="prototype">
<property name="circleA" ref="circleA"/>
</bean>
@Test(expected = BeanCurrentlyInCreationException.class)
public void testCircleBySetterAndPrototype () throws Throwable {
try {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( "chapter3/circleInjectBySetterAndPrototype.xml");
System.out.println(ctx.getBean("circleA"));
}catch (Exception e) {
Throwable e1 = e.getCause().getCause().getCause();
throw e1;
}
}
对于“singleton”作用域Bean,可以通过“setAllowCircularReferences(false);”来禁用循环引用。
@Test(expected = BeanCurrentlyInCreationException.class)
public void testCircleBySetterAndSingleton2() throws Throwable {
try {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
ctx.setConfigLocation("chapter3/circleInjectBySetterAndSingleton.xml");
ctx.refresh();
}catch (Exception e) {
Throwable e1 = e.getCause().getCause().getCause();
throw e1;
}
}
第三部分 更多DI知识
延迟初始化Bean
延迟初始化也叫做惰性初始化,指不提前初始化Bean,而是只有在真正使用时才创建及初始化Bean。
Spring容器会在创建容器时提前初始化“singleton”作用域的Bean。
延迟初始化的Bean通常会在第一次使用时被初始化;或者在被非延迟初始化Bean作为依赖对象注入时在会随着初始化该Bean时被初始化,因为在这时使用了延迟初始化Bean。
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"
lazy-init="true"/>
</bean>
使用depends-on
depends-on是指指定Bean初始化及销毁时的顺序,使用depends-on属性指定的Bean要先初始化完毕后才初始化当前Bean,由于只有“singleton”Bean能被Spring管理销毁,所以当指定的Bean都是“singleton”时,使用depends-on属性指定的Bean要在指定的Bean之后销毁。
注:文档中说销毁Bean的顺序:Dependent beans that define a depends-on relationship with a given bean aredestroyed first, prior to the given bean itself being destroyed.意思是:在depends-on属性中定义的“依赖Bean”要在定义该属性的Bean之前销毁。但实际是错误的,定义“depends-on”属性的Bean会首先被销毁,然后才是“depends-on”指定的Bean被销毁。大家可以试验一下。
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<bean id="decorator" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
depends-on="helloApi">
<property name="helloApi"><ref bean="helloApi"/></property>
</bean>
“decorator”指定了“depends-on”属性为“helloApi”,所以在“decorator”Bean初始化之前要先初始化“helloApi”,而在销毁“helloApi”之前先要销毁“decorator”。
public class ResourceBean {
private FileOutputStream fos;
private File file;
//初始化方法
public void init() {
System.out.println("ResourceBean:========初始化");
//加载资源,在此只是演示
System.out.println("ResourceBean:========加载资源,执行一些预操作");
try {
this.fos = new FileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
//销毁资源方法
public void destroy() {
System.out.println("ResourceBean:========销毁");
//释放资源
System.out.println("ResourceBean:========释放资源,执行一些清理操作");
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public FileOutputStream getFos() {
return fos;
}
public void setFile(File file) {
this.file = file;
}
}
public class DependentBean {
ResourceBean resourceBean;
public void write(String ss) throws IOException {
System.out.println("DependentBean:=======写资源");
resourceBean.getFos().write(ss.getBytes());
}
//初始化方法
public void init() throws IOException {
System.out.println("DependentBean:=======初始化");
resourceBean.getFos().write("DependentBean:=======初始化=====".getBytes());
}
//销毁方法
public void destroy() throws IOException {
System.out.println("DependentBean:=======销毁");
//在销毁之前需要往文件中写销毁内容
resourceBean.getFos().write("DependentBean:=======销毁=====".getBytes());
}
public void setResourceBean(ResourceBean resourceBean) {
this.resourceBean = resourceBean;
}
}
<bean id="resourceBean" class="cn.javass.spring.chapter3.bean.ResourceBean" init-method="init" destroy-method="destroy">
<!-- Spring容器能自动把字符串转换为java.io.File -->
<property name="file" value="D:/test.txt"/>
</bean>
<!-- init-method="init" 指定初始化方法,在构造器注入和setter注入完毕后执行 -->
<!-- destroy-method="destroy":指定销毁方法,只有“singleton”作用域能销毁,“prototype”作用域的一定不能,其他作用域不一定能 -->
<bean id="dependentBean" class="cn.javass.spring.chapter3.bean.DependentBean" init-method="init" destroy-method="destroy" depends-on="resourceBean">
<property name="resourceBean" ref="resourceBean"/>
</bean>
在此配置中,dependentBean初始化在resourceBean之前被初始化,resourceBean销毁会在dependentBean销毁之后执行。
@Test
public void testDependOn() throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("chapter3/depends-on.xml");
//一点要注册销毁回调,否则我们定义的销毁方法不执行
context.registerShutdownHook();
DependentBean dependentBean = context.getBean("dependentBean", DependentBean.class);
dependentBean.write("aaa");
}
ResourceBean:========初始化
ResourceBean:========加载资源,执行一些预操作
DependentBean:=========初始化
DependentBean:=========写资源
DependentBean:=========销毁
ResourceBean:========销毁
ResourceBean:========释放资源,执行一些清理操作
自动装配
目前Spring3.0支持“no”、“byName ”、“byType”、“constructor”四种自动装配,默认是“no”指不支持自动装配的,其中Spring3.0已不推荐使用之前版本的“autodetect”自动装配,推荐使用Java 5+支持的(@Autowired)注解方式代替;如果想支持“autodetect”自动装配,请将schema改为“spring-beans-2.5.xsd”或去掉。
default
表示使用默认的自动装配,默认的自动装配需要在< beans>标签中使用default-autowire属性指定,其支持“no”、“byName”、“byType”、“constructor”四种自动装配,如果需要覆盖默认自动装配,请继续往下看;
no
意思是不支持自动装配,必须明确指定依赖。
byName
通过设置Bean定义属性autowire=”byName”,意思是根据名字进行自动装配,只能用于setter注入。比如我们有方法“setHelloApi”,则“byName”方式Spring容器将查找名字为helloApi的Bean并注入,如果找不到指定的Bean,将什么也不注入。
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
autowire="byName"/>
在根据名字注入时,将把当前Bean自己排除在外:比如“hello”Bean类定义了“setHello”方法,则hello是不能注入到“setHello”的。
byType
通过设置Bean定义属性autowire=”byType”,意思是指根据类型注入,用于setter注入,比如如果指定自动装配方式为“byType”,而“setHelloApi”方法需要注入HelloApi类型数据,则Spring容器将查找HelloApi类型数据,如果找到一个则注入该Bean,如果找不到将什么也不注入,如果找到多个Bean将优先注入标签“primary”属性为true的Bean,否则抛出异常来表明有个多个Bean发现但不知道使用哪个。
根据类型只找到一个Bean
此处注意了,在根据类型注入时,将把当前Bean自己排除在外
<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
autowire="byType"/>
根据类型找到多个Bean
对于集合类型(如List、Set)将注入所有匹配的候选者,而对于其他类型遇到这种情况可能需要使用“autowire-candidate”属性为false来让指定的Bean放弃作为自动装配的候选者,或使用“primary”属性为true来指定某个Bean为首选Bean。
- 通过设置Bean定义的“autowire-candidate”属性为false来把指定Bean后自动装配候选者中移除
<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<!-- 从自动装配候选者中去除 -->
<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"
autowire-candidate="false"/>
<bean id="bean1" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
autowire="byType"/>
- 通过设置Bean定义的“primary”属性为false来把指定自动装配时候选者中首选Bean
<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<!-- 自动装配候选者中的首选Bean-->
<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl" primary="true"/>
<bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
autowire="byType"/>
constructor
通过设置Bean定义属性autowire=”constructor”,功能和“byType”功能一样,根据类型注入构造器参数,只是用于构造器注入方式。
<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<!-- 自动装配候选者中的首选Bean-->
<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl" primary="true"/>
<bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
autowire="constructor"/>
@Test
public void testAutowireByConstructor() throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("chapter3/autowire-byConstructor.xml");
HelloApi helloApi = context.getBean("bean", HelloApi.class);
helloApi.sayHello();
}
autodetect
自动检测是使用“constructor”还是“byType”自动装配方式,已不推荐使用。如果Bean有空构造器那么将采用“byType”自动装配方式,否则使用“constructor”自动装配方式。此处要把3.0的xsd替换为2.5的xsd,否则会报错。
<?xml version="1.0" encoding="UTF-8"?>
<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">
<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<!-- 自动装配候选者中的首选Bean-->
<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl" primary="true"/>
<bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
autowire="autodetect"/>
</beans>
不是所有类型都能自动装配:
- 不能自动装配的数据类型:Object、基本数据类型(Date、CharSequence、Number、URI、URL、Class、int)等;
- 通过“< beans>”标签default-autowire-candidates属性指定的匹配模式,不匹配的将不能作为自动装配的候选者,例如指定“*Service,*Dao”,将只把匹配这些模式的Bean作为候选者,而不匹配的不会作为候选者;
- 通过将“< bean>”标签的autowire-candidate属性可被设为false,从而该Bean将不会作为依赖注入的候选者。
数组、集合、字典类型的根据类型自动装配和普通类型的自动装配是有区别的:
- 数组类型、集合(Set、Collection、List)接口类型:将根据泛型获取匹配的所有候选者并注入到数组或集合中,如“List< HelloApi> list”将选择所有的HelloApi类型Bean并注入到list中,而对于集合的具体类型将只选择一个候选者,“如ArrayList< HelloApi> list”将选择一个类型为ArrayList的Bean注入,而不是选择所有的HelloApi类型Bean进行注入;
- 字典(Map)接口类型:同样根据泛型信息注入,键必须为String类型的Bean名字,值根据泛型信息获取,如“Map < String, HelloApi> map”将选择所有的HelloApi类型Bean并注入到map中,而对于具体字典类型如“HashMap< String, HelloApi > map”将只选择类型为HashMap的Bean注入,而不是选择所有的HelloApi类型Bean进行注入。
自动装配注入方式能和配置注入方式一同工作吗?当然可以,大家只需记住配置注入的数据会覆盖自动装配注入的数据。
依赖检查
用于检查Bean定义的属性都注入数据了,不管是自动装配的还是配置方式注入的都能检查,如果没有注入数据将报错,从而提前发现注入错误,只检查具有setter方法的属性。
依赖检查有none、simple、object、all四种方式
- none:默认方式,表示不检查;
- objects:检查除基本类型外的依赖对象,配置方式为:dependency - check=”objects”,此处我们为HelloApiDecorator添加一个String类型属性“message”,来测试如果有简单数据类型的属性为null,也不报错;
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<!-- 注意我们没有注入helloApi,所以测试时会报错 -->
<bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
dependency-check="objects">
<property name="message" value="Haha"/>
</bean>
注意由于我们没有注入bean需要的依赖“helloApi”,所以应该抛出异常UnsatisfiedDependencyException,表示没有发现满足的依赖
@Test(expected = UnsatisfiedDependencyException.class)
public void testDependencyCheckByObject() throws IOException {
//将抛出异常
new ClassPathXmlApplicationContext("chapter3/dependency-check-object.xml");
}
- simple:对基本类型进行依赖检查,包括数组类型,其他依赖不报错;配置方式为:dependency - check=”simple”,以下配置中没有注入message属性,所以会抛出异常
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<!-- 注意我们没有注入message属性,所以测试时会报错 -->
<bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
dependency-check="simple">
<property name="helloApi" ref="helloApi"/>
</bean>
- all:对所以类型进行依赖检查,配置方式为:dependency-check=”all”,如下配置方式中如果两个属性其中一个没配置将报错
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
dependency-check="all">
<property name="helloApi" ref="helloApi"/>
<property name="message" value="Haha"/>
</bean>
方法注入
所谓方法注入其实就是通过配置方式覆盖或拦截指定的方法,通常通过代理模式实现。Spring提供两种方法注入:查找方法注入和方法替换注入。
查找方法注入
又称为Lookup方法注入,用于注入方法返回结果,也就是说能通过配置方式替换方法返回结果。使用< lookup-method name=”方法名” bean=”bean名字”/>配置;其中name属性指定方法名,bean属性指定方法需返回的Bean。
方法定义格式:访问级别必须是public或protected,保证能被子类重载,可以是抽象方法,必须有返回值,必须是无参数方法,查找方法的类和被重载的方法必须为非final:
< public|protected> [abstract] < return-type> theMethodName(no-arguments);
因为“singleton”Bean在容器中只有一个实例,而“prototype”Bean是每次获取容器都返回一个全新的实例,所以如果“singleton”Bean在使用“prototype” Bean情况时,那么“prototype”Bean由于是“singleton”Bean的一个字段属性,所以获取的这个“prototype”Bean就和它所在的“singleton”Bean具有同样的生命周期,所以不是我们所期待的结果。因此查找方法注入就是用于解决这个问题。
public class Printer {
private int counter = 0;
public void print(String type) {
System.out.println(type + " printer: " + counter++);
}
}
public abstract class HelloImpl5 implements HelloApi {
private Printer printer;
@Override
public void sayHello() {
printer.print("setter");
createPrototypePrinter().print("prototype");
createSingletonPrinter().print("singleton");
}
public abstract Printer createPrototypePrinter();
//Spring拦截了该方法并使用注入的Bean替换了返回结果。
public Printer createSingletonPrinter() {
System.out.println("该方法不会被执行,如果输出就错了");
return new Printer();
}
public void setPrinter(Printer printer) {
this.printer = printer;
}
}
<!-- prototype -->
<bean id="prototypePrinter" class="cn.javass.spring.chapter3.bean.Printer" scope="prototype"/>
<!-- singleton -->
<bean id="singletonPrinter" class="cn.javass.spring.chapter3.bean.Printer" scope="singleton"/>
<bean id="helloApi1" class="cn.javass.spring.chapter3.HelloImpl5" scope="singleton">
<property name="printer" ref="prototypePrinter"/>
<lookup-method name="createPrototypePrinter" bean="prototypePrinter"/>
<lookup-method name="createSingletonPrinter" bean="singletonPrinter"/>
</bean>
<bean id="helloApi2" class="cn.javass.spring.chapter3.HelloImpl5" scope="prototype">
<property name="printer" ref="prototypePrinter"/>
<lookup-method name="createPrototypePrinter" bean="prototypePrinter"/>
<lookup-method name="createSingletonPrinter" bean="singletonPrinter"/>
</bean>
@Test
public void testLookup() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("chapter3/lookupMethodInject.xml");
System.out.println("=======singleton sayHello======");
HelloApi helloApi1 = context.getBean("helloApi1", HelloApi.class);
helloApi1.sayHello();
helloApi1 = context.getBean("helloApi1", HelloApi.class);
helloApi1.sayHello();
System.out.println("=======prototype sayHello======");
HelloApi helloApi2 = context.getBean("helloApi2", HelloApi.class);
helloApi2.sayHello();
helloApi2 = context.getBean("helloApi2", HelloApi.class);
helloApi2.sayHello();
}
=======singleton sayHello======
setter printer: 0
prototype printer: 0
singleton printer: 0
setter printer: 1
prototype printer: 0
singleton printer: 1
=======prototype sayHello======
setter printer: 0
prototype printer: 0
singleton printer: 2
setter printer: 0
prototype printer: 0
singleton printer: 3
替换方法注入
也叫“MethodReplacer”注入,和查找注入方法不一样的是,他主要用来替换方法体。
<replaced-method name="方法名" replacer="MethodReplacer实现">
<arg-type>参数类型</arg-type>
</replaced-method>
public class PrinterReplacer implements MethodReplacer {
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
System.out.println("Print Replacer");
//注意此处不能再通过反射调用了,否则会产生循环调用,知道内存溢出
//method.invoke(obj, new String[]{"hehe"});
return null;
}
}
<bean id="replacer" class="cn.javass.spring.chapter3.bean.PrinterReplacer"/>
<bean id="printer" class="cn.javass.spring.chapter3.bean.Printer">
<replaced-method name="print" replacer="replacer">
<arg-type>java.lang.String</arg-type>
</replaced-method>
</bean>
@Test
public void testMethodReplacer() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("chapter3/methodReplacerInject.xml");
Printer printer = context.getBean("printer", Printer.class);
printer.print("我将被替换");
}
结果:
Print Replacer
第四部分 Bean的作用域
Spring提供“singleton”和“prototype”两种基本作用域,另外提供“request”、“session”、“global session”三种web作用域;Spring还允许用户定制自己的作用域。
基本的作用域
singleton
指“singleton”作用域的Bean只会在每个Spring IoC容器中存在一个实例,而且其完整生命周期完全由Spring容器管理。对于所有获取该Bean的操作Spring容器将只返回同一个Bean。
- 通过在类上定义静态属性保持该实例
public class Singleton {
//1.私有化构造器
private Singleton() {}
//2.单例缓存者,惰性初始化,第一次使用时初始化
private static class InstanceHolder {
private static final Singleton INSTANCE = new Singleton();
}
//3.提供全局访问点
public static Singleton getInstance() {
return InstanceHolder.INSTANCE;
}
//4.提供一个计数器来验证一个ClassLoader一个实例
private int counter=0;
}
- 通过注册表方式
public class SingletonBeanRegister implements SingletonBeanRegistry {
//单例Bean缓存池,此处不考虑并发
private final Map<String, Object> BEANS = new HashMap<String, Object>();
public boolean containsSingleton(String beanName) {
return BEANS.containsKey(beanName);
}
public Object getSingleton(String beanName) {
return BEANS.get(beanName);
}
@Override
public int getSingletonCount() {
return BEANS.size();
}
@Override
public String[] getSingletonNames() {
return BEANS.keySet().toArray(new String[0]);
}
@Override
public void registerSingleton(String beanName, Object bean) {
if(BEANS.containsKey(beanName)) {
throw new RuntimeException("[" + beanName + "] 已存在");
}
BEANS.put(beanName, bean);
}
}
<bean class="cn.javass.spring.chapter3.bean.Printer" scope="singleton"/>
Spring不仅会缓存单例对象,Bean定义也是会缓存的,对于惰性初始化的对象是在首次使用时根据Bean定义创建并存放于单例缓存池。
prototype
即原型,指每次向Spring容器请求获取Bean都返回一个全新的Bean,相对于“singleton”来说就是不缓存Bean,每次都是一个根据Bean定义创建的全新Bean。
public class BeanDefinition {
//单例
public static final int SCOPE_SINGLETON = 0;
//原型
public static final int SCOPE_PROTOTYPE = 1;
//唯一标识
private String id;
//class全限定名
private String clazz;
//作用域
private int scope = SCOPE_SINGLETON;
//鉴于篇幅,省略setter和getter方法;
}
public class BeanDifinitionRegister {
//bean定义缓存,此处不考虑并发问题
private final Map<String, BeanDefinition> DEFINITIONS = new HashMap<String, BeanDefinition>();
public void registerBeanDefinition(String beanName, BeanDefinition bd) {
//1.本实现不允许覆盖Bean定义
if(DEFINITIONS.containsKey(bd.getId())) {
throw new RuntimeException("已存在Bean定义,此实现不允许覆盖");
}
//2.将Bean定义放入Bean定义缓存池
DEFINITIONS.put(bd.getId(), bd);
}
public BeanDefinition getBeanDefinition(String beanName) {
return DEFINITIONS.get(beanName);
}
public boolean containsBeanDefinition(String beanName) {
return DEFINITIONS.containsKey(beanName);
}
}
public class DefaultBeanFactory {
//Bean定义注册表
private BeanDifinitionRegister DEFINITIONS = new BeanDifinitionRegister();
//单例注册表
private final SingletonBeanRegistry SINGLETONS = new SingletonBeanRegister();
public Object getBean(String beanName) {
//1.验证Bean定义是否存在
if(!DEFINITIONS.containsBeanDefinition(beanName)) {
throw new RuntimeException("不存在[" + beanName + "]Bean定义");
}
//2.获取Bean定义
BeanDefinition bd = DEFINITIONS.getBeanDefinition(beanName);
//3.是否该Bean定义是单例作用域
if(bd.getScope() == BeanDefinition.SCOPE_SINGLETON) {
//3.1 如果单例注册表包含Bean,则直接返回该Bean
if(SINGLETONS.containsSingleton(beanName)) {
return SINGLETONS.getSingleton(beanName);
}
//3.2单例注册表不包含该Bean,
//则创建并注册到单例注册表,从而缓存
SINGLETONS.registerSingleton(beanName, createBean(bd));
return SINGLETONS.getSingleton(beanName);
}
//4.如果是原型Bean定义,则直接返回根据Bean定义创建的新Bean,
//每次都是新的,无缓存
if(bd.getScope() == BeanDefinition.SCOPE_PROTOTYPE) {
return createBean(bd);
}
//5.其他情况错误的Bean定义
throw new RuntimeException("错误的Bean定义");
}
public void registerBeanDefinition(BeanDefinition bd) {
DEFINITIONS.registerBeanDefinition(bd.getId(), bd);
}
private Object createBean(BeanDefinition bd) {
//根据Bean定义创建Bean
try {
Class clazz = Class.forName(bd.getClazz());
//通过反射使用无参数构造器创建Bean
return clazz.getConstructor().newInstance();
} catch (ClassNotFoundException e) {
throw new RuntimeException("没有找到Bean[" + bd.getId() + "]类");
} catch (Exception e) {
throw new RuntimeException("创建Bean[" + bd.getId() + "]失败");
}
}
}
<bean class="cn.javass.spring.chapter3.bean.Printer" />
@Test
public void testPrototype () throws Exception {
//1.创建Bean工厂
DefaultBeanFactory bf = new DefaultBeanFactory();
//2.创建原型 Bean定义
BeanDefinition bd = new BeanDefinition();
bd.setId("bean");
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bd.setClazz(HelloImpl2.class.getName());
bf.registerBeanDefinition(bd);
//对于原型Bean每次应该返回一个全新的Bean
System.out.println(bf.getBean("bean") != bf.getBean("bean"));
}
Web应用中的作用域
request作用域
表示每个请求需要容器创建一个全新Bean。比如提交表单的数据必须是对每次请求新建一个Bean来保持这些表单数据,请求结束释放这些数据。
session作用域
表示每个会话需要容器创建一个全新Bean。比如对于每个用户一般会有一个会话,该用户的用户信息需要存储到会话中,此时可以将该Bean配置为web作用域。
globalSession
类似于session作用域,只是其用于portlet环境的web应用。如果在非portlet环境将视为session作用域。
自定义作用域
在日常程序开发中,几乎用不到自定义作用域,除非又必要才进行自定义作用域。
public interface Scope {
//用于从作用域中获取Bean,其中参数objectFactory是当在当前作用域没找到合适Bean时使用它创建一个新的Bean;
Object get(String name, ObjectFactory<?> objectFactory);
Object remove(String name);
//用于注册销毁回调,如果想要销毁相应的对象则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象;
void registerDestructionCallback(String name, Runnable callback);
//用于解析相应的上下文数据,比如request作用域将返回request中的属性。
Object resolveContextualObject(String key);
//作用域的会话标识,比如session作用域将是sessionId。
String getConversationId();
}
public class ThreadScope implements Scope {
private final ThreadLocal<Map<String, Object>> THREAD_SCOPE =
new ThreadLocal<Map<String, Object>>() {
protected Map<String, Object> initialValue() {
//用于存放线程相关Bean
return new HashMap<String, Object>();
}
};
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
//如果当前线程已经绑定了相应Bean,直接返回
if(THREAD_SCOPE.get().containsKey(name)) {
return THREAD_SCOPE.get().get(name);
}
//使用objectFactory创建Bean并绑定到当前线程上
THREAD_SCOPE.get().put(name, objectFactory.getObject());
return THREAD_SCOPE.get().get(name);
}
@Override
public String getConversationId() {
return null;
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
//此处不实现就代表类似proytotype,容器返回给用户后就不管了
}
@Override
public Object remove(String name) {
return THREAD_SCOPE.get().remove(name);
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
}
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry>
<!-- 指定scope关键字 -->
<key>
<value>thread</value>
</key>
<!-- scope实现 -->
<bean class="cn.javass.spring.chapter3.ThreadScope"/>
</entry>
</map>
</property>
</bean>
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"
scope="thread"/>
@Test
public void testSingleThread() {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("chapter3/threadScope.xml");
HelloApi bean1 = beanFactory.getBean("helloApi", HelloApi.class);
HelloApi bean2 = beanFactory.getBean("helloApi", HelloApi.class);
//在同一线程中两次获取的Bean应该是相等的
Assert.assertEquals(bean1, bean2);
}
@Test
public void testTwoThread() throws InterruptedException {
final BeanFactory beanFactory = new ClassPathXmlApplicationContext("chapter3/threadScope.xml");
final HelloApi[] beans = new HelloApi[2];
Thread thread1 = new Thread() {
public void run() {
beans[0] = beanFactory.getBean("helloApi", HelloApi.class);
}};
Thread thread2 = new Thread() {
public void run() {
beans[1] = beanFactory.getBean("helloApi", HelloApi.class);
}};
thread1.start();thread1.sleep(1000);
thread2.start();thread2.sleep(1000);
//在两个线程中两次获取的Bean应该是相等的
Assert.assertEquals(beans[0], beans[1]);
}