码迷,mamicode.com
首页 > 编程语言 > 详细

Spring04-----DI

时间:2018-11-25 11:39:10      阅读:136      评论:0      收藏:0      [点我收藏+]

标签:lazy   system   iter   举例   限制   false   ber   entryset   有一个   

 一. 依赖注入相关概念

传统应用程序设计中所说的依赖一般指的是“类之间的关系”。具体有以下几种关系:

泛化:表示类与类之间的继承关系、接口与接口之间的继承关系;

实现:表示类对接口的实现

依赖:当类与类之间有使用关系时就属于依赖关系,不同于关联关系,依赖不具有“拥有关系”,而是一种“相识关系”,只是在某个特定地方(比如某个方法体内)才有关系。

关联:表示类与类或者类与接口之间的依赖关系,表现为“拥有关系”;具体到代码可以用实例变量来表示;

聚合:属于是关联的特殊情况,体现部分--整体关系,是一种弱拥有关系;整体和部分可以有不一样的生命周期;是一种弱关联;

组合:属于是关联的特殊情况,也体现了体现部分-整体关系,是一种强“拥有关系”;整体与部分有相同的生命周 期,是一种强关联;

Spring IoC容器的依赖有两层含义:Bean依赖容器和容器注入Bean的依赖资源;

1. Bean依赖容器

也就是说Bean要依赖于容器,这里的依赖是指容器负责创建Bean并管理Bean的生命周期,正是由 于由容器来控制创建Bean并注入依赖,也就是控制权被反转了,这也正是IoC名字的由来,此处的有依赖是指Bean和 容器之间的依赖关系。

2.容器注入Bean的依赖资源

容器负责注入Bean的依赖资源,依赖资源可以是Bean、外部文件、常量数据等,在 Java中都反映为对象,并且由容器负责组装Bean之间的依赖关系,此处的依赖是指Bean之间的依赖关系,可以认为是 传统类与类之间的“关联”、“聚合”、“组合”关系。

3. 为什么要依赖注入

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

  • 采用对象组合,Bean的功能可能由几个依赖Bean的功能组合而成,其Bean本身可能只提供少许功能或根本无任何功能,全部委托给依赖Bean,对象组合具有动态性,能更方便的替换掉依赖Bean,从而改变Bean功能;
  • 而如果采用类继承,Bean没有依赖Bean,而是采用继承方式添加新功能,,而且功能是在编译时就确定了,不具有动态性,而且采用类继承导致Bean与子Bean之间高度耦合,难以复用。

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

note: 其实依赖注入只是一种装配对象的手段,设计的类结构才是基础,如果设计的类结构不支持依赖注入,Spring IoC容器也注入不了任何东西,从而从根本上说“如何设计好类结构才是关键,依赖注入只是一种装配对象手段”

二.Spring IoC容器注入依赖资源的手段

  • 构造器注入:就是容器实例化Bean时注入那些依赖,通过在在Bean定义中指定构造器参数进行注入依赖,包括实例 工厂方法参数注入依赖,但静态工厂方法参数不允许注入依赖;
  • setter注入:通过setter方法进行注入依赖;
  • 方法注入:能通过配置方式替换掉Bean方法,也就是通过配置改变Bean方法功能;

1. 构造器注入

构造器注入可以根据参数索引注入、参数类型注入或Spring3支持的参数名注入,但参数名注入是有限制的(暂时不用参数名)。静态工厂和实例工厂和它都是类似的

技术分享图片

 

综合例子:

