简介
很早以前接触过spring,听说过关于这个框架最多的就是关于依赖注入和它带来的各种好处。那么spring框架的这些概念到底是什么意思呢?它对于我们实际中的开发有什么帮助呢?在这里,我们以一个初学者的视角一步步来理解和体会其中的作用。
从OO设计原则说起
当我们使用一些面向对象的语言来设计以及实现应用的时候,经常会强调一些基本的设计原则。其中最基本的一条就是面向抽象编程而不是具体的实现。为什么要面向抽象而不是一个具体的实现呢?我们来看一个如下的示例:
假设如上图我们有一个BusinessHandler类,它需要通过一个cvs文件读取信息来获取一组用户账号信息。这个时候,我们一个最直接的设计就是上图这样,我们直接将BusinessHandler和CVSAccountDAO对象关联起来。
基于前面讨论的面向对象设计原则来说,这样做有一些不合理。因为我们的类是和具体的类耦合在一起的,这里是解析一个CVS文件,如果我们程序要做单元测试的时候,BusinessHandler就必须要有一个对应的CVSAccountDAO实现以及关联的文件。因为这牵涉到对外部环境的依赖,可以说这个测试已经不是一个单纯的单元测试。所以,我们应该让BusinessHandler仅仅依赖于一个抽象的接口而不是具体的实现,这样后面要做单元测试的时候,我们也方便提供一些mock的实现。因此,按照这个思路,我们修改后的设计如下:
现在,到这一步,从BusinessHandler到AccountDAO这一步来说,它们的关系已经定义的比较好了。一个参考的实现如下:
BusinessHandler:
public class BusinessHandler {
private AccountDAO dao;
public BusinessHandler(AccountDAO dao) {
this.dao = dao;
}
public List<Account> getAccountList() {
return dao.FindAll();
}
}
AccountDAO:
public interface AccountDAO {
List<Account> findAll();
}
组装起来
前面定义好的BusinessHandler和AccountDAO接口这样的关系是挺好的。可是我们在一些应用中的时候还是会碰到一些问题,比如说我们在应用程序中需要将BusinessHandler和一个具体的实现组装起来的时候,比如下图这样:
这个时候,我们的应用程序App类里需要引用到AccountDAO的一个具体实现。而且从实现的要求来说,必然是CVSAccountDAO。那么这部分参考实现的代码如下:
public class App {
public static void main() {
AccountDAO dao = new CVSAccountDAO();
BusinessHandler handler = new BusinessHandler(dao);
List<Account> list = handler.getAccountList();
//...
}
}
这部分代码如果是最后的实现的话,其实也没什么问题。因为我们这里定义了一个类和它对一个接口的依赖,所以需要创建这个被依赖的对象然后在作为参数提供给依赖对象。这里就是多了这么一步而已没什么问题。可是在实际情况中,随着问题更加复杂化,很多情况也会出现。比如说,如果我们的CVSAccountDAO它也有一个依赖呢?比如说我们要读取的文件并不在本地,它是在网络上某个地址的。这个时候,我们的系统设计就可能会是这样了:
这个时候,如果我们再来拼装它们的话,可能会是如下的代码:
public static void main(String[] args) {
FildReader reader = new NetworkFileReader();
AccountDAO dao = new CVSAccountDAO(reader);
BusinessHandler handler = new BusinessHandler(dao);
List<Account> list = handler.getAccountList();
// ...
}
这个时候,我们会发现因为引入越多的抽象我们的代码显得越发的繁杂。因为我们引入越来越多的接口 ,在具体的拼装的时候就需要将这些关联实现都拼接起来。
而且,这里还有一个问题。就是我们最终的应用里还是和具体的类实现关联起来了。如果后续我们需要替换一下AccountDAO的时候,比如将CVSAccountDAO换成SQLAccountDAO时,必须要修改后面的代码。这里不能保证我们在增加新的实现或者修改业务的时候原来的代码保持稳定。
IOC和依赖注入
我们来看前面代码里的问题。我们在实际应用程序里,实际上只是使用到BusinessHandler,虽然BusinessHandler要真正能够跑起来需要有一堆的依赖,但是这个不是我们所关心的。我们只需要一个符合我们期望的对象。比如我想要组装一个mock的实现,或者一个针对数据库访问的实现。在前面的代码里,每次如果要组装哪个对象需要自己在代码里显式的标注好,而且后面要是业务逻辑变了我们就需要自己去改这部分代码。
因此,原来的思路是我们自己来构造一个对象,然后自己拿到应用里使用。而IOC的实现就换了一种思路,我们可不可以把要什么样的对象告诉一个第三方或者服务方。然后这边就可以得到想要的对象了。不是什么都自己来构造,而是由别人来构造。按照这个思路,我们的设计图将变成如下:
这个时候,应用程序App只是直接和一个Assembler来打交道,然后它给了一个我们需要的BusinessHandler对象来使用。这种方式有几个好处,一个是对象的创建不需要我直接在代码里处理,尤其当依赖关系比较复杂的时候,代码精简了很多。一个是对于这些对象的生命周期管理也统一由Assembler管理了。
在这里,我们针对IOC和DI的概念再思考和讨论一下。IOC的意思是inversion of control。既然是控制反转,那么是哪里的控制,对什么的控制呢?怎么个反转法?这可能需要从一些应用程序的开发和运行流程说起。在有的程序里,我们需要开发人员自己定义程序的入口,比如一个main函数,然后在这个函数里执行各种流程。这个时候,我们在程序里指定各种步骤,第一步做什么,第二步做什么等等。这时候,各种依赖和步骤都是在我们程序主体中,也可以说是程序员定义的过程中来确定的。相当于我们在控制着程序的一切。
但是,在某些情况下。比如一些事件触发的处理,或者某些框架里定义的一些行为的处理。一些典型的示例有想js里,一些页面按钮的点击操作,它会触发一个onclick方法。在一些桌面应用的开发里,也有类似的实现。我们实现一个对应的事件处理方法。但是这里不是我们写的程序来决定自己什么时候调用这个事件处理方法,而是在这个事件触发的时候我们定义的这个方法被调用。这个时候,我们好像失去了对程序执行流程的控制,变成了我们提供一些实现,由框架或者系统来调用它们。这个过程可以说是一种控制反转。反转的是程序执行的流程。
再结合前面的示例来看,我们需要有这么一个assembler,由它来提供我们需要对象的创建。这里,我们相当于将对象创建和关联的控制移交给这个assembler了。这也是一种控制的反转。而这里,我们原本需要管理的就是对需要对象构造一系列的依赖,以达到构造一个期望对象的目的。这种通过某个assembler来达到实现依赖管理和对象构造的方式也叫依赖注入,也就是DI(dependency injection)。所以说,依赖注入用来描述我们这个问题的场景更加确切一些。DI相当于IOC的一种特殊情况。IOC反转的可不仅仅是对于依赖的管理。
spring中依赖的定义和注入
前面提到了我们把对象的创建和提供都由第三方来提供。在spring里,这就是创建和存储对象的地方,称其为容器。既然这里对象的创建还是由我们来指派只是不需要我们来一个个的new,那么该怎么来具体的指派它们呢?最常用的几种方式是通过xml文件的定义。以上面的对象关系为例,我们最开始定义的BusinessHandler对AccountDAO的依赖是在定义构造函数的时候引入的。于是在xml里,如果我们希望将CVSAccountDAO作为真正的参数传入给BusinessHandler,则需要在文件里定义一组内容如下:
<bean id="handler" class="BusinessHandler">
<constructor-arg index="0" ref="CVSAccountDAO" />
</bean>
<bean id="CVSAccountDAO" class="CVSAccountDAO"/>
而我们应用程序里创建对象和使用它们的代码如下:
public static void main( String[] args )
{
String springConfig = "applicationContext.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(springConfig);
BusinessHandler handler = (BusinessHander) context.getBean("BusinessHandler");
handler.getAccountList();
// ...
}
这样,对象创建的繁琐步骤就都交给spring容器了,我们只要拿来用就可以。
当然,除了上述的依赖关系定义,如果我们前面BusinessHandler对AccountDAO的依赖定义成如下的方式:
public class BusinessHandler {
private AccountDAO dao;
public void setAccountDAO(AccountDAO dao) {
this.dao = dao;
}
public List<Account> getAccountList() {
return dao.FindAll();
}
}
这种方式的依赖和前面的构造函数式的依赖不同,我们称之为属性注入,如果系统设定特定的对象构造,则对应的配置文件如下:
<bean id="handler" class="BusinessHandler">
<property name="dao" ref="CVSAccountDAO"/>
</bean>
<bean id="CVSAccountDAO" class="CVSAccountDAO"/>
前面应用程序的代码不需要做任何的改变。同样,如果我们需要在程序里修改一下依赖对象的关系,只需要在里面创建对应对象的bean,并修改一下他们的引用关系就可以了。这样可以保证原来程序的代码不用变更。
总结
依赖注入,其本质上就是保证实现代码里没有对具体实现的依赖,达到一个松耦合的情况。这种方式方便我们在程序开发的时候独立的开发个部分组件,而且更好的进行测试。同时,spring容器的特性方便对象的组装。我们也不用在程序里依赖关系复杂的时候去写这么一大堆的对象构造和拼装的辅助代码,也使得后续的实现更加简洁。
参考材料
http://www.amazon.com/Spring-Practice-Willie-Wheeler/dp/1935182056/ref=sr_1_1?s=books&ie=UTF8&qid=1419323762&sr=1-1&keywords=spring+in+practice
http://www.amazon.com/Spring-Action-Craig-Walls/dp/161729120X/ref=sr_1_2?s=books&ie=UTF8&qid=1419323762&sr=1-2&keywords=spring+in+practice
http://martinfowler.com/articles/injection.html
http://www.amazon.com/gp/product/1935182056/ref=s9_simh_gw_p14_d2_i2?pf_rd_m=ATVPDKIKX0DER&pf_rd_s=desktop-2&pf_rd_r=1C8K4JW160M7X63CVR4G&pf_rd_t=36701&pf_rd_p=1970566782&pf_rd_i=desktop
- 大小: 6.1 KB
- 大小: 10.7 KB
- 大小: 13.8 KB
- 大小: 18.6 KB
- 大小: 15.4 KB
分享到:
相关推荐
Spring Ioc 注解 依赖注入
spring IOC容器依赖注入XML配置详解 运行环境:eclipse 构建工具:maven 不提供maven构建,maven用来解决jar包的依赖
spring-core:核心模块 依赖注入IOC和DI的最基本实现 spring-beans:Bean工厂与装配 spring-context:上下文,即IOC容器 spring-context-support:对IOC的扩展,以及IOC子容器 spring-context-indexer:类管理组件和...
编程语言+JAVAspring+IoC容器+依赖注入**:这是一...它介绍了JAVAspring的IoC容器的概念、原理和作用,以及如何使用JAVAspring的IoC容器来实现依赖注入,包括设值注入和构造注入的方式,以及一些配置文件和注解的用法。
SpringIOC_泛型依赖注入,泛型依赖注入,注入一个组件的时候,他的泛型也是参考标准
Spring与IoC系列四:基于注解的依赖注入。对于DI使用注解,将不再需要在Spring配置文件中声明Bean实例。Spring中使用注解,需要在原有Spring运行环境基础上再做一些改变
NULL 博文链接:https://samter.iteye.com/blog/408519
spring ioc容器初始化流程图 spring ioc容器依赖注入流程图 spring aop实现原理流程图
springIOC 小例子 附带源码 适合新手学习
本文介绍了Spring框架中的控制反转IOC和依赖注入DI,欢迎阅读,共同学习,一起进步。 Spring框架基础参考:深入学习Spring基础 文章目录一.入门程序和IOC简介二.IOC-控制反转三.DI-依赖注入四. IOC和DI五.Spring...
依赖注入:Dependency Injection IOC的作用:降低程序间的偶合(依赖关系) 依赖关系的管理:以后都交给spring来维护 在当前类需要用到其他类的对象时,由spring为我们提供,我们只需在配置文件中说明 依赖关系...
其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的...
以Spring5为基础进行的仿写,测试代码有简单的依赖注入和aop日志打印
迷你书是《Spring揭秘》的精选版,节选了原书中介绍Spring IoC容器的6个章节。《Spring揭秘》以幽默生动的语言、辅以有趣的故事和典故,循循善诱地...6.1 Spring 2.5的基于注解的依赖注入 6.2 Spring 3.0展望 6.3 小结
Spring的核心:IOC与AOP。IOC是控制反转或依赖注入,AOP是面向切面编程。
Spring框架的核心在于其强大的IoC(控制反转)机制,该机制为Java应用提供了一种优雅的依赖注入方式。本文深入探讨了Spring IoC容器的加载过程及其源码实现,揭示了Spring中最为根本的概念之一。这包括从...
基于XML的依赖注入测试程序,配合博客资源学习。是基于XML的依赖注入方法的测试程序!
模拟Spring IOC 自己实现依赖注入 自己的Spring 自己的代码模拟spring的依赖注入 自己写的 绝对独一无二
主要介绍了Spring.Net IOC依赖注入原理流程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
主要介绍了浅谈Spring IoC容器的依赖注入原理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