spring05----DI的配置使用

一. 依赖和依赖注入的基本概念

1. 类之间的关系

传统应用程序设计中所说的依赖一般指“类之间的关系”,那先让我们复习一下类之间的关系
泛化:表示类与类之间的继承关系、接口与接口之间的继承关系;
实现:表示类对接口的实现;
依赖:当类与类之间有使用关系时就属于依赖关系,不同于关联关系,依赖不具有“拥有关系”,而是一种“相识 关系”,只在某个特定地方(比如某个方法体内)才有关系。
关联:表示类与类或类与接口之间的依赖关系,表现为“拥有关系”;具体到代码可以用实例变量来表示;
聚合:属于是关联的特殊情况,体现部分-整体关系,是一种弱拥有关系;整体和部分可以有不一样的生命周期;是 一种弱关联;
组合:属于是关联的特殊情况,也体现了体现部分-整体关系,是一种强“拥有关系”;整体与部分有相同的生命周 期,是一种强关联;
Spring IoC容器的依赖有两层含义:Bean依赖容器和容器注入Bean的依赖资源:
Bean依赖容器:也就是说Bean要依赖于容器,这里的依赖是指容器负责创建Bean并管理Bean的生命周期,正是由 于由容器来控制创建Bean并注入依赖,也就是控制权被反转了,这也正是IoC名字的由来,此处的有依赖是指Bean和 容器之间的依赖关系。
容器注入Bean的依赖资源:容器负责注入Bean的依赖资源,依赖资源可以是Bean、外部文件、常量数据等,在 Java中都反映为对象,并且由容器负责组装Bean之间的依赖关系,此处的依赖是指Bean之间的依赖关系,可以认为是 传统类与类之间的“关联”、“聚合”、“组合”关系。

2. 为什么要引入DI?

动态替换Bean依赖对象,程序更灵活:替换Bean依赖对象,无需修改源文件:应用依赖注入后,由于可以采用配置 文件方式实现,从而能随时动态的替换Bean的依赖对象,无需修改java源文件;
更好实践面向接口编程,代码更清晰:在Bean中只需指定依赖对象的接口,接口定义依赖对象完成的功能,通过容 器注入依赖实现;
更好实践优先使用对象组合,而不是类继承:因为IoC容器采用注入依赖,也就是组合对象,从而更好的实践对象组合

  • 采用对象组合,Bean的功能可能由几个依赖Bean的功能组合而成,其Bean本身可能只提供少许功能或根本无任何功能,全部委托给依赖

           Bean,对象组合具有动态性,能更方便的替换掉依赖Bean,从而改变Bean功能;

  • 而如果采用类继承,Bean没有依赖Bean,而是采用继承方式添加新功能,,而且功能是在编译时就确定了,不具有动态性,而且采用类继

          承导致Bean与子Bean之间高度耦合,难以复用。

增加Bean可复用性:依赖于对象组合,Bean更可复用且复用更简单;
降低Bean之间耦合:由于我们完全采用面向接口编程,在代码中没有直接引用Bean依赖实现,全部引用接口,而且 不会出现显示的创建依赖对象代码,而且这些依赖是由容器来注入,很容易替换依赖实现类,从而降低Bean与依赖之间 耦合;
代码结构更清晰:要应用依赖注入,代码结构要按照规约方式进行书写,从而更好的应用一些最佳实践,因此代码 结构更清晰。

二. 依赖注入的方式

1. 构造器注入

构造器注入可以根据参数索引注入、参数类型注入或Spring3支持的参数名注入(但参数名注入是有限制的,个人觉得好麻烦啊,因此只介绍前两种)

(1)根据参数索引注入

使用标签“<constructor-arg index="1"  value="1"/>"来指定注入的依赖,其中index表示索引,从0开始,即第一个参数索引为0,value来指定注入的常量值,配置方式如下:

<constructor-arg index="0"  value="Hello World!"/>

(2)根据参数类型注入

使用标签“ <constructor-arg type="java.lang.String"  value="Hello World!"/>” 来指定注入的依赖,其中“type”表示需要匹配的参数类型,可以是基本类型也可以是其它类型,如“int”、“java.lang.String”、“value”来指定注入的常量值,配置方式如下:

