简介
很早以前接触过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容器主要通过两种方式实现依赖注入: 1. **构造器注入**:通过类的构造方法来注入依赖项。 2. **设值注入**:通过setter方法来注入依赖项。 #### 四、使用注解进行依赖注入 随着Spring框架的发展,除了...
Spring的核心特性之一就是它的Inversion of Control(IoC,控制反转)容器,也被称为Dependency Injection(DI,依赖注入)。这个概念是Spring框架使应用程序组件之间解耦的关键。让我们深入探讨一下Spring的IoC和...
在XmlBeanFactory之上,Spring提供了更抽象的实现,如AbstractBeanFactory和DefaultListableBeanFactory,它们通过模板模式提供了更多的功能,如依赖注入、AOP代理等。 在实际使用中,Spring通常使用...
在“Spring基础:IoC容器(1)”这个主题中,我们将深入探讨Spring的核心组件——IoC容器,以及它如何管理对象的生命周期和依赖关系。 IoC(Inversion of Control),也称为控制反转,是一种设计模式,它将对象的创建...
Spring框架是Java开发中不可或缺的一部分,它以IoC(Inversion of Control,控制反转)和DI(Dependency Injection,依赖注入)为核心,极大地简化了应用程序的复杂性。在本教程中,我们将深入探讨如何通过XML配置在...
在传统的程序设计中,我们通常手动创建对象并管理它们之间的依赖关系,而在Spring中,这些任务由IOC容器来处理,实现了从依赖管理到依赖注入的转变。 控制反转(IOC)意味着应用程序不再直接创建对象,而是将对象的...
总的来说,这篇博客文章会涵盖Spring IoC容器的基本概念、配置方式、依赖注入以及可能深入到源码分析和实际开发中的应用。通过学习这些内容,开发者能够更有效地利用Spring框架,构建更加灵活、可维护的Java应用程序...
Spring 框架的核心是控制反转(IOC)和依赖注入(DI),它可以将对象的创建和依赖关系管理,实现高内聚、低耦合的开发模式。 下面是 Spring 框架的核心知识点: 1. 控制反转(IOC):控制反转是 Spring 框架的...
spring-core:核心模块 依赖注入IOC和DI的最基本实现 spring-beans:Bean工厂与装配 spring-context:上下文,即IOC容器 spring-context-support:对IOC的扩展,以及IOC子容器 spring-context-indexer:类管理组件和...
Spring支持三种类型的依赖注入: 1. **构造器注入**:通过构造函数传入依赖对象。 2. **设值注入**:使用setter方法注入依赖。 3. **接口注入**:通过实现特定的接口注入依赖。 Spring容器会根据配置自动调用合适...
编程语言+JAVAspring+IoC容器+依赖注入**:这是一...它介绍了JAVAspring的IoC容器的概念、原理和作用,以及如何使用JAVAspring的IoC容器来实现依赖注入,包括设值注入和构造注入的方式,以及一些配置文件和注解的用法。
本资料包"SpringIOC_泛型依赖注入.zip"主要关注的是Spring如何通过泛型进行依赖注入,这是一种更加灵活且类型安全的注入方式。 在Java编程中,泛型是一种强大的特性,允许我们在类、接口和方法中使用类型参数,从而...
Spring IoC作为一种革命性的找对象方式,通过控制反转和依赖注入技术,极大提升了Java应用的开发效率和代码质量。然而,正如文中所言,“只有最适合,没有最佳的哲学”,在实际应用中,应根据具体场景选择最合适的...
**Spring 框架中的控制反转 (IoC) 和依赖注入 (DI)** 在软件开发中,控制反转(Inversion of Control,简称IoC)是一种设计原则,它将对象的创建和管理权从代码中剥离出来,转交给一个外部容器(如Spring框架)。...
在这个di-demo项目中,我们将深入探讨IoC和依赖注入的概念,并通过具体的代码示例来理解它们。 首先,IoC指的是应用程序的控制流程从代码内部转移到外部容器。在传统的编程中,对象通常会自行创建它所需要的依赖...
通过学习和实践基于注解的依赖注入,我们可以更好地掌握Spring框架的核心特性,提升应用的开发效率和质量。在04-di-annotation这个文件中,你将找到关于这一主题的详细教程和示例,帮助你深入理解和应用这些概念。
总结来说,这个"Spring与IoC系列三:基于XML的依赖注入测试程序di.rar"涵盖了Spring框架的核心特性——依赖注入,通过XML配置文件管理对象间的依赖关系,是学习和实践Spring框架不可或缺的一部分。通过学习和操作这...
SpringIOC是Spring Framework中的核心组件之一,负责管理应用程序中的对象、依赖关系和生命周期。 在 Spring IOC 中,对象的创建和管理是通过 BeanFactory 或 ApplicationContext 实现的。BeanFactory 是 Spring ...
Spring的依赖注入(Dependency Injection,DI)是IOC的重要组成部分。DI允许Spring容器负责为Bean提供它所需要的其他Bean。这样,代码不再需要直接创建对象,而是通过构造函数、setter方法或者工厂方法来接收依赖。...
DI(Dependency Injection,依赖注入)是IOC的一种实现方式,通过配置或编程的方式,将对象之间的依赖关系在运行时动态注入,而不是由对象自己去查找和创建依赖。 在Spring IOC中,主要的注入方式有以下几种: 1. ...