Spring核心原理:依赖注入Dependency Injection(控制反转Inversion of Control)

一,什么是依赖

Spring 把相互协作的关系称为依赖关系。假如 A 对象调用了 B 对象的方法,我们可称A 对象依赖于 B 对象。

 

二,什么是依赖注入(控制反转)

依赖注入(Dependency Injection)和控制反转(Inversion of Control)是同一个概念。

当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在Spring里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者实例的工作通常由Spring容器来完成,然后注入调用者,因此也称为依赖注入

三,依赖注入的好处

依赖注入让 Spring 的 Bean 以被指文件组织在一起,而不是以硬编码的方式耦合在一起。程序完成无须理会被调用者的实现,也不无须主动定位工厂,这是最好的解耦方式。实例之间的依赖关系由 IoC 容器负责管理。

 

四,Spring实现 依赖注入

1.设值注入

设值注入是指 IoC 容器使用属性的setter方法来注入被依赖的实例。

设值注入是指通过setter方法传入被调用者的实例。这种注入方式简单、直观,因而在Spring的依赖注入里大量使用。

a).先创建一个实体对象(Bean)

-------------------------------------interface
//定义Person接口
public interface Person
{
 //Person接口里定义一个使用斧子的方法
 public void useAxe();
}
//定义Axe接口
public interface Axe
{
 //Axe接口里有个砍的方法
 public void chop();
}
-------------------------------------implement
//Chinese实现Person接口
public class Chinese implements Person
{
 //面向Axe接口编程,而不是具体的实现类
 private Axe axe;
 //默认的构造器
 public Chinese()
 {}
 //设值注入所需的setter方法
 public void setAxe(Axe axe)
 {
  this.axe = axe;
 }

 //实现Person接口的useAxe方法
 public void useAxe()
 {
  System.out.println(axe.chop());
 }
}

//Axe的第一个实现类 StoneAxe
public class StoneAxe implements Axe
{
 //默认构造器
 public StoneAxe()
 {}
 //实现Axe接口的chop方法
 public String chop()
 {
  return "石斧砍柴好慢";
 }
}

b).再配置文件applicationContext.xml,实例化bean 

<!-- 下面是标准的XML文件头 -->
<?xml version="1.0" encoding="gb2312"?>
<!-- 下面一行定义Spring的XML配置文件的dtd -->
"http://www.springframework.org/dtd/spring-beans.dtd">
<!-- 以上三行对所有的Spring配置文件都是相同的 -->
<!-- Spring配置文件的根元素 -->
<BEANS>
   <!—定义第一bean,该bean的id是chinese, class指定该bean实例的实现类 -->
   <BEAN class="lee.Chinese" id="chinese"
       <!-- property元素用来指定需要容器注入的属性,axe属性需要容器注入此处是设值注入,因此Chinese类必须拥有setAxe方法 -->
          <property name="axe"
          <!-- 此处将另一个bean的引用注入给chinese bean -->
          <REF local="”stoneAxe”/">

          </property>

         <!-- 另一种写法
        <property name="parm1" ref="stoneAxe" />  
        <property name="parm2" value="孙悟空"/> 
        -->
    </BEAN>
    <!-- 定义stoneAxe bean -->
    <BEAN class="lee.StoneAxe" id="stoneAxe" />
</BEANS>

c).最后测试是否能够得到注入的bean。

public class BeanTest
{
 //主方法,程序的入口
 public static void main(String[] args)throws Exception
 {
  //因为是独立的应用程序,显式地实例化Spring的上下文。
  ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
  //通过Person bean的id来获取bean实例,面向接口编程,因此
  //此处强制类型转换为接口类型

        //Person person = ctx.getBean("chinese",Person.class); //这种写法也可以
  Person p = (Person)ctx.getBean("chinese");
  //直接执行Person的userAxe()方法。
  p.useAxe();
 }
}

d).输出:石斧砍柴好慢

        ※从配置文件中,可以看到Spring管理bean的灵巧性。bean与bean之间的依赖关系放在配置文件里组织,而不是写在代码里。通过配置文件的指定,Spring能精确地为每个bean注入属性。因此,配置文件里的bean的class元素,不能仅仅是接口,而必须是真正的实现类。
  ※Spring会自动接管每个bean定义里的property元素定义。Spring会在执行无参数的构造器后、创建默认的bean实例后,调用对应的setter方法为程序注入属性值。property定义的属性值将不再由该bean来主动创建、管理,而改为被动接收Spring的注入。
  ※每个bean的id属性是该bean的惟一标识,程序通过id属性访问bean,bean与bean的依赖关系也通过id属性完成。

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

Person实例不仅不需要了解Axe实例的具体实现,甚至无须了解Axe的创建过程。程序在运行到需要Axe实例的时候,Spring创建了Axe实例,然后注入给需要Axe实例的调用者。Person实例运行到需要Axe实例的地方,自然就产生了Axe实例,用来供Person实例使用。


2.构造注入

这种方式在构造实例时,已为其完成了依赖关系的初始化。这种利用构造器来设置依赖关系的方式,被称为构造注入。

所谓构造注入,指通过构造函数来完成依赖关系的设定,而不是通过setter方法。

a).先创建一个实体对象(Bean)