(3)举例:

<!-- 通过构造器参数索引方式依赖注入 -->
    <bean id="byIndex" class="com.test.spring.HelloImpl2">
       <constructor-arg index="0" value="Hello World"></constructor-arg>
       <constructor-arg index="1" value="2"></constructor-arg>
    </bean>
    <!-- 通过构造器参数类型方式依赖注入 -->
    <bean id="byName" class="com.test.spring.HelloImpl2">
       <constructor-arg type="java.lang.String" value="Hello Spring"></constructor-arg>
       <constructor-arg type="int" value="3"></constructor-arg>
    </bean>
 1 public class HelloImpl2 implements HelloApi{
 2     private String message;
 3     private int a;
 4     public HelloImpl2(String message,int a) {
 5         this.message=message;
 6         this.a=a;
 7     }
 8     public void sayHello() {
 9         System.out.println(message+": "+a);
10     }
11 }
View Code
 1 public class HelloTest {
 2     public static void main(String args[]) {
 3         ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
 4         HelloImpl2 aHelloImpl2=(HelloImpl2) context.getBean("byIndex");
 5         aHelloImpl2.sayHello();
 6         
 7         HelloImpl2 bHelloImpl2=(HelloImpl2) context.getBean("byName");
 8         bHelloImpl2.sayHello();
 9     }
10         
11 }
12 
13 Hello World: 2
14 Hello Spring: 3
View Code

2. 静态工厂注入

<bean id="bystaticFactory" class="com.test.spring.HelloApiStaticFactory" factory-method="newInstance">
        <constructor-arg index="0" value="haha"></constructor-arg>
        <constructor-arg index="1" value="3"></constructor-arg>
    </bean>
 1 public class HelloApiStaticFactory {
 2     //工厂方法
 3     public static HelloApi newInstance(String message,int a) {
 4         //返回需要的Bean实例
 5         return new HelloImpl2(message,a);
 6     }
 7 
 8 }
 9 
10 
11 public class HelloImpl2 implements HelloApi{
12     private String message;
13     private int a;
14     public HelloImpl2(String message,int a) {
15         this.message=message;
16         this.a=a;
17     }
18     public void sayHello() {
19         System.out.println(message+": "+a);
20     }
21 }
View Code
 1 public class HelloTest {
 2     public static void main(String args[]) {
 3         ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
 4         HelloImpl2 aHelloImpl2=(HelloImpl2) context.getBean("bystaticFactory");
 5         aHelloImpl2.sayHello();
 6         
 7     }
 8         
 9 }
10 
11 
12 haha: 3
View Code

3. 实例工厂类

<bean id="byinstanceFactory" factory-bean="instanceFactory" factory-method="newInstance">
        <constructor-arg index="0" value="haha"></constructor-arg>
        <constructor-arg index="1" value="3"></constructor-arg>
    </bean>
    
    <bean id="instanceFactory" class="com.test.spring.HelloApiInstanceFactory"></bean>
 1 public class HelloApiInstanceFactory {
 2     public HelloApi newInstance(String message,int a) {
 3         return new HelloImpl2(message,a);
 4     }
 5 
 6 }
 7 
 8 public class HelloImpl2 implements HelloApi{
 9     private String message;
10     private int a;
11     public HelloImpl2(String message,int a) {
12         this.message=message;
13         this.a=a;
14     }
15     public void sayHello() {
16         System.out.println(message+": "+a);
17     }
18 }
View Code
 1 public class HelloTest {
 2     public static void main(String args[]) {
 3         ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
 4         HelloImpl2 aHelloImpl2=(HelloImpl2) context.getBean("byinstanceFactory");
 5         aHelloImpl2.sayHello();
 6         
 7     }
 8         
 9 }
10 
11 haha: 3
View Code

4. setter注入

setter注入,是通过在通过构造器、静态工厂或实例工厂实例好Bean后,通过调用Bean类的setter方法进行注入依赖。setter注入方式只有一种根据setter名字进行注入。如下:

