教程

OncePerRequestFilter 的用法

1、概览 本文将带你了解 Spring 中一种特殊类型的 Filter(过滤器)OncePerRequestFilter。 通过实例了解它的功能和用法。 2、OncePerRequestFilter 是什么? 回顾一下 Filter 的工作原理。Filter 可以在 Servlet 执行之前或之后调用。当请求被调度给一个 Servlet 时,RequestDispatcher 可能会将其转发给另一个 Servlet。另一个 Servlet 也有可能使用相同的 Filter。在这种情况下,同一个 Filter 会被调用多次。 但是,有时需要确保每个请求只调用一次特定的 Filter。一个常见的用例是在使用 Spring Security 时。当请求通过过滤器链(Filter Chain)时,对请求的身份证认证应该只执行一次。 在这种情况下,可以继承 OncePerRequestFilter。Spring 保证 OncePerRequestFilter 只对指定请求执行一次。 3、使用 OncePerRequestFilter 处理同步请求 定义一个继承了 OncePerRequestFilter 的 AuthenticationFilter Filter 类,并覆写 doFilterInternal() 方法: public class AuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String usrName = request.getHeader(“userName”); logger.info("Successfully authenticated user " + userName); filterChain.

在 Spring Webflux 中使用 @Cacheable 注解缓存结果

1、概览 本文将带你了解如何在 Spring WebFlux 中使用 @Cacheable 注解实现缓存,以及一些常见的问题和解决办法。 2、@Cacheable 和响应式类型 在本文撰稿时,@Cacheable 还不能和响应式框架无缝整合。主要问题在于,目前还没有非阻塞式的缓存实现(JSR-107 缓存 API 是阻塞式的)。只有 Redis 提供了响应式驱动。 虽然,仍然可以在方法上使用 @Cacheable。这会缓存封装对象(Mono 或 Flux),但不会缓存方法的实际结果。 2.1、项目设置 创建一个使用响应式 MongoDB 驱动的 Spring WebFlux 项目。并且用 Testcontainers 代替真实运行的 MongoDB 进行测试。 测试类使用 @SpringBootTest 进行注解,如下: final static MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:4.0.10")); @DynamicPropertySource static void mongoDbProperties(DynamicPropertyRegistry registry) { mongoDBContainer.start(); registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl); } 这几行代码会启动 MongoDB 实例,并将 URI 传递给 Spring Boot 以自动配置 Mongo Repository。 创建带有保存和获取 Item 方法的 ItemService 类: @Service public class ItemService { private final ItemRepository repository; public ItemService(ItemRepository repository) { this.

Spirng Boot 返回:415 Unsupported MediaType

1、概览 本文将带你了解如何解决在 Spring Boot API 返回状态码 “415 Unsupported MediaType” 的问题,以及出现该问题的原因。 2、背景 我们一个老客户要求我们给他的系统再开发一个桌面应用,类似于用户管理。新的桌面应用,直接调用原系统的 API 服务。 3、API 请求 通过 API 来获取所有用户: curl -X GET https://baeldung.service.com/user 成功获取响应。接着,获取一个单独的用户: curl -X GET https://baeldung.service.com/user/{user-id} 响应如下: { "id": 1, "name": "Jason", "age": 23, "address": "14th Street" } 一切也OK,根据响应,可以确定用户拥有以下参数:id、name、age 和 address。 现在,尝试添加新用户: curl -X POST -d '{"name":"Abdullah", "age":28, "address":"Apartment 2201"}' https://baeldung.service.com/user/ 结果,这次服务器响应了 HTTP 状态码为 415 的错误响应: { "timestamp": "yyyy-MM-ddThh:mm:ss.SSS+00:00", "status": 415, "error": "Unsupported Media Type", "path": "/user/" } 在弄清 “为什么会出现这个错误?” 之前,需要先弄清楚 “这个错误是什么?”。

在 Spring Boot 中使用 JSP

在前后端分离架构、SPA 应用大行其道的今天,模板引擎已经逐渐被淘汰了。更别提 JSP 这种上古模板引擎了。 Spring Boot 推荐使用 FreeMarker、Groovy、Thymeleaf 或者 Mustache 作为模板引擎。不推荐 JSP,主要是 JSP 的编译方式比价特殊,它需要先把 JSP 代码编译为 Servlet,最后通过执行 Servlet 来输出模板内容。 当然,在 Spring Boot 中使用 JSP 也是可以的,只需要些许配置即可。 创建 Spring Boot 项目 创建 Spring Boot(3.0.3)项目,在 pom.xml 中添加如下依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Servlet --> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> </dependency> <!-- Tomcat 嵌入式 JSP 解析器 --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <!-- JSP jstl --> <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> 注意,jstl 需要自己手动添加版本号,它没有被 Spring Boot 管理。 在 Spring Boot 支持的嵌入式容器中只有 Tomcat 支持使用 JSP,Undertow 和 Jetty 均不支持!