-------------------------------------interface
//定义Person接口
public interface Person
{
 //Person接口里定义一个使用斧子的方法
 public void useAxe();
}
//定义Axe接口
public interface Axe
{
 //Axe接口里有个砍的方法
 public void chop();
}
-------------------------------------implement

//Chinese实现Person接口
public class Chinese implements Person
{
 //面向Axe接口编程,而不是具体的实现类
 private Axe axe;
 //默认的构造器
 public Chinese()
 {}
 //构造注入所需的带参数的构造器
 public Chinse(Axe axe)
 {
  this.axe = axe;
 }
 //实现Person接口的useAxe方法
 public void useAxe()
 {
  System.out.println(axe.chop());
 }
}

//Axe的第一个实现类 StoneAxe
public class StoneAxe implements Axe
{
 //默认构造器
 public StoneAxe()
 {}
 //实现Axe接口的chop方法
 public String chop()
 {
  return "石斧砍柴好慢";
 }
}

b).再配置文件applicationContext.xml,实例化bean 

此时无须Chinese类里的setAxe方法,构造Person实例时,Spring为Person实例注入所依赖的Axe实例。

<!-- 下面是标准的XML文件头 -->
<xml version="1.0" encoding="gb2312"?>
<!-- 下面一行定义Spring的XML配置文件的dtd -->
 "http://www.springframework.org/dtd/spring-beans.dtd">
<!-- 以上三行对所有的Spring配置文件都是相同的 -->
<!-- Spring配置文件的根元素 -->
<BEANS>
     <!—定义第一个bean,该bean的id是chinese, class指定该bean实例的实现类 -->
     <BEAN class="lee.Chinese" id="chinese">

          <!-- constructor-arg,用来表示是通过构造方式来注入参数的 -->
      <!-- index="0",表示是构造方法中的第一个参数,如果只有一个参数,则可以不用设置这个属性值 -->

          <constructor-arg index="0">
    <ref local="axe999" />
    </constructor-arg>

     </BEAN>
     <!-- 定义stoneAxe bean -->
     <BEAN class="lee.SteelAxe" id="axe999"/>
</BEANS>

c).最后测试是否能够得到注入的bean。

public class BeanTest
{
 //主方法,程序的入口
 public static void main(String[] args)throws Exception
 {
  //因为是独立的应用程序,显式地实例化Spring的上下文。
  ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
  //通过Person bean的id来获取bean实例,面向接口编程,因此
  //此处强制类型转换为接口类型

        //Person person = ctx.getBean("chinese",Person.class); //这种写法也可以
  Person p = (Person)ctx.getBean("chinese");
  //直接执行Person的userAxe()方法。
  p.useAxe();
 }
}

d).输出:石斧砍柴好慢

 

设值注入是现创建一个默认的bean实例,然后调用对应的构造方法注入依赖关系。而构造注入则在创建bean实例时,已经完成了依赖关系的


五,处理bean依赖关系的步骤

1、根据定义bean的配置创建并初始化BeanFactory实例
2、每个bean的依赖将以属性、构造器参数、或静态工厂方法参数的形式出现。当这些bean被实际创建时,这些依赖也将会提供给该bean。
3、每个属性或构造器参数既可以是一个实际的值,也可以是对该容器中另一个bean的引用。 

<bean id="helloBean" class="com.spring.demo.HelloWorld">  
       <property name="msg" value="Hello World!"/>  
 </bean> 

<bean id="hello" class="com.spring.demo.HelloWorld">    
     <constructor-arg index="0">   
           <value>HelloWorld!</value>   
       </constructor-arg>    
 </bean> 
4、每个指定的属性或构造器参数值必须能够被转换成特定的格式或构造参数所需的类型。

Spring会在容器被创建时验证容器中每个bean的配置,包括验证那些bean所引用的属性是否指向一个有效的bean。在bean被实际创建之前,bean的属性并不会被设置。伴随着bean被实际创建,作为该bean的依赖bean以及依赖bean的依赖bean也将被创建和分配。

 

六,两种注入方式的对比

 1、相比之下,设值注入具有如下的优点:
(1)、与传统的 JavaBean 的写法更相似,程序开发人员更容易理解、接受。通过 Setting 方法设定依赖关系显得更加直观、自然。
(2)、对于复杂的依赖关系,如果采用构造注入,会导致构造过于臃肿,难以阅读。Spring 在创建 Bean 实例时,需要同时实例化其依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题。
(3)、尤其是在某些属性可选的情况下,多参数的构造器更加笨重。
 
2、构造注入也不是绝对不如设值注入,在某些特定的场景下,构造注入比设值注入更优秀。构造注入也有如下优势:
(1)、构造注入可以在构造器中决定依赖关系的注入顺序,有限依赖的优先注入。例如,组件中某些其他依赖关系的注入,尝尝需要依赖于 Datasource 的注入。采用构造注入,可以在代码中清晰地决定注入顺序。
(2)、对于依赖关系无须变化的 Bean ,构造注入更有用处。因为没有 setting 方法,所有的依赖关系全部在构造器内设定。因此,无须担心后续代码对依赖关系产生的破坏。
(3)、依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系。对组件的调用者而言,组件内部的依赖关系完成透明,更符合高内聚的原则。


两种方式总结:建议采用以设值注入为住,构造注入为辅的注入策略。对于依赖关系无须变换的注入,尽量采用构造注入;而其他的依赖关系的注入,则考虑采用设值注入。 



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值