在 WebFlux 中拦截请求并添加自定义请求头

1、概览 Filter(拦截器/过滤器)是 Spring 提供的一个机制,可以在 Controller 处理请求或向客户端返回响应之前拦截并处理请求。 本文将带你了解如何使用 WebFlux 拦截客户端请求以及如何添加自定义 Header。 2、Maven 依赖 添加 spring-boot-starter-webflux 响应式 Web 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> <version>3.1.5</version> </dependency> 3、拦截请求 Spring WebFlux Filter 可分为 WebFilter 和 HandlerFilterFunction 两类。可使用这些过滤器拦截 Web 请求,并添加新的自定义 Header 或修改现有 Header。 3.1、使用 WebFilter WebFilter 以链式拦截方式处理 Web 请求。WebFilter 在全局范围内生效,一旦启用,将拦截所有的请求和响应。 首先,定义基于注解的 Controller: @GetMapping(value= "/trace-annotated") public Mono<String> trace(@RequestHeader(name = "traceId") final String traceId) { return Mono.just("TraceId: ".concat(traceId)); } 然后,拦截 Web 请求,使用 TraceWebFilter 实现添加一个新的 Header traceId: @Component public class TraceWebFilter implements WebFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { exchange.

Spring Boot 3.2.5 发布

🐞 Bug 修复 BindValidationFailureAnalyzer 使用了错误的 target #40364 Log4j2LoggingSystem 通过一个永远不会被移除的 SpringEnvironmentPropertySource 污染 Log4j2 的环境 #40326 使用 Maven 时,配置 spring-boot.excludes 或 spring-boot-includes 用户属性会导致构建失败,提示:Cannot find default setter #40323 @ServletComponentScan 无法在模拟的 Web 环境中注册 Servlet 组件 #40321 在将 Log4j2 配置为使用单个 JVM 范围日志记录器上下文的情况下部署到 Tomcat 时,加载自定义的 deny-all 过滤器可能会导致 StackOverflowError 错误 #40312 Jetty 支持不设置虚拟线程名称 #40152 3.2.0 之后,当 Hibernate Scanner 未禁用时,可执行 JAR 应用的启动速度变慢 #40125 线程中断时,LaunchedClassLoader 可能会抛出 NoClassDefFoundError 错误 #40096 📔 文档 Producible 的 javadoc 中 @WriteOperation 和 @DeleteOperation 的链接文本有误 #40386 明确参数和构造函数绑定的要求 #40157 🔨 依赖升级 升级到 ActiveMQ 5.

在 Spring 测试中禁用 @EnableScheduling

1、简介 本文将带你了解如何测试启用了定时任务(@EnableScheduling)的 Spring 应用,以及如何在测试过程中禁用定时任务。 2、示例 首先来看一个示例,假设我们有一个系统,允许公司的代表向客户发送通知。其中一些通知是时间敏感的,应该立即发送,但有些通知应该等到下一个工作日再发送。因此,我们需要一个机制来定期尝试发送这些通知: public class DelayedNotificationScheduler { private NotificationService notificationService; @Scheduled(fixedDelayString = "${notification.send.out.delay}", initialDelayString = "${notification.send.out.initial.delay}") public void attemptSendingOutDelayedNotifications() { notificationService.sendOutDelayedNotifications(); } } attemptSendingOutDelayedNotifications() 方法上添加了 @Scheduled 注解。当 initialDelayString 配置的时间过去后,该方法将被首次调用。执行结束后,Spring 会在 fixedDelayString 参数配置的时间过后再次调用该方法。该方法本身将实际逻辑委托给了 NotificationService。 当然,我们还需要开启调度功能。为此,需要在 @Configuration 类上添加 @EnableScheduling 注解。 3、集成测试中定时任务的问题 首先,为通知应用编写一个基本的集成测试: @SpringBootTest( classes = { ApplicationConfig.class, SchedulerTestConfiguration.class }, properties = { "notification.send.out.delay: 10", "notification.send.out.initial.delay: 0" } ) public class DelayedNotificationSchedulerIntegrationTest { @Autowired private Clock testClock; @Autowired private NotificationRepository repository; @Autowired private DelayedNotificationScheduler scheduler; @Test public void whenTimeIsOverNotificationSendOutTime_thenItShouldBeSent() { ZonedDateTime fiveMinutesAgo = ZonedDateTime.

如何测试 Spring Application Event?

1、概览 本文将带你了解如何测试 Spring Application Event,以及如何使用 Spring Modulith 的测试库。 2、Application Event Spring 提供了 Application Event(观察者设计模式),允许组件在保持松散耦合的同时相互通信。我们可以使用 ApplicationEventPublisher Bean 发布内部事件,这些事件都是纯 Java 对象,所有已注册的监听器(Listener)都会收到通知。 例如,当成功创建 Order 时,OrderService 组件可以发布 OrderCompletedEvent: @Service public class OrderService { private final ApplicationEventPublisher eventPublisher; // 构造函数 public void placeOrder(String customerId, String... productIds) { Order order = new Order(customerId, Arrays.asList(productIds)); // 验证和下单的业务逻辑 OrderCompletedEvent event = new OrderCompletedEvent(savedOrder.id(), savedOrder.customerId(), savedOrder.timestamp()); eventPublisher.publishEvent(event); } } 把 OrderCompletedEvent 对象作为应用事件发布,不同模块中监听这些事件的组件会收到通知。 假设 LoyaltyPointsService 会对这些事件做出反应,以奖励下单客户积分。要实现这一点,可以使用 Spring 的 @EventListener 注解:

Spring Data JPA 出现异常后不回滚,继续处理事务

1、概览 JPA 中的事务机制是一个功能强大的工具,它通过提交所有更改或在出现异常时回滚更改来确保原子性和数据完整性。然而,在某些情况下,我们可能需要在遇到异常的情况下继续进行事务处理而不回滚数据更改。 2、出现异常后事务自动回滚 在事务中可能会出现两种主要的异常情况。 2.1、在 Service 层出现异常后回滚事务 我们可能遇到回滚(Rollback)的第一个地方是在 Service 层,外部异常可能会影响数据库更改。 让我们通过下面的示例更仔细地了解一下这种情况。首先,添加 InvoiceEntity,作为数据模型: @Entity @Table(uniqueConstraints = {@UniqueConstraint(columnNames = "serialNumber")}) public class InvoiceEntity { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; // 自增 ID private String serialNumber; // 序列号 private String description; // 说明 // Getter / Setter } 如上,包含了一个自增 ID、一个需要在整个系统中唯一的序列号和一个说明。 现在,创建负责发票事务操作的 InvoiceService: @Service public class InvoiceService { @Autowired private InvoiceRepository repository; @Transactional public void saveInvoice(InvoiceEntity invoice) { repository.save(invoice); sendNotification(); } private void sendNotification() { throw new NotificationSendingException("Notification sending is failed"); } } 在 saveInvoice() 方法中,我们添加了事务性保存发票(invoice)和发送相关通知的逻辑。但是,在发送通知的过程中,会抛出异常:

Spring Data JPA 查询 JSOB 类型的列

1、简介 Spring Data JPA 为与关系数据库交互提供了强大的抽象层。然而,传统的关系表可能并不适合存储复杂的、半结构化的数据,如产品详细信息或用户偏好。这就是 JSONB 数据类型的用武之地。 本文将带你学习使用 Spring Data JPA 查询 JSONB 列的各种方法。 2、JSONB 列 JSONB(JavaScript Object Notation for Databases)是一种数据类型,专门用于在 PostgreSQL 等关系数据库中存储 JSON 数据。它允许我们在单列中使用键值对和嵌套对象来表示复杂的数据结构。通过 JPA Provider(如 Hibernate),Spring Data JPA 允许我们将这些 JSONB 列映射到实体类中的属性。 3、映射 JSONB 列 我们可以使用 @Column 注解的 columnDefinition 属性,在实体类中明确定义列类型: @Column(columnDefinition = "jsonb") private String attributes; 这种方法主要与 PostgreSQL 相关,因为 PostgreSQL 本身支持 jsonb 数据类型。通过在实体类的相应属性中添加此注解,我们就能为数据库提供所需列类型的提示。Spring Data JPA 通常会根据数据库列定义自动检测 jsonb 数据类型,因此在很多情况下,此注解是可选的。 4、设置项目依赖和测试数据 创建一个基本的 Spring Boot 项目,其中包含测试 JSONB 查询所需的依赖和测试数据。 4.1、项目设置 首先,在 Maven pom.xml 文件中添加必要的依赖:

@Transactional 能和 @Async 一起用吗?

1、简介 本文将带你了解 Spring 中 @Transactional 和 @Async 注解之间的兼容性。 2、 了解 @Transactional 和 @Async @Transactional 注解是 Spring 提供的声明式事务注解。可以让多个业务方法在同一个事务中执行,只有所有方法都正常执行完毕后事务才会提交。如果任何一个方法在调用过程中抛出了异常,那么事务就会回滚。 @Async 注解用于执行异步任务,如果从一个线程调用 @Async 方法或类,Spring 会使用另一个线程来运行该方法,从而提高执行效率。 在有些情况下,我们需要在代码中同时使用 @Transactional 和 @Async 来保业务数据的一致性以及性能。 3、@Transactional 能和 @Async 一起用吗? 异步 和 事务 如果使用不当,可能会带来数据不一致等问题。 关于这一点,需要充分了解 Spring 的事务上下文和上下文之间的数据传播。 3.1、创建示例应用 本文使用银行的转账功能来说明事务和异步代码的使用。简而言之,是一个转账的场景,从一个账户中扣除资金并将其添加到另一个账户。 我们可以把它想象成数据库操作,比如 select 相关账户并 update 其资金余额: public void transfer(Long depositorId, Long favoredId, BigDecimal amount) { Account depositorAccount = accountRepository.findById(depositorId) .orElseThrow(IllegalArgumentException::new); Account favoredAccount = accountRepository.findById(favoredId) .orElseThrow(IllegalArgumentException::new); depositorAccount.setBalance(depositorAccount.getBalance().subtract(amount)); favoredAccount.setBalance(favoredAccount.getBalance().add(amount)); accountRepository.save(depositorAccount); accountRepository.save(favoredAccount); } 首先使用 findById() 查找相关账户,如果给定 ID 的账户不存在,则抛出 IllegalArgumentException 异常。

在 Spring Boot Filter 中获取响应体

1、简介 本文将带你了解如何在 Spring Boot Filter(过滤器)中获取 ServletResponse 的响应体。 2、场景 在使用 Spring Boot 中使用 Filter 时,从 ServletResponse 访问响应体非常麻烦。这是因为响应体不是随时可用的,它是在 Filter 链执行完毕后才写入输出流的。 但是,有些操作(如生成哈希签名)需要在发送给客户端之前读取完整的响应正文的内容。因此,需要找到读取响应体内容的方法。 3、使用 ContentCachingResponseWrapper 创建一个自定义 Filter,并使用 Spring 提供的 ContentCachingResponseWrapper 类进行包装: @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { ContentCachingResponseWrapper responseCacheWrapperObject = new ContentCachingResponseWrapper((HttpServletResponse) servletResponse); filterChain.doFilter(servletRequest, responseCacheWrapperObject); byte[] responseBody = responseCacheWrapperObject.getContentAsByteArray(); MessageDigest md5Digest = MessageDigest.getInstance("MD5"); byte[] md5Hash = md5Digest.digest(responseBody); String md5HashString = DatatypeConverter.printHexBinary(md5Hash); responseCacheWrapperObject.getResponse().setHeader("Response-Body-MD5", md5HashString); // ... } 简而言之,wrapper 类允许我们封装 HttpServletResponse 以缓存响应正文内容,并调用 doFilter() 将请求传递给下一个 Filter。

Spring Boot 使用 git-commit-id-maven-plugin 打包应用

原文地址:https://www.cnblogs.com/Naylor/p/18024689 简介 git-commit-id-maven-plugin 是一个maven 插件,用来在打包的时候将 git-commit 信息打进 Jar 中。 这样做的好处是可以将发布的某版本和对应的代码关联起来,方便查阅和线上项目的维护。至于它的作用,用官方说法,这个功能对于大型分布式项目来说是无价的。 功能 你是否经常遇到这样的问题: 测试提交了一个bug,开发人员无法确认是哪个版本有这个问题?当前测试环境部署的是某个版本吗?生产环境会不会也有这个问题? 公司内部的项目,总共几十、几百个服务,每天都有服务的生产环境部署,一个服务甚至一天上线好几次,对于项目管理来说无法清晰了解某一时刻某个服务的版本 如何验证我的代码是否已经上线? 。。。。。。 以上种种,都有一个共同的诉求,就是我希望在打包的时候将最后一次 git commit id 和当前 jar 关联起来并可试试查询 jar 对应的 git commit id 。 实践 引入插件 本例 Spring Boot 版本为 2.7.6,java 版本为 11 此插件已经上传到中央仓库(https://central.sonatype.com/artifact/io.github.git-commit-id/git-commit-id-maven-plugin?smo=true) 在项目pom.xml 中引入如下插件 <project> ...... <build> <plugins> <!-- git-commit-id-maven-plugin :打包的时候携带git提交信息 --> <plugin> <groupId>io.github.git-commit-id</groupId> <artifactId>git-commit-id-maven-plugin</artifactId> <version>5.0.0</version> <executions> <execution> <id>get-the-git-infos</id> <goals> <goal>revision</goal> </goals> <phase>initialize</phase> </execution> </executions> <configuration> <generateGitPropertiesFile>true</generateGitPropertiesFile> <generateGitPropertiesFilename>${project.build.outputDirectory}/git.</generateGitPropertiesFilename> <includeOnlyProperties> <includeOnlyProperty>^git.build.(time|version)$</includeOnlyProperty> <includeOnlyProperty>^git.commit.id.(abbrev|full)$</includeOnlyProperty> </includeOnlyProperties> <format>txt</format> <commitIdGenerationMode>full</commitIdGenerationMode> </configuration> </plugin> </plugins> </build> </project> generateGitPropertiesFilename:用于指定生成的 gitCommitInfo 存放到哪个位置,后缀可以任意指定,如果不指定将使用 format 的值 format:指定文件后缀,一般为 properties、json commitIdGenerationMode:记录完整信息,若 format 为 json,此值必须为 full 此外为了能成功打出 jar 包,还需要如下插件的配合:

在 Spring Boot 中动态管理 Kafka Listener

1、概览 本文将带你了解如何在 Spring Boot 应用中动态地启动和停止 Kafka Listener。 2、依赖 首先,添加 spring-kafka 依赖: <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>3.1.2</version> </dependency> 3、配置 Kafka 消费者 生产者是向 Kafka Topic 发布(写入)事件的应用。 在本教程中,我们使用单元测试来模拟生产者向 Kafka Topic 发送事件。订阅 Topic 并处理事件流的消费者由应用中的 Listener(监听器)表示。该 Listener 被配置为处理来自 Kafka 的传入消息。 通过 KafkaConsumerConfig 类来配置 Kafka 消费者,其中包括 Kafka broker 的地址、消费者组 ID 以及 Key 和 Value 的反序列化器: @Bean public DefaultKafkaConsumerFactory<String, UserEvent> consumerFactory() { Map<String, Object> props = new HashMap<>(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class); props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); props.put(JsonDeserializer.TRUSTED_PACKAGES, "com.baeldung.spring.kafka.start.stop.consumer"); return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), new JsonDeserializer<>(UserEvent.