Spring 依赖注入案例分析

先看一段代码:

假设你编写了两个类,一个是人(Person),一个是手机(Mobile)。 人需要用手机打电话,就要用到手机的 dialUp 方法。 传统的写法是这样:

public class Person{
    public boolean makeCall(long number) {
        Mobile mobile = new Mobile();
        return mobile.dialUp(number);
    }
}

可以看到,类 PersonmakeCall 方法对 Mobile 类具有依赖,必须手动生成一个新的实例 new Mobile() 才可以进行之后的工作。

依赖注入的思想是这样:当一个类(Person)对另一个类(Mobile)有依赖时,不在该类(Person)内部对依赖的类(Moblile)进行实例化,而是之前配置一个 beans.xml,告诉容器所依赖的类(Mobile),在实例化该类(Person)时,容器自动注入(传入一个 Mobile 实例作为实参)一个所依赖的类(Mobile)的实例。

MobileInterface 接口定义如下:

Interface MobileInterface {
    public boolean dialUp(long number);
}

Mobile 类实现 MobileInterface 接口:

class Mobile implements MobileInterface {
	@Override
	public boolean dialUp(long number) {
		switch(number) {
			case  number.length < 3:
				return 0;
			case number.length >= 3:
				return 1;
		}
	}
}

Person 类定义如下:

class Person {
    private MobileInterface mobileInstance;
    // setter方式注入Mobile接口的实现类的实例
    public void setMobileInstance(MobileInterface mobileInstance) {
        this.mobileInstance = mobileInstance;
    }
    public boolean makeCall(long number) {
        return this.mobileInstance.dialUp(number);
    }
}

在 xml 文件中配置依赖关系:

<bean id="person" class="Person">
    <property name="mobileInstance">
        <ref local="mobileInstance"/>
    </property>    
</bean>
<bean id="mobileInstance" class="Mobile"/>

这样,Person 类在实现拨打电话的时候,并不知道 Mobile 类的存在,它只知道调用一个接口 MobileInterface,而 MobileInterface 的具体实现是通过 Mobile 类完成,并在使用时由容器自动注入。

一、mobileInstance.dialUp(number)mobileInstance 是个接口声明的对象,怎么可以直接调用该接口实现类的方法?

这是 Java 的接口与回调,能确保不会丢失精度,保证类型的统一。这也是接口的一个好处。在进行方法调用的时候,具体调用的是什么类的方法是通过动态绑定的。

也就是说虽然 mobileInstance.dialUp(number) 表面上看是调用的接口的方法,这样会出错。但是在运行期间 mobileInstance 一般会被动态绑定到一个实现类的,如果没绑定这样也确实会出错。
下面看看 beans.xml 的配置:

<bean id="person" class="Person">
    <property name="mobileInstance">
        <ref local="mobileInstance"/>
    </property>    
</bean>
<bean id="mobileInstance" class="Mobile"/>

这里显然在最后一个 bean 中为 mobileInstance 指定的是它的一个实现类 Mobile。也就是说 Mobile 的对象调用 dialUp(number) 是具有意义的,而且可行的。spring 在这个过程中“偷天换柱”的把接口动态绑定到了 Mobile 的对象上。也就是说 mobileInstance.dialUp(number) 在调用时实际上调用的是 Mobile 对象的 dialUp(number) 方法了。这样就可以显示正常结果了。

二、此处怎么依赖注入的?

这个就简单了,用的是 java 的反射机制1。在 spring 配置文件加载期间,spring 会用反射机制给 bean 中配置的对象属性注入属性值。

是怎么注入的呢?看配置文件:

<bean id="person" class="Person">
    <property name="mobileInstance">
        <ref local="mobileInstance"/>
    </property>    
</bean>
<bean id="mobileInstance" class="Mobile"/>
  • 第一句 <bean id="person" class="Person"> 告诉 spring 我们这里有个 bean 他的类是 Person,我给他的唯一 id 是 person,此时 spring 会 new 一个 Person 实例对象,结果放在 Map 的 value 中,对应的 key 就是你这里配置的 id。所以你可以 get(id) 的形式来获取对象。
  • 第二句 <property name="mobileInstance"> 说明我前面 new 的 Person 对象中有一个属性是 mobileInstance
  • 第三句 <ref local="mobileInstance"/> 是给属性赋值(调用 mobileInstance 对象的 set 方法(public void setMobileInstance(MobileInterface mobileInstance)),如果没有 set 方法会报错),他指向一个引用(第四句)
  • 第四句 <bean id="mobileInstance" class="Mobile"/> 创建一个 id 是 mobileInstance 的 Mobile 实例对象,这样我们再回到第三句就可以通过 id 进行对应的引用了(把 Mobile 的实例对象作为实参传入 mobileInstance 的 set 方法)。

也就是说第三句实现了依赖注入的功能,把 Mobile 实例对象赋值给了 Person 对象的 mobileInstance 属性。严格说就是 id 为 person 的 Person 对象的 mobileInstance 属性指向了 id 为 mobileInstance 的 Mobile 实例对象(这里的一系列操作是 spring 通过 Java 的反射机制1帮我们完成的,对属性赋值需要调用属性的 set 方法)。

其实这个配置文件体现了 Spring 的 IoC(控制反转)机制——通过依赖注入和反射机制实现的。

IoC Container 就相当于把以下创建实例和依赖注入的工作统一管理(工厂模式):

Person person = new Person();
Mobile mobile = new Mobile();
person.setMobileInstance(mobile);  // 依赖注入
person.makeCall(1888888);

在 person.makeCall(1888888) 中的 mobileInstance.dialUp(number) ,实际上是调用了 mobile 的 dialUp(number) 方法。



  1. 通过java反射机制,获取到 person 的属性,同时根据配置文件中所配置的依赖类实例化给 person 的属性。
    bean 配置就是让容器要来管理一个 Mobile 的对象,spring 容器会帮你来创建这个对象实例,不过spring 创建对象用的是反射来实现的,大概就是这个样子:
    Class clazz = Class.forName("Mobile"); Object obj = clazz.newInstance();
    不使用反射的话,代码应该是这样的:
    private MobileInterface mobileInstance = new Mobile(); ↩︎ ↩︎

发布了26 篇原创文章 · 获赞 22 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Run_Bomb/article/details/100576696