<bean id="bySetter" class="com.test.spring.HelloImpl2">
        <property name="message" value="Hello World!"></property>
        <property name="a" value="3"></property>
    </bean>
 1 public class HelloImpl2 implements HelloApi{
 2     private String message;
 3     private int a;
 4     public void setMessage(String message) {
 5         this.message = message;
 6     }
 7     public void setA(int a) {
 8         this.a = a;
 9     }
10     public void sayHello() {
11         System.out.println(message+": "+a);
12     }
13     
14 }
View Code
 1 public class HelloTest {
 2     public static void main(String args[]) {
 3         ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
 4         HelloImpl2 aHelloImpl2=(HelloImpl2) context.getBean("bySetter");
 5         aHelloImpl2.sayHello();
 6         
 7     }
 8         
 9 }
10 
11 
12 Hello World!: 3
View Code

note: setter注入的方法名要遵循“ JavaBean getter/setter 方法名约定”:

JavaBean:是本质就是一个POJO类,但具有一下限制:
该类必须要有公共的无参构造器,如public HelloImpl4() {};
属性为private访问级别,不建议public,如private String message;
属性必要时通过一组setter(修改器)和getter(访问器)方法来访问;
setter方法,以“set” 开头,后跟首字母大写的属性名,如“setMesssage”,简单属性一般只有一个方法参 数,方法返回值通常为“void”;
getter方法,一般属性以“get”开头,对于boolean类型一般以“is”开头,后跟首字母大写的属性名,如 “getMesssage”,“isOk”;

  还有一些其他特殊情况,比如属性有连续两个大写字母开头,如“URL”,则setter/getter方法为:“setURL” 和“getURL”,其他一些特殊情况请参看“Java Bean”命名规范。

5. 注入常量

注入常量是依赖注入中最简单的。配置方式如下所示:

 <property name="message" value="Hello World!"/>

注意此处“value”中指定的全是字符串,由Spring容器将此字符串转换成属性所需要的类型,如果转换出错,将抛出相应的异常。

Spring容器目前能对各种基本类型把配置的String参数转换为需要的类型。

注:Spring类型转换系统对于boolean类型进行了容错处理,除了可以使用“true/false”标准的Java值进行注入,还 能使用“yes/no”、“on/off”、“1/0”来代表“真/假”,所以大家在学习或工作中遇到这种类似问题不要觉得是人 家配置错了,而是Spring容错做的非常好。

6. 注入Bean ID(???)

7. 注入集合类型

包括Collection类型、Set类型、List类型

(1)List类型:需要使用<List>标签来配置注入,具体配置如下:

  • 可选的“value-type”属性,表示列表中条目的数据的类型,默认是String类型。
  • 也可以采用发行来代替具体的类型,比如 value-type="java.util.List<String>"
<bean id="listBean" class="com.test.spring.ListBean">
        <property name="values">
            <list value-type="int">
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </list>
        </property>
    </bean>
 1 public class ListBean {
 2     private List values;
 3 
 4     public List getValues() {
 5         return values;
 6     }
 7 
 8     public void setValues(List values) {
 9         this.values = values;
10     }
11 
12 }
View Code
 1 public class HelloTest {
 2     public static void main(String args[]) {
 3         ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
 4         ListBean listBean=(ListBean) context.getBean("listBean");
 5         System.out.println(listBean.getValues().get(1));
 6         System.out.println(listBean.getValues().get(1).getClass());
 7     }
 8         
 9 }
10 
11 
12 2
13 class java.lang.Integer
View Code

(2)set类型:需要使用<set>标签来配置注入。其配置参数和<list>完全一样。

8. 注入数组类型

需要使用<array>标签来配置注入,其中标签属性“value-type”和“merge”和<list>标签含义 完全一样,具体配置如下:

<bean id="arrayBean" class="com.test.spring.ArrayBean">
        <property name="array">
            <array value-type="int">
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </array>
        </property>
    </bean>
 1 public class ArrayBean {
 2     private int array[];
 3 
 4     public int[] getArray() {
 5         return array;
 6     }
 7 
 8     public void setArray(int[] array) {
 9         this.array = array;
10     }
11     
12 
13 }
14 
15 
16 public class HelloTest {
17     public static void main(String args[]) {
18         ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
19         ArrayBean arrayBean=(ArrayBean) context.getBean("arrayBean");
20         System.out.println(arrayBean.getArray()[1]);
21         
22     }
23         
24 }
25 
26 
27 2
View Code