(1)bean.xml

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns="http://www.springframework.org/schema/beans" 
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd ">
  <!-- 方法一:通过静态工厂方式注入 -->
   <bean id="byIndex" class="com.test.spring.MyStaticFactory" factory-method="newInstance">
        <constructor-arg index="0" value="Hermioner"/>
        <constructor-arg index="1" value="Nice"/>
   </bean>
   
   <bean id="byType" class="com.test.spring.MyStaticFactory" factory-method="newInstance">
        <constructor-arg type="java.lang.String" value="Hermioner"/>
        <constructor-arg type="java.lang.String" value="Nice"/>
   </bean>
   
   <!-- 方法2:通过实例化工厂方式注入 -->
   <!-- <bean id="instanceFactory" class="com.test.spring.MyInstanceFactory"/>
   <bean id="byIndex" factory-bean="instanceFactory" factory-method="newInstance">
        <constructor-arg index="0" value="Hermioner"/>
        <constructor-arg index="1" value="Nice"/>
   </bean>
   
   <bean id="byType" factory-bean="instanceFactory" factory-method="newInstance">
        <constructor-arg type="java.lang.String" value="Hermioner"/>
        <constructor-arg type="java.lang.String" value="Nice"/>
   </bean> -->
   
    <!-- 方法3:通过构造参数索引和参数类型方式依赖注入 -->
   <!-- <bean id="byIndex" class="com.test.spring.Hello">
         <constructor-arg index="0" value="Hermioner"/>
        <constructor-arg index="1" value="Nice"/>
   </bean>
   
   <bean id="byType" class="com.test.spring.Hello">
        <constructor-arg type="java.lang.String" value="Hermioner"/>
        <constructor-arg type="java.lang.String" value="Nice"/>
   </bean> -->
</beans>

(2)需要被注入的Bean类

package com.test.spring;

public class Hello {
    private String name;
    private String nice;
    public Hello() {
        this.name="Hermioner";
        this.nice="Nice";
    }
    
    public Hello(String name,String nice) {
        this.name=name;
        this.nice=nice;
    }

    public void sayHello() {
        System.out.println("hello world");
        System.out.println(name);
        System.out.println(nice);
    }
}

hello world
Hermioner
Nice
hello world
Hermioner
Nice

 

 

(3)静态工厂类

package com.test.spring;

public class MyStaticFactory {
    public static Hello newInstance(String name,String nice) {
        return new Hello(name,nice);
    }

}

(4)实例工厂类

package com.test.spring;

public class MyInstanceFactory {
    public Hello newInstance(String name,String nice) {
        return new Hello(name,nice);
    }

}

(5)测试类

package helloworld;

import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.test.spring.Hello;

public class HelloTest {
    @Test
    public void testHelloWorld() {
        //三种方式通用这里的代码
        BeanFactory beanFactory=new ClassPathXmlApplicationContext("bean.xml");
//获取根据参数索引依赖注入的Bean Hello hello
=beanFactory.getBean("byIndex",Hello.class); hello.sayHello(); Hello hello2=beanFactory.getBean("byType",Hello.class); hello2.sayHello(); } }


2. setter注入

 setter注入,是通过在通过构造器、静态工厂或实例工厂实例好Bean后,通过调用Bean类的setter方法进行注入依赖,如下图:

技术分享图片

note:setter注入方式只有一种,是根据setter名字进行注入;

举例说明:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns="http://www.springframework.org/schema/beans" 
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd ">
 
 <bean id="bySetter" class="com.test.spring.Hello">
     <property name="name" value="Hermioner"/>
     <property name="nice" value="Nice"/>
 </bean>
</beans>
package com.test.spring;

public class Hello {
    private String name;
    private String nice;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getNice() {
        return nice;
    }
    public void setNice(String nice) {
        this.nice = nice;
    }
    
    public void sayHello() {
        System.out.println(name);
        System.out.println(nice);
    }
    
}
package helloworld;

import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.test.spring.Hello;

public class HelloTest {
    @Test
    public void testHelloWorld() {
        
        BeanFactory beanFactory=new ClassPathXmlApplicationContext("bean.xml");
        Hello hello=beanFactory.getBean("bySetter",Hello.class);
        hello.sayHello();
    }

}
Hermioner
Nice

note: 其实测试代码一点都没有变,变得只是配置

那么Spring是如何知道setter方法呢?如何将值传进去得?

其实方法名是要遵守约定的,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”;

3. 方法注入(待更新)

三. 注入类型

1. 注入常量

注入常量是依赖注入中最简单得,配置方式如下:

 

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

 

以上两种方式都可以,从配置来看第一种更简洁。注意此处“value”中指定的全是字符串,由Spring容器将此字 符串转换成属性所需要的类型,如果转换出错,将抛出相应的异常。
Spring容器目前能对各种基本类型把配置的String参数转换为需要的类型。
注:Spring类型转换系统对于boolean类型进行了容错处理,除了可以使用“true/false”标准的Java值进行注入,还 能使用“yes/no”、“on/off”、“1/0”来代表“真/假”

 

2. 注入Bean ID

这部分会涉及到idref和ref两个标签得区别使用,下面举例来对比说明:

(1)idref

       它相当于value这个标签,我们读到得数据只是具体得id名字字符串

package com.test.spring;

public class Hello {//note;这个类中并没有引用到Student这个类
    private String name;public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }    
    
}
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd ">
    
    <bean id="helloID" class="com.test.spring.Hello">
        <property name="name">   
               <idref bean="anotherID"/>
        </property> 
    </bean>
    
    <bean id="anotherID" class="com.test.spring.Student">
        <property name="age" value="23"/>
    </bean>

</beans>
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.test.spring.Hello;
import com.test.spring.User;

public class HelloTest {
    @Test
    public void testHelloWorld() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        
        Hello hello=(Hello) context.getBean("helloID");
        System.out.println(hello.getName());
    }

}


anotherID

 

(2)ref

       它是调用了另外一个bean实例

举例说明:

package com.test.spring;

public class Hello {
    private String name;
    private Student student;//Hello类中必须要用到Student类,否则ref这个标签会在运行得时候报错,因此也证明了是注入Bean id
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Student getStudent() {
        return student;
    }
    public void setStudent(Student student) {
        this.student = student;
    }

    
    
}
package com.test.spring;

public class Student {
    private String age;

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

}
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd ">
    
    <bean id="helloID" class="com.test.spring.Hello">
        <property name="student">
               <ref bean="anotherID"/>
        </property> 
    </bean>
    
    <bean id="anotherID" class="com.test.spring.Student">
        <property name="age" value="23"/>
    </bean>

</beans>
package helloworld;

import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.test.spring.Hello;
import com.test.spring.User;

public class HelloTest {
    @Test
    public void testHelloWorld() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        
        Hello hello=(Hello) context.getBean("helloID");
        System.out.println(hello.getStudent().getAge());
    }

}


23

idref和ref得总结对比:ref调用的是bean的实例,bean里面有什么都会被调用,而idref则调用的bean的name(一段字符串),但是相较于value属性,idref又具有验证判断效果,它会使xml解析器在解析xml的时候判断idref中的bean是否真的存在于容器中,不然就会报错。

如果把idref中得anotherID改成anotherID2,就会在运行得时候报ID错误

技术分享图片

3. 注入集合、数组和字典

Spring不仅能注入简单类型数据,还能注入集合(Collection、无序集合Set、有序集合List)类型、数组(Array)类 型、字典(Map)类型数据、Properties类型数据,接下来就让我们一个个看看如何注入这些数据类型的数据。

3.1 注入集合类型:包括Collection类型、Set类型、List类型数据

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

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd ">
    
    <bean id="beanID" class="com.test.spring.Hello">
        <property name="values">
           <list>    
              <value>1</value>
              <value>2</value>
              <value>3</value>
           </list>
        </property>
    </bean>
</beans>


package com.test.spring;

import java.util.List;

public class Hello {
    private List<Integer> values;

    public List<Integer> getValues() {
        return values;
    }

    public void setValues(List<Integer> values) {
        this.values = values;
    }
    
}


import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.test.spring.Hello;
import com.test.spring.User;

public class HelloTest {
    @Test
    public void testHelloWorld() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Hello hello=context.getBean("beanID",Hello.class);
        Integer result=hello.getValues().get(0);
        System.out.println(result);
    }

}


1

(2)Set类型:需要使用<set>标签来配置注入,其配置参数及含义和<lsit>标签完全一样,在此就不阐述了

(3)Collection类型::因为Collection类型是Set和List类型的基类型,所以使用<set>或<list>标签都可以进行注 入,配置方式完全和以上配置方式一样,只是将测试类属性改成“Collection”类型

