(转)Spring读书笔记-----Spring核心机制:依赖注入

Java应用(从applets的小范围到全套n层服务端企业应用)是一种典型的依赖型应用,它就是由一些互相适当地协作的对象构成的。因此,我们说这些对象间存在依赖关系。加入A组件调用了B组件的方法,我们就可以称A组件依赖于B组件。我们通过使用依赖注入,Java EE应用中的各种组件不需要以硬编码方式耦合在一起,甚至无需使用工厂模式。当某个Java 实例需要其他Java 实例时,系统自动提供所需要的实例,无需程序显示获取,这种自动提供java实例我们谓之为依赖注入,也可以称之为控制反转(Inversion of Control IoC)。

其实不管是控制反转还是依赖注入,他们都可以这样理解:当某个Java实例(调用者)需要另一个Java实例(被调用者)时,在传统的程序设计过程中,通常有调用者来创建被调用者的实例。但是在依赖注入/控制反转模式下,创建被调用者的工作不再是有调用者来完成,而是由Spring容器来完成,然后注入调用者。

对于Spring而言,Spring采用动态、灵活的方式来管理各种对象。对象与对象之间的具体实现都是透明的。Spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对POJO之间依赖关系的管理。

依赖注入通常有如下两种:

1、  设置注入:IoC容器使用属性的setter方法来注入被依赖的实例。

2、  构造注入:IoC容器使用构造器来注入被依赖的实例。

一、设值注入

设值注入是指IoC容器使用属性的setter方法来注入被依赖的实例。这种注入方式比较简单、直观。

下面是Person接口,该接口定义了一个Person规范。

[java] view plaincopyprint?

  1. public interface Person {
  2. //定义使用斧子的方法
  3. public void useAxe();
  4. }
public interface Person {
	//定义使用斧子的方法
	public void useAxe();
}

Axe接口:

[java] view plaincopyprint?

  1. public interface Axe {
  2. //Axe接口里面有个砍的方法
  3. public String chop();
  4. }
public interface Axe {
	//Axe接口里面有个砍的方法
	public String chop();
}

Person的实现类。

[java] view plaincopyprint?

  1. public class Chinese implements Person {
  2. private Axe axe;
  3. private String name;
  4. // 设值注入所需的setter方法
  5. public void setAxe(Axe axe) {
  6. this.axe = axe;
  7. }
  8. public void setName(String name) {
  9. this.name = name;
  10. }
  11. // 实现Person接口的userAxe方法
  12. public void useAxe() {
  13. // 调用axe的chop方法,表明Person对象依赖于Axe对象
  14. System.out.println("我是"+name+"用"+axe.chop());
  15. }
  16. }
public class Chinese implements Person {
	private Axe axe;
	private String name;

	// 设值注入所需的setter方法
	public void setAxe(Axe axe) {
		this.axe = axe;
	}

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

	// 实现Person接口的userAxe方法
	public void useAxe() {
		// 调用axe的chop方法,表明Person对象依赖于Axe对象
		System.out.println("我是"+name+"用"+axe.chop());
	}

}

上面的代码实现了Person接口的userAxe()方法,实现该方法时调用了axe的的chop()方法,这就是典型的依赖关系。

在这里Spring容器的作用就是已松耦合的方式来管理这种调用关系。在上面的Chinese类中,Chinese类并不知道它要调用的axe实例在哪里,也不知道axe实例是如何实现的,它只是需要调用一个axe实例,这个Axe实例将由Spring容器负责注入。

Axe的实现类:StoneAxe类

[java] view plaincopyprint?

  1. public class StoneAxe implements Axe{
  2. public String chop() {
  3. return "石斧砍柴好慢啊!!!";
  4. }
  5. }
public class StoneAxe implements Axe{

	public String chop() {
		return "石斧砍柴好慢啊!!!";
	}

}

直到这里,程序依然不知道Chinese类和Axe实例耦合,Spring也不知道!实际上,Spring需要使用XML配置文件来指定实例之间的依赖关系。

Spring采用了XML文件作为配置文件。

对于本应用的XML配置文件如下:

[html] view plaincopyprint?

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns="http://www.springframework.org/schema/beans"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <!-- 配置Chinese实例,其实现类是Chinese -->
  7. <bean id="chinese" class="com.spring.service.impl.Chinese">
  8. <!-- 将StoneAxe注入给axe属性 -->
  9. <property name="axe" ref="stoneAxe" />
  10. <property name="name" value="孙悟空"/>
  11. </bean>
  12. <!-- 配置stoneAxe实例 -->
  13. <bean id="stoneAxe" class="com.spring.service.impl.StoneAxe" />
  14. </beans>
