SpringBoot 分层解耦

在这里插入图片描述

从没有分层思想到传统 Web 分层,再到 Spring Boot 分层架构

1. 没有分层思想

在最初的项目开发中,很多开发者并没有明确的分层思想,所有逻辑都堆砌在一个类或一个方法中。这样的开发方式通常会导致以下问题:

  • 代码混乱:没有清晰的层次,业务逻辑、数据访问、UI 逻辑等混在一起,导致代码难以维护。

  • 重复代码多:由于没有模块化的结构,很多功能会被重复实现。

  • 难以扩展和测试:随着项目变大,增加新功能或修改功能会变得困难,因为不同的功能可能会交叉影响。

    举例:
    假设我们有一个简单的用户查询功能:

    public class UserService {public User getUserById(Long id) {// 数据库查询逻辑String query = "SELECT * FROM users WHERE id = " + id;// 模拟查询User user = database.query(query);  // 数据库直接操作// 业务逻辑if (user == null) {throw new RuntimeException("User not found");}return user;}
    }
    

    在这个例子中,UserService 类中直接包含了数据查询的逻辑,没有分层的思想,导致数据访问和业务逻辑耦合在一起。

2. 传统 Web 分层架构

为了应对这些问题,传统的 Web 开发逐步引入了分层架构(Layered Architecture)。经典的 Web 分层架构一般包括以下几层:

  • 表现层(Controller 层):负责接收请求、验证数据、调用业务逻辑层,最终将结果返回给用户。
  • 业务逻辑层(Service 层):处理核心的业务逻辑,接收来自控制层的请求并做出响应,调用数据层来处理数据。
  • 数据访问层(Repository/DAO 层):与数据库或其他存储系统交互,执行具体的增、删、改、查操作。
  • 实体层(Entity 层):用于定义实体类,映射数据库中的表。

这个分层模式解决了很多问题,比如代码重复、可维护性差、功能耦合等。但它还是会面临一些性能上的问题,因为每一层之间都需要调用,导致了性能开销,尤其是在大型项目中。

下面是传统 Web 分层架构的一个简单实现:

  • 表现层(Controller 层)
@RestController
@RequestMapping("/users")
public class UserController {private final UserService userService;@Autowiredpublic UserController(UserService userService) {this.userService = userService;}@GetMapping("/{id}")public ResponseEntity<User> getUser(@PathVariable Long id) {User user = userService.getUserById(id);return ResponseEntity.ok(user);}
}
  • 业务逻辑层(Service 层)
@Service
public class UserService {private final UserRepository userRepository;@Autowiredpublic UserService(UserRepository userRepository) {this.userRepository = userRepository;}public User getUserById(Long id) {return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));}
}
  • 数据访问层(Repository 层)
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

好处:

  • 各层职责明确,业务逻辑和数据访问相互独立,易于维护。
  • 可以通过依赖注入(DI)实现松耦合,使得模块间的依赖关系减少,增强可扩展性和可测试性。
3. Spring Boot 分层架构

Spring Boot 提供了一种更简洁、更灵活的分层架构方案,并且通过框架自动化配置来减少开发者的负担。Spring Boot 在传统分层架构的基础上做了很多优化,包括:

  • 自动配置:通过 @SpringBootApplication 注解,Spring Boot 会自动扫描并配置相关的 Bean,减少了大量的手动配置。
  • 模块化:Spring Boot 支持将应用划分为多个模块,每个模块处理不同的功能,增加了系统的可扩展性和可维护性。
  • 简化配置:Spring Boot 提供了大量的默认配置,减少了手动配置的复杂性,使得开发者可以专注于业务逻辑的实现。

举例:

Spring Boot 的分层架构和传统的 Web 分层架构基本相似,主要区别在于 Spring Boot 的自动化配置和简化的开发流程。通过 Spring Boot,开发者无需手动配置很多内容,系统的构建过程更加简单和高效。

假设我们有一个 Spring Boot 项目,使用 Spring Data JPA 实现数据访问:

  • Controller 层(表现层):
@RestController
@RequestMapping("/users")
public class UserController {private final UserService userService;@Autowiredpublic UserController(UserService userService) {this.userService = userService;}@GetMapping("/{id}")public ResponseEntity<User> getUser(@PathVariable Long id) {User user = userService.getUserById(id);return ResponseEntity.ok(user);}
}
  • Service 层(业务逻辑层):
@Service
public class UserService {private final UserRepository userRepository;@Autowiredpublic UserService(UserRepository userRepository) {this.userRepository = userRepository;}public User getUserById(Long id) {return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));}
}
  • Repository 层(数据访问层):
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