比如: public Collection<String> getValues() { return values; } 

3.2 注入数组类型

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

技术分享图片

3.3 注入字典(Map)类型

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

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd ">
    
    <bean id="beanID" class="com.test.spring.Hello">
        <property name="values">
           <map key-type="java.lang.String" value-type="java.lang.String">
               <entry>
                   <key>
                       <value>1</value>
                   </key>
                   
                   <value>22</value>   //NOTE:如果22独占一行得化,最终输出会22独占一行,相当于加了回车
               </entry>
           </map>
        </property>
    </bean>
</beans>
package com.test.spring;
import java.util.Map;

public class Hello {
    public Map<String, String> getValues() {
        return values;
    }

    public void setValues(Map<String, String> values) {
        this.values = values;
    }

    private Map<String, String> values;
  
}
package helloworld;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.test.spring.Hello;

public class HelloTest {
    @Test
    public void testHelloWorld() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Hello hello=context.getBean("beanID",Hello.class);
        HashMap map=(HashMap) hello.getValues();
        Iterator<Map.Entry<String, String>> iterator=map.entrySet().iterator();
        while(iterator.hasNext()) {
            Map.Entry<String,String> entry=(java.util.Map.Entry<String, String>) iterator.next();
            String key=entry.getKey();
            String value=entry.getValue();
            System.out.println("the key is:"+key+","+"the value is:"+value);
        }
    }

}
the key is:1,the value is:22

3.4 Properties注入

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

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd ">
    
    <bean id="beanID" class="com.test.spring.Hello">
        <property name="properties">
           <props>
              <prop key="A">a</prop>
              <prop key="B">b</prop>
           </props>
        </property>
    </bean>
</beans>
package com.test.spring;

import java.util.Properties;

public class Hello {
    private Properties properties;

    public Properties getProperties() {
        return properties;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}
package helloworld;

import java.util.Properties;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.test.spring.Hello;

public class HelloTest {
    @Test
    public void testHelloWorld() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Hello hello=context.getBean("beanID",Hello.class);
        Properties properties=hello.getProperties();
        System.out.println(properties.get("A"));
    }

}

a

 4. 引用其它Bean

引用其他Bean的步骤与注入常量的步骤一样,可以通过构造器注入及setter注入引用其他Bean,只是引用其他 Bean的注入配置稍微变化了一下:可以将“<constructor-arg index="0" value="Hello World!"/>”和“<property name="message" value="Hello World!"/>”中的value属性替换成bean属性,其中bean属性指定配置文件中的其 他Bean的id或别名。另一种是把<value>标签替换为<.ref bean=”beanName”>,

4.1 通过构造器注入方式

通过” <constructor-arg>”标签的ref属性来引用其他Bean,这是最简化的配置

技术分享图片

4.2 setter注入方式

通过” <property>”标签的ref属性来引用其他Bean,这是最简化的配置

技术分享图片

综合例子:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd ">
    
    <bean id="beanID1" class="com.test.spring.Hello">
        <constructor-arg index="0" ref="studentID"/>
    </bean>
    
    <bean id="beanID2" class="com.test.spring.Hello">
         <property name="student"><ref bean="studentID"/></property> 
    </bean>
    
    <bean id="studentID" class="com.test.spring.Student">
    </bean>
</beans>


package com.test.spring;

public class Hello {
    private Student student;
    public Hello() {
        
    }
    public Hello(Student student) {
        this.student=student;
    }

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }
    
    public void sayHello() {
        System.out.println("hello world");
    }
    
}

package com.test.spring;

public class Student {
    private String age;

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

}

package helloworld;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.test.spring.Hello;

public class HelloTest {
    @Test
    public void testHelloWorld() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Hello hello=context.getBean("beanID2",Hello.class);
        hello.sayHello();
    }

}


hello world

5. 内部Bean定义

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

技术分享图片

举例:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd ">
    
    <bean id="bean1" class="com.test.spring.Hello">
        <property name="hello2">  //必须在Hello中引入Hello2,否则Bean无法实例化成功
             <bean id="innerbean" class="com.test.spring.Hello2"/>
        </property>
    </bean>
    