Spring Boot @ConditionalOnThreading 注解

1、简介 本文将带你了解一个相对较新的 Spring Boot 条件注解 @ConditionalOnThreading。 2、条件注解 条件注解提供了一种仅在满足各种特定条件时才在 BeanFactory 中注册 Bean 的方法。开发人员通过使用 Condition 接口为每个注解单独定义这些条件。 Spring Boot 为常见用例提供了大量预定义的条件注解。常见的示例有 @ConditionalOnProperty、@ConditionalOnBean 和 @ConditionalOnClass。 3、@ConditionalOnThreading 原理 @ConditionalOnThreading 只是 Spring Boot 中另一个预定义的条件注解。它是在 3.2 版本中添加的,在本文撰稿时,该版本本身还是候选发布版。也就是说,需要使用专用的 Spring Artifacts 仓库。 <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <releases> <enabled>false</enabled> </releases> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <releases> <enabled>false</enabled> </releases> </pluginRepository> </pluginRepositories> @ConditionalOnThreading 注解仅允许在 Spring 内部配置为使用特定类型的线程时创建 Bean。所谓线程类型,是指平台线程或虚拟线程。回顾一下,从 Java 21 开始,我们就可以 使用虚拟线程来代替平台线程 了。

国际化 Bean Validation 校验失败的错误消息

1、概览 在 Web 应用中,我们通常需要通过 spring-validation 对客户端提交的数据进行校验。如果校验失败则会抛出异常。默认情况下,关于校验失败细节的异常信息是英文。本文将会带你了解如何对校验失败时的异常消息进行国际化(也称为“本地化”)。 2、依赖 首先在 pom.xml 中添加 Spring Boot Starter Web 和 Spring Boot Starter Validation: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> 最新版本可在 Maven Central 上找到。 3、本地化信息存储 在 Java 应用中,通常使用 properties 文件存储本地化信息。一般称为 Resource Bundle。 这些文件是由键值对组成的纯文本文件。key 是信息检索的标识符,而对应的 value 则是相应语言的本地化消息。 接下来,我们创建 2 个 properties 文件。 CustomValidationMessages.properties 是默认的 properties 文件,文件名不包含任何语言名称。只要客户端指定的语言不支持,应用就会使用默认语言: field.personalEmail=Personal Email validation.notEmpty={field} cannot be empty validation.email.notEmpty=Email cannot be empty 再创建一个额外的中文语言的 properties 文件 - CustomValidationMessages_zh.properties。只要客户端指定 zh 或 zh-tw 等变体作为本地语言,应用语言就会切换为中文:

Spring Boot 整合 Kafka Stream

