实例讲解Java的Spring框架中的控制反转和依赖注入_java

近来总是接触到 IoC(Inversion of Control,控制反转)、DI(Dependency Injection,依赖注入)等编程原则或者模式,而这些是著名 Java 框架 Spring、Struts 等的核心所在。针对此查了 Wikipedia 中各个条目,并从图书馆借来相关书籍,阅读后有些理解,现结合书中的讲解以及自己的加工整理如下:
 

eg1
问题描述:
开发一个能够按照不同要求生成Excel或 PDF 格式的报表的系统,例如日报表、月报表等等。
 
解决方案:
根据“面向接口编程”的原则,应该分离接口与实现,即将生成报表的功能提取为一个通用接口ReportGenerator,并提供生成 Excel 和 PDF格式报表的两个实现类 ExcelGenerator 和 PDFGenerator,而客户Client 再通过服务提供者 ReportService 获取相应的报表打印功能。
 
实现方法:
根据上面所述,得到如下类图:

代码实现:
 

interface ReportGenerator {
 public void generate(Table table);
} 

class ExcelGenerator implements ReportGenerator {
 public void generate(Table table) {
  System.out.println("generate an Excel report ...");
 }
} 

class PDFGenerator implements ReportGenerator {
 public void generate(Table table) {
  System.out.println("generate an PDF report ...");
 }
} 

class ReportService {
 // 负责创建具体需要的报表生成器
 private ReportGenerator generator = new PDFGenerator();
 // private static ReportGenerator generator = new ExcelGenerator(); 

 public void getDailyReport(Date date) {
  table.setDate(date);
  // ...
  generator.generate(table);
 } 

 public void getMonthlyReport(Month month) {
  table.setMonth(month);
  // ...
  generator.generate(table);
 }
} 

public class Client {
 public static void main(String[] args) {
  ReportService reportService = new ReportService();
  reportService.getDailyReport(new Date());
  //reportService.getMonthlyReport(new Date());
 }
}

 
eg2 
问题描述:
如上面代码中的注释所示,具体的报表生成器由 ReportService 类内部硬编码创建,由此 ReportService 已经直接依赖于 PDFGenerator 或 ExcelGenerator ,必须消除这一明显的紧耦合关系。
 
解决方案:引入容器
引入一个中间管理者,也就是容器(Container),由其统一管理报表系统所涉及的对象(在这里是组件,我们将其称为 Bean),包括 ReportService 和各个 XXGenerator 。在这里使用一个键-值对形式的 HashMap 实例来保存这些 Bean。
 
实现方法:
得到类图如下:

代码实现:

class Container {
 // 以键-值对形式保存各种所需组件 Bean
 private static Map<String, Object> beans; 

 public Container() {
  beans = new HashMap<String, Object>(); 

  // 创建、保存具体的报表生起器
  ReportGenerator reportGenerator = new PDFGenerator();
  beans.put("reportGenerator", reportGenerator); 

  // 获取、管理 ReportService 的引用
  ReportService reportService = new ReportService();
  beans.put("reportService", reportService);
 } 

 public static Object getBean(String id) {
  return beans.get(id);
 }
} 

class ReportService {
 // 消除紧耦合关系,由容器取而代之
 // private static ReportGenerator generator = new PDFGenerator();
 private ReportGenerator generator = (ReportGenerator) Container.getBean("reportGenerator"); 

 public void getDailyReport(Date date) {
  table.setDate(date);
  generator.generate(table);
 } 

 public void getMonthlyReport(Month month) {
  table.setMonth(month);
  generator.generate(table);
 }
} 

public class Client {
 public static void main(String[] args) {
  Container container = new Container();
  ReportService reportService = (ReportService)Container.getBean("reportService");
  reportService.getDailyReport(new Date());
  //reportService.getMonthlyReport(new Date());
 }
}

 
时序图大致如下:

效果:
如上面所示,ReportService 不再与具体的 ReportGenerator 直接关联,已经用容器将接口和实现隔离开来了,提高了系统组件 Bean 的重用性,此时还可以使用配置文件在 Container 中实时获取具体组件的定义。
 