<?xml version="1.0" encoding="UTF-8"?>
<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-3.0.xsd">

	<!-- 配置Chinese实例,其实现类是Chinese -->
	<bean id="chinese" class="com.spring.service.impl.Chinese">
		<!-- 将StoneAxe注入给axe属性 -->
		<property name="axe" ref="stoneAxe" />
		<property name="name" value="孙悟空"/>
	</bean>

	<!-- 配置stoneAxe实例 -->
	<bean id="stoneAxe" class="com.spring.service.impl.StoneAxe" />
</beans>

在配置文件中,Spring配置Bean实例通常会指定两个属性:

id:指定该Bean的唯一标识,程序会通过id属性值来访问该Bean实例。

class:指定该Bean的实现类,此处不可再用接口,必须是实现类,Spring容器会使用XML解析器读取该属性值,并利用反射来创建该实现类的实例。

从上面可以看出Bean于Bean之间的依赖关系放在配置文件里组织,而不是写在代码里。通过配置文件的指定,Spring能够精确地为每个Bean注入属性。因此,配置文件里的<bean…/>元素的class属性值不能是接口,而必须是真正的实现类。

Spring会自动接管每个<bean…/>定义里的<property …/>元素定义,Spring会在调用无参数的构造器、创建默认的Bean实例后,调用相应的setter方法为程序注入属性值。<property…/>定义的属性值将不再有该Bean来主动设置、管理,而是接受Spring的注入。

每个Bean的id属性是该Bean的唯一标识,程序通过id属性访问Bean,Bean与Bean的依赖关系也是通过id属性关联。

测试程序:

[java] view plaincopyprint?

  1. public class BeanTest {
  2. public static void main(String[] args) {
  3. //创建Spring容器
  4. ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
  5. //获取Chinese实例
  6. Person person = ctx.getBean("chinese",Person.class);
  7. person.useAxe();
  8. }
  9. }
public class BeanTest {
	public static void main(String[] args) {
		//创建Spring容器
		ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
		//获取Chinese实例
		Person person = ctx.getBean("chinese",Person.class);
		person.useAxe();
	}

}

执行上面的程序,执行结果如下:

主程序调用Person的userAxe()方法时,该方法的方法体内需要使用Axe实例,但程序没有任何地方将特定的Person实例和Axe实例耦合在一起,也就是说程序没有为Person实例传入Axe实例,Axe实例有Spring在运行期间注入。

Person实例不仅不需要了解Axe实例的具体实现,甚至无须了解Axe的创建过程。Spring容器根据配置文件的指定,创建Person实例时,不仅创建了Person的默认实例,同时也为该实例依赖注入其所依赖的Axe实例。

Bean与Bean之间的依赖关系有Spring管理,Spring采用setter方法为目标Be阿玛尼注入所依赖的Bean,这种方式被称之为设值注入。

从上面的实例我们可以看出,依赖注入以配置文件管理Bean实例之间的耦合,让Bean实例之间的耦合从代码层次分离出来。

Spring IoC容器有如下3个基本要点:

1、  应用程序的各个组件面向接口编程。面向接口编程可以将各个组件的耦合提升到接口层次,从而有利于项目后期的扩展。

2、  应用程序的各组件不再由程序主动产生,而是由Spring容器来负责产生,并初始化。

3、  Spring采用配置文件、或者Annotation来管理Bean的实现类、依赖关系,Spring容器则根据配置文件,利用反射机制来创建时间,并为之注入依赖关系。

二、构造注入

构造注入就是利用构造器来设置依赖关系的方式。

Japanese类:

