Spring

Spring 在运行动态地创建 Prototype Scope Bean

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 创建一个配置:

根据属性(Properties)动态注册 Spring 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。

Spring 实现两级缓存

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.

在 Servlet Filter 中自动装配 Spring Bean

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 的详细信息:

在 Spring 中使用 AOP 记录执行日志

1、概览 Aspect-Oriented Programming(面向切面编程,简称 AOP)是一种范式,它能让我们在整个应用中隔离事务管理或日志记录等交叉问题,而不会干扰业务逻辑。 本文将带你了解如何在 Spring 中使用 AOP 记录执行日志。 2、不使用 AOP 记录日志 通常,我们会在方法的开始和结束处输出日志。这样,就能跟踪应用的执行流程。此外,还可以捕获传递给特定方法的值及其返回值。 示例如下: public String greet(String name) { logger.debug(">> greet() - {}", name); String result = String.format("Hello %s", name); logger.debug("<< greet() - {}", result); return result; } 尽管上面的实现看起来是一个标准的解决方案,但日志语句在代码中增加了复杂性。 如果没有日志记录,只需要一行代码就可以完成: public String greet(String name) { return String.format("Hello %s", name); } 3、面向切面(AOP)编程 顾名思义,面向切面的编程(Aspect-Oriented Programming)侧重于切面,而不是对象和类。我们可以使用 AOP 编程为特定应用实现额外功能,而无需修改其当前实现。 3.1、AOP 概念 在深入学习之前,让我们先从高层次来了解一下 AOP 的基本概念。 切面(Aspect):横切关注点或我们希望在整个应用中应用的功能。 连接点(Join Point):应用流程中我们希望应用切面的点。 Advice:在特定连接点应执行的操作。 Pointcut:应在其中应用某一切面的连接点集合。 另外,Spring AOP 仅支持方法执行的连接点。可以考虑使用 AspectJ 等编译时库为字段、构造函数、静态初始化器等创建切面。

Spring Webclient 自定义 JSON 反序列化

1、概览 本文将带你了解如何在 Spring WebClient 中自定义 JSON 的反序列化(Deserialization)。 2、为什么需要自定义反序列化? Spring WebFlux 模块中的 Spring WebClient 通过 Encoder 和 Decoder 组件处理序列化和反序列化。编码器(Encoder)和解码器(Decoder)是表示读取和写入内容的接口。默认情况下,spring-core 模块提供 byte[]、ByteBuffer、DataBuffer、Resource 和 String 编码器和解码器实现。 Jackson 是一个 JSON 库,它通过 ObjectMapper 将 Java 对象序列化为 JSON,并将 JSON 字符串反序列化为 Java 对象。ObjectMapper 包含内置的配置选项,可以使用 Deserialization Feature 进行开启或关闭。 当 Jackson 库提供的默认行为无法满足我们的特定需求时,就有必要定制反序列化过程。为了在序列化和反序列化过程中修改行为,ObjectMapper 提供了一系列配置选项供我们设置。因此,我们需要将这个自定义的 ObjectMapper 注册到 Spring WebClient 中,以便在序列化和反序列化中使用。 3、如何自定义 ObjectMapper? 自定义 ObjectMapper 可以在全局应用程序级别与 WebClient 关联,也可以与特定请求关联。 以一个获取客户订单详细信息的 GET 端点为例。 OrderResponse Model 如下: { "orderId": "a1b2c3d4-e5f6-4a5b-8c9d-0123456789ab", "address": [ "123 Main St", "Apt 456", "Cityville" ], "orderNotes": [ "Special request: Handle with care", "Gift wrapping required" ], "orderDateTime": "2024-01-20T12:34:56" } 对于上述响应的一些反序列化规则如下:

Spring Boot 实现 gPRC 服务调用