eg3
问题描述:
然而,观察上面的类图,很容易发现 ReportService 与 Container 之间存在双向关联,彼此互相有依赖关系。并且,如果想要重用 ReportService,由于它也是直接依赖于单独一个 Container 的具体查找逻辑。若其他容器具体不同的组件查找机制(如 JNDI),此时重用 ReportService 意味着需要修改 Container 的内部查找逻辑。
 
解决方案:引入 Service Locator
再次引入一个间接层 Service Locator,用于提供组件查找逻辑的接口,请看Wikipedia 中的描述 或者 Java EE 对其的描述1 、描述2 。这样就能够将可能变化的点隔离开来。
 
实现方法:
类图如下:

代码实现:
 

// 实际应用中可以是用 interface 来提供统一接口
class ServiceLocator {
 private static Container container = new Container(); 

 public static ReportGenerator getReportGenerator() {
  return (ReportGenerator)container.getBean("reportGeneraator");
 }
} 

class ReportService {
 private ReportGenerator reportGenerator = ServiceLocator.getReportGenerator(); 

 // ...
}

eg4

问题描述:
然而,不管是引入 Container 还是使用 Service Locator ,ReportService 对于具体组件的查找、创建的方式都是‘主动'的,这意味着作为客户的 ReportService 必须清楚自己需要的是什么、到哪里获取、如何获取。一下子就因为 What、Where、How 而不得不增加了具体逻辑细节。
 
例如,在前面‘引入Container '的实现方法中,有如下代码: 

class ReportService {
 // 消除紧耦合关系,由容器取而代之
 // private static ReportGenerator generator = new PDFGenerator();
 // 通过 Container..getBean("reportGenerator") ‘主动'查找
 private ReportGenerator generator = (ReportGenerator) Container
   .getBean("reportGenerator");

 
在‘引入 Service Locator '的实现方法中,有如下代码: 

class ServiceLocator {
 privatestatic Container container = new Container();
 publicstatic ReportGenerator getReportGenerator() {
  // 还是container.getBean(), 用了委托而已
  return (ReportGenerator) container.getBean("reportGeneraator");
 }
}
class ReportService {
 // ReportService 最终还是‘主动'查找,委托给ServiceLocator 而已
 private ReportGenerator reportGenerator = ServiceLocator.getReportGenerator();
}

 
解决方案:
在这种情况下,变‘主动'为‘被动'无疑能够减少 ReportService 的内部知识(即查找组件的逻辑)。根据控制反转(IoC)原则,可江此种拉(Pull,主动的)转化成推(Push,被动的)的模式。
 
例如,平时使用的 RSS 订阅就是Push的应用,省去了我们一天好几次登录自己喜爱的站点主动获取文章更新的麻烦。
 
而依赖注入(DI)则是实现这种被动接收、减少客户(在这里即ReportService)自身包含复杂逻辑、知晓过多的弊病。
 
实现方法:
因为我们希望是‘被动'的接收,故还是回到 Container 的例子,而不使用 Service Locator 模式。由此得到修改后的类图如下:

而原来的类图如下,可以对照着看一下,注意注释的提示:

代码实现:
为了使例子能够编译、运行,并且稍微利用跟踪代码的运行结果来显式整个类图实例化、互相协作的先后顺序,在各个类的构造器中加入了不少已编号的打印语句,以及两个无关紧要的类,有点啰唆,具体如下: 

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
// 为了能够编译运行,多了两个无关紧要的类
class Month { }
class Table {
 publicvoid setDate(Date date) { }
 publicvoid setMonth(Month month) { }
}
// ------------ 以下均无甚重要改变 ----------------- //
interface ReportGenerator {
 publicvoid generate(Table table);
}
class ExcelGenerator implements ReportGenerator {
 public ExcelGenerator() {
  System.out.println("2...开始初始化 ExcelGenerator ...");
 }
 publicvoid generate(Table table) {
  System.out.println("generate an Excel report ...");
 }
}
class PDFGenerator implements ReportGenerator {
 public PDFGenerator() {
  System.out.println("2...开始初始化 PDFGenerator ...");
 }
 publicvoid generate(Table table) {
  System.out.println("generate an PDF report ...");
 }
}
//------------ 以上均无甚重要改变 ----------------- //
class Container {
 // 以键-值对形式保存各种所需组件 Bean
 privatestatic Map<String, Object> beans;
 public Container() {
  System.out.println("1...开始初始化 Container ...");
  beans = new HashMap<String, Object>();
  // 创建、保存具体的报表生起器
  ReportGenerator reportGenerator = new PDFGenerator();
  beans.put("reportGenerator", reportGenerator);
  // 获取、管理 ReportService 的引用
  ReportService reportService = new ReportService();
  // 注入上面已创建的具体 ReportGenerator 实例
  reportService.setReportGenerator(reportGenerator);
  beans.put("reportService", reportService);
  System.out.println("5...结束初始化 Container ...");
 }
 publicstatic Object getBean(String id) {
  System.out.println("最后获取服务组件...getBean() --> " + id + " ...");
  returnbeans.get(id);
 }
}

class ReportService {
 // private static ReportGenerator generator = new PDFGenerator();
 // 消除上面的紧耦合关系,由容器取而代之
 // private ReportGenerator generator = (ReportGenerator) Container
 //   .getBean("reportGenerator");
 // 去除上面的“主动”查找,提供私有字段来保存外部注入的对象
 private ReportGenerator generator;
 // 以 setter 方式从外部注入
 publicvoid setReportGenerator(ReportGenerator generator) {
  System.out.println("4...开始注入 ReportGenerator ...");
  this.generator = generator;
 }

 private Table table = new Table();
 public ReportService() {
  System.out.println("3...开始初始化 ReportService ...");
 }
 publicvoid getDailyReport(Date date) {
  table.setDate(date);
  generator.generate(table);
 }
 publicvoid getMonthlyReport(Month month) {
  table.setMonth(month);
  generator.generate(table);
 }
}

publicclass Client {
 publicstaticvoid main(String[] args) {
  // 初始化容器
  new Container();
  ReportService reportService = (ReportService) Container
    .getBean("reportService");
  reportService.getDailyReport(new Date());
  // reportService.getMonthlyReport(new Date());
 }
}

 
运行结果:

1...开始初始化 Container ...
2...开始初始化 PDFGenerator ...
3...开始初始化 ReportService ...
4...开始注入 ReportGenerator ...
5...结束初始化 Container ...

最后获取服务组件...getBean() --> reportService ...
generate an PDF report ...

 
注意:
1、根据上面运行结果的打印顺序,可见代码中加入的具体编号是合理的,模拟了程序执行的流程,于是也就不再画序列图了。
2、注意该例子中对IoC、DI的使用,是以ReportService为客户端(即组件需求者)为基点的,而代码中的Client 类main()中的测试代码才是服务组件的最终用户,但它需要的不是组件,而是组件所具有的服务。
3、实际在Spring框剪中,初始化Container显然不是最终用户Client应该做的事情,它应该由服务提供方事先启动就绪。
4、在最终用户Client中,我们还是用到Container.getBean("reportService")来获取事先已在Container的构造函数中实例化好的服务组件。而在具体应用中,通常是用XML等配置文件将可用的服务组件部署到服务器中,再由Container读取该配置文件结合反射技术得以创建、注入具体的服务组件。
 
分析:
之前是由ReportService主动从Container中请求获取服务组件,而现在是被动地等待Container注入(Inject,也就是Push)服务组件。控制权明显地由底层模块(ReportService 是组件需求者)转移给高层模块(Container 是组件提供者),也就是控制反转了。
 

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索java
, spring
, 依赖注入
控制反转
控制反转和依赖注入、依赖注入 控制反转、依赖注入与控制反转、c 控制反转和依赖注入、spring 依赖注入,以便于您获取更多的相关知识。

时间: 2024-09-08 15:01:37

实例讲解Java的Spring框架中的控制反转和依赖注入_java的相关文章

实例讲解Java的Spring框架中的AOP实现_java

简介面向切面编程(AOP)提供另外一种角度来思考程序结构,通过这种方式弥补了面向对象编程(OOP)的不足. 除了类(classes)以外,AOP提供了 切面.切面对关注点进行模块化,例如横切多个类型和对象的事务管理. (这些关注点术语通常称作 横切(crosscutting) 关注点.) Spring的一个关键的组件就是 AOP框架. 尽管如此,Spring IoC容器并不依赖于AOP,这意味着你可以自由选择是否使用AOP,AOP提供强大的中间件解决方案,这使得Spring IoC容器更加完善.

举例讲解Java的Spring框架中AOP程序设计方式的使用_java

1.什么是AOP AOP是Aspect Oriented Programming的缩写,意思是面向方面编程,AOP实际是GoF设计模式的延续. 2.关于Spring AOP的一些术语: A.切面(Aspect):在Spring AOP中,切面可以使用通用类或者在普通类中以@Aspect 注解(@AspectJ风格)来实现 B.连接点(Joinpoint):在Spring AOP中一个连接点代表一个方法的执行 C.通知(Advice):在切面的某个特定的连接点(Joinpoint)上执行的动作.通

详解Java的Spring框架中bean的定义以及生命周期_java

bean的定义形成应用程序的骨干是由Spring IoC容器所管理的对象称为bean.bean被实例化,组装,并通过Spring IoC容器所管理的对象.这些bean由容器提供,例如,在XML的<bean/>定义,已经看到了前几章的形式配置元数据创建. bean定义包含所需要的容器要知道以下称为配置元数据的信息: 如何创建一个bean Bean 生命周期的详细信息 Bean 依赖关系 上述所有配置元数据转换成一组的下列属性构成每个bean的定义. Spring配置元数据 Spring IoC容

Spring的控制反转和依赖注入

Spring的官网:https://spring.io/  Struts与Hibernate可以做什么事? Struts, Mvc中控制层解决方案 可以进行请求数据自动封装.类型转换.文件上传.效验- Hibernate, 持久层的解决方案: 可以做到, 把对象保存到数据库, 从数据库中取出的是对象. 传统的开发模式 基于mvc模式进行项目开发: 基于mvc的项目框架结构: Entity / dao / service / action 为什么引入Spring:  思考:     1. 对象创建

Java的Spring框架中AOP项目的一般配置和部署教程_java

0.关于AOP面向切面编程(也叫面向方面编程):Aspect Oriented Programming(AOP),是软件开发中的一个热点,也是Spring框架中的一个重要内容.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率. AOP是OOP的延续. 主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等. 主要的意图是:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对

深入理解Java的Spring框架中的IOC容器_java

Spring IOC的原型spring框架的基础核心和起点毫无疑问就是IOC,IOC作为spring容器提供的核心技术,成功完成了依赖的反转:从主类的对依赖的主动管理反转为了spring容器对依赖的全局控制. 这样做的好处是什么呢? 当然就是所谓的"解耦"了,可以使得程序的各模块之间的关系更为独立,只需要spring控制这些模块之间的依赖关系并在容器启动和初始化的过程中将依据这些依赖关系创建.管理和维护这些模块就好,如果需要改变模块间的依赖关系的话,甚至都不需要改变程序代码,只需要将更

实例讲解Java的MyBatis框架对MySQL中数据的关联查询_java

mybatis 提供了高级的关联查询功能,可以很方便地将数据库获取的结果集映射到定义的Java Bean 中.下面通过一个实例,来展示一下Mybatis对于常见的一对多和多对一关系复杂映射是怎样处理的. 设计一个简单的博客系统,一个用户可以开多个博客,在博客中可以发表文章,允许发表评论,可以为文章加标签.博客系统主要有以下几张表构成: Author表:作者信息表,记录作者的信息,用户名和密码,邮箱等. Blog表   :  博客表,一个作者可以开多个博客,即Author和Blog的关系是一对多.

深入解析Java的Spring框架中的混合事务与bean的区分_java

混合事务在ORM框架的事务管理器的事务内,使用JdbcTemplate执行SQL是不会纳入事务管理的. 下面进行源码分析,看为什么必须要在DataSourceTransactionManager的事务内使用JdbcTemplate. 1.开启事务DataSourceTransactionManager protected void doBegin(Object transaction,TransactionDefinition definition) { DataSourceTransactio

深入解析Java的Spring框架中bean的依赖注入_java

每一个基于java的应用程序都有一个共同工作来展示给用户看到的内容作为工作的应用几个对象.当编写一个复杂的Java应用程序,应用程序类应该尽可能独立其他Java类来增加重复使用这些类,并独立于其他类别的测试它们,而这样做单元测试的可能性.依赖注入(或有时称为布线)有助于粘合这些类在一起,同时保持他们的独立. 考虑有其中有一个文本编辑器组件的应用程序,要提供拼写检查.标准的代码将看起来像这样:   public class TextEditor { private SpellChecker spe