优化与简化:

  • 自动配置:通过 @SpringBootApplication,开发者无需手动配置 Spring 的应用上下文,Spring Boot 自动处理大部分配置。
  • 约定优于配置:Spring Boot 提供了大量的默认配置(例如数据库连接、Web 配置),使得开发者可以专注于业务逻辑的实现。

分层架构的好处

  • 高内聚,低耦合:每个层次有明确的责任,减少了模块间的依赖,使得代码更加清晰。
  • 易于维护:每层职责单一,修改某一层的实现不会影响其他层,降低了修改时的风险。
  • 易于扩展:可以独立对某一层进行修改或替换,比如使用不同的数据库或者更换业务逻辑的实现方式。
  • 便于测试:分层架构使得单元测试变得容易,可以对每一层进行独立的测试。

实现案例:Spring Boot 的分层架构

1. Controller 层(表现层)
@RestController
@RequestMapping("/users")
public class UserController {private final UserService userService;@Autowiredpublic UserController(UserService userService) {this.userService = userService;}@GetMapping("/{id}")public ResponseEntity<User> getUser(@PathVariable Long id) {User user = userService.getUserById(id);return ResponseEntity.ok(user);}
}
2. Service 层(业务层)
@Service
public class UserService {private final UserRepository userRepository;@Autowiredpublic UserService(UserRepository userRepository) {this.userRepository = userRepository;}public User getUserById(Long id) {return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));}
}
3. Repository 层(数据访问层)
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

依赖注入(DI)与控制反转(IoC)

依赖注入(DI)和控制反转(IoC)是 Spring 框架的核心概念,它们有助于降低系统的耦合度,提高模块间的解耦性,使得系统更具扩展性和可维护性。在深入了解 DI 的概念时,我们要首先理解其背后的控制反转(IoC)思想,以及如何通过 DI 来实现这一思想。

1. 依赖注入(DI)的定义

依赖注入(Dependency Injection,简称 DI) 是一种设计模式,它的核心思想是将对象的依赖关系交给外部容器管理,从而实现松耦合。Spring 通过 DI 模式,在应用启动时自动将所需的依赖注入到类中,而不需要开发者手动去管理这些依赖。这些依赖关系通常通过构造器、setter 方法或字段注入的方式实现。

通过 DI,开发者无需显式创建对象或管理对象之间的依赖关系,而是将所有的对象创建交由 Spring 容器负责。这种方式使得组件之间的依赖关系得以清晰地定义,并且在后期维护和测试时,能够轻松地替换或模拟这些依赖。

2. 依赖注入的原理

依赖注入的原理建立在控制反转(Inversion of Control,简称 IoC)基础上。控制反转(IoC) 是一种设计原则,它的核心思想是将对象的创建、管理和依赖关系控制权从程序员手中反转给外部容器(例如 Spring 容器)。因此,Spring 容器通过 IoC 控制了对象的生命周期、依赖关系、配置以及其他相关信息,而开发者只需要关注核心业务逻辑。

在 Spring 中,IoC 容器负责以下几个方面:

  • 对象的创建:通过配置文件或注解配置对象的创建方式,Spring 容器负责实例化和配置所有对象。
  • 依赖的注入:Spring 容器通过构造器、Setter 方法或字段注入来自动提供类所需要的依赖对象。
  • 对象生命周期管理:Spring 容器负责管理对象的生命周期,例如单例模式、原型模式等。
3. 依赖注入的方式

Spring 提供了多种方式来实现依赖注入,每种方式适用于不同的场景。下面是常见的三种注入方式:

3.1. 构造器注入

构造器注入是最推荐的方式,尤其在依赖关系是不可变的依赖对象是必须的时非常合适。通过构造器注入,所有的依赖对象都会在对象实例化时被注入,这样可以确保依赖项在对象创建时就被完整地注入,避免了空依赖问题。

优势:

  • 保证了对象在创建时即被完全注入,确保了依赖关系的完整性。
  • 不容易出现空依赖,因为如果某个依赖没有注入,Spring 会抛出异常,提示开发者缺少必要的依赖。

示例:

@Service
public class UserService {private final UserRepository userRepository;@Autowired  // 注解表明构造器注入public UserService(UserRepository userRepository) {this.userRepository = userRepository;}
}

在这个例子中,UserServiceuserRepository 被通过构造器注入。Spring 会自动为构造器注入 UserRepository 的实例。

