Spring 依赖注入案例分析
先看一段代码:
假设你编写了两个类,一个是人(Person),一个是手机(Mobile)。 人需要用手机打电话,就要用到手机的 dialUp
方法。 传统的写法是这样:
public class Person{public boolean makeCall(long number) {Mobile mobile = new Mobile();return mobile.dialUp(number);}
}
可以看到,类 Person
的 makeCall
方法对 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 {@Overridepublic 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) 方法。
- Java 接口与回调参考:《Java 之接口与回调》
- Spring 的依赖注入方式:
- 《Spring常用的三种注入方式》
- 《Spring 依赖注入方式详解》
通过java反射机制,获取到 person 的属性,同时根据配置文件中所配置的依赖类实例化给 person 的属性。
bean 配置就是让容器要来管理一个 Mobile 的对象,spring 容器会帮你来创建这个对象实例,不过spring 创建对象用的是反射来实现的,大概就是这个样子:
Class clazz = Class.forName("Mobile"); Object obj = clazz.newInstance();
不使用反射的话,代码应该是这样的:
private MobileInterface mobileInstance = new Mobile();
↩︎ ↩︎
Spring 依赖注入案例分析
先看一段代码:
假设你编写了两个类,一个是人(Person),一个是手机(Mobile)。 人需要用手机打电话,就要用到手机的 dialUp
方法。 传统的写法是这样:
public class Person{public boolean makeCall(long number) {Mobile mobile = new Mobile();return mobile.dialUp(number);}
}
可以看到,类 Person
的 makeCall
方法对 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 {@Overridepublic 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) 方法。
- Java 接口与回调参考:《Java 之接口与回调》
- Spring 的依赖注入方式:
- 《Spring常用的三种注入方式》
- 《Spring 依赖注入方式详解》
通过java反射机制,获取到 person 的属性,同时根据配置文件中所配置的依赖类实例化给 person 的属性。
bean 配置就是让容器要来管理一个 Mobile 的对象,spring 容器会帮你来创建这个对象实例,不过spring 创建对象用的是反射来实现的,大概就是这个样子:
Class clazz = Class.forName("Mobile"); Object obj = clazz.newInstance();
不使用反射的话,代码应该是这样的:
private MobileInterface mobileInstance = new Mobile();
↩︎ ↩︎
发布评论