zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Android 统一配置依赖管理
    Android图片压缩工具MCompressor
    Android Studio 打包自定义apk文件名
    sourceTree的下载与安装
    Mac环境下SVN的配置和使用
    AndroidStudio环境搭建
    设计模式之策略模式
    设计模式之状态模式
    设计模式之观察者模式
    mysql 查询小demo
  • 原文地址:https://www.cnblogs.com/Hermioner/p/10188709.html
Copyright © 2011-2022 走看看