3.2. Setter 注入

Setter 注入通过 setter 方法注入依赖对象。此方式适用于可选依赖的场景,也就是说,某些依赖对象不是必需的,系统可以正常工作即使这些依赖没有被注入。

优势:

  • 更灵活,允许依赖项在对象实例化后被动态修改。
  • 可以通过 setter 方法来修改对象的依赖关系,便于配置和修改。

示例:

@Service
public class UserService {private UserRepository userRepository;@Autowired  // 注解表明 Setter 注入public void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}
}

在这个例子中,UserRepository 的实例通过 setUserRepository 方法注入。Spring 会通过反射调用 setter 方法进行注入。

3.3. 字段注入

字段注入是通过直接在字段上使用 @Autowired 注解来实现依赖注入。虽然这种方式非常简洁,但通常不推荐使用,因为它无法通过构造函数来保证依赖的完整性,并且也不容易进行单元测试。

优势:

  • 简洁,代码量少,开发效率高。

缺点:

  • 由于没有通过构造器或 setter 方法,依赖关系不够明确,可能导致代码难以理解和维护。
  • 不容易进行单元测试,特别是在无法控制依赖注入的情况下。

示例:

@Service
public class UserService {@Autowired  // 直接注入字段private UserRepository userRepository;
}

这种方式下,userRepository 通过 Spring 自动注入,无需手动调用构造器或 setter 方法。

4. 注意事项

虽然依赖注入为应用带来了很多优势,但在使用过程中,我们也需要注意以下几个问题:

4.1. 循环依赖

循环依赖 是指两个或多个对象相互依赖,导致 Spring 容器无法正常实例化对象。例如,A 类依赖 B 类,B 类又依赖 A 类,这样会导致一个死循环。

Spring 可以通过三级缓存机制解决循环依赖问题:

  1. 一级缓存:保存已创建的 Bean 实例。
  2. 二级缓存:保存 Bean 的代理对象。
  3. 三级缓存:保存 Bean 的引用,以便能够完成后续的注入。

尽管 Spring 会自动处理循环依赖,但应尽量避免出现复杂的循环依赖,特别是跨层级的依赖循环。

4.2. 空依赖

确保依赖项不为空,特别是当某些依赖是必需的时。如果某个依赖对象为空,将会导致应用运行时出现异常。构造器注入是最安全的方式,因为它强制确保所有必需的依赖项在对象创建时就被注入。

4.3. 选择注入方式
  • 构造器注入:推荐在大多数情况下使用,尤其当依赖关系是不可变的且必须提供时。构造器注入有助于在对象创建时就完成依赖注入,避免了空依赖问题。
  • Setter 注入:适合可选依赖或需要在对象创建后动态设置的依赖。需要注意的是,如果某些依赖是必需的,使用 setter 注入时应该做好适当的校验。
  • 字段注入:虽然这种方式简单,但通常不推荐使用,因为它隐藏了依赖关系,可能导致代码难以理解、测试和维护。

​ 依赖注入(DI)是 Spring 的核心特性,它通过控制反转(IoC)实现对象的自动管理与注入,从而降低了模块之间的耦合度。使用依赖注入能够提高代码的灵活性、可维护性,并简化测试工作。开发者应根据不同的场景和需求选择合适的注入方式,并注意避免循环依赖和空依赖等问题

控制反转(IoC)的原理与实现

控制反转(Inversion of Control,简称 IoC)是 Spring 框架的核心思想之一。IoC 的核心概念是将对象的创建和管理权从程序员手中反转到 Spring 容器。这种设计使得对象之间的依赖关系不再由程序员手动管理,而是由框架统一处理,从而实现松耦合和模块化。

1. 控制反转的定义

控制反转(Inversion of Control, IoC) 是一种设计原则,它的核心思想是将对象的创建、配置、依赖管理等控制权交给外部容器,而不是让程序员直接控制。这种方式使得系统的各个模块更加独立,且系统间的依赖关系更加清晰。在 Spring 框架中,IoC 容器负责管理对象的生命周期、依赖关系和配置,从而实现了控制反转。

在传统的编程中,开发者通常自己实例化和管理对象,而在 IoC 模式下,Spring 容器负责根据配置文件或注解自动实例化、配置和管理对象。程序员只需专注于业务逻辑的实现,Spring 容器则负责管理对象的创建和生命周期。

2. 控制反转的实现

Spring 提供了多种方式来实现 IoC,其中最常见的两种方法是通过注解和 XML 配置来定义和管理 Bean(对象):

2.1. 通过注解实现 IoC

Spring 通过一系列的注解使得对象的声明、创建和管理变得非常简洁和自动化。开发者只需要使用特定的注解标记类,Spring 容器就会自动将这些类作为 Bean 加入到容器中进行管理。常见的注解有:

  • @Component:用于将一个普通的 Java 类标记为 Spring 管理的 Bean。
  • @Service:通常用于标记服务层的 Bean,继承自 @Component
  • @Repository:用于标记数据访问层的 Bean,继承自 @Component
  • @Controller:用于标记控制器类,继承自 @Component,通常用于 Spring MVC 中。

例如,使用 @Service 注解来标记一个服务类:

@Service
public class UserService {public void addUser() {// 添加用户的业务逻辑}
}

在这个例子中,UserService 类被 @Service 注解标记为一个 Spring Bean。Spring 会自动检测这个注解并将该类实例化并加入到 IoC 容器中,开发者无需手动管理对象的创建和生命周期。

2.2. 通过 XML 配置实现 IoC

在 Spring 框架的早期版本中,IoC 容器的配置主要是通过 XML 文件来实现的。开发者可以在 XML 配置文件中显式声明 Bean,以及它们的依赖关系。这种方式较为冗长,但在大型系统中提供了更好的控制和灵活性。

例如,在 XML 配置文件中声明一个 UserService Bean:

<bean id="userService" class="com.example.UserService"/>

这种方式明确指出了 Spring 容器中需要管理的 UserService 类,并且通过 <bean> 标签配置了类的全限定名。容器将会根据这个配置自动创建 UserService 的实例,并管理其生命周期。

2.3. 自动装配(Autowired)

无论是使用注解方式还是 XML 配置方式,Spring 都提供了 自动装配(Autowiring)的机制来自动注入依赖。自动装配允许 Spring 容器根据配置自动为 Bean 注入其依赖的其他 Bean,减少了显式定义依赖的复杂性。

自动装配常通过 @Autowired 注解来实现。这个注解可以用在构造器、Setter 方法或字段上,Spring 会自动查找和注入符合要求的 Bean 实例。

构造器注入:

@Service
public class UserService {private final UserRepository userRepository;@Autowiredpublic UserService(UserRepository userRepository) {this.userRepository = userRepository;}
}

在这个例子中,UserService 依赖于 UserRepository,Spring 会自动注入 UserRepository 实例。

字段注入:

@Service
public class UserService {@Autowiredprivate UserRepository userRepository;
}

在字段注入的方式下,userRepository 字段会自动注入一个合适的 UserRepository 实例。

2.4 常见的问题

​ 在 Spring 的自动装配机制中,虽然可以大大简化依赖注入的过程,但也可能遇到一些常见的问题,如依赖冲突和多个 Bean 注入相同的依赖。为了处理这些问题,Spring 提供了几种方式来解决这些冲突和确保正确的依赖注入。以下是一些常见问题及其解决方案:

1. 依赖冲突:多个 Bean 注入相同的依赖

如果容器中存在多个同类型的 Bean,而 Spring 无法确定注入哪个 Bean,就会出现依赖冲突。这时,可以通过以下几种方式来解决:

a) 使用 @Qualifier 注解指定 Bean 名称

@Qualifier 注解可以与 @Autowired 配合使用,指定要注入的 Bean 名称。当容器中有多个符合条件的 Bean 时,Spring 会根据 @Qualifier 中的值来选择注入特定的 Bean。

@Service
public class UserService {private final UserRepository userRepository;@Autowiredpublic UserService(@Qualifier("userRepositoryImpl") UserRepository userRepository) {this.userRepository = userRepository;}
}

在这个例子中,如果容器中存在多个 UserRepository 的实现,@Qualifier 注解确保了 userRepositoryImpl 被正确注入。

b) 使用 Bean 名称作为注解参数

如果通过 XML 配置的方式,可以为 Bean 配置一个唯一的名称,使用该名称来指定注入的 Bean。

<bean id="userRepositoryImpl" class="com.example.UserRepositoryImpl" />
<bean id="userService" class="com.example.UserService"><constructor-arg ref="userRepositoryImpl" />
</bean>
c) 使用 @Primary 注解

@Primary 注解可用来标记一个 Bean 作为首选 Bean。当多个符合条件的 Bean 存在时,Spring 会选择标记为 @Primary 的 Bean 进行注入。

@Service
@Primary
public class UserRepositoryImpl implements UserRepository {// 实现代码
}