1、简介 gRPC 是一个高性能的开源 RPC 框架,最初由谷歌开发。它有助于消除样板代码,并在数据中心内外连接多语言服务。该 API 基于 Protocol Buffer,它提供了一个 protoc 编译器,用于生成不同支持的语言的代码。 我们可以将 gRPC 视为 REST、SOAP 或 GraphQL 的替代品,它建立在 HTTP/2 的基础上,可以使用多路复用或流式连接等功能。 本文将带你了解如何使用 Spring Boot 实现 gRPC 服务提供者和消费者。 2、面临的问题 首先,Spring Boot 并不直接支持 gRPC。它只支持 Protocol Buffer,允许我们实现基于 protobuf 的 REST 服务。因此,需要通过使用第三方库来加入 gRPC: 平台相关的编译器:protoc 编译器是平台相关的。因此,如果在构建过程中需要生成存根(Stub),构建过程会变得更加复杂且容易出错。 依赖: 需要在 Spring Boot 应用中添加兼容的依赖。不幸的是,Java 的 protoc 添加了 javax.annotation.Generated 注解(Issues),这迫使我们需要添加一个依赖项来编译旧的 Java EE 注解库。 服务器运行时:gRPC 服务提供者需要在服务器中运行。gRPC for Java 项目提供了一个 shaded Netty,我们需要将其包含在 Spring Boot 应用中,或者用 Spring Boot 已经提供的服务器来代替。 消息传输: Spring Boot 提供了不同的客户端,如 RestClient(阻塞)或 WebClient(非阻塞),但遗憾的是,这些客户端无法配置和用于 gRPC,因为 gRPC 在阻塞和非阻塞调用中都使用了 自定义 transport 技术。 配置: 由于 gRPC 使用了自己的技术,我们需要配置属性以按照 Spring Boot 的方式对其进行配置。 3、示例项目 幸运的是,可以使用第三方 Spring Boot Starter 来应对这些挑战,例如 LogNet 或 grpc ecosystem project。这两个 Starter 都很容易整合,但后者同时支持提供者和消费者以及许多其他集成功能,因此本文选择了后者。

在 Spring Boot 中把 YAML 属性绑定到 Map

1、概览 本文将带你了解如何在 Spring Boot 中把 YAML 属性注入到 Map。 2、Spring 中的 YAML 文件 使用 YAML 文件存储外部配置数据是 Spring 开发人员的常见做法。Spring 支持使用 YAML 作为 Properties 的替代。 Spring 底层使用 SnakeYAML 来解析 YAML。 话不多说,来看看典型的 YAML 文件是什么样的: server: port: 8090 application: name: myapplication url: http://myapplication.com 如你所见,YAML 文件不言自明,而且更易于人阅读。YAML 提供了一种精美而简洁的方式来存储层次化的配置数据。 默认情况下,Spring Boot 会在应用启动时从 application.properties 或 application.yml 中读取配置属性。不过,可以使用 @PropertySource 来加载自定义 YAML 文件。 熟悉了 YAML 文件后,来看看如何在 Spring Boot 中将 YAML 属性注入到 Map 中。 3、将 YAML 属性注入到 Map 通过 @ConfigurationProperties 注解,Spring Boot 可轻松地将配置文件中的外部属性直接注入 Java 对象。

Spring 配置 Kafka 死信队列

1、简介 本文将带你了解如何在 Spring 中为 Apache Kafka 配置死信队列。 2、死信队列 死信队列(Dead Letter Queue,DLQ)用于存储由于各种原因无法正确处理的消息,例如间歇性系统故障、无效的消息模式或损坏的内容。这些消息可以稍后从 DLQ 中移除,以进行分析或重新处理。 下图是 DLQ 机制的简化流程: 通常情况下,使用 DLQ 是一个不错的主意,但也有一些情况下应该避免使用 DLQ。例如,在对消息的精确顺序很重要的队列中,不建议使用 DLQ,因为重新处理 DLQ 消息会打乱消息的到达顺序。 3、Spring Kafka 中的死信队列 在 Spring Kafka 中,与 DLQ 概念相对应的是死信 Topic(DLT)。 接下来,我们通过一个简单的支付系统来介绍 DLT 应该如何使用。 3.1、Model 类 从 Model 类开始: public class Payment { private String reference; private BigDecimal amount; private Currency currency; // Get、Set 方法省略 } 再实现一个用于创建事件的方法: static Payment createPayment(String reference) { Payment payment = new Payment(); payment.setAmount(BigDecimal.valueOf(71)); payment.

Spring 事务最佳实践

概览 本文将带你了解各种 Spring 事务的最佳实践,以保证底层业务的数据完整性。 数据完整性至关重要。如果没有适当的事务处理,应用就很容易出现竞赛条件,从而给底层业务带来可怕的后果。 模拟竞赛条件 以一个实际问题为例,说明在构建基于 Spring 的应用时,应该如何处理事务。 使用以下 Service 层和 Dao 层组件来实现转账服务: 使用最简单的 Dao 层实现来说明不按业务要求处理事务会发生什么情况: @Repository @Transactional(readOnly = true) public interface AccountRepository extends JpaRepository<Account, Long> { @Query(value = """ SELECT balance FROM account WHERE iban = :iban """, nativeQuery = true) long getBalance(@Param("iban") String iban); @Query(value = """ UPDATE account SET balance = balance + :cents WHERE iban = :iban """, nativeQuery = true) @Modifying @Transactional int addBalance(@Param("iban") String iban, @Param("cents") long cents); } getBalance 和 addBalance 方法都使用 Spring 的 @Query 注解来定义原生 SQL 查询,以检索或者修改用户的账户余额。