note:也可以注入二维数组:

9. 注入字典(Map)类型

需要使用<map>标签来配置注入,,其属性 “key-type”和“value-type”分别指定“键”和“值”的数据类型,其含义和<list>标签的“value-type”含义一 样,在此就不罗嗦了,并使用<key>子标签来指定键数据,<value>子标签来指定键对应的值数据,具体配置如下

<bean id="mapBean" class="com.test.spring.MapBean">
      <property name="values">
           <map key-type="java.lang.String" value-type="java.lang.Integer">
            <entry key="hello" value="23"></entry>
        </map>
      </property>        
    </bean>
 1 public class MapBean {
 2     private Map<String, Integer> values;
 3 
 4     public Map<String, Integer> getValues() {
 5         return values;
 6     }
 7 
 8     public void setValues(Map<String, Integer> values) {
 9         this.values = values;
10     }
11     
12 
13 }
View Code
 1 public class HelloTest {
 2     public static void main(String args[]) {
 3         ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
 4         MapBean mapBean=(MapBean) context.getBean("mapBean");
 5         System.out.println(mapBean.getValues().get("hello"));
 6     }
 7         
 8 }
 9 
10 23
View Code

10. Properties注入

Spring能注入java.util.Properties类型数据,需要使用<props>标签来配置注入,键和值类型必 须是String,不能变,子标签<prop key=”键”>值</prop>来指定键值对,具体配置如下

<bean id="propertiesBean" class="com.test.spring.PropertiesBean">
         <property name="values">
             <props value-type="int" merge="default">   <!-- 虽然指定了value-type,但实际上该属性不起作用,因为Properties的key和value必须都是String类型 -->
                 <prop key="1">a</prop>
                 <prop key="2">b</prop>
             </props>
         </property>
    </bean>
 1 public class PropertiesBean {
 2     private Properties values;
 3 
 4     public Properties getValues() {
 5         return values;
 6     }
 7 
 8     public void setValues(Properties values) {
 9         this.values = values;
10     }
11     
12 
13 }
View Code
 1 public class HelloTest {
 2     public static void main(String args[]) {
 3         ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
 4         PropertiesBean propertiesBean=(PropertiesBean) context.getBean("propertiesBean");
 5         System.out.println(propertiesBean.getValues());
 6     }
 7         
 8 }
 9 
10 {2=b, 1=a}
View Code

11. 注入Bean之间的关系(注入依赖Bean)

 可以有两种方式,一种是通过构造函数,另外一种是通过setter方式来注入依赖的Bean

“<constructor-arg index="0" value="Hello World!"/>”和“<property name="message" value="Hello World!"/>”中的value属性替换成bean属性,其中bean属性指定配置文件中的其 他Bean的id或别名。另一种是把<value>标签替换为<.ref bean=”beanName”>

(1)构造参数方式注入依赖的Bean

<ref="beanName">----只可以是这种

    <bean id="di" class="com.test.spring.HelloImpl2">
    </bean>
    <!-- 通过过构造器注入 -->
    <bean id="bean1" class="com.test.spring.HelloApiDecorator">
        <constructor-arg index="0" ref="di"/>
    </bean>

(2)setter方式注入依赖的Bean

<.ref bean=”beanName”>---只可以是这种

   <!-- 通过构造器注入 -->
    <bean id="bean2" class="com.test.spring.HelloApiDecorator">
         <property name="helloApi">
             <ref bean="di"/>
         </property>
    </bean>

(3)举例