</beans>
package com.test.spring;

public class Hello {
    private Hello2 hello2;//必须注入Hello2
    public Hello2 getHello2() {
        return hello2;
    }

    public void setHello2(Hello2 hello2) {
        this.hello2 = hello2;
    }

    public void sayHello() {
        System.out.println("hello world");
    }
    
}
package com.test.spring;

public class Hello2 {
     private String name;
        
        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public void sayHello() {
            System.out.println("hello world2");
        }
        
}
package helloworld;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.test.spring.Hello;

public class HelloTest {
    @Test
    public void testHelloWorld() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Hello hello=context.getBean("bean1",Hello.class);
        hello.sayHello();
    }

}

hello world

6. 处理null值

通过<null>标签来处理Null

技术分享图片

四. DI值循环依赖

1. 什么是循环依赖

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

循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出错误。

Spring容器循环依赖包括构造器循环依赖和setter循环依赖,那Spring容器如何解决循环依赖呢?首先让我们来定 义循环引用类:

public class TestA {
    private TestB testB;

    public TestA(TestB testB) {
        this.testB = testB;
    }
    public TestA() {

    }

    public TestB getTestB() {
        return testB;
    }

    public void setTestB(TestB testB) {
        this.testB = testB;
    }
}


public class TestB {
    private TestC testC;

    public TestB(TestC testC) {
        this.testC = testC;
    }

    public TestB() {

    }

    public TestC getTestC() {
        return testC;
    }

    public void setTestC(TestC testC) {
        this.testC = testC;
    }
}


public class TestC {
    private TestA testA;

    public TestC(TestA testA) {
        this.testA = testA;
    }
    public TestC() {

    }
    public TestA getTestA() {
        return testA;
    }

    public void setTestA(TestA testA) {
        this.testA = testA;
    }
}

2. Spring如何解决循环依赖

有两种方式:

(1)构造器循环依赖

表示通过构造器注入构成的循环依赖,这种依赖是无法解决的,只能抛出BeanCreationException异常表示循环依赖。
程序在创建TestA时,构造器需要TestB,去创建TestB,TestB中的构造器又需要TestC,又去创建TestC,TestC的构造器又需要TestA,形成一个环,没有办法创建

<bean id="a" class="com.bean.test.TestA">
        <constructor-arg index="0" ref="b"></constructor-arg>
    </bean>
    <bean id="b" class="com.bean.test.TestB">
        <constructor-arg index="0" ref="c"></constructor-arg>
    </bean>
    <bean id="c" class="com.bean.test.TestC">
        <constructor-arg index="0" ref="a"></constructor-arg>
    </bean>
public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("com/bean/test/applicationContext.xml");
        System.out.println(context.getBean("a", TestA.class));
    }
}

程序抛出异常:

Exception in thread "main" org.springframework.beans.factory.BeanCreationException:

分析构造器循环依赖:(创建TestA时,发现需要TestB,TestB又需要TestC,TestC又需要TestA,需要注意的是,这时候TestC需要的TestA并没有创建完成,TestA还在等着后面需要的bean的成功创建他才可以成功创建)Spring容器将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持 在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出 BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。(note:因为A早已经在池子中了,当在C中时,发现依赖了

A,因此想创建A,可以发现A已经再池中了,它早已经再等待被创建,好清除掉,所以造成了死循环)

(2)setter循环依赖

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

<bean id="a" class="com.bean.test.TestA" scope="singleton">
        <property name="testB" ref="b"></property>
    </bean>
    <bean id="b" class="com.bean.test.TestB" scope="singleton">
        <property name="testC" ref="c"></property>
    </bean>
    <bean id="c" class="com.bean.test.TestC" scope="singleton">
        <property name="testA" ref="a"></property>
    </bean>

执行测试类,没有抛出异常,正常输出

com.bean.test.TestA@37760a63

setter方法也是Spring中的默认解决方法,可以解决循环依赖的问题。具体实现原理是:setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但并未完成其他步骤的bean完成。Spring是先将Bean对象实例化之后再设置对象属性的。

