教程

Spring Security 7 中的重大变化

虽然 Spring Security 7.0 尚未确定发布日期,但是我们还是需要提前做一些准备工作,因为在已知的信息中,在 Spring Security 7.0 中会有一大批大家熟悉的 API 被移除。这些 API 在 Spring Security 6 中已经处于废弃状态,但是还能用,但是到了 Spring Security 7.0,这些就被移除了,所以我们还是有必要来看看 Spring Security 7.0 中的一些比较典型的变化。 1、Lambda 配置 Lambda DSL 自 Spring Security 5.2 版本以来就存在,它允许使用 lambda 表达式配置 HTTP Security。 我们来看看使用 lambda 配置 HTTP 安全性与之前的配置风格相比有何差别: 使用 lambda 的配置 @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/blog/**").permitAll() .anyRequest().authenticated() ) .formLogin(formLogin -> formLogin .loginPage("/login") .

Spring Data JPA 异常 “IllegalArgumentException: Not a Managed Type”

1、概览 使用 Spring Data JPA 时,应用启动出现异常。大致如下: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerAdapter' ... Caused by: java.lang.IllegalArgumentException: Not a managed type: ...OurEntity at org.hibernate.metamodel.internal.MetamodelImpl.managedType(MetamodelImpl.java:583) at org.hibernate.metamodel.internal.MetamodelImpl.managedType(MetamodelImpl.java:85) ... 大意是说,一些 Bean 创建失败了,导致应用启动失败。 根异常是 IllegalArgumentException:“Not a managed type”,本文将带你了解出现这个异常的原因,以及如何解决该异常。 2、缺少 @Entity 注解 出现这种异常的一个可能原因是,忘记使用 @Entity 注解来标记实体。 2.1、重现问题 假设有以下实体类: public class EntityWithoutAnnotation { @Id private Long id; } 及其对应的 Spring Data JPA repository: public interface EntityWithoutAnnotationRepository extends JpaRepository<EntityWithoutAnnotation, Long> { } 最后是 Application 启动类,它会扫描上面定义的所有类: @SpringBootApplication public class EntityWithoutAnnotationApplication { } 尝试使用此 Application 来启动 Spring Context:

JPA 中的 PersistenceUnit 和 PersistenceContext

1、概览 Persistence Context(持久化上下文)和 Persistence Unit(持久化单元)是 JPA 中的两个重要概念,用来管理应用中实体的生命周期。 本文将带你了解 JPA 中的 EntityManager(实体管理器) 的作用,以及 Persistence Context 和 Persistence Unit 的重要性和用例。 2、EntityManager 和 EntityManagerFactory 首先来看看 EntityManager 和 EntityManagerFactory 接口,它们在管理持久性(Persistence)、实体和数据库交互方面发挥着重要作用。 2.1、EntityManager EntityManager 是一个与 Persistence Context 交互的接口。它对实体执行 CRUD 操作、跟踪更改并确保在事务提交时与数据库同步。EntityManager 代表一个 Persistence Context,并在事务范围内运行。 2.2、EntityManagerFactory EntityManagerFactory 是一个创建 EntityManager 的接口,有效地发挥着工厂的作用。创建时,EntityManagerFactory 会与特定的 Persistence Unit 关联,从而创建 EntityManager 的实例。 3、PersistenceContext PersistenceContext 是一个短暂的、事务范围的上下文,用于管理实体的生命周期。它代表一组存储在内存中的 “托管实体”,是实体管理器的一级缓存。如果事务开始,就会创建持久化上下文,并最终在事务提交或回滚时关闭或清除。 持久化上下文会自动检测对托管实体所做的更改,并确保所有实体更改与持久化存储(Persistence Storage)同步。 我们可以使用 @PersistenceContext 注解定义持久化上下文(Persistence Context)的类型: @PersistenceContext private EntityManager entityManager; JPA 中有两种持久化上下文: TRANSACTION 和 EXTENDED。 首先,使用 @Entity 注解创建与 PRODUCT 表相对应的实体:

Spring Boot 使用 Grafana Loki 来收集和显示日志

1、简介 Grafana 实验室受 Prometheus 的启发开发了开源日志聚合系统 Loki。该系统的目的是存储日志数据并编制索引,从而方便高效地查询和分析由不同应用和系统生成的日志。 本文将带你了解如何在 Spring Boot 中使用 Loki 收集和汇总日志,并使用 Grafana 显示日志。 2、运行 Loki 和 Grafana 服务 首先以 Docker 容器的方式启动 Loki 和 Grafana 服务,以便收集和观察日志。 在 docker-compose 文件中定义 Loki 和 Grafana 服务: version: "3" networks: loki: services: loki: image: grafana/loki:2.9.0 ports: - "3100:3100" command: -config.file=/etc/loki/local-config.yaml networks: - loki grafana: environment: - GF_PATHS_PROVISIONING=/etc/grafana/provisioning - GF_AUTH_ANONYMOUS_ENABLED=true - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin entrypoint: - sh - -euc - | mkdir -p /etc/grafana/provisioning/datasources cat <<EOF > /etc/grafana/provisioning/datasources/ds.

使用 Spring REST Docs 生成 API 文档

1、概览 API 文档在团队开发中极其重要,特别是在 API 接口及其复杂的情况下,良好的 API 文档不仅能提升开发效率,还能显示产品的质量。如果一家公司的 API 文档写得马马虎虎,那么它的 API 也可能写得马马虎虎。 程序员都讨厌写自己文档和别人不写文档。 本文将带你了解如何使用 Spring REST Docs 自动地生成 API 文档。 2、API 接口 一个简单 API 如下: @RestController @RequestMapping("/books") public class BookController { private final BookService service; public BookController(BookService service) { this.service = service; } @GetMapping public List<Book> getBooks(@RequestParam(name = "page") Integer page) { return service.getBooks(page); } } 该 API 返回系统中的 Book 数据。不过,由于可用图书数量庞大,不可能一次性返回所有。所以给客户端提供了一个查询参数 page,用于控制数据分页。 而且,需要把该参数设置为必须参数(默认就是必须参数),避免客户端一次性请求过多数据。如果客户端未提交该查询参数,就会收到状态为 400 的错误提示。 3、文档 编写文档的通常方法是 “手写文档”,这意味着开发人员必须把同一件事写两遍。首先写代码,然后再写对应的文档。太麻烦! 文档是一种相当正式的文件,其目标是清晰明了,并不需要太多花里胡哨的东西。因此,我们可以根据代码生成文档,这样就不用重复写同样的东西,而且所有的改动都会反映在文档中。 我们可以通过 Spring REST Docs 来生成 API 文档。不过,它不是从代码中生成文档,因为代码没有提供太多上下文,而是从测试中生成文档。这可以表达相当复杂的案例和示例。另一个好处是,如果测试失败,文档也不会生成。

Spring Data JPA 实现 updateOrInsert(更新或保存)

1、简介 在应用开发中,执行 “更新或插入” 操作(也称为 “upsert”)的需要是很常见的。这个操作涉及将新记录存入数据库表中,如果记录不存在,则插入新记录;如果记录已经存在,则更新现有记录。 本文将带你了解使用 Spring Data JPA 执行 “更新或插入” 操作的不同方法。 2、实体类 定义 CreditCard 实体类用于演示: @Entity @Table(name="credit_card") public class CreditCard { @Id @GeneratedValue(strategy= GenerationType.SEQUENCE, generator = "credit_card_id_seq") @SequenceGenerator(name = "credit_card_id_seq", sequenceName = "credit_card_id_seq", allocationSize = 1) private Long id; private String cardNumber; private String expiryDate; private Long customerId; // Get / Set 方法省略 } 3、实现 本文介绍三种不同的方法来实现 “更新或插入”。 3.1、使用 Repository 方法 使用从 CrudRepository 接口继承的 save(entity) 方法在 Repository 中编写一个带事务的 default 方法。

Spring Boot 在测试时禁用 @Cacheable 缓存

1、简介 缓存是一种有效的策略,当执行结果在一段已知时间内没有变化时,可以避免重复执行逻辑,从而提高性能。 Spring Boot 提供了 @Cacheable 注解,可以在方法上定义该注解,它就会缓存方法的结果。在某些情况下,例如在测试环境中进行测试时,我们可能需要禁用缓存来观察某些修改后的行为。 本文将带你了解如何配置 Spring Boot 中的缓存,以及如何在需要时禁用缓存。 2、缓存配置 设置一个简单的用例,通过 ISBN(国际标准书号)查询图书评论,并在某个逻辑中使用 @Cacheable 对该方法返回的结果进行缓存。 实体类 BookReview 如下,它包含 bookRating、isbn 等信息: @Entity @Table(name="BOOK_REVIEWS") public class BookReview { @Id @GeneratedValue(strategy= GenerationType.SEQUENCE, generator = "book_reviews_reviews_id_seq") @SequenceGenerator(name = "book_reviews_reviews_id_seq", sequenceName = "book_reviews_reviews_id_seq", allocationSize = 1) private Long reviewsId; private String userId; private String isbn; private String bookRating; // Get / Set 方法省略 } 在 BookRepository 中添加一个简单的 findByIsbn() 方法,用于按 isbn 查询书评: public interface BookRepository extends JpaRepository<BookReview, Long> { List<BookReview> findByIsbn(String isbn); } BookReviewsLogic 类包含一个在 BookRepository 中调用 findByIsbn() 的方法。我们添加了 @Cacheable 注解,将指定 isbn 的结果缓存在 book_reviews 缓存中:

解决 PostgreSQL 的 PSQLException: “FATAL: sorry, too many clients already” 异常

1、概览 当 PostgreSQL 服务器无法接受客户端应用的连接请求时,就会抛出 PostgreSQL PSQLException:FATAL: sorry, too many clients already 这个异常。 本文将带你了解如何解决以及防止这个异常。 2、理解问题 DB 服务器启动时的连接数有限。有时,连接会用完。因此,数据库服务器无法提供新的连接。这时,它就会抛出异常:FATAL: sorry, too many clients already。 首先,来了解一下这个问题是如何、何时以及为何出现的。假设有四个 Client 应用连接到 PostgreSQL 数据库: 大多数情况下,应用会在启动时创建数据库连接池。假设数据库管理员为 PostgreSQL 数据库服务器配置了最多 90 个连接。需要注意的是,数据库会保留一些连接供内部使用。实际可供客户端使用的连接数甚至更少。运维为每个客户端应用配置的连接池大小为 30。 现在,假设运维从 Client-1 开始依次启动 Client 应用。当 Client-4 试图启动时,由于 Client-1、Client-2 和 Client-3 已经创建并阻塞了 90 个连接。因此,当 Client-4 向数据库服务器请求出创建 30 个连接时,服务器拒绝了请求,并显示错误 “FATAL: sorry, too many clients already”。 当开发人员尝试使用 psql、psgAdmin、DBeaver 等数据库管理工具连接 PostgreSQL 服务器时,也会出现该错误。因为这些工具也会尝试获取与数据库的连接,如果连接不足,它们也可能遇到同样的错误。 3、排除故障 首先,通过在 PostgreSQL 数据库中运行查询来确定最大连接数: show max_connections 默认情况下,该值为 100,可以通过修改数据库配置文件 postgresql.conf 中的 max_connections 属性来 设置 它:

新版 Spring Security 中的路径匹配机制

Spring Security 是一个功能强大且可高度定制的安全框架,它提供了一套完整的解决方案,用于保护基于 Spring 的应用。在 Spring Security 中,路径匹配是权限控制的核心部分,它决定了哪些请求可以访问特定的资源。本文将带你详细了解 Spring Security 中的路径匹配策略,并提供相应的代码示例。 在旧版的 Spring Security 中,路径匹配方法有很多,但是新版 Spring Security 对这些方法进行了统一的封装,都是调用 requestMatchers 方法进行处理: public C requestMatchers(RequestMatcher... requestMatchers) { Assert.state(!this.anyRequestConfigured, "Can't configure requestMatchers after anyRequest"); return chainRequestMatchers(Arrays.asList(requestMatchers)); } requestMatchers 方法接收一个 RequestMatcher 类型的参数,RequestMatcher 是一个接口,这个接口是一个用来确定 HTTP 请求是否与给定的模式匹配的工具。这个接口提供了一种灵活的方式来定义请求的匹配规则,从而可以对不同的请求执行不同的安全策略。 所以在新版 Spring Security 中,不同的路径匹配分方案实际上就是不同的 RequestMatcher 的实现类。 1. AntPathRequestMatcher AntPathRequestMatcher 是 Spring 中最常用的请求匹配器之一,它使用 Ant 风格的路径模式来匹配请求的 URI。 1.1 什么是 Ant 风格的路径模式 Ant 风格的路径模式(Ant Path Matching)是一种用于资源定位的模式匹配规则,它源自 Apache Ant 这个 Java 构建工具。在 Ant 中,这种模式被用来指定文件系统中的文件和目录。由于其简单性和灵活性,Ant 风格的路径模式也被其他许多框架和应用程序所采用,包括 Spring Security。

把 Google Protobuf Timestamp 转换为 LocalDate

1、概览 Protocol Buffer (Protobuf) 是一种用于序列化结构化数据的二进制格式。它是由 Google 开发的,并且被广泛应用于跨平台和跨语言的数据通信。Protocol Buffer 使用简洁、高效的编码方案,可以将结构化数据定义为消息类型,并生成针对不同编程语言的数据访问代码。这使得在不同的系统之间传输和解析数据变得更加简单和高效。Protocol Buffer 具有广泛的支持,包括 Java、C++、Python、Golang 等编程语言。 Protobuf Timestamp 类型代表一个时间点,与任何特定时区无关。本文将带你了解如何把 Protobuf Timestamp 实例转换为 Java 的本地时间类型,如 LocalDate。 2、Maven 依赖 在 pom.xml 中添加 protobuf-java 依赖: <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>4.26.1</version> </dependency> 该依赖提供了 Timestamp 和其他与 Protobuf 相关的类。 3、Timestamp 类 Protobuf Timestamp 类表示自 Unix 纪元以来的时间点。与时区或本地日历没有关系。 它表示的是某个时间点的秒数和纳秒数。下面是使用 Java Instant 对象计算当前时间戳的示例: Instant currentTimestamp = Instant.now(); Timestamp timestamp = Timestamp.newBuilder() .setSeconds(currentTimestamp.getEpochSecond()) .setNanos(currentTimestamp.getNano()) .build(); 如上,通过 Instant 对象计算时间戳。首先,创建一个 Instant 对象,表示给定点的日期和时间。然后,提取秒和纳秒,并将它们传递给 Timestamp 实例。 4、把 Timestamp 实例转换为 LocalDate 在将 Timestamp 转换为 LocalDate 时,必须考虑时区及其与 UTC 的相关偏移,以准确表示本地日期。要将 Timestamp 转换为 LocalDate,首先要创建一个具有特定秒和纳秒的 Timestamp 实例: