实例讲解Java的Spring框架中的控制反转和依赖注入
作者:haolloyin 发布时间:2023-04-24 17:44:49
近来总是接触到 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 是组件提供者),也就是控制反转了。
猜你喜欢
- 突然心血来潮,想自己做个小程序玩玩,但是怎么把他做成一个exe文件,让大家能够更好的理解和使用呢,百度了一下,说是需要exe4j来生成,但是
- 前面讲了 Spock框架Mock对象方法经验总结一、静态方法Mock静态方法我们使用PowerMock结合Mockito的方案,首先在测试类
- 参考答案java.sql.Date 是 java.util.Date 的子类java.util.Date 是 JDK 中的日期类,精确到时、
- 一、消息中间件相关知识1、概述消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列
- 1、实体类package com.yl.bean;import java.io.Serializable;import java.util.
- 易于理解版package com.zhebie.ternary;public class ternary { public static v
- 前言在讲这两种方式之前,我们先来说明一下什么是java中的jar文件jar (Java Archive File),翻译过来就是java的档
- 说到Java的本地存储,肯定使用IO流进行操作。首先,我们需要一个创建文件的函数createNewFile:public static bo
- 数据库事务是被当作单个工作单元的操作序列。这些操作要么全部完成或全部不成功。事务管理是面向企业应用程序,以确保数据的完整性和一致性RDBMS
- 这篇文章主要介绍了Spring boot2X负载均衡和反向代理实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参
- 问题描述问题原因出现该问题的原因是项目Project当中的jdk与电脑当中的jdk版本不一致造成的。解决方法1、查看本机的jdk版本:命令提
- 其实本没有没打算写这篇的,但还是要写一下写这篇博客的起因是因为,现在呆着的这家公司居然没有统一的API返回格式?,询问主管他居然告诉我用HT
- 上篇文章我们讲解了使用Hibernate Validation来校验数据,当校验完数据后,如果发生错误我们需要给客户返回一个错误信息,因此这
- 本文介绍了Java设计模式之享元模式,供大家参考,具体内容如下1、关于享元模式享元模式有点类似于单例模式,都是只生成一个对象被共享使用。享元
- 前言在RocketMQ中为,我们创建消息生产者时,只需要设置NameServer地址,消息就能正确地发送到对应的Broker中,那么Rock
- 本文实例为大家分享了JDBC实现学生管理系统的具体代码,供大家参考,具体内容如下1、学生类package manage;import jav
- 目录1.问题引出2.解决办法3.另外一种自定义序列化机制(介绍Externalizable)1.问题引出在某些情况下,我们可能不想对于一个对
- 由于要在Web项目中采用RFID读取功能,所以有必要开发Activex,一般情况下开发Activex都采用VC,VB等,但对这两块不是很熟悉
- 四大函数式接口新时代的程序员:lambda 表达式,链式编程,函数式接口,Stream 流式计算函数式接口: 只有一个方法的接口@Funct
- 在早期开发的时候,我们完成的都是静态页面也就是html页面,随着时间轴的发展,慢慢的引入了jsp页面,当在后端服务查询到数据之后可以转发到j