如果没有 @Qualifier 或其他明确指定的 Bean,Spring 将自动选择带有 @Primary 注解的 Bean 进行注入。

2.5 . 构造器注入 vs. 字段注入

虽然构造器注入和字段注入都能实现自动装配,但它们各有优缺点。通常推荐使用构造器注入,因为它有以下优点:

  • 不可变性:构造器注入使得依赖变成不可变的,一旦 Bean 被创建,依赖就被固定了,这有助于减少潜在的错误。
  • 可测试性:构造器注入使得依赖更容易进行单元测试。由于所有依赖都显式地通过构造器传入,它们很容易被模拟或替换。
  • 更清晰的依赖关系:构造器注入使依赖关系显式化,而字段注入可能让依赖关系不清晰。

字段注入的缺点是容易忽略依赖关系,并且无法保证 Bean 在创建时已完全初始化。

2.6. 注入空值 (Null) 或 Bean 未初始化

如果你尝试自动装配的 Bean 为 null 或没有被正确初始化,可能会导致 NullPointerException 或其他错误。常见的解决方案包括:

a) 确保 Bean 已经声明并被容器管理

通过 @Component, @Service, @Repository 等注解确保类被 Spring 容器管理,或者在 XML 配置文件中正确声明 Bean。

b) 使用 @Autowired(required = false)

如果某个 Bean 可能不存在,可以使用 @Autowiredrequired 属性来设置是否必须注入该 Bean。如果为 false,则该 Bean 可以为空。

@Autowired(required = false)
private UserRepository userRepository;

这意味着如果 Spring 容器中没有找到符合条件的 UserRepositoryuserRepository 就会保持为 null,不会抛出异常。

2.7. 多个依赖存在时选择自动装配的策略

如果存在多个 Bean 可以满足自动装配条件(例如,有多个实现了同一接口的 Bean),而没有使用 @Qualifier@Primary 来区分,Spring 容器会抛出异常。为了避免这种情况,确保有明确的选择方式(如使用 @Qualifier@Primary)。

2.8. 循环依赖问题

Spring 自动装配可能会引发循环依赖问题。例如,A 依赖于 B,而 B 又依赖于 A。Spring 通过构造器注入时会检测到这种循环,导致应用无法启动。为了解决这个问题:

a) 使用 Setter 注入解决循环依赖

Setter 注入在 Spring 中比构造器注入支持循环依赖,因为在 Bean 创建时,Spring 会首先实例化 Bean,再通过 Setter 方法注入依赖。

@Service
public class UserService {private UserRepository userRepository;@Autowiredpublic void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}
}

​ Spring 的自动装配机制是一个强大的特性,能够自动将 Bean 注入到需要的地方,减少了手动配置的复杂性。然而,在使用过程中,可能会遇到多个 Bean 的冲突、循环依赖等问题。通过合理使用 @Autowired, @Qualifier, @Primary 等注解,结合构造器注入或 Setter 注入的选择,可以有效地解决这些问题,提高代码的可维护性和可测试性。

3. 控制反转的优点(详解及扩展)

控制反转(IoC)作为 Spring 框架的核心思想,极大地优化了传统 Java 开发中的代码结构和模块设计。以下对 IoC 的主要优点进行逐一阐述,并通过详实的例子和场景解读其具体意义和实际效果。


3.1. 松耦合

概念解析

在软件设计中,松耦合是一种降低模块之间依赖性的重要设计原则。传统编程方式中,模块通过直接实例化依赖对象的方式完成功能。这种方式的问题在于,当需要替换依赖对象或增加新功能时,通常需要修改调用方代码,从而导致耦合度过高,降低了系统的可维护性和扩展性。

IoC 的出现改变了这一状况。通过依赖注入(Dependency Injection, DI)的方式,Spring 容器负责动态地为模块注入所需的依赖,而调用方只需要面向接口编程,不关注具体实现类。


详尽示例
场景说明

我们构建一个支付系统,支持不同支付方式(例如支付宝、微信支付、银行卡支付),要求能够根据用户选择动态切换支付方式,并且未来可能需要增加新支付方式(例如 Apple Pay)。

传统实现方式(高耦合)

传统编程方式中,支付服务(OrderService)需要明确地实例化具体支付类:

public class OrderService {private AlipayService alipayService = new AlipayService();public void processPayment() {alipayService.pay();}
}
  • 问题:
    1. 当需要切换到微信支付时,必须修改 OrderService 类,代码侵入性强。
    2. 新增支付方式(例如 Apple Pay)时,OrderService 类需要增加逻辑,违反了开闭原则。
