Spring Security 6.3 中的新特性

1、简介 Spring Security 6.3 版在框架中引入了一系列安全增强功能。本文将带你了解其中一些最值得注意的特性,重点介绍它们的优点和用法。 2、被动式的 JDK 序列化支持 Spring Security 6.3 包含被动式的 JDK 序列化支持(Passive JDK Serialization Support),首先先来了解一下相关的问题和困扰。 2.1、Spring Security 的序列化设计 在 6.3 版本之前,Spring Security 对其类在不同版本之间通过 JDK Serialization 进行序列化和反序列化有严格的策略。这个限制是框架的一个有意设计决策,旨在确保安全性和稳定性。其理由是防止使用不同版本的Spring Security 反序列化在一个版本中序列化的对象时出现不兼容性和安全漏洞。 这一设计的一个关键方面是在整个 Spring Security 项目中使用全局 serialVersionUID。在 Java 中,序列化和反序列化过程使用唯一标识符 serialVersionUID 来验证加载的类是否与序列化对象完全一致。 通过为 Spring Security 的每个发布版本维护一个唯一的全局 serialVersionUID,该框架可确保一个版本的序列化对象无法使用另一个版本进行反序列化。这种方法有效地创建了一个版本屏障,防止了序列化版本 serialVersionUID 值不匹配的对象被反序列化。 例如,Spring Security 中的 SecurityContextImpl 类表示 Security Context 信息。该类的序列化版本包含该版本特有的 serialVersionUID。当尝试在不同版本的 Spring Security 中反序列化该对象时,serialVersionUID 不匹配会阻止该过程成功进行。 2.2、序列化设计带来的困扰 在优先增强安全性的同时,这种设计策略也带来了一些困扰。开发人员通常将 Spring Security 与 Spring Session 等其他 Spring 库集成,以管理用户登录会话(Session)。这些会话包含重要的用户身份认证和 Security Context 信息,通常通过 Spring Security 类实现。此外,为了优化用户体验并提高应用的可扩展性,开发人员通常会在各种持久存储解决方案(包括数据库)中存储这些会话数据。

使用 Spring Validator 接口进行数据校验

1、概览 Spring Validator 接口为验证对象提供了一种灵活且可定制的方式。本文将带你了解如何在 Spring 应用中使用 Validator 接口验证对象。 2、Spring Validator 接口 Validator 接口是 Spring 框架的一部分,它提供了一种验证对象的方法。 这是一个简单的接口,定义了两个方法:supports() 和 validate(),用于确定 Validator 是否能验证对象并执行验证逻辑。 2.1、supports(Class clazz) 方法 Validator 接口中的 supports() 方法决定 Validator 能否验证特定类的实例。该方法的参数 Class<?> clazz 代表被验证对象的类。它是一个泛型类(Class<?>),可以灵活用于不同的对象类型。 具体来说,Spring 利用 isAssignableFrom() 方法来检查对象是否可以合法地转换为 Validator 支持类的对象。因此,如果 Validator 能处理所提供的 clazz 对象,它就会返回 true,否则就会返回 false,以表示应使用另一个 Validator。 @Override public boolean supports(Class<?> clazz) { return User.class.isAssignableFrom(clazz); } 在上例中,Validator 配置为仅支持验证类型为 User 或其子类的对象。isAssignableFrom() 方法通过继来承验证兼容性 - 对于 User 及其子类,它返回 true,对于任何其他类类型,它返回 false。 2.2、validate(Object target, Errors errors) 方法 validate() 方法则用于为 Validator 支持的对象定义自定义验证逻辑。

Spring Boot 中使用 GraphQL 实现文件上传

1、简介 GraphQL 改变了开发人员与 API 交互的方式,为传统的 REST 方法提供了一个精简、强大的替代方案。 然而,在 Java 中使用 GraphQL 处理文件上传,特别是在 Spring Boot 应用中,由于 GraphQL 对二进制数据的处理方式,需要进行一些设置。本文将带你了解如何在 Spring Boot 应用中使用 GraphQL 上传文件。 2、GraphQL文件上传 与 HTTP 文件上传 在使用 Spring Boot 开发 GraphQL API 时,遵循最佳实践通常涉及利用标准的 HTTP 请求来处理文件上传。 通过专用的 HTTP 端点管理文件上传,然后通过 URL 或 ID 等标识符将这些上传链接到 GraphQL Mutation,开发人员可以有效地将直接嵌入 GraphQL Query 的文件上传的复杂性和处理开销降至最低。这种方法不仅简化了上传过程,还有助于避免与文件大小限制和序列化需求相关的潜在问题,从而有助于实现更加精简和可扩展的应用结构。 不过,在某些情况下,有必要将文件上传直接整合到 GraphQL 查询中。在这种情况下,需要进行一些特别的定制,在用户体验和应用程序性能之间取得平衡。因此,我们需要定义一种专门的量 Scalar Type 来处理上传。此外,这种方法还需要部署特定的机制来验证输入,并将上传的文件正确映射到 GraphQL 操作中的变量。此外,文件上传需要使用 Content Type 为 multipart/form-data 的请求体,因此需要实现一个自定义的 HttpHandler。 3、 在 GraphQL 中实现文件上传 首先,需要使用 Spring Boot 官方的 GraphQL Starter。

Spring Prototype Bean 需要手动销毁吗?

1、简介 本文将带你了解 Spring 如何处理 Prototype Bean 并管理其生命周期,主要介绍是否有必要手动销毁 Prototype Bean、何时销毁以及如何销毁。 Spring 提供了多种 Bean Scope,本文主要聚焦 Prototype。 2、Prototype Bean 及其生命周期 Scope(作用域)确定了 Bean 在其存在的上下文中的生命周期和可见性。根据 Scope 的定义 ,IoC 容器负责管理 Bean 的生命周期。Prototype(原型)Scope 指示容器在每次使用 getBean() 请求或注入到另一个 Bean 时创建一个新的 Bean 实例。在创建和初始化方面,可以放心地依赖于 Spring。然而,销毁 Bean 的过程则不同。 在了解销毁 Bean 的必要性之前,先看看如何创建一个 Prototype Bean,如下: @Component // 指定 Bean 的 Scope 为 Prototype @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class PrototypeExample { } 3、Prototype Bean 需要手动销毁吗? Spring 不会自动销毁 Prototype Bean。与 Singleton Scope 不同,IoC 容器并不负责 Prototype Bean 整个的生命周期。容器会实例化、配置和组装 Prototype Bean,但之后将停止跟踪其状态。

Spring Framework v6.1.11 发布

Spring Framework v6.1.11 正式发布。 ⭐ 新特性 在 SpEL 的 ReflectionHelper 中确保 MethodHandle 的 varargs 组件类型不为 null#33193 在响应过程中出现 Reactor-Netty PrematureCloseException 时,WebClient 异常消息令人困惑 #33127 当发现无效的 factoryBeanObjectType 属性时,在异常中包含 Bean 名称 #33117 在响应式缓存切面使用 Error Handler #33073 getTypeForFactoryMethod 应捕获 NoClassDefFoundError 异常#33075 🐞 Bug 修复 SpEL 无法使用数组调用 varargs MethodHandle 函数 #33191 SpEL 无法调用变量参数为零的 varargs MethodHandle 函数 #33190 嵌套的 Bean 实例 Supplier 调用不会保留先前的工厂方法 #33180 DefaultErrorResponseBuilder 未实现 headers(Consumer) #33156 修复对 Set 方法参数的违规适配 #33150 使用 kotlinx-serialization 时,Web Controller 调用无效正文导致结果为 500,而不是 400 #33138 "file:.

ExecutorService 与 CompletableFuture 指南

