为什么在 Spring 中不推荐使用字段注入

1、概览 当我们在 IDE 中运行代码分析工具时,它可能会对带有 @Autowired 注解的字段发出 “Field injection is not recommended” 的警告。 在本教程中,我们将探讨为什么不推荐字段注入,以及我们可以使用哪些替代方法。 2、依赖注入(DI) 依赖注入是一种设计模式,用于管理对象之间的依赖关系。它通过外部容器提供对象的依赖,使得对象之间的关系更加灵活、可配置和可测试。依赖注入提高了代码的可维护性、可测试性和可扩展性。它是 Spring 框架的核心功能之一。 我们可以通过三种方式注入依赖对象,即: 构造函数注入。 Setter 方法注入。 字段注入。 第三种方法是使用 @Autowired 注解将依赖直接注入类中。虽然这可能是最简单的方法,但它可能会导致一些潜在的问题。 更重要的是,即使是在 Spring 的官方文档 中,也不再介绍字段注入。 3、Null 安全 让我们定义 EmailService 类并使用字段注入 EmailValidator 依赖: @Service public class EmailService { @Autowired private EmailValidator emailValidator; } 现在,让我们添加 process() 方法: public void process(String email) { if(!emailValidator.isValid(email)){ throw new IllegalArgumentException(INVALID_EMAIL); } // ... } 我们可以使用默认构造函数直接创建 EmailService 实例。但只有在提供了 EmailValidator 依赖的情况下,EmailService 才能正常工作。 EmailService emailService = new EmailService(); emailService.

在 Spring Boot 应用中使用 Postgresql 作为 Message Broker

1、简介 在本教程中,我们将介绍如何使用 PostgreSQL 的 LISTEN / NOTIFY 命令来在 Spring Boot 应用中实现简单的 MQ。 2、PostgreSQL 的 LISTEN/NOTIFY 机制简介 简单地说,这些命令允许连接的客户端通过普通的 PostgreSQL 连接交换信息。客户端使用 NOTIFY 命令向 channel 发送通知以及可选的 string payload。 channel 可以是任何有效的 SQL 标识符,其工作原理与传统 MQ 系统中的 topic 类似。这意味着 payload 将发送给该特定 channel 的所有活动的监听器(listener)。如果没有 payload,监听者只会收到一个空通知。 要开始接收通知,客户端需要使用 LISTEN 命令,该命令将 channel 名称作为唯一参数。该命令会立即返回,因此客户端可以使用同一连接继续执行其他任务。 通知机制具有一些重要的特性: channel 名称在数据库中是唯一的。 客户端使用 LISTEN/NOTIFY 时无需特殊授权。 在事务中使用 NOTIFY 时,客户端只有在事务成功完成时才会收到通知。 此外,如果在一个事务中使用相同的 payload 向同一 channel 发送多个 NOTIFY 命令,客户端将只收到一个通知。 3、使用 PostgreSQL 作为 Message Broker 的场景 鉴于 PostgreSQL 通知的特性,我们不禁要问,什么时候使用它而不是 RabbitMQ 或类似的成熟 message broker。这需要权衡利弊。一般来说,选择后者意味着:

如何解决 Spring Boot POST 请求中的 403 错误

1、概览 在 web 开发过程中,经常会遇到 HTTP 403 forbidden error。 在本教程中,我们将学习如何解决 Spring Boot POST 请求中的 403 错误。我们将首先了解 403 错误的含义,然后探讨在 Spring Boot 应用程序中解决该错误的步骤。 2、403 Error 是什么? HTTP 403 错误(通常称为 “Forbidden” 错误)是一种状态代码,表示服务器理解了请求,但选择不授权。这通常意味着客户端没有权限访问请求的资源。 需要注意的是,该错误不同于 401 错误,后者表示服务器需要对客户端进行身份认证,但尚未收到有效凭证。 “401” 表示需要认证,“403” 表示认证过了(不需要认证),但是没有权限。 3、导致 403 Error 的原因 在 Spring Boot 应用程序中,有几个因素会触发 403 错误。其中之一就是客户端未能提供身份认证凭据。在这种情况下,服务器因无法认证客户端的权限而拒绝请求,从而导致 403 错误。 另一个可能的原因在于服务器配置(server configuration)。例如,出于安全原因,服务器可能被配置为拒绝来自某些 IP 地址或用户代理(user agent)的请求。如果请求来自这些被阻止的资源,服务器会响应 403 错误。 此外,Spring Security 默认启用跨站请求伪造(CSRF)保护。CSRF 是一种通过欺骗受害者提交恶意请求并利用受害者的凭证代表其执行非预期功能的攻击。如果用于防范此类攻击的 CSRF token 丢失或不正确,服务器也可能会响应 403 错误。 4、项目设置 要了解如何解决 403 错误,让我们创建一个带有 spring-boot-starter-web 和 spring-boot-starter-security 依赖的 Spring Boot 项目:

使用 OpenFeign 发起 PATCH 请求

1、概览 通过 REST API 更新资源时,可以使用 PATCH 方法。该方法专门用于“更新部分字段”的场景。当需要完全更改现有资源时(全量替换),可以使用 PUT 方法。 在本教程中,我们将学习如何在 OpenFeign 中设置 HTTP PATCH 方法。我们还将演示在 Feign client 测试 PATCH 方法时出现的异常情况,以及解决方案。 2、Spring Boot 中的应用示例 假设我们需要构建一个简单的微服务,调用下游服务进行部分更新。 2.1、Maven 依赖 首先,我们要添加 spring-boot-starter-web 和 spring-cloud-starter-openfeign 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> 2.2、实现 Feign Client 现在,让我们使用 Spring Web 注解在 Feign 中实现 PATCH 方法。 首先,让我们建立一个 User model,它有几个简单的属性。 public class User { private String userId; private String userName; private String email; } 接下来,我们将使用 updateUser 方法来实现一个 UserClient 接口:

在 Spring Boot 开发模式中使用 Testcontainers 和 Docker

在本文中,你将学习如何使用 Spring Boot 内置的 Testcontainers 和 Docker Compose 支持,在开发模式下运行外部服务。Spring Boot 在当前的最新版本 3.1 中引入了这些功能,你已经可以在 Spring Boot 应用的测试中利用 Testcontainers。在应用程序启动时运行外部数据库、message broker 或其他外部服务的功能是我一直期待的。尤其是竞争框架 Quarkus 已经提供了名为 Dev Services 的类似功能,这在我的开发过程中非常有用。此外,还有另一个令人兴奋的功能 - 与 Docker Compose 集成。 源代码 如果你想自己尝试,可以查看我的源代码。因为我经常使用 Testcontainers,所以你可以在我的多个仓库中找到示例。下面是我们今天要使用的仓库列表: https://github.com/piomin/sample-spring-boot-on-kubernetes.git https://github.com/piomin/sample-spring-microservices-advanced.git https://github.com/piomin/sample-spring-kafka-microservices.git 你可以克隆它们,然后按照指导查看如何在开发模式下使用 Spring Boot 内置的 Testcontainers 和 Docker Compose 支持。 在测试中使用 Testcontainers 让我们从标准使用示例开始。第一个仓库中有一个连接 Mongo 数据库的 Spring Boot 应用程序。为了构建自动测试,我们必须包含以下 Maven 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>mongodb</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency> 现在,我们可以创建测试了。我们需要用 @Testcontainers 来注解我们的测试类。然后,我们必须声明 MongoDBContainer Bean。在 Spring Boot 3.

Spring Boot 中内置的测试容器(Testcontainers)

1、概览 在本教程中,我们将讨论 Spring Boot 3.1 中引入的增强型 Testcontainers 支持。 这个更新提供了一种更简化的配置容器的方法,并允许我们启动它们进行本地开发。因此,使用 Testcontainers 进行开发和运行测试变得更加无缝和高效。 2、SpringBoot 3.1 之前的 Testcontainers 在测试阶段,我们可以使用 Testcontainers 创建一个类似生产的环境。这样,我们就不需要模拟,就能写出与实现细节无关的高质量自动测试。 在本文的代码示例中,我们将使用一个简单的 Web 应用程序,其中包含一个 MongoDB 数据库作为持久层,并具有一个小型的 REST 接口。 @RestController @RequestMapping("characters") public class MiddleEarthCharactersController { private final MiddleEarthCharactersRepository repository; // constructor not shown @GetMapping public List<MiddleEarthCharacter> findByRace(@RequestParam String race) { return repository.findAllByRace(race); } @PostMapping public MiddleEarthCharacter save(@RequestBody MiddleEarthCharacter character) { return repository.save(character); } } 在集成测试期间,我们将启动一个包含数据库服务器的 Docker 容器。由于容器暴露的数据库端口将动态分配,我们无法在 properties 文件中定义数据库 URL。因此,对于版本早于 3.1 的 Spring Boot 应用程序,我们需要使用 @DynamicPropertySource 注解才能将这些属性添加到 DynamicPropertyRegistry 中:

在 Spring Boot 中使用 Logback 和 Log4j2 记录日志

日志系统对应用程序的重要性不言而喻。Spring Boot 为应用程序的日志记录提供了强大的支持,并提供了各种自定义选项。在本教程中,你将学习如何在 Spring Boot 应用程序中使用 Logback 和 Log4j2 实现日志记录 Spring Boot 中默认的的日志支持 当你创建一个添加了任何 starter 的 Spring Boot 应用程序时,它们都依赖于 spring-boot-starter,而 spring-boot-starter 又依赖于 spring-boot-starter-logging,这就为日志记录添加了 logback 依赖。 Spring Boot 默认日志配置已在 spring-boot-starter 中通过 CONSOLE appender 进行了配置。你可以在 org/springframework/boot/logging/logback 包下查看打包在 spring-boot.jar 中的默认配置文件(defaults.xml、base.xml、console-appender.xml、file-appender.xml)。 因此,默认情况下,Spring Boot 配置了 Slf4j 和 Logback 来记录日志,你可以在应用程序中按如下方式记录日志: import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Service class CustomerService { private static final Logger log = LoggerFactory.getLogger(CustomerService.class); public Customer findById(Long id) { log.info("Fetching customer by id: {}", id); .

在 Spring Boot 中自定义 Date/LocalDateTime/LocalDate 的序列化、反序列化格式

Spring Boot 中默认使用的 JSON 框架是 jackson,它负责把 JSON 请求体反序列化为 Java 对象,并把响应给客户端的 Java 对象序列化为 JSON 字符串。 本文将会详细介绍如何在 Spring Boot 应用中自定义 jackson 对 Date、LocalDateTime、LocalDate 等日期对象的序列化、反序列化格式。 Jackson 对 Date/LocalDateTime/LocalDate 的默认处理方式 为了处理 java.time 类型的日期类,你还需要在项目中添加 com.fasterxml.jackson.datatype:jackson-datatype-jsr310 模块。 <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </dependency> 默认情况下,Jackson 将 Date 对象序列化为时间戳。对于 LocalDateTime 和 LocalDate 对象,jackson 会序列化为一个 long[],其中,从第一个元素开始分别表示日期的:年、月、日、时、分、秒、毫秒。 示例: package cn.springdoc.test; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Date; import java.util.HashMap; import java.util.Map; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class JacksonTest { public static void main(String[] args) throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); // 注册 JavaTime 模块 objectMapper.

Spring 中的事务管理器(TransactionManager)

事务管理 ,一个被说烂的也被看烂的话题,还是面试中常问到的问题之一。 本文会从设计角度,一步步的剖析 Spring 事务管理的设计思路(都会设计事务管理器了,还能玩不转?)。 一、为什么需要事务管理? 先看看如果没有事务管理器的话,如果想让多个操作(方法/类)处在一个事务里应该怎么做: // MethodA: public void methodA(){ Connection connection = acquireConnection(); try{ int updated = connection.prepareStatement().executeUpdate(); methodB(connection); connection.commit(); }catch (Exception e){ rollback(connection); }finally { releaseConnection(connection); } } // MethodB: public void methodB(Connection connection){ int updated = connection.prepareStatement().executeUpdate(); } 或者用 ThreadLocal 存储 Connection? static ThreadLocal<Connection> connHolder = new ThreadLocal<>(); // MethodA: public void methodA(){ Connection connection = acquireConnection(); connHolder.set(connection); try{ int updated = connection.prepareStatement().executeUpdate(); methodB(); connection.commit(); }catch (Exception e){ rollback(connection); }finally { releaseConnection(connection); connHolder.

使用 Spring Doc 为 Spring REST API 生成 OpenAPI 3.0 文档

1、概览 文档是构建 REST API 的重要组成部分。在本教程中,我们将介绍 Spring Doc,它可简化 API 文档的生成和维护,这些文档基于 OpenAPI 3 规范,适用于 Spring Boot 3.x 应用程序。 2、设置 springdoc-openapi Spring Boot 3.x 要求使用 springdoc-openapi version 2: <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.1.0</version> </dependency> 2.1、 OpenAPI 描述的路径 正确设置依赖后,我们就可以运行应用程序,并在 /v3/api-docs 路径访问 OpenAPI 描述,这是默认路径: http://localhost:8080/v3/api-docs 此外,我们还可以使用 springdoc.api-docs 属性在 application.properties 中自定义描述的路径。例如,我们可以将路径设置为 /api-docs: springdoc.api-docs.path=/api-docs 然后,我们就可以通过以下网址访问文档描述: http://localhost:8080/api-docs OpenAPI 描述定义默认为 JSON 格式。对于 yaml 格式,我们可以从以下网址获取定义: http://localhost:8080/api-docs.yaml 3、整合 Swagger UI 除了生成 OpenAPI 3 规范之外,我们还可以将 springdoc-openapi 与 Swagger UI 集成,以与我们的 API 规范进行交互并测试端点。 Springdoc-openapi 依赖项中已经包含了 Swagger UI,因此我们可以通过如下路径访问 API 文档: