学习依赖注入和控制反转的概念,然后借助代码示例研究Spring Framework如何支持它们。
控制反转
在开始任何事情之前,让我们学习什么是控制反转 。
控制反转是面向对象编程中使用的术语,通过该术语可以将对象或一组对象的控制赋予框架或由框架提供的容器。
![Spring依赖注入](https://i-blog.csdnimg.cn/blog_migrate/bf34d3c994ee9eb0f4d6d814e07c9c5c.png)
尽管上面的图像是幽默的,但它描述了什么是控制反转 。 如果我们将人类视为软件组件或服务,则意味着人类应执行唤醒,参加会议或支付账单之类的动作。 对于诸如跟踪会议之类的其他事情,要设置警报或提醒,人们可以使用电话或任何智能设备。
有关Spring依赖注入的更多信息:
Spring的Inversion of Control与此类似。 我们希望我们的软件组件能够完成其给定的工作。 我们从组件中取出配置和依赖项,并将它们提供给称为控制容器反转或IOC容器的容器。 以下部分将介绍更多内容。
想更多地了解Spring Framework?
读这个:
什么是依存关系?
一个应用程序由多个类组成。 通常,每个班级都有自己的专门责任。 这导致我们的类与不同的类集成在一起以完成某些功能。 当类A调用类B的方法时,则类A 依赖于类B。
紧密耦合的物体
了解具有依赖关系如何导致紧密耦合对象问题。 请参见下面的代码。
这是一个FileUploadService
,用于捕获文件,检查文件是否具有预期的扩展名之一并要求FileStorageService
存储该文件。
public class FileUploadService {
private List<String> validFiles = Arrays.asList("xls", "doc"."txt", "ppt");
private FileStorageService service = new AzureBlobStorageService();
public FileUploadService() {}
//
// Other methods
//
}
在上面的代码中,我们使用“ 程序到接口”原理来实例化FileStorageService
。 但是,各个实现仍在该类中进行了硬编码。 同样, validFiles
是硬编码的。 两者都引起紧密耦合的对象。
松散耦合的对象
让我们稍微更新一下FileUploadService
,我们将获得松散耦合的对象。
public class FileUploadService {
private List<String> validFiles;
private FileStorageService service;
public FileUploadService(List<String> validFiles, FileStorageService service){
this.validFiles = validFiles;
this.service = service;
}
}
class User {
public static void main(String[] ar) {
List<String> validFiles = Arrays.asList("xls", "ppt", "doc");
FileStorageService service = new AzureBlobStorageService();
FileUploadService fileUploadService = new FileUploadService(validFiles, service);
}
}
- 第3行:该变量已声明且未初始化。 无硬编码值。
- 第4行:仅引用
FileStorageService
类型。 没有附加的实现。 - 第6行:所有参数构造函数。
让我们看看User
类中发生了什么,该类实际上是FileUploadService
的用户。
- 第17行:通过将所有必需的参数传递给构造函数来创建
FileUploadService
实例。
依赖注入
我们刚才所做的称为依赖注入 。
依赖注入是面向对象编程中的一个术语,通过该术语,对象将专注于完成分配的功能并利用其他对象。 对象将无法处理必要的配置和初始化。 但是,对象将提供一种通过字段分配,字段设置器或构造函数初始化它们及其依赖性的方法。 这样,外部实体可以初始化事物而不是实际对象。
在基于Spring的应用程序中, 控制容器反转 (IoC容器)进行依赖项注入。 我们将在接下来的部分中看到这一点。 首先,让我们看看为什么我们甚至需要这样的容器。
为什么我们需要IoC容器?
我已经修改了前面的代码示例。 现在是ResumeUploaderService
。 Candidate
可以将其简历共享给ResumeUploaderService
。 该服务在验证扩展后,应将其共享给ResumeStorageService
。 根据组织的当前策略,简历将存储在文件系统的机密文件夹中(通过FileSystemResumeStorageService
)。
public class ResumeUploaderService {
private List<String> validFiles;
private ResumeStorageService service;
public ResumeUploaderService(List<String> validFiles, ResumeStorageService service) {
this.validFiles = validFiles;
this.service = service;
}
}
class Candidate {
public static void main(String[] ar) {
List<String> validFiles = Arrays.asList("pdf", "doc");
String filePath = "/Users/app/confidential/storage/resume";
ResumeStorageService service = new FileSystemResumeStorageService(filePath);
ResumeUploaderService fileUploadService = new ResumeUploaderService(validFiles, service);
}
}
- 第4行:
ResumeUploaderService
具有-对ResumeStorageService
的引用。 - 第6行:构造函数,它接受并设置
ResumeStorageService
的实现。
要上传简历, Candidate
必须实例化ResumeUploaderService
并传递简历。 但是有了所有dependency injection
东西,候选人的工作就变得困难了。 候选人不仅必须实例化ResumeUploaderService
而且还必须实例化ResumeStorageService
。 因为,没有后者就无法实例化前者。
- 第17行:应聘者决定将简历存储在哪里(我知道..很有趣!)
- 第18行:候选人决定使用
FileSystemResumeStorageService
还是AzureBlobStorageService
。 - 第20行:最后,候选人实例化
ResumeUploaderService
。
以下是上面的重要问题
- 消费者知道太多。
- 消费者而不是使用服务,而是对其进行初始化。
- 消费者不必担心
ResumeUploaderService
如何ResumeUploaderService
其工作(缺乏抽象)。 - 最终用户,我们必须了解所有内容,并且必须初始化系统中的所有内容。
这清楚地表明,我们需要可以处理所有配置和初始化的东西。 唯一的责任是管理初始化。
控制容器(IoC容器)的反转
Spring提供了一个IoC容器来解决这个问题。 该容器实例化所有对象,同时这样做还解决了它们的依赖关系。 类ApplicationContext
代表Spring IOC容器。 Application上下文负责实例化,配置和连接Bean。
记住,Bean只是在Spring的Application Context中注册的Java对象。
要配置,实例化或编写bean,应用程序上下文需要一些说明。 这些指令可以以XML配置,Java注释或代码的形式提供。
Spring依赖注入
在Spring中,每个对象都是一个bean。 每个对象都有一个id
或name
。 ApplicationContext会跟踪所有此类行为和ID。 消费者请求bean时,应用程序上下文将返回bean的实例。 查看下面的代码以详细了解bean的创建和连接。
![Spring依赖注入](https://i-blog.csdnimg.cn/blog_migrate/b837405303654688731e0bb7eef79a24.png)
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("resumeStorageService")
public class FileSystemResumeStorageService implements ResumeStorageService {
@Value("${resume.storage.path}")
private String storagePath; // Storage path assigned based on properties file
//
// Skipped methods
//
}
- 第4行:告诉Spring将此类注册为Bean并通过给定名称进行标识。 如果未提供名称,则将类名称视为标识符。
- 第8行:现在,存储路径是直接从属性文件中注入的。 无需消费者通过它。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class ResumeUploaderService {
@Autowired
@Qualifier("resumeStorageService")
private ResumeStorageService storageService;
public ResumeUploaderService(ResumeStorageService storageService) {
this.storageService = storageService;
}
//
// Skipped methods
//
}
- 第5行:将类声明为Spring Bean,并将类名称声明为标识符。
- 第10行:告诉spring自动连接
ResumeStorageService
实现,该实现由"resumeStorageService"
标识。
如果我们要附加ResumeStorageService
的其他实现,则ResumeUploaderService
根本不会更改。
import org.springframework.beans.factory.annotation.Autowired;
public class Candidate {
@Autowired private ResumeUploaderService resumeUploaderService;
public void upload(Byte[] resume) {
resumeUploaderService.uploadResume(resume);
}
}
- 第4行:要求Spring分配一个
resumeUploaderService
实例。
一切都那么干净和专注。 没有一个类正在初始化另一个类或为另一个类设置任何配置。 一切都由Spring的控制容器反转(IoC容器)管理 。
摘要
您已经结束了Spring Dependency Injection and Inversion of Control指南。 您了解了什么是依赖关系,以及如何将类紧密耦合或松散耦合 。 我们理解了面向对象程序设计中的依赖注入和控制反转的概念。 您还了解到, Spring 的控制容器反转 (IoC容器)在我们的Spring应用程序中管理所有依赖项注入。
关于Springs依赖注入,还有很多东西要学习。 我们将在后续的教程中介绍它们。
翻译自: https://www.javacodegeeks.com/2019/02/spring-dependency-injection-inversion-control.html