刚开始学习Spring框架,把学到的东西拿出来总结和分享一下。
在刚开始的时候,Johnson把用配置文件来管理Java实例协作关系的方式叫做控制反转(Inversion of Control),这个名词不是很好理解,于是,后来Martine给起了另外一个名字:依赖注入。
虽然名字不一样,但是含义是同样的:当某个Java实例(调用者)需要另外一个Java实例(被调用者)时。例如,为了计算1+2的值,我们有两个类,A和B。A类保存1、2的值,B类包含计算1+2的方法add()。我们通常的做法是在A类中创建一个新的B的实例,然后通过这个实例调用B的add()方法获取结果。
但是在依赖注入的模式下,创建B类的这个过程不需要由调用者A来完成了(在这个意义上便有了控制反转的名字),创建B的工作交给了Spring的容器来完成,然后注入创建者(通过xml的配置完成注入工作),因此也称为依赖注入。
下面通过一个例子来讲解:
如果一个人需要使用一个斧头。
在原始社会,他就需要自己制造一个斧头(相当于使用关键词new创建新的对象)。
在工业社会,他可以找一个斧头工厂,然后从这个工厂里买斧头(相当于Java的工厂设计模式)。
而在伟大的共产主义社会,他甚至都不用去找工厂,只需要这个社会来提供(注入)给他就行(该模式则为注入模式)。
在第一种模式下,问题显而易见。可扩展性差,耦合度高:当斧头发生变化时(例如构造方法变化),我们甚至还需要来修改人的代码来完成工作。其次就是人和斧头的职责不清。对于人来说,他只需要使用斧头的功能即可,并不需要关心斧头的创建过程,但是这种模式下,人却需要主动创建“斧头”,多了这么一项“创建斧头”的职责,所以导致了混乱。
在第二种模式下,虽然可以让人和斧头解耦合,但是带来的问题是,人却和工厂偶尔在一起了,人依然需要主动定位工厂在哪里。
第三种模式是最理想的,人完全不用理会斧头是怎么实现的,也不用去定位工厂在哪以获得斧头,“社会”会给他提供斧头,他只需要使用斧头的功能就行。而这个“社会”,即确定实例之间的依赖关系的容器,则是LoC容器。
依赖注入通常有以下两种:
指LoC容器使用属性的setter方法来注入被依赖的实例。下面用一个程序来说明。
Person接口
public interface Person{
//定义一个使用斧头的方法
public void useAxe();
}
Axe接口
public interface Axe{
//Axe有一个砍的方法
public String chop();
}
接着实现一个石斧的类
public class StoneAxe implements Axe{
public String chop(){
return "石斧砍柴好慢";
}
}
实现一个使斧头的Chinese类
public class Chinese implements Persion{
private Axe axe;
public void setAxe(Axe axe){
this.axe = axe;
}
public void useAxe(){
//调用axe的chop()方法
System.out.println(axe.chop());
}
}
上面的Chinese的useAxe()方法就是我们说的最典型的依赖关系,即A调用B,B调用C等等。在这里,他需要一个具体的Axe实例,如果想使用这个方法,就需要
Chinese chinese = new Chinese();
chinese.setAxe(new StoneAxe());
chinese.useAxe();
在Spring框架中,该StoneAxe实例将由Spring容器负责注入,依赖一个bean.xml文件来配置实例间的依赖关系。该文件位于src目录下,具体如下:
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
classpath:/org/springframework/beans/factory/xml/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
">
<bean id="chinese" class="com.weaver.Chinese">
<property name="axe" ref="stoneAxe"/>
bean>
<bean id="stoneAxe" class="com.weaver.StoneAxe"/>
beans>
属性的说明如下:
id:该Bean的唯一标识,程序通过id属性值来访问该Bean实例。
class:指定该Bean的实现类。Spring容器会使用xml解析器读取该属性值,并利用反射来创建该实现类的实例。
Spring会在调用无参数的构造器,创建默认的Bean实例后,调用对应的setter方法为程序注入属性值。
在该例中,property 标签中的name 属性对应的就是Chinese类中的axe私有属性。
在下面的主程序代码中,只是简单的获取了Person实例,并调用该实例的useAxe方法。
public class BeanTest{
public static void main(String[] args) throws Exception{
//创建Spring容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
//获取chinese实例
Person p = ctx.getBean("chinese", Person.class);
p.useAxe();
}
}
执行结果如下:
石斧砍柴好慢
从上面程序可以看出Spring容器就是一个巨大的工厂,它可以生产出所有类型的Bean实例。
程序获取Bean实例的方法是getBean()。一旦获取了这个实例后,使用这个实例就没有什么特别之处了。分析程序可以看出,当调用useAxe()方法时,需要一个Axe实例,但是程序没有在任何地方将特定的Person实例和Axe实例耦合在一起, Axe实例由Spring在运行期间注入。Person实例不仅不需要了解Axe实例的具体实现,也不需要了解Axe实例的创建过程。
假设未来的某一天,我们需要改变Axe的实现,例如将石斧变成钢斧。对应的只需要修改bean.xml就可以完成了。从变化的过程中我们也可以看出,chinese实例与具体的Axe实现类之间没有任何关系,chinese实例仅仅与Axe接口耦合,这就保证了chinese实例与Axe实例间的松耦合——这也是Spring强调面向接口编程的原因。
构造注入在构造实例时,已经为其完成了依赖关系的初始化。这种利用构造器来设置依赖关系的方式,被称为构造注入。
将前面的chinese类稍微修改如下,增加两个构造器
public class chinese implements Person{
private Axe axe;
public chinese(){
}
public chinese(Axe axe){
this.axe = axe;
}
public void useAxe(){
System.out.pringln(axe.chop());
}
}
与此同时,将前面的bean.xml文件中的
标签中的内容修改如下:
<bean id="chinese" class="com.SpringTest.Chinese">
<constructor-arg ref="stoneAxe"/>
bean>
这样就完成了构造注入。其中
元素指定构造器参数,该参数类型是Axe。Spring会调用Chinese类里带有Axe参数的构造器来创建chinese实例。
设置注入的优点如下:
程序开发人员更容易理解,通过setter方法设定依赖关系显得更加直观自然。
对于复杂的依赖关系,如果采用构造器注入,会导致构造器过于臃肿,难以阅读。同时由于Spring在创建Bean实例时,需要同时实例化其依赖的所有实例,会导致性能下降。采用设置注入,则能避免这些问题。
多参数的构造器会显得更加笨重
构造器的优点如下:
最后,建议采用以设置注入为主,构造注入为辅的注入策略。