1、简介 流式数据在现实生活中的一些例子包括传感器数据、股票市场事件流和系统日志。在本文中,我们通过构建一个简单的字数统计流式应用来介绍如何在 Spring Boot 中使用 Kafka Streams。 2、概览 Kafka Streams 在 Kafka Topic 和关系型数据库表之间提供了一种对偶性。它使我们能够对一个或多个流式事件进行连接、分组、聚合和过滤等操作。 Kafka 流的一个重要概念是处理器拓扑(Processor Topology)。处理器拓扑是 Kafka Stream 对一个或多个事件流进行操作的蓝图。从本质上讲,处理器拓扑可视为有向无环图。在这个图中,节点分为源节点、处理器节点和汇节点,而边则代表流事件的流向。 位于拓扑结构顶端的源接收来自 Kafka 的流数据,将其向下传递到执行自定义操作的处理器节点,并通过汇节点流出到新的 Kafka Topicc。在进行核心处理的同时,还利用检查点(Checkpoint)定期保存数据流的状态,以实现容错和弹性。 3、依赖 首先在 POM 中添加 spring-kafka 和 kafka-streams 依赖: <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>2.7.8</version> </dependency> <dependency> <groupId>org.apache.kafka</groupId <artifactId>kafka-streams</artifactId> <version>2.7.1</version> </dependency> 4、示例 示例应用从输入的 Kafka Topic 中读取流式事件。读取记录后,它会对记录进行处理,分割文本并计算单个字数。随后,它将更新的字数发送到 Kafka 输出。除了输出 Topic 外,还要创建一个简单的 REST 服务,通过 HTTP 端点公开该计数。 总之,输出 Topic 将不断更新从输入事件中提取的单词及其更新计数。 4.1、配置 在 Java 配置类中定义 Kafka Stream 配置: @Configuration @EnableKafka @EnableKafkaStreams public class KafkaConfig { @Value(value = "${spring.

Spring Boot 启动加速

1、简介 本文将带你了解如何通过调整 Spring 应用的配置、JVM 参数和使用 GraalVM 原生镜像来缩短 Spring Boot 的启动时间。 2、调整 Spring 应用 首先,创建一个 Spring Boot(2.5.4)应用,添加 Spring Web、Spring Actuator 和 Spring Security 依赖。 还要添加 spring-boot-maven-plugin 插件,并配置将应用打包到 jar 文件中: <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring-boot.version}</version> <configuration> <finalName>springStartupApp</finalName> <mainClass>com.baeldung.springStart.SpringStartApplication</mainClass> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> 使用标准的 java -jar 命令运行 jar 文件,并查看应用的启动时间。 c.b.springStart.SpringStartApplication : Started SpringStartApplication in 3.403 seconds (JVM running for 3.961) 如上,应用启动时间约为 3.4 秒。我们把这个时间作为下文调整的参考。 2.1、延迟初始化 Spring 支持延迟初始化。延迟初始化意味着 Spring 不会在启动时创建所有 Bean。此外,Spring 在需要 Bean 之前不会注入任何依赖。从 Spring Boot 2.

在 Spring Boot 中配置主从数据库实现读写分离

前言 现在的 Web 应用大都是读多写少。除了缓存以外还可以通过数据库 “主从复制” 架构,把读请求路由到从数据库节点上,实现读写分离,从而大大提高应用的吞吐量。 通常,我们在 Spring Boot 中只会用到一个数据源,即通过 spring.datasource 进行配置。前文 《在 Spring Boot 中配置和使用多个数据源》 介绍了一种在 Spring Boot 中定义、使用多个数据源的方式。但是这种方式对于实现 “读写分离” 的场景不太适合。首先,多个数据源都是通过 @Bean 定义的,当需要新增额外的从数据库时需要改动代码,非常不够灵活。其次,在业务层中,如果需要根据读、写场景切换不同数据源的话只能手动进行。 对于 Spring Boot “读写分离” 架构下的的多数据源,我们需要实现如下需求: 可以通过配置文件新增数据库(从库),而不不需要修改代码。 自动根据场景切换读、写数据源,对业务层是透明的。 幸运的是,Spring Jdbc 模块类提供了一个 AbstractRoutingDataSource 抽象类可以实现我们的需求。 它本身也实现了 DataSource 接口,表示一个 “可路由” 的数据源。 核心的代码如下: public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { // 维护的所有数据源 @Nullable private Map<Object, DataSource> resolvedDataSources; // 默认的数据源 @Nullable private DataSource resolvedDefaultDataSource; // 获取 Jdbc 连接 @Override public Connection getConnection() throws SQLException { return determineTargetDataSource().

Spring Security 配置 Content Security Policy(CSP)

1、概览 跨站脚本攻击(Cross-Site Scripting,XSS)一直稳居最常见的 十大网络攻击 之列。XSS 攻击发生在 Web 服务器处理用户恶意输入时,未经验证或编码即在页面上渲染。与 XSS 攻击类似,代码注入和点击劫持通过窃取用户数据和冒充用户身份来对 Web 应用造成严重影响。 本文将会带你了解如何使用 Spring Security 通过内容安全策略(Content-Security-Policy)保护 Web 应用免受点击劫持、代码注入和 XSS 攻击。 2、Content Security Policy 内容安全策略(Content Security Policy,简称 CSP)是一种 HTTP 响应头,可大大减少 现代浏览器 中的代码注入攻击,如 XSS、点击劫持 等。 Web 服务器通过 Content-Security-Policy Header 部指定了浏览器可以渲染的资源的列表。这些资源可以是浏览器渲染的任何内容,例如 CSS、JavaScript、图像等。 该 Header 的语法如下: Content-Security-Policy: <directive>; <directive>; <directive> ; ... 此外,还可以将此策略设置为 HTML 页面中 <meta> 标签的一部分: <meta http-equiv="Content-Security-Policy" content="<directive>;<directive>;<directive>; ..."> 每个 指令 都包含一个具有多个值的 key。指令可以不止一个,每个指令之间用分号(;) 分隔: Content-Security-Policy: script-src 'self' https://baeldung.com; style-src 'self'; 如上例所示,有两个指令(script-src 和 style-src),而指令 script-src 有两个值(self 和 https://baeldung.