<!-- 定义依赖Bean -->
    <bean id="di" class="com.test.spring.HelloImpl2">
    </bean>
    <!-- 通过过构造器注入 -->
    <bean id="bean1" class="com.test.spring.HelloApiDecorator">
        <constructor-arg index="0" ref="di"/>
    </bean>
    
    <!-- 通过构造器注入 -->
    <bean id="bean2" class="com.test.spring.HelloApiDecorator">
         <property name="helloApi">
             <ref bean="di"/>
         </property>
    </bean>
 1 public interface HelloApi {
 2     public void sayHello();
 3 
 4 }
 5 
 6 
 7 public class HelloImpl2 implements HelloApi{
 8     public void sayHello() {
 9         System.out.println("hello");
10     }
11     
12 }
13 
14 
15 public class HelloApiDecorator implements HelloApi{
16     private HelloApi helloApi;
17     public HelloApiDecorator() {
18         
19     }
20     public HelloApiDecorator(HelloApi helloApi) {
21         this.helloApi=helloApi;
22     }
23     public void sayHello() {
24         System.out.println("before decorator");
25         helloApi.sayHello();
26         System.out.println("after decorator");
27     }
28     public void setHelloApi(HelloApi helloApi) {
29         this.helloApi = helloApi;
30     }
31     
32 
33 }
View Code
 1 public class HelloTest {
 2     public static void main(String args[]) {
 3         ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
 4         //通过构造器方式注入
 5         HelloApi helloApi=(HelloApi) context.getBean("bean1");
 6         helloApi.sayHello();
 7         //通过setter方法注入
 8         HelloApi helloApi2=(HelloApi) context.getBean("bean2");
 9         helloApi2.sayHello();
10     }
11         
12 }
13 
14 
15 before decorator
16 hello
17 after decorator
18 before decorator
19 hello
20 after decorator
View Code

12. 内部定义Bean

内部Bean就是在<property>或<constructor-arg>内通过<bean>标签定义的Bean,该Bean不管是否指定id或 name,该Bean都会有唯一的匿名标识符,而且不能指定别名,该内部Bean对其他外部Bean不可见,具体配置如下

<bean id="bean1" class="com.test.spring.HelloApiDecorator">
        <property name="helloApi">
            <bean id="bean2" class="com.test.spring.HelloImpl2"/>
        </property>
</bean>
 1 public class HelloApiDecorator implements HelloApi{
 2     private HelloApi helloApi;
 3     public HelloApiDecorator() {
 4         
 5     }
 6     public HelloApiDecorator(HelloApi helloApi) {
 7         this.helloApi=helloApi;
 8     }
 9     public void sayHello() {
10         System.out.println("before decorator");
11         helloApi.sayHello();
12         System.out.println("after decorator");
13     }
14     public void setHelloApi(HelloApi helloApi) {
15         this.helloApi = helloApi;
16     }
17     
18 
19 }
View Code
 1 public class HelloImpl2 implements HelloApi{
 2     private String name;
 3     private String index;
 4     
 5     public void setName(String name) {
 6         this.name = name;
 7     }
 8 
 9     public void setIndex(String index) {
10         this.index = index;
11     }
12 
13     public void sayHello() {
14         System.out.println("hello");
15     }
16     
17 }
View Code
1 public class HelloTest {
2     public static void main(String args[]) {
3         ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
4         HelloApi bean=context.getBean("bean1",HelloApi.class);
5         bean.sayHello();
6     }
7         
8 }
View Code
1 before decorator
2 hello
3 after decorator
View Code

13. 注入null值

Spring通过<value>标签或value属性注入常量值,所有注入的数据都是字符串,那如何注入null值呢?通过 “null”值吗?当然不是因为如果注入“null”则认为是字符串Spring通过<null/>标签注入null值。即可以采用如下 配置方式:

<bean class="....HelloImpl">
        <property name="message"><null/></property>
        <property name="index" value="1"/>
    </bean>

三. 循环依赖

1. 什么是循环依赖

循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用 CircleC,CircleC引用CircleA,则它们最终反映为一个环。此处不是循环调用,循环调用是方法之间的环调用。如图 所示:

循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出错误。
Spring容器循环依赖包括构造器循环依赖和setter循环依赖,那Spring容器如何解决循环依赖呢?首先让我们来定 义循环引用类