1、简介 本文将带你了解 Java 中用于处理并发任务的两个重要的类:ExecutorService 和 CompletableFuture。 主要介绍它们的功能、如何有效地使用它们、以及它们之间的区别。 2、ExecutorService 概览 ExecutorService 是 Java java.util.concurrent 包中的一个功能强大的接口,可简化对需要并发运行的任务的管理。它抽象掉了线程创建、管理和调度的复杂性,让我们可以专注于需要完成的实际工作。 ExecutorService 提供了 submit() 和 execute() 等方法,用于提交我们希望并发运行的任务。然后,这些任务会进入队列并分配给线程池中的可用线程。如果任务返回结果,我们可以使用 Future 对象来检索结果。不过,使用 Future 上的 get() 等方法检索结果会阻塞调用线程,直到任务完成。 3、CompletableFuture 概览 CompletableFuture 是在 Java 8 中引入的。它专注于以更声明式的方式组合异步操作并处理它们的最终结果。CompletableFuture 充当一个容器,保存异步操作的最终结果。它可能不会立即返回结果,但提供了方法来定义在结果可用时要执行的操作。 ExecutorService 在检索结果时会阻塞线程,而 CompletableFuture 则以非阻塞方式运行。 4、关注点和职责 虽然 ExecutorService 和 CompletableFuture 都能解决 Java 中的异步编程问题,但它们的各自的关注点和职责却截然不同。 4.1、ExecutorService ExecutorService 专注于管理线程池和并发执行任务。它提供了创建具有不同配置(如固定大小、缓存和定时调度)的线程池的方法。 来看一个使用 ExecutorService 创建并维护三个线程的示例,如下: ExecutorService executor = Executors.newFixedThreadPool(3); Future<Integer> future = executor.submit(() -> { // 任务执行逻辑 return 42; }); 调用 newFixedThreadPool(3) 方法会创建一个包含三个线程的线程池,可以确保同时执行的任务不会超过三个。然后使用 submit() 方法提交任务供线程池执行,并返回一个代表计算结果的 Future 对象。

自定义 ExecutorService 中线程的名称

1、概览 ExecutorService 在 Java 中提供了一种方便的方式来管理线程并执行并发任务。在使用 ExecutorService 时,为线程和线程池分配有意义的名称可以提高调试、监控和理解线程的效果。 本文将带你了解在 Java 的 ExecutorService 中为线程和线程池命名的不同方式。 2、设置线程的名称 如果不使用 ExecutorService,可以在 Java 中通过 Thread#setName 方法轻松设置线程名。 ExecutorService 使用默认的线程池和线程名称,如 pool-1-thread-1、pool-1-thread-2 等,但也可以为 ExecutorService 管理的线程指定自定义的线程名称。 首先,创建一个简单程序,执行 ExecuterService,输出默认的线程和线程池名称: ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 5; i++) { executorService.execute(() -> System.out.println(Thread.currentThread().getName())); } 运行程序,输出如下: pool-1-thread-1 pool-1-thread-2 pool-1-thread-1 pool-1-thread-3 pool-1-thread-2 2.1、使用自定义 ThreadFactory 在 ExecutorService 中,新线程是通过 ThreadFactory 创建的。ExecutorService 默认使用 Executors.defaultThreadFactory 创建线程来执行任务。 通过向 ExecuterService 提供不同的自定义 ThreadFactory,我们可以更改线程的名称、优先级等。 首先,创建 ThreadFactory 的实现:MyThreadFactory。然后,为 MyThreadFactory 创建的新线程设置自定义线程名称:

解决 JPA 中的 PostgreSQL JSON 类型不匹配异常

1、简介 本文将带你了解在使用 JPA 与 PostgreSQL 交互时出现异常 “PSQLException error: column is of type json but the expression is of type character varying” 的原因,以及解决办法。 2、常见的原因 在 PostgreSQL 中,JSON 或 JSONB 数据类型用于存储 JSON 数据。但是,如果我们试图将字符串(character varyin)插入到期望使用 JSON 的列中,PostgreSQL 就会抛出 “column is of type json but expression is of type character varying” 错误。这种情况在使用 JPA 和 PostgreSQL 时尤其常见,因为 JPA 可能会尝试将字符串保存到 JSON 列,从而导致此错误。 3、异常演示 创建一个基本的 Spring Boot 项目,在 Maven pom.xml 文件中添加 PostgreSQL 依赖: <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.7.1</version> <scope>runtime</scope> </dependency> 创建一个映射到 student 表的 JPA 实体类:

Hibernate 异常 UnknownEntityException:Could not resolve root entity

1、概览 本文将带你了解 Hibernate 出现异常 “UnknownEntityException:Could not resolve root entity” 的原因,以及解决办法。 2、理解异常 通常,在 HQL 或 JPQL 查询中解析已知映射实体名称失败时,Hibernate 就会抛出 “UnknownEntityException: Could not resolve root entity” 异常。 简而言之,Hibernate 依赖 JPA 实体来完成对象关系映射的所有工作。因此,它希望查询中指定的实体名称与 @Entity 注解所注解的类名称相匹配。 因此,导致异常的最常见原因之一就是 使用了与有效实体类名称不匹配的名称。 3、示例 知道了导致 Hibernate 出现 UnknownEntityException 的原因后,来看看如何在实践中重现这种情况。 首先,创建一个 Person 类,如下: @Entity public class Person { @Id private int id; private String firstName; private String lastName; // 标准 Getter / Setter 方法省略 } 如上,使用 id、firstName 和 lastName 来定义一个 Person。 @Entity 注解表示 Person 类是一个 JPA 实体,@Id 表示代表主键的字段。

JDBC INSERT 返回自增 Id

1、简介 在使用 JDBC 向数据库插入数据时,如果主键 ID 是自增的,那么我们需要获取到新插入的这条数据的 ID。JDBC 提供了一种在 INSERT 操作后立即获取其自增 ID 的机制。 2、示例项目 为了方便测试,本例使用H2 内存数据库。 在 pom.xml 文件中添加 h2 数据库依赖: <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.1.214</version> </dependency> 首先,连接到 H2 数据库,并在数据库中创建 EMPLOYEES 测试表: private static void populateDB() throws SQLException { String createTable = """ CREATE TABLE EMPLOYEES ( id SERIAL PRIMARY KEY , first_name VARCHAR(50), last_name VARCHAR(50), salary DECIMAL(10, 2) ); """; PreparedStatement preparedStatement = connection.prepareStatement(createTable); preparedStatement.execute(); } 3、检索自增 ID 执行 INSERT 语句时,如果表有自动生成的键(如 MySQL 中的 AUTO_INCREMENT、PostgreSQL 中的 SERIAL 或 H2 数据库中的 IDENTITY),JDBC 可以使用 getGeneratedKeys() 方法检索这些键。