[java] view plaincopyprint?

  1. public class Japanese implements Person{
  2. private Axe axe;
  3. //默认构造器
  4. public Japanese(){
  5. }
  6. //构造注入所需的带参数构造器
  7. public Japanese(Axe axe){
  8. this.axe = axe;
  9. }
  10. public void useAxe() {
  11. System.out.println(axe.chop());
  12. }
public class Japanese implements Person{

	private Axe axe;
	//默认构造器
	public Japanese(){

	}

	//构造注入所需的带参数构造器
	public Japanese(Axe axe){
		this.axe = axe;
	}

	public void useAxe() {
		System.out.println(axe.chop());
	}

上面的Chinese类并没有setter方法,仅仅只是提供了一个带Axe属性的构造器,Spring将通过该构造器为Chinese注入所依赖的Bean实例。

构造注入的配置文件需要做一些修改。为了使用构造注入,使用<constructor-arg…/>元素来指定构造器的参数。如下

[html] view plaincopyprint?

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns="http://www.springframework.org/schema/beans"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <!-- 配置Japanese实例 -->
  7. <bean id="japanese" class="com.spring.service.impl.Japanese">
  8. <!-- 使用构造注入 ,为Japanese实例注入SteelAxe实例-->
  9. <constructor-arg ref="stoneAxe"/>
  10. </bean>
  11. <!-- 配置stoneAxe实例 -->
  12. <bean id="stoneAxe" class="com.spring.service.impl.StoneAxe" />
  13. </beans>
<?xml version="1.0" encoding="UTF-8"?>
<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-3.0.xsd">

	<!-- 配置Japanese实例 -->
	<bean id="japanese" class="com.spring.service.impl.Japanese">
		<!-- 使用构造注入 ,为Japanese实例注入SteelAxe实例-->
		<constructor-arg ref="stoneAxe"/>
	</bean>

	<!-- 配置stoneAxe实例 -->
	<bean id="stoneAxe" class="com.spring.service.impl.StoneAxe" />
</beans>

上面的配置文件使用<contructor-arg…/>元素指定了一个构造器参数,该参数类型是Axe,这指定Spring调用Chinese类里带一个Axe参数的构造器来创建chinese实例,因为使用了有参数的构造器创建实例,所以当Bean实例被创建完成后,该Bean的依赖关系也就已经设置完成。

他的执行效果与设值注入的执行效果一样。但是还是有点却别:创建Person实例中Axe的属性时机不同—设值注入式先通过无参数的构造器创建一个Bean实例,然后调用它的setter方法注入依赖关系,而构造注入则是直接调用有参数的构造器,当Bean实例创建完成后,依赖关系也已经完成。

三、两种注入方式的对比

Spring支持两种依赖注入方式,这两种依赖注入方式并没有好坏之分,只是适合的场景有所不同。

设值注入有如下优点:

1、 
与传统的JavaBean的写法更相似,程序开发人员更加容易理解,接受。通过setter方法设定依赖关系显得更加直观、自然。

2、 
对于复杂的依赖关系,如果采用构造注入,会导致构造器过于臃肿,难以阅读。Spring在创建Bean实例时,需要同时实例化其依赖的全部实例,因此导致性能下降。而设值注入,则可以避免这些问题。

3、 
尤其是在某些属性可选的情况下,多参数的构造器更加笨重。

但是构造器也有如下优势:

1、 
构造注入可以再构造器中决定依赖关系的注入顺序,优先依赖的优先注入。

2、 
对于依赖关系无须变化的Bean,构造注入更有用处。因为没有setter方法,所有的依赖关系全部在构造器中设定,因此,无须担心后续的代码对依赖关系产生破坏。

3、 
依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系。对组件的调用者而言,组件内部的依赖关系完全透明,更加符合高内聚的原则。

通过上面的对比。所以建议用以设值注入为主,构造注入为辅的注入策略。对于依赖关系无须变化的注入,尽量采用构造注入;而其他的依赖关系,则考虑设值注入。

读李刚《轻量级 Java EE 企业应用实战》

时间: 2024-10-14 10:17:54

(转)Spring读书笔记-----Spring核心机制:依赖注入的相关文章

(转) Spring读书笔记-----Spring的Bean之配置依赖

前一篇博客介绍了Spring中的Bean的基本概念和作用域(Spring读书笔记-----Spring的Bean之Bean的基本概念),现在介绍Spring Bean的基本配置. 从开始我们知道Java应用中各组件的相互调用的实质可以归纳为依赖关系.根据注入方式的不同,Bean的依赖注入可以分为两种形式: 1.  属性:通过<property …/>元素配置,对应设置注入. 2.  构造器参数:通过<constructor-arg…/>元素配置,对应构造注入. 不管是属性,还是构造

spring-第一篇之spring核心机制依赖注入(DI)/控制翻转(IoC)

1.spring的核心机制:依赖注入(DI)/控制翻转(IoC) 什么是依赖:A对象需要调用B对象,所以A依赖于B. 什么是注入:A对象注入一个属性B对象. 什么是依赖注入(DI):A对象依赖于B对象,spring容器负责将B对象添加到A对象中,即将A对象的依赖对象B添加到A对象中. 什么是控制翻转(IoC):A对象依赖于B对象,它将通过new B对象的方式主动获取得B对象实例:但spring则将B对象直接送到A面前供其使用,A被动接受B对象. 依赖注入(DI)与控制翻转(IoC)的关系:只是从

Spring读书笔记-----Spring的Bean之设置Bean值

[java] view plaincopyprint? Java实例的属性值可以有很多种数据类型.基本类型值.字符串类型.java实例甚至其他的Bean实例.java集合.数组等.所以Spring允许通过如下几个元素为Bean实例的属性指定值: value ref bean list.set.map.props 一.value:设置普通属性值 <value.../>元素用于指定字符串类型.基本类型的属性值.Spring使用XML解析器来解析出这些数据,然后利用java.beans.Proper

Spring读书笔记-----Spring的Bean之Bean的基本概念

从前面我们知道Spring其实就是一个大型的工厂,而Spring容器中的Bean就是该工厂的产品.对于Spring容器能够生产那些产品,则取决于配置文件中配置. 对于我们而言,我们使用Spring框架所做的就是两件事:开发Bean.配置Bean.对于Spring矿建来说,它要做的就是根据配置文件来创建Bean实例,并调用Bean实例的方法完成“依赖注入”. 一.Bean的定义 <beans…/>元素是Spring配置文件的根元素,<bean…/>元素师<beans../>

(转)Spring读书笔记-----Spring的Bean之Bean的基本概念

从前面我们知道Spring其实就是一个大型的工厂,而Spring容器中的Bean就是该工厂的产品.对于Spring容器能够生产那些产品,则取决于配置文件中配置. 对于我们而言,我们使用Spring框架所做的就是两件事:开发Bean.配置Bean.对于Spring矿建来说,它要做的就是根据配置文件来创建Bean实例,并调用Bean实例的方法完成“依赖注入”. 一.Bean的定义 <beans…/>元素是Spring配置文件的根元素,<bean…/>元素师<beans../>

详解 Spring 3.0 基于 Annotation 的依赖注入实现(转)

使用 @Repository.@Service.@Controller 和 @Component 将类标识为 Bean Spring 自 2.0 版本开始,陆续引入了一些注解用于简化 Spring 的开发.@Repository 注解便属于最先引入的一批,它用于将数据访问层 (DAO 层 ) 的类标识为 Spring Bean.具体只需将该注解标注在 DAO 类上即可.同时,为了让 Spring 能够扫描类路径中的类并识别出 @Repository 注解,需要在 XML 配置文件中启用 Bean

Spring 学习 2- IOC原理 控制反转/依赖注入

控制反转/依赖注入 最近,买了本spring入门书:spring In Action .大致浏览了下感觉还不错.就是入门了点.Manning的书还是不错的,我虽然不像哪些只看Manning书的人那样专注于Manning,但怀着崇敬的心情和激情通览了一遍.又一次接受了IOC .DI.AOP等Spring核心概念. 先就IOC和DI谈一点我的看法. IOC(DI):其实这个Spring架构核心的概念没有这么复杂,更不像有些书上描述的那样晦涩.Java程序员都知道:java程序中的每个业务逻辑至少需要

详解 Spring 3.0 基于 Annotation 的依赖注入实现--转载

使用 @Repository.@Service.@Controller 和 @Component 将类标识为 Bean Spring 自 2.0 版本开始,陆续引入了一些注解用于简化 Spring 的开发.@Repository 注解便属于最先引入的一批,它用于将数据访问层 (DAO 层 ) 的类标识为 Spring Bean.具体只需将该注解标注在 DAO 类上即可.同时,为了让 Spring 能够扫描类路径中的类并识别出 @Repository 注解,需要在 XML 配置文件中启用 Bean

详解 Spring 3.0 基于 Annotation 的依赖注入实现

使用 @Repository.@Service.@Controller 和 @Component 将类标识为 Bean Spring 自 2.0 版本开始,陆续引入了一些注解用于简化 Spring 的开发.@Repository 注解便属于最先引入的一批,它用于将数据访问层 (DAO 层 ) 的类标识为 Spring Bean.具体只需将该注解标注在 DAO 类上即可.同时,为了让 Spring 能够扫描类路径中的类并识别出 @Repository 注解,需要在 XML 配置文件中启用 Bean