使用 IoC 实现松耦合

通过接口编程和 IoC 容器管理实现松耦合:

  1. 定义支付接口:

    public interface PaymentService {void pay();
    }
    
  2. 实现具体支付方式:

    public class AlipayService implements PaymentService {@Overridepublic void pay() {System.out.println("通过支付宝支付");}
    }public class WeChatPayService implements PaymentService {@Overridepublic void pay() {System.out.println("通过微信支付");}
    }
    
  3. 注入依赖:

    public class OrderService {private PaymentService paymentService;public OrderService(PaymentService paymentService) {this.paymentService = paymentService;}public void processPayment() {paymentService.pay();}
    }
    
  4. Spring 配置:

    <bean id="paymentService" class="com.example.AlipayService"/>
    <bean id="orderService" class="com.example.OrderService"><constructor-arg ref="paymentService"/>
    </bean>
    
  5. 替换支付方式:

    修改配置即可切换到微信支付:

    <bean id="paymentService" class="com.example.WeChatPayService"/>
    
优势分析
  • 解耦OrderService 仅依赖于接口,支付方式的具体实现由 IoC 容器管理。
  • 易扩展:新增支付方式时,只需实现 PaymentService 接口,代码完全无需修改。

3.2. 集中管理

概念解析

Spring 容器通过 IoC,将对象的创建、配置、生命周期管理统一起来,开发者不再需要手动编写冗长的实例化代码,也不必在多个类中分散管理依赖关系。所有的依赖和配置都可以通过注解或配置文件进行集中管理,极大地简化了项目的维护和升级。


详尽示例
场景说明

一个用户服务(UserService)需要依赖用户数据访问层(UserRepository)。传统方式中,这些依赖的初始化通常散落在多个代码文件中。

传统实现方式(分散管理)
public class App {public static void main(String[] args) {UserRepository repository = new UserRepository();UserService userService = new UserService(repository);userService.processUser();}
}
  • 问题:
    1. 每次使用 UserService 都需要手动实例化 UserRepository
    2. 如果 UserRepository 的依赖链条复杂,会导致初始化代码难以维护。
使用 Spring 集中管理
  1. 定义依赖:

    @Component
    public class UserRepository {// 数据操作逻辑
    }@Component
    public class UserService {private final UserRepository repository;@Autowiredpublic UserService(UserRepository repository) {this.repository = repository;}
    }
    
  2. 启用自动扫描和集中管理:

    <context:component-scan base-package="com.example"/>
    
  3. 使用ApplicationContext获取 UserService:

    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = context.getBean(UserService.class);
    userService.processUser();
    
优势分析
  • 所有依赖关系集中在 Spring 配置或注解中,避免手动创建对象。
  • 修改依赖关系时,只需修改配置,无需修改业务逻辑。

3.3. 提高可扩展性

概念解析

使用 IoC 后,系统的依赖关系通过接口定义,具体实现由容器管理。因此,开发者可以轻松替换实现类或新增模块,而无需修改调用代码。这种设计极大提高了系统的灵活性和扩展性。


详尽示例
场景说明

假设一个日志系统,初始版本只支持控制台日志,后续需要增加文件日志或远程日志。