循环引用类:

 1 public class CircleA {
 2     private CircleB circleB;
 3 
 4     public CircleA() {
 5     }
 6 
 7     public CircleA(CircleB circleB) {
 8         this.circleB = circleB;
 9     }
10 
11     public void setCircleB(CircleB circleB) {
12         this.circleB = circleB;
13     }
14 
15     public void a() {
16         circleB.b();
17     }
18 }
View Code
 1 public class CircleB {
 2     private CircleC circleC;
 3     public CircleB() {
 4         
 5     }
 6     public CircleB(CircleC circleC) {
 7         this.circleC=circleC;
 8     }
 9     public void setCircleC(CircleC circleC) {
10         this.circleC = circleC;
11     }
12     public void b() {
13         circleC.c();
14     }
15 
16 }
View Code
 1 public class CircleC {
 2     private CircleA circleA;
 3     public CircleC() {
 4         
 5     }
 6     public CircleC(CircleA circleA) {
 7         this.circleA=circleA;
 8     }
 9     public void setCircleA(CircleA circleA) {
10         this.circleA = circleA;
11     }
12     public void c() {
13         circleA.a();
14     }
15 
16 }
View Code

2. spring如何解决循环依赖

Spring容器循环依赖包括构造器循环依赖和setter循环依赖。

(1)构造器循环依赖-------无法解决,只能抛出异常

表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出 BeanCurrentlyInCreationException异常表示循环依赖。

如在创建CircleA类时,构造器需要CircleB类,那将去创建CircleB,在创建CircleB类时又发现需要CircleC类,则又去 创建CircleC,最终在创建CircleC时发现又需要CircleA;从而形成一个环,没办法创建。

Spring容器将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持 在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出 BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。

首先看一下配置文件:

<bean id="circleA" class="com.test.spring.CircleA">
         <constructor-arg index="0" ref="circleB"/>   通过构造器方式
    </bean>
    
    <bean id="circleB" class="com.test.spring.CircleB">
         <constructor-arg index="0" ref="circleC"/>
    </bean>
    
    <bean id="circleC" class="com.test.spring.CircleC">
         <constructor-arg index="0" ref="circleA"/>
    </bean>

然后测试一下:

@Test(expected=BeanCurrentlyInCreationException.class)
    public void testA() throws Throwable{
        try {
            new ClassPathXmlApplicationContext("bean.xml");
        }catch (Exception e) {
            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

(2)setter循环依赖

表示通过setter注入方式构成的循环依赖。

对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的Bean来 完成的,而且只能解决单例作用域的Bean循环依赖。
如下代码所示,通过提前暴露一个单例工厂方法,从而使其他Bean能引用到该Bean。

1 addSingletonFactory(beanName, new ObjectFactory() {  
2     public Object getObject() throws BeansException {  
3         return getEarlyBeanReference(beanName, mbd, bean);  
4     }  
5 });  
6    
View Code

具体步骤如下:

  •   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 id="circleA" class="com.test.spring.CircleA">
         <property name="circleB" ref="circleB"></property>
    </bean>
    
    <bean id="circleB" class="com.test.spring.CircleB">
         <property name="circleC" ref="circleC"></property>    通过setter方式
    </bean>
    
    <bean id="circleC" class="com.test.spring.CircleC">
         <property name="circleA" ref="circleA"></property>
    </bean>

测试:

public static void main(String args[]) {
        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
        CircleA bean=context.getBean("circleA",CircleA.class);
        CircleB bean2=context.getBean("circleB",CircleB.class);
        CircleC bean3=context.getBean("circleC",CircleC.class);
    }
运行是不会抛出异常的,因为单例模式下提前暴露了一个正在创建的bean,可是prototype的话将会抛出异常,比如:

<bean id="circleA" class="com.test.spring.CircleA" scope="prototype">
<property name="circleB" ref="circleB"></property>
</bean>

这会抛出异常的

note:

对于“singleton”作用域Bean,可以通过“setAllowCircularReferences(false);”来禁用循环引用:

补充:出现循环依赖是设计上的问题,一定要避免!

 

参考文献:

https://jinnianshilongnian.iteye.com/blog/1415278

原文地址:https://www.cnblogs.com/Hermioner/p/10188709.html