Spring先是用构造实例化Bean对象,此时Spring会将这个实例化结束的对象放到一个Map中,并且Spring提供了获取这个未设置属性的实例化对象引用的方法。 结合我们的实例来看,当Spring实例化了TestA、TestB、TestC后,紧接着会去设置对象的属性,此时TestA依赖TestB,就会去Map中取出存在里面的单例TestB对象,以此类推(这是Spring中得相关源码已经实现了得)

 五. 更多ID的知识

5.1 延迟初始化Bean

延迟初始化也叫做惰性初始化,指不提前初始化Bean,而是只有在真正使用时才创建及初始化Bean。

配置方式很简单只需在<bean>标签上指定 “lazy-init” 属性值为“true”即可延迟初始化Bean。
Spring容器会在创建容器时提前初始化“singleton”作用域的Bean,“singleton”就是单例的意思即整个容器 每个Bean只有一个实例,后边会详细介绍。Spring容器预先初始化Bean通常能帮助我们提前发现配置错误,所以如果 没有什么情况建议开启,除非有某个Bean可能需要加载很大资源,而且很可能在整个应用程序生命周期中很可能使用不 到,可以设置为延迟初始化。
具体配置如下:

<bean id="helloApi" 
class="cn.javass.spring.chapter2.helloworld.HelloImpl" 
lazy-init="true"/>

5.2 使用depends-on

depends-on是指指定Bean初始化及销毁时的顺序,使用depends-on属性指定的Bean要先初始化完毕后才初始 化当前Bean,由于只有“singleton”Bean能被Spring管理销毁,反着初始化的顺序来销毁。

举例说明:

<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”
“depends-on”属性可以指定多个Bean,若指定多个Bean可以用“;”、“,”、空格分割。
那“depends-on”有什么好处呢?主要是给出明确的初始化及销毁顺序,比如要初始化“decorator”时要确保 “helloApi”Bean的资源准备好了,否则使用“decorator”时会看不到准备的资源;而在销毁时要先在 “decorator”Bean的把对“helloApi”资源的引用释放掉才能销毁“helloApi”,否则可能销毁 “helloApi”时而 “decorator”还保持着资源访问,造成资源不能释放或释放错误。

需要注意的特点

1、Spring管理的bean都是单例模式(singleton)

2、实例化对象应该顺序化的,比如A依赖B,B依赖C,C依赖D...

3、一个bean可以依赖多个bean,可以通过逗号(",")或者分号(";")来定义多个依赖对象:

<bean id=a Class="com.twovv.A" depends-on="b,c,d" />
<bean id=b Class="com.twovv.B" />
<bean id=c Class="com.twovv.C" />
<bean id=d Class="com.twovv.D" />

4、初始化依赖关系不允许形成环闭依赖,下面的依赖关系定义是错误的,Spring将无法确定该如何实例化:

<bean id=a Class="com.twovv.A" depends-on="b" />
<bean id=b Class="com.twovv.B" depends-on="a" />

5.3 自动装配(待更新)

5.4 依赖检查(待更新)

六、 Bean的作用域

什么是作用域呢?即“scope”,在面向对象程序设计中一般指对象或变量之间的可见范围。而在Spring容器 中是指其创建的Bean对象相对于其他Bean对象的请求可见范围。
Spring提供“singleton”和“prototype”两种基本作用域,另外提供“request”、“session”、“global session”三种web作用域;Spring还允许用户定制自己的作用域

基本的作用域

(1)singleton:指“singleton”作用域的Bean只会在每个Spring IoC容器中存在一个实例,而且其完整生命 周期完全由Spring容器管理。对于所有获取该Bean的操作Spring容器将只返回同一个Bean。

(2)prototype:即原型,指每次向Spring容器请求获取Bean都返回一个全新的Bean,相对于“singleton"来说就是不缓存Bean,每次都是一个根据Bean定义创建的全新Bean.



Spring04-----DI

标签:lazy   system   iter   举例   限制   false   ber   entryset   有一个   

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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!