Spring读书笔记-----Spring核心机制:依赖注入
spring框架为我们提供了三种注入方式,分别是set注入,构造方法注入,接口注入。今天就和大家一起来学习一下
依赖注入的基本概念
依赖注入(Dependecy Injection),也称为IoC(Invert of Control),是一种有别于传统的面向对象开发的思想,主要用于对应用进行解耦。简单的理解就是说,本来是由应用服务自己创建的对象,数据,交给第三方来负责创建,准备,并且由第三方将对应的内容注入到应用服务中来,从而实现了对象的创建于对象的应用之间的解耦,通过这种方式,应用服务可以最小程度地减少与对象实体之前的关联(只需要使用即可,而不关心其来源,对应的实现等等),从而保持了应用服务与对象之间的弱耦合关系。
依赖注入的简单实现
可能通过文字的表达,对于依赖注入不是很好理解,不过,通过简单的代码实现,就可以很轻松的理解了,下面通过原始的做法以及依赖注入的做法来进行对比,来加深对依赖注入的理解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
/** * 日志服务 */ class LogService{ // 注意这里,这里是手动创建对应的LogDao实例对象 private LogDao logDao = new DBLogDao(); public void save(){ logDao.save(); } } /** * 日志DAO接口 */ interface LogDao{ void save(); } /** * 日志DAO的具体实现,将日志保存到数据库中 */ class DBLogDao implements LogDao{ @Override public void save() { System.out.println( "Save to Database" ); } } |
从上面的代码中可以看到,当需要LogDao对象的时候,是直接在服务中创建具体的实现,也就是new DBLogDao(),这种方式虽然方便,但是存在一定的缺点,比如说,当想要切换对应的实现,比如说XMLLogDao的时候,就需要打开对应的代码,创建XMLLogDao对象,并且将其交给LogService;而且,如果LogDao的创建过程比较繁琐的时候,LogService在这种实现方式中,就需要知道LogDao的实现过程,而这显然是不太合理的,因为LogService只需要知道LogDao的存在,以及使用方式即可,而并不需要知道它的创建过程。
接下来来看下依赖注入或者说控制反转是怎么解决这些问题的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
class LogService{ // 注意这里,这里并没创建对应的对象 private LogDao logDao; // 通过属性将LogDao注入到LogService中,这也就是依赖注入的来源 // 依赖别人讲所需要的对象注入进来 public void setLogDao(LogDao logDao) { this .logDao = logDao; } public void save(){ logDao.save(); } } // LogDao接口及其实现DBLogDao同上,这里省略 /** * 模拟的容器类,负责创建各个对象,并且将对应的依赖对象注入进去 */ class Container{ public void create(){ // 创建对象 LogService logService = new LogService(); LogDao logDao = new DBLogDao(); // 注入LogDao对象 logService.setLogDao(logDao); } } |
可能这里你会觉得说,create方法中也是手动创建了DBLogDao对象,其实不然,对于create方法来说,它只是负责创建对象,并不管对象的用途,也就是说,这里create方法可以通过各种其他手段,比如利用反射技术,再通过配置文件来配置对应的类的信息,这样,当需要修改具体的实现的时候,只需要修改配置文件,create就会创建对应的对象,并且将其注入到LogService中,而这个过程对于LogService来说是透明的,LogService只知道自己有一个LogDao的对象,而不知道,也不需要知道LogDao对象是怎么来的。也就是实现了创建与使用的解耦。
至于控制反转名词,其实也是很显然的嘛,本来是LogService自己创建的对象,现在将其交给Container来创建了,那么创建对象的权限不就是反转了嘛^_^
一般来说,依赖注入有三种方式,分别是属性注入,也就是上面我们看到的内容,还有一种是构造器注入,也就是通过构造器注入对应的对象,还有一种不常用的接口注入,其实本质上也是属于属性注入。
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规范。
- public interface Person {
- //定义使用斧子的方法
- public void useAxe();
- }
Axe接口:
- public interface Axe {
- //Axe接口里面有个砍的方法
- public String chop();
- }
Person的实现类。
- 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类
- public class StoneAxe implements Axe{
- public String chop() {
- return "石斧砍柴好慢啊!!!";
- }
- }
直到这里,程序依然不知道Chinese类和Axe实例耦合,Spring也不知道!实际上,Spring需要使用XML配置文件来指定实例之间的依赖关系。
Spring采用了XML文件作为配置文件。
对于本应用的XML配置文件如下:
- <?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属性关联。
测试程序:
- 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类:
- 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…/>元素来指定构造器的参数。如下
- <?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 企业应用实战》
Spring读书笔记-----Spring核心机制:依赖注入的更多相关文章
- (转) Spring读书笔记-----Spring的Bean之配置依赖
前一篇博客介绍了Spring中的Bean的基本概念和作用域(Spring读书笔记-----Spring的Bean之Bean的基本概念),现在介绍Spring Bean的基本配置. 从开始我们知道Jav ...
- (转)Spring读书笔记-----Spring核心机制:依赖注入
Java应用(从applets的小范围到全套n层服务端企业应用)是一种典型的依赖型应用,它就是由一些互相适当地协作的对象构成的.因此,我们说这些对象间存在依赖关系.加入A组件调用了B组件的方法,我们就 ...
- Spring的核心机制依赖注入
原文地址:http://developer.51cto.com/art/200610/33311.htm 本文主要讲解依赖注入(设值注入.构造注入),作用是可以使Spring将各层的对象以松耦合的方式 ...
- spring-第一篇之spring核心机制依赖注入(DI)/控制翻转(IoC)
1.spring的核心机制:依赖注入(DI)/控制翻转(IoC) 什么是依赖:A对象需要调用B对象,所以A依赖于B. 什么是注入:A对象注入一个属性B对象. 什么是依赖注入(DI):A对象依赖于B对象 ...
- Spring的核心机制——依赖注入(Dependency Inject)
Spring不仅提供对象,还提供对象的属性值,而不是由使用该对象的程序所提供的. Java应用是由一些相互协作的对象所组成的,在Spring中这种相互协作的关系就叫依赖关系. 如果A组件调用了B组件的 ...
- Spring的核心机制——依赖注入(Dependency Inject)
Spring不仅提供对象,还提供对象的属性值,而不是由使用该对象的程序所提供的. Java应用是由一些相互协作的对象所组成的,在Spring中这种相互协作的关系就叫依赖关系. 如果A组件调用了B组件的 ...
- Spring学习笔记(8)——依赖注入
spring依赖注入使用构造器注入使用属性setter方法注入使用Field注入(用于注解方式) 注入依赖对象可以采用手工装配或自动装配,在实际应用中建议使用手工装配,因为自动装配会产生未知情况,开发 ...
- Spring读书笔记-----Spring的Bean之设置Bean值
[java] view plaincopyprint? Java实例的属性值可以有很多种数据类型.基本类型值.字符串类型.java实例甚至其他的Bean实例.java集合.数组等.所以Spring允许 ...
- Spring读书笔记-----Spring的Bean之Bean的基本概念
从前面我们知道Spring其实就是一个大型的工厂,而Spring容器中的Bean就是该工厂的产品.对于Spring容器能够生产那些产品,则取决于配置文件中配置. 对于我们而言,我们使用Spring框架 ...
随机推荐
- Flash3D学习计划(一)——3D渲染的一般管线流程
一:什么是渲染管线 渲染管线也称为渲染流水线,是显示芯片内部处理图形信号相互独立的并行处理单元.一个流水线是一序列可以并行和按照固定顺序进行的阶段.每个阶段都从它的前一阶段接收输入,然后把输出发给随后 ...
- 小程序 座位管理系统(二)(nodejs+mongodb+小程序)
图片从左至右:登录图.湘大新闻页.教学楼页. 说明: Node.js+mongodb.有些数据放在小程序里,有些数据放在mongodb里.和一相比布局稍作改动,密码改成了"111111&q ...
- 小程序redirectTo不跳转
微信小程序解决方案专辑:http://www.wxapp-union.com/special/solution.html 上面有很多新手坑,多搜搜一般都有. 举个例子: redirectTo不跳转的原 ...
- 1019(C++)
计算n个数的最小公倍数,可用欧几里得算法计算两个数字的最大公约数,再计算两个数最小公倍数 有了2个数最小公倍数算法就简单了,即为:计算第一和第二个数得到最小公倍数lc,再计算lc和第三个数最小公倍数. ...
- oracle分页sql模板
select t2.* from (select t1.*,rownum rn from (select * from mytest) t1 where rownum<=860010) t2 w ...
- Git的微操作
合并分支代码,简单操作: 1.切换到master主干代码 2.到git repositories 视图,点击需要合并的分支,例如v1.1.9 点击merge 进行合并 3.然后push to Upst ...
- 配置Linux实现静态路由
配置Linux实现静态路由 背景和原理 路由器的功能是实现一个网段到另一个网段之间的通信,路由分为静态路由.动态路由. 默认路由和直连路由.静态路由是手工指定的,使用静态路由的好处是网络安全保密性高. ...
- 2017.4.7 java异常处理总结
目录 1.java异常处理的几种错误做法 2.异常处理示例 3.常用异常 4.异常类的继承关系 5.异常处理机制 6.Throw和Throws的区别 7.e.toString(), e.getCaus ...
- Cygwin-安装和配置ssh服务
Cygwin介绍: Cygwin是一个在windows平台上执行的类UNIX模拟环境.它对于学习UNIX/Linux操作环境,或者从UNIX到Windows的应用程序移植,或者进行某些特殊的开发工作, ...
- 利用 getsockname 和 getpeername 来获取某一个链接的本地地址和远端地址
在两台计算机上建立一个网络连接,需要五个要素:本机地址 本机端口 协议类型 远端端口 远端地址.那么如何从一个建立好的连接上获取这些信息呢.就需要用到 getsockname 和 getpeerna ...