实现步骤
  1. 定义日志接口:

    public interface Logger {void log(String message);
    }
    
  2. 实现多种日志方式:

    public class ConsoleLogger implements Logger {@Overridepublic void log(String message) {System.out.println("Console log: " + message);}
    }public class FileLogger implements Logger {@Overridepublic void log(String message) {// 写入文件逻辑System.out.println("File log: " + message);}
    }
    
  3. 使用 Spring 配置切换日志实现:

    <bean id="logger" class="com.example.ConsoleLogger"/>
    

    替换为文件日志:

    <bean id="logger" class="com.example.FileLogger"/>
    
优势分析
  • 新增日志方式时,只需实现接口,无需修改原有代码。
  • 容器通过配置注入不同实现,动态满足不同需求。

3.4. 便于单元测试

概念解析

IoC 允许开发者在测试时替换真实依赖为 Mock 对象,避免外部依赖(如数据库、API 等)对测试的影响,简化单元测试的编写。


详尽示例
场景说明

一个 UserService 类依赖 UserRepository,需要通过 ID 查询用户信息。

测试代码
  1. 定义UserService:

    public class UserService {private UserRepository repository;public UserService(UserRepository repository) {this.repository = repository;}public String getUserName(int id) {return repository.findById(id).getName();}
    }
    
  2. 使用 Mock 对象进行测试:

    @Test
    public void testGetUserName() {// 创建 Mock 对象UserRepository mockRepository = mock(UserRepository.class);when(mockRepository.findById(1)).thenReturn(new User(1, "MockUser"));// 注入 Mock 对象UserService userService = new UserService(mockRepository);// 验证assertEquals("MockUser", userService.getUserName(1));
    }
    
优势分析
  • 测试独立性强:无需数据库或外部依赖。
  • 快速定位问题,测试运行速度快。
  • 在这里插入图片描述
总结

控制反转(IoC)是 Spring 框架的核心概念之一,它通过将对象的创建和管理交给容器来实现系统组件之间的松耦合。通过 IoC,开发者可以专注于业务逻辑的实现,而将对象的管理和依赖关系交给 Spring 容器。Spring 提供了注解和 XML 配置两种方式来实现 IoC,帮助开发者更加灵活和高效地构建应用。控制反转不仅提升了系统的可维护性、可扩展性,还方便了单元测试和模块的独立开发。
在这里插入图片描述
如果这篇文章对您有帮助,就点个关注呗 😄👍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/484661.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024 数学建模国一经验分享

2024 数学建模国一经验分享 背景&#xff1a;武汉某211&#xff0c;专业&#xff1a;计算机科学 心血来潮&#xff0c;就从学习和组队两个方面指点下后来者&#xff0c;帮新人避坑吧 2024年我在数学建模比赛中获得了国一&#xff08;教练说论文的分数是湖北省B组第一&#xff0…

Linux 35.6 + JetPack v5.1.4之RTP实时视频Python框架

Linux 35.6 JetPack v5.1.4之RTP实时视频Python框架 1. 源由2. 思路3. 方法论3.1 扩展思考 - 慎谋而后定3.2 扩展思考 - 拒绝拖延或犹豫3.3 扩展思考 - 哲学思考3.4 逻辑实操 - 方法论 4 准备5. 分析5.1 gst-launch-1.05.1.1 xvimagesink5.1.2 nv3dsink5.1.3 nv3dsink sync05…

渤海证券基于互联网环境的漏洞主动防护方案探索与实践

来源&#xff1a;中国金融电脑 作者&#xff1a;渤海证券股份有限公司信息技术总部 刘洋 伴随互联网业务的蓬勃发展&#xff0c;证券行业成为黑客进行网络攻击的重要目标之一&#xff0c;网络攻击的形式也变得愈发多样且复杂。网络攻击如同悬于行业之上的达摩克利斯之剑&…

隐私安全大考,Facebook 如何应对?

随着数字时代的到来和全球互联网用户的快速增长&#xff0c;隐私安全问题已上升为网络世界的重要议题。社交媒体巨头Facebook因其庞大的用户群体和大量的数据处理活动&#xff0c;成为隐私问题的聚焦点。面对隐私安全的大考&#xff0c;Facebook采取了一系列策略来应对这些挑战…

04 创建一个属于爬虫的主虚拟环境

文章目录 回顾conda常用指令创建一个爬虫虚拟主环境Win R 调出终端查看当前conda的虚拟环境创建 spider_base 的虚拟环境安装完成查看环境是否存在 为 pycharm 配置创建的爬虫主虚拟环境选一个盘符来存储之后学习所写的爬虫文件用 pycharm 打开创建的文件夹pycharm 配置解释器…

旅游管理系统的设计与实现

文末获取源码和万字论文&#xff0c;制作不易&#xff0c;感谢点赞支持。 毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;旅游管理系统的设计与实现 摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#…

QT 中 sqlite 数据库使用

一、前提 --pro文件添加sql模块QT core gui sql二、使用 说明 --用于与数据库建立连接QSqlDatabase--执行各种sql语句QSqlQuery--提供数据库特定的错误信息QSqlError查看qt支持的驱动 QStringList list QSqlDatabase::drivers();qDebug()<<list;连接 sqlite3 数据库 …

HENU祖传课堂测试第三弹:Java的文件输入输出

题目&#xff1a;设定文件file1内容&#xff1a;年级,班级&#xff0c;学号&#xff0c;姓名分为四行。 读取文件file1中的内容&#xff0c;若其字符<3个将其转入file2,如若是字符&#xff1e;3个转入file3 代码如下 import java.io.*; import java.nio.file.*; import j…

React Native 速度提升 550%

React Native 爱好者们!🌟 您准备好听一些激动人心的消息了吗?React Native 刚刚发布了其最大的更新之一:一种全新的架构,彻底改变了我们构建移动应用程序的方式。如果您想知道这对您的项目和开发体验意味着什么,请继续关注!我们正在深入探讨这个改变游戏规则的事物;您…

Qt中的 tableView 设置 二进制 十六进制 序号表头

二 进制序号 因为QTableView的垂直表头并不支持使用委托来自定义。 相反&#xff0c;可以通过将自定义的QWidget作为QHeaderView的标签来实现这一目标。 代码&#xff1a; #include <QApplication> #include <QMainWindow> #include <QVBoxLayout> #include …

中国移动量子云平台:算力并网590量子比特!

在技术革新的浪潮中&#xff0c;量子计算以其独特的并行处理能力和指数级增长的计算潜力&#xff0c;有望成为未来技术范式变革和颠覆式创新应用的新源泉。中国移动作为通信行业的领军企业&#xff0c;致力于量子计算技术研究&#xff0c;推动量子计算产业的跨越式发展。 量子云…

D614 PHP+MYSQL +失物招领系统网站的设计与现 源代码 配置 文档

失物招领系统 1.摘要2. 系统开发的背景和意义3.功能结构图4.界面展示5.源码获取 1.摘要 随着互联网的迅速发展&#xff0c;人们的生产生活方式逐渐发生改变&#xff0c;传统的失物招领也可以通过网络处理。本网站是基PHP技术的一款综合性较强的西南民族大学PHP失物招领系统。 …

YOLOv8实战道路裂缝缺陷识别

本文采用YOLOv8作为核心算法框架&#xff0c;结合PyQt5构建用户界面&#xff0c;使用Python3进行开发。YOLOv8以其高效的实时检测能力&#xff0c;在多个目标检测任务中展现出卓越性能。本研究针对道路裂缝数据集进行训练和优化&#xff0c;该数据集包含丰富的道路裂缝图像样本…

并发编程(15)——基于同步方式的线程安全的栈和队列

文章目录 十四、day141. 线程安全的栈1.1 存在隐患的栈容器1.2 优化后的栈容器 2. 线程安全的队列2.1 基于智能指针的线程安全的队列2.2 不同互斥量管理队首、队尾的队列 十四、day14 在并发编程&#xff08;1&#xff09;并发编程&#xff08;5&#xff09;中&#xff0c;我们…

容器第五天(day042)

1.安装 yum install -y docker-compose 2.配置 配置文件名字&#xff1a;docker-compose.yaml或docker-compose.yml 3.启动 docker-compose up -d

离散数学重点复习

第一章.集合论 概念 1.集合是不能精确定义的基本数学概念.通常是由指定范围内的满足给定条件的所有对象聚集在一起构成的 2.制定范围内的每一个对象称为这个集合的元素 3.固定符号如下: N:自然数集合 Z:整数集合 Q:有理数集合 R:实数集合 C:复数集合 4.集合中的元素是…

docker学习笔记(四)--DockerFile

文章目录 一、什么是Dockerfile二、docker build命令三、dockerfile指令3.1 FROM3.2 ENV3.3 WORKDIR3.4 RUN3.5 CMD3.6 ENTRYPOINT3.7 EXPOSE3.8 ARG3.9 ADD3.10 COPY3.11 VOLUME 四、dockerfile示例 一、什么是Dockerfile Dockerfile 是用于构建 Docker 镜像的脚本文件&#…

动手学深度学习-线性神经网络-1线性回归

目录 线性回归的基本元素 线性模型 损失函数 解析解 随机梯度下降 用模型进行预测 矢量化加速 正态分布与平方损失 从线性回归到深度网络 神经网络图 生物学 小结 回归&#xff08;regression&#xff09;是能为一个或多个自变量与因变量之间关系建模的一类方法。…

BERT模型的输出格式探究以及提取出BERT 模型的CLS表示,last_hidden_state[:, 0, :]用于提取每个句子的CLS向量表示

说在前面 最近使用自己的数据集对bert-base-uncased进行了二次预训练&#xff0c;只使用了MLM任务&#xff0c;发现在加载训练好的模型进行输出CLS表示用于下游任务时&#xff0c;同一个句子的输出CLS表示都不一样&#xff0c;并且控制台输出以下警告信息。说是没有这些权重。…

设计模式:18、组合模式

目录 0、定义 1、组合模式的三种角色 2、组合模式的UML类图 3、示例代码 0、定义 将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使用户对单个对象和组合对象的使用具有一致性。 1、组合模式的三种角色 抽象组件&#xff08;Component&#xff09;&#…