一、循环依赖 1.1、什么是循环依赖 首先,什么是循环依赖?这个其实好理解,就是两个 Bean 互相依赖,类似下面这样:
@Service public class AService { @Autowired BService bService; } @Service public class BService { @Autowired AService aService; } AService 和 BService 互相依赖:
这个应该很好理解。
1.2、循环依赖的类型 一般来说,循环依赖有三种不同的形态,上面 1.1 小节是其中一种。
另外两种分别是三者依赖,如下图:
这种循环依赖一般隐藏比较深,不易发觉。
还有自我依赖,如下图:
一般来说,如果我们的代码中出现了循环依赖,则说明我们的代码在设计的过程中可能存在问题,我们应该尽量避免循环依赖的发生。不过一旦发生了循环依赖,Spring 默认也帮我们处理好了,当然这并不能说明循环依赖这种代码就没问题。实际上在目前最新版的 Spring 中,循环依赖是要额外开启的,如果不额外配置,发生了循环依赖就直接报错了。
另外,Spring 并不能处理所有的循环依赖,后面会和大家进行分析。
二、循环依赖解决思路 2.1、解决思路 那么对于循环依赖该如何解决呢?其实很简单,加入一个缓存就可以了。
来看下面这张图:
如上图所示,引入了一个缓存池。
当我们需要创建 AService 的实例的时候,会首先通过 Java 反射创建出来一个原始的 AService,这个原始 AService 可以简单理解为刚刚 new 出来(实际是刚刚通过反射创建出来)还没设置任何属性的 AService,此时,我们把这个 AService 先存入到一个缓存池中。
接下来我们就需要给 AService 的属性设置值了,同时还要处理 AService 的依赖,这时我们发现 AService 依赖 BService,那么就去创建 BService 对象,结果创建 BService 的时候,发现 BService 依赖 AService,那么此时就先从缓存池中取出来 AService 先用着,然后继续 BService 创建的后续流程,直到 BService 创建完成后,将之赋值给 AService,此时 AService 和 BService 就都创建完成了。
1、简介 本文将带你了解如何在 Spring Boot 中自动装配具有多个实现的接口,以及一些常见用例。这是一个强大的功能,允许开发人员动态地将接口的不同实现注入到组件中。
2、默认行为 默认情况下,当接口存在多个实现并试图将该接口自动装配到组件中时,会遇到异常:“required a single bean, but X were found”。原因很简单: Spring 不知道我们想在该组件中注入哪个实现。幸运的是,Spring 提供了多种工具,可以明确要注入的实现。
3、@Qualifier 通过 @Qualifier 注解,我们可以在接口的多个实现组件中指定要自动装配的 Bean。我们可以将其应用到组件本身,为其自定义名称:
@Service @Qualifier("goodServiceA-custom-name") public class GoodServiceA implements GoodService { // 实现 } 然后,用 @Qualifier 对参数进行注解,以指定想要的实现:
@Autowired public SimpleQualifierController( @Qualifier("goodServiceA-custom-name") GoodService niceServiceA, @Qualifier("goodServiceB") GoodService niceServiceB, GoodService goodServiceC ) { this.goodServiceA = niceServiceA; this.goodServiceB = niceServiceB; this.goodServiceC = goodServiceC; } 如上,使用自定义 Qualifier 自动装配了 GoodServiceA。
而,GoodServiceB 实现却没有自定义 Qualifier:
@Service public class GoodServiceB implements GoodService { // 实现 } 这种情况下,通过类名对组件进行了自动装配。这种自动装配的限定符(Qualifier)使用驼峰形式,例如,如果类名是 MyAwesomeClass,那么 myAwesomeClass 就是一个有效的限定符。
1、概览 WebClient 是一个简化 HTTP 请求执行过程的接口。与 RestTemplate 不同,它是一个响应式非阻塞客户端,可以消费和操作 HTTP 响应。虽然它被设计为非阻塞型,但也可用于阻塞型场景。
本文将带你了解 WebClient 接口中的关键方法,包括 retrieve()、exchangeToMono() 和 exchangeToFlux(),以及它们之间的差异。
2、示例项目设置 首先,创建一个 Spring Boot 应用,在 pom.xml 中添加 spring-boot-starter-webflux 依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> <version>3.2.4</version> </dependency> 该依赖提供了 WebClient 接口,用于执行 HTTP 请求。
另外,来看看 https://jsonplaceholder.typicode.com/users/1 请求的 GET 响应示例:
{ "id": 1, "name": "Leanne Graham", // ... } 创建一个名为 User 的 POJO 类:
class User { private int id; private String name; // 构造函数、Getter、Setter 方法省略 } 来自 JSONPlaceholder API 的 JSON 响应将被反序列化并映射到 User 类的实例。
1、简介 本文将带你了解如何测试启用了定时任务(@EnableScheduling)的 Spring 应用,以及如何在测试过程中禁用定时任务。
2、示例 首先来看一个示例,假设我们有一个系统,允许公司的代表向客户发送通知。其中一些通知是时间敏感的,应该立即发送,但有些通知应该等到下一个工作日再发送。因此,我们需要一个机制来定期尝试发送这些通知:
public class DelayedNotificationScheduler { private NotificationService notificationService; @Scheduled(fixedDelayString = "${notification.send.out.delay}", initialDelayString = "${notification.send.out.initial.delay}") public void attemptSendingOutDelayedNotifications() { notificationService.sendOutDelayedNotifications(); } } attemptSendingOutDelayedNotifications() 方法上添加了 @Scheduled 注解。当 initialDelayString 配置的时间过去后,该方法将被首次调用。执行结束后,Spring 会在 fixedDelayString 参数配置的时间过后再次调用该方法。该方法本身将实际逻辑委托给了 NotificationService。
当然,我们还需要开启调度功能。为此,需要在 @Configuration 类上添加 @EnableScheduling 注解。
3、集成测试中定时任务的问题 首先,为通知应用编写一个基本的集成测试:
@SpringBootTest( classes = { ApplicationConfig.class, SchedulerTestConfiguration.class }, properties = { "notification.send.out.delay: 10", "notification.send.out.initial.delay: 0" } ) public class DelayedNotificationSchedulerIntegrationTest { @Autowired private Clock testClock; @Autowired private NotificationRepository repository; @Autowired private DelayedNotificationScheduler scheduler; @Test public void whenTimeIsOverNotificationSendOutTime_thenItShouldBeSent() { ZonedDateTime fiveMinutesAgo = ZonedDateTime.
1、概览 本文将带你了解如何测试 Spring Application Event,以及如何使用 Spring Modulith 的测试库。
2、Application Event Spring 提供了 Application Event(观察者设计模式),允许组件在保持松散耦合的同时相互通信。我们可以使用 ApplicationEventPublisher Bean 发布内部事件,这些事件都是纯 Java 对象,所有已注册的监听器(Listener)都会收到通知。
例如,当成功创建 Order 时,OrderService 组件可以发布 OrderCompletedEvent:
@Service public class OrderService { private final ApplicationEventPublisher eventPublisher; // 构造函数 public void placeOrder(String customerId, String... productIds) { Order order = new Order(customerId, Arrays.asList(productIds)); // 验证和下单的业务逻辑 OrderCompletedEvent event = new OrderCompletedEvent(savedOrder.id(), savedOrder.customerId(), savedOrder.timestamp()); eventPublisher.publishEvent(event); } } 把 OrderCompletedEvent 对象作为应用事件发布,不同模块中监听这些事件的组件会收到通知。
假设 LoyaltyPointsService 会对这些事件做出反应,以奖励下单客户积分。要实现这一点,可以使用 Spring 的 @EventListener 注解:
1、简介 本文将带你了解 Spring 中 @Transactional 和 @Async 注解之间的兼容性。
2、 了解 @Transactional 和 @Async @Transactional 注解是 Spring 提供的声明式事务注解。可以让多个业务方法在同一个事务中执行,只有所有方法都正常执行完毕后事务才会提交。如果任何一个方法在调用过程中抛出了异常,那么事务就会回滚。
@Async 注解用于执行异步任务,如果从一个线程调用 @Async 方法或类,Spring 会使用另一个线程来运行该方法,从而提高执行效率。
在有些情况下,我们需要在代码中同时使用 @Transactional 和 @Async 来保业务数据的一致性以及性能。
3、@Transactional 能和 @Async 一起用吗? 异步 和 事务 如果使用不当,可能会带来数据不一致等问题。
关于这一点,需要充分了解 Spring 的事务上下文和上下文之间的数据传播。
3.1、创建示例应用 本文使用银行的转账功能来说明事务和异步代码的使用。简而言之,是一个转账的场景,从一个账户中扣除资金并将其添加到另一个账户。
我们可以把它想象成数据库操作,比如 select 相关账户并 update 其资金余额:
public void transfer(Long depositorId, Long favoredId, BigDecimal amount) { Account depositorAccount = accountRepository.findById(depositorId) .orElseThrow(IllegalArgumentException::new); Account favoredAccount = accountRepository.findById(favoredId) .orElseThrow(IllegalArgumentException::new); depositorAccount.setBalance(depositorAccount.getBalance().subtract(amount)); favoredAccount.setBalance(favoredAccount.getBalance().add(amount)); accountRepository.save(depositorAccount); accountRepository.save(favoredAccount); } 首先使用 findById() 查找相关账户,如果给定 ID 的账户不存在,则抛出 IllegalArgumentException 异常。
1、概览 本文将带你了解如何在 Spring 中使用运行时参数创建一个 Prototype Scope Bean。
在 Spring 中,有许多不同的 Bean Scope,默认的 Scope 是 Singleton(单例)。Singleton Scope 的 Bean 将始终产生相同的对象。
如果每次都需要从容器中获得一个新实例,可以使用 Pototype Scope Bean。然而,在大多数情况下,如果我们想要从一个 Singleton Bean 实例化 Pototype Bean,或者将动态参数传递给 Pototype Bean,可能会遇到一些问题。
2、使用动态参数创建 Prototype Bean 有时,我们需要在每次初始化时将动态参数作为输入来初始化 Spring Bean。通过 Spring,可以使用多种方法为 Prototype Bean 分配不同的动态参数。
首先,创建一个 Prototype Bean Employee :
public class Employee { private String name; public Employee(String name) { this.name = name; } public void printName() { System.out.println(name); } } 为 Employee Prototype Bean 创建一个配置:
1、概览 本文将带你了解如何根据自定义属性动态注册 Bean。
主要是学习 BeanDefinitionRegistryPostProcessor 接口,以及如何使用它将 Bean 添加到 Application Context 中。
2、设置 创建一个简单的 Spring Boot 应用。
首先,定义一个要动态注册的 Bean。然后,提供一个属性来决定如何注册 Bean。最后,定义一个配置类,它将根据自定义属性注册 Bean。
2.1、依赖 添加 Maven 依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>3.2.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>3.2.3</version> <scope>test</scope> </dependency> 添加 spring-boot-starter 和 spring-boot-starter-test 依赖。
2.2、Bean 类 接下来,根据自定义 application properties 定义要注册的 ApiClient:
public class ApiClient { private String name; private String url; private String key; // Getter、Setter 和构造函数 public String getConnectionProperties() { return "Connecting to " + name + " at " + url; } } 假设我们希望根据提供的属性使用这个 Bean 连接到不同的 API。我们不想为每个 API 创建类定义,而是希望动态地为每个 API 定义属性并注册该 Bean。
1、概览 缓存数据意味着我们的应用无需访问速度较慢的存储层,从而提高了性能和响应速度。我们可以使用任何内存实现库(如 Caffeine)来实现缓存。
虽然这样做可以提高数据检索的性能,但如果应用部署在多个副本上,那么实例之间就无法共享缓存。为了解决这个问题,可以引入一个分布式缓存层,所有实例都可以访问它。
本文将带你了解如何在 Spring 中使用 Spring 的缓存支持(spring-cache)实现两级缓存,以及在本地缓存层缓存失效时如何调用分布式缓存层。
2、示例 Spring Boot 应用 创建一个简单的应用,调用数据库获取一些数据。
2.1、Maven 依赖 首先,添加 spring-boot-starter-web 依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.1.5</version> </dependency> 2.2、实现 Service 实现一个 Spring Service,从 Repository 中获取数据。
首先,创建 Customer 实体类:
public class Customer implements Serializable { private String id; private String name; private String email; // 标准 Getter / Setter } 然后,实现 CustomerService 类和 getCustomer 方法:
@Service public class CustomerService { private final CustomerRepository customerRepository; public Customer getCustomer(String id) { return customerRepository.
1、简介 Servlet Filter(过滤器)为拦截和处理传入的请求提供了强大的机制。
本文将会带你了解在 Servlet Filter 中无缝获取 Spring Bean 的各种方法,这种需求在 Spring Web 应用中很常见。
2、Servlet Filter 中 @Autowired 的限制 Spring 的依赖注入机制 @Autowired,是一种方便的方式来将依赖注入到由 Spring 管理的组件中。但它无法与 Servlet Filter 完美配合。这是因为 Servlet Filter 是由 Servlet 容器初始化的,通常是在 Spring 的 ApplicationContext 完全加载和初始化之前 。
因此,当容器实例化 Servlet Filter 时 ,Spring Context 可能尚未可用,从而导致在尝试使用 @Autowired 注解时 ,依赖为 null 或未初始化。
3、项目设置 创建一个通用的 LoggingService,它将自动装配到 Filter 中:
@Service public class LoggingService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); public void log(String message,String url){ logger.info("Logging Request {} for URI : {}",message,url); } } 然后,创建 Filter,拦截传入的 HTTP 请求,并使用 LoggingService 依赖记录 HTTP 方法和 URI 的详细信息: