文章目录
前言
学习 IoC 和 DI 主要有两个重要原因。
一是优化软件架构,在复杂系统中,组件间依赖关系复杂,硬编码会导致紧密耦合。IoC 和 DI 通过外部容器管理依赖,让组件专注自身业务,降低耦合度,方便系统维护和扩展。
二是提高代码可测试性,单元测试需要隔离外部依赖,没有 IoC 和 DI 时,很难模拟依赖行为,而它们可以方便地在测试中注入模拟依赖,更好地测试业务逻辑。
一、控制反转(IoC)
将对象的控制权从应用程序代码转移到外部容器。
如何理解控制反转以及它的作用呢,当然是看看如果没有控制反转,会怎么样。
1.如果没有控制反转,会怎么样?
在传统的面向对象编程中,对象之间的依赖关系是通过硬编码的方式建立的。
以一个简单的电子商务系统为例,假设有一个OrderService类用于处理订单相关的业务逻辑,它需要访问数据库来获取商品信息、用户信息以及保存订单记录。在没有 IoC 的情况下,OrderService可能会直接在其内部实例化数据库访问对象,如下所示:
class OrderService {
private ProductRepository productRepo = new ProductRepositoryImpl();
private UserRepository userRepo = new UserRepositoryImpl();
private OrderRepository orderRepo = new OrderRepositoryImpl();
public void placeOrder(Order order) {
User user = userRepo.getUser(order.getUserId());
List<Product> products = productRepo.getProducts(order.getProductIds());
// 处理订单逻辑,如计算总价、验证库存等
orderRepo.saveOrder(order);
}
}
OrderService直接负责创建ProductRepository、UserRepository和OrderRepository的实例,那么可能会导致什么问题呢?
1.1紧密耦合:
OrderService与这三个仓库类紧密耦合在一起。如果需要更换数据库访问(如从关系型数据库切换到非关系型数据库),或者对仓库类的实现进行修改(如修改构造函数、添加新的方法参数),那么OrderService的代码也必须随之修改。
1.2可测试性差:
在对OrderService进行单元测试时,很难隔离它与数据库访问对象的依赖关系。因为这些依赖对象是在OrderService内部创建的,测试代码无法轻易地替换为模拟对象,从而导致测试变得复杂。
1.3代码复用性受限:
由于OrderService与特定的仓库类实现紧密绑定,很难在其他场景下复用OrderService,除非新场景也使用完全相同的仓库类实现。
2. IoC 的核心思想与实现机制
2.1核心思想:
IoC 的核心思想是将对象的控制权从应用程序代码转移到外部容器。就像一个工厂(容器)负责生产各种零件(对象),并按照一定的规则(配置)将它们组装(管理依赖关系)起来,而使用这些零件的机器(应用程序中的其他对象)只需要关心如何使用这些零件来完成自己的工作,而不需要关心零件是如何生产和组装的。
2.2实现机制 - 配置驱动:
2.2.1XML 配置方式:
Spring 框架可以通过 XML 文件来配置对象之间的关系。在上述电子商务系统的例子中,可以创建一个application - context.xml文件来配置OrderService及其依赖的仓库类:
<?xml version="1.0" encoding="UTF - 8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema - instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring - beans.xsd">
<bean id="productRepository" class="com.example.repository.ProductRepositoryImpl"/>
<bean id="userRepository" class="com.example.repository.UserRepositoryImpl"/>
<bean id="orderRepository" class="com.example.repository.OrderRepositoryImpl"/>
<bean id="orderService" class="com.example.service.OrderService">
<constructor - arg ref="productRepository"/>
<constructor - arg ref="userRepository"/>
<constructor - arg ref="orderRepository"/>
</bean>
</beans>
在这个配置文件中,首先定义了三个仓库类的 Bean,然后定义了OrderService的 Bean,并通过构造函数注入的方式将三个仓库类的 Bean 注入到OrderService中。Spring 的 IoC 容器会读取这个配置文件,根据配置信息创建对象并管理它们之间的关系。
2.2.2Java 配置方式:
除了 XML 配置,还可以使用 Java 类来进行配置。创建一个AppConfig类,如下所示:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public ProductRepository productRepository() {
return new ProductRepositoryImpl();
}
@Bean
public UserRepository userRepository() {
return new UserRepositoryImpl();
}
@Bean
public OrderRepository orderRepository() {
return new OrderRepositoryImpl();
}
@Bean
public OrderService orderService(ProductRepository productRepository,
UserRepository userRepository,
OrderRepository orderRepository) {
return new OrderService(productRepository, userRepository, orderRepository);
}
}
2.2.3基于注解的自动扫描:
Spring 还支持通过注解自动扫描来发现和管理 Bean。在仓库类和OrderService类上添加适当的注解,如:
// 标记为仓库类,由Spring管理
@Repository
class ProductRepositoryImpl implements ProductRepository {
// 实现方法
}
@Repository
class UserRepositoryImpl implements UserRepository {
// 实现方法
}
@Repository
class OrderRepositoryImpl implements OrderRepository {
// 实现方法
}
// 标记为服务类,由Spring管理
@Service
class OrderService {
private ProductRepository productRepository;
private UserRepository userRepository;
private OrderRepository orderRepository;
// 通过构造函数注入,Spring会自动匹配类型并注入
public OrderService(ProductRepository productRepository,
UserRepository userRepository,
OrderRepository orderRepository) {
this.productRepository = productRepository;
this.userRepository = userRepository;
this.orderRepository = orderRepository;
}
public void placeOrder(Order order) {
User user = userRepository.getUser(order.getUserId());
List<Product> products = productRepository.getProducts(order.getProductIds());
// 处理订单逻辑,如计算总价、验证库存等
//...
orderRepository.saveOrder(order);
}
}
通过@Repository注解标记仓库类,@Service注解标记服务类,Spring 会自动扫描这些带有注解的类并将它们作为 Bean 进行管理。同时,在OrderService的构造函数中,Spring 会根据参数类型自动将对应的仓库类 Bean 注入进去。
3. IoC 容器的职责
3.1创建对象:
IoC 容器负责根据配置(XML、Java 配置或注解)创建对象。它会在适当的时候(例如应用程序启动时或者第一次请求某个对象时)实例化对象。对于单例模式的对象(这是 Spring 中默认的 Bean 作用域),容器只会创建一个实例,并在整个应用程序生命周期中重复使用这个实例。对于其他作用域(如原型作用域,每次请求都会创建一个新的实例)的对象,容器会按照相应的规则进行创建。
3.2生命周期管理:
除了创建对象,容器还管理对象的生命周期。它可以在对象创建时执行初始化操作,例如调用对象的初始化方法或者执行一些依赖注入的操作。在对象不再需要时,容器可以负责销毁对象,并执行一些清理资源的操作。以一个数据库连接池为例,当应用程序启动时,IoC 容器可以创建数据库连接池对象,并初始化一定数量的连接。在应用程序运行过程中,对象被反复使用,当应用程序关闭时,容器可以负责关闭连接池,释放所有的数据库连接。
二、依赖注入(DI)
1. 作为 IoC 实现方式的依赖注入
依赖注入(DI)是实现控制反转(IoC)的一种具体手段。它强调的是如何将对象所依赖的其他对象传递给这个对象。可以把 IoC 看作是一种设计理念,而 DI 是实现这种理念的具体技术。
依赖注入的核心思想是将这些被依赖者对象的创建和获取从依赖者对象中分离出来,由外部(通常是一个容器,如 Spring 的 IoC 容器)负责将被依赖者对象 “注入” 到依赖者对象中。这样,依赖者对象不需要知道被依赖者对象是如何创建的,只需要使用它提供的功能即可。
2. 为什么需要依赖注入
2.1降低耦合度
在没有依赖注入的情况下,依赖者对象会直接在自己的代码中创建被依赖者对象。
如:
class UserService {
private UserRepository userRepository = new UserRepositoryImpl();
// 业务方法
}
这种方式使得UserService和UserRepositoryImpl紧密耦合。如果需要更换UserRepository的实现(比如从使用关系型数据库改为使用非关系型数据库),就必须修改UserService的代码。而通过依赖注
入,UserService只依赖于UserRepository的接口,具体的实现可以在外部配置,从而降低了耦合度。
2.2提高可维护性和可测试性
从维护角度看,当系统规模变大,对象之间的依赖关系复杂时,直接在类内部创建依赖对象会使代码难以理解和修改。而依赖注入将依赖关系的配置集中在外部,使得代码结构更加清晰。
对于测试来说,依赖注入也非常重要。假设我们要对UserService进行单元测试,在没有依赖注入时,UserService直接创建了UserRepositoryImpl,这使得在测试时很难隔离UserRepository的真实行为。例如,如果UserRepositoryImpl涉及数据库访问,在单元测试中可能不希望真正访问数据库。通过依赖注入,可以轻松地将一个模拟的UserRepository(只返回测试数据,不进行真实的数据库操作)注入到UserService中,从而方便地对UserService的业务逻辑进行单元测试。
3.依赖注入的具体方式
3.1构造函数注入
构造函数注入是指通过依赖者对象的构造函数来传递被依赖者对象。当创建依赖者对象时,必须同时提供被依赖者对象作为构造函数的参数。
如:
class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 业务方法
}
在这里,UserService的构造函数接受一个UserRepository对象作为参数。当创建UserService的实例时,外部(如 IoC 容器)会将一个UserRepository对象传递进来,从而将依赖注入到UserService中。
3.2Setter 注入
Setter 注入是通过依赖者对象的 Setter 方法来传递被依赖者对象。依赖者对象先被创建,然后可以通过调用 Setter 方法来设置其依赖对象。
如:
class UserService {
private UserRepository userRepository;
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 业务方法
}
在这个例子中,OrderService的构造函数明确地列出了它所依赖的三个对象。当创建OrderService的实例时,外部容器会将这三个对象注入进来,使得OrderService能够正常工作。
3.3字段注入
字段注入是直接在依赖者对象的字段上使用注解(如 Spring 中的@Autowired),让外部容器自动将被依赖者对象注入到该字段中。
如:
import org.springframework.beans.factory.annotation.Autowired;
@Service
class UserService {
@Autowired
private UserRepository userRepository;
// 业务方法
}
@Autowired注解告诉 Spring 容器自动将一个UserRepository对象注入到UserService的userRepository字段中。
标签:依赖,DI,对象,反转,UserRepository,OrderService,IoC,注入 From: https://blog.csdn.net/bufangbufang/article/details/144249098