1、概览 在本教程中,我们将对 Spring Feign(声明式 REST 客户端)和 Spring WebClient(Spring 5 中引入的响应式 Web 客户端)的优缺点进行比较。
2、阻塞式和非阻塞式客户端 在微服务系统中,服务之间的通常通过 RESTful API 进行通信。因此,需要一个 Web 客户端来执行请求。
接下来,我们将了解阻塞式 Feign 客户端与非阻塞式 WebClient 之间的区别。
2.1、阻塞式 Feign 客户端 Feign 客户端是一种声明式 REST 客户端,能快速、简单地开发 Web 客户端。使用 Feign 时,只需定义接口并对其进行相应注解。然后,Spring 会在运行时生成实际的 Web 客户端实现。
@FeignClient 的实现使用 “每个请求一个线程” 的同步模型。因此,对于每个请求,当前线程会阻塞,直到收到响应为止。如果同时发起多个请求,就要使用大量的线程,每个打开的线程都会占用内存和CPU。
2.2、非阻塞式 WebClient 客户端 WebClient 是 Spring WebFlux 的一部分。它是 Spring Reactive Framework 提供的一种非阻塞解决方案,用于解决 Feign 客户端等同步实现的性能瓶颈。
Feign 客户端会为每个请求创建一个线程并阻塞它,直到收到响应为止,而 WebClient 会执行 HTTP 请求并将 “等待响应” 任务添加到队列中。随后,在收到响应后,从队列中执行 “等待响应” 任务,最后将响应发送给 Subscriber (订阅者)函数。
Reactive 框架实现了由 Reactive Streams API 支持的事件驱动架构。正如我们所看到的,这使我们能够编写只需要最少数量的阻塞线程就能执行 HTTP 请求的服务。
JdbcTemplate 是由 spring-jdbc 提供的一个 JDBC 工具类模块,可以快速地对数据库进行 CURD 操作。
本文将会教你如何在使用 JdbcTemplate 执行 INSERT 操作时获取到自增ID。
项目设置 创建任意 spring boot 项目。添加 mysql-connector-j 以及 spring-boot-starter-jdbc 依赖。
你可以通过 start.springboot.io 快速创建此示例项目。
<dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> 配置数据源 在 application.yaml 中配置基础的数据源信息。
spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true username: root password: root 配置 JdbcTemplate 通过配置类,注册 JdbcTemplate Bean。
import javax.sql.DataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; @Configuration public class JdbcTemplateConfiguration { @Bean public JdbcTemplate jdbcTemplate (DataSource dataSource) { return new JdbcTemplate(dataSource); } } 创建示例表 CREATE TABLE `users` ( `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', `account` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '账户', `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名称', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系统用户'; 其中 id 字段是自增的。
1、概览 在本教程中,我们将了解如何在运行时重新初始化 Spring Context 中的 Singleton Bean。
默认情况下,Singleton Scope Bean 不会在应用生命周期中重新初始化。不过,有时可能需要重新创建 Bean,例如在需要更新属性时。我们将介绍几种实现此目的的方法。
2、示例 一个小示例。创建一个 Bean,从配置文件中读取属性保存在内存中。如果配置文件中的属性值被修改,那么 Bean 就要重新加载配置。
2.1、Singleton Bean 首先创建 ConfigManager 类:
@Service("ConfigManager") public class ConfigManager { private static final Log LOG = LogFactory.getLog(ConfigManager.class); private Map<String, Object> config; private final String filePath; public ConfigManager(@Value("${config.file.path}") String filePath) { this.filePath = filePath; initConfigs(); } private void initConfigs() { Properties properties = new Properties(); try { properties.load(Files.newInputStream(Paths.get(filePath))); } catch (IOException e) { LOG.error("Error loading configuration:", e); } config = new HashMap<>(); for (Map.
1、简介 在本教程中,我们将学习如何在 Spring Cloud Gateway 中根据客户端的实际 IP 地址来限制请求速率。
简而言之,我们将在路由上设置 RequestRateLimiter Filter,然后配置网关根据 IP 地址来限制客户端的请求。
2、路由配置 首先,我们需要配置 Spring Cloud Gateway 以对特定路由进行速率限制。为此,我们将使用由 spring-boot-starter-data-redis-reactive 实现的经典 令牌桶(Token Bucket) Rate Limiter。简而言之, Rate Limiter 创建一个带有唯一 Key 的 Bucket,该 Key 用于标识 Bucket 自身,并具有固定的初始 Token 容量,随时间自动生成 Token。然后,对于每个请求,Rate Limiter 会获取其关联的 Bucket,并试图减少其 Bucket 中的一个 Token。如果 Bucket 中的 Token 数量不足,将拒绝传入的请求。
在分布式系统中,我们希望所有系统对同一客户端的请求都采用相同的限速策略。所以,我们使用分布式缓存 Redis 来存储 Bucket。在本例中,我们预先配置了一个 Redis 实例。
接下来,配置一个带有 Rate Limiter 的路由。监听 /example 端点,并将请求转发至 http://example.org:
@Bean public RouteLocator myRoutes(RouteLocatorBuilder builder) { return builder.routes() .route("requestratelimiter_route", p -> p .
HTTP 请求头一般都是由客户端设置,服务端获取,用以处理业务。但是偶尔也有一些需求,需要服务端对一些请求,添加自定义的请求头。例如:统一为所有请求添加一个名为 X-Requested-Id 的请求头,用于跟踪不同请求。
Servlet 中的请求对象 jakarta.servlet.http.HttpServletRequest 并未提供设置 Header 的方法。
但是,但是通过一些方法,可以实现。本教程将会教你如何在 Spring Boot 应用中添加自定义请求头到 HttpServletRequest。
思路 既然可以从 HttpServletRequest 获取到 Header,那它一定是把 Header 存储在了某个地方。我们只要找到 HttpServletRequest 存储 Header 的容器,就可以对其进行添加、编辑、删除操作。
不同 HttpServletRequest 的实现中,对 Header 的存储方式不一定一样。目前,在 Spring Boot 中最流行的 Servlet 实现就是 Tomcat 和 Undertow,所以下文将会针对这两个服务器进行讲解。
示例应用 创建一个 Spring Boot 应用。我们要在 Filter 中为所有请求添加一个 X-Requested-Id 请求头,并在 Controller 中获取并返回。
Controller 如下:
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import jakarta.servlet.http.HttpServletRequest; @RestController @RequestMapping("/demo") public class DemoController { @GetMapping public String getHeaders (HttpServletRequest request) { // 获取 X-Requested-Id Header 值 String requestedId = request.
1、概览 Springdoc-OpenAPI 是一个为 Spring Boot 应用程序自动生成 API 文档的框架。它实现了 OpenAPI 3 规范,使用它,通过 UI 界面就可以与 API 进行交互,非常方便。
在本教程中,我们将学习如何使用 Spring Security 通过表单登录和 Basic Authentication 来认证 springdoc 中的端点访问。
2、项目设置 创建一个 Spring Boot Web 应用程序,该应用程序将通过 Spring Security 保护 API,并使用 Springdoc 生成文档。
2.1、依赖 首先添加 springdoc-openapi-ui,它整合了 Swagger-UI,用于提供 UI 界面:
http://localhost:8080/swagger-ui.html 其次,添加 springdoc-openapi-security,用于为 Spring Security 提供支持:
<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-ui</artifactId> <version>1.6.13</version> </dependency> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-security</artifactId> <version>1.6.13</version> </dependency> 2.2、示例 API 创建一个示例 REST Controller,Springdoc 会为其生成文档。
此外,我们将通过 Swagger-UI 来演示与 FooController 中受保护(需要认证)的端点进行交互。
@RestController @RequestMapping(value = "foos", produces = MediaType.
1、简介 在本教程中,我们将学习如何使用 WebClient 从服务器以流式下载一个大文件。我们通过创建一个简单的 Controller 和两个客户端进行演示。最后,我们将了解如何以及何时使用 Spring 的 DataBuffer 和 DataBufferUtils 工具类。
2、服务器 创建一个可以下载文件的简单 Controller。
首先,构建一个 FileSystemResource,传递一个文件路径,然后将其封装为 ResponseEntity 的 body:
@RestController @RequestMapping("/large-file") public class LargeFileController { @GetMapping ResponseEntity<Resource> get() { return ResponseEntity.ok() .body(new FileSystemResource(Paths.get("/tmp/large.dat"))); } } 其次,我们需要生成下载所用的示例文件,文件内容并不重要,所以我们使用 fallocate 在磁盘上生成一个指定大小的“空”内容文件。如下:
fallocate -l 128M /tmp/large.dat 然后就可以开始编写客户端了。
3、WebClient 使用 ExchangeStrategies 处理大文件 先用一个简单而有限的 WebClient 下载文件。使用 ExchangeStrategies 来提高 exchange() 操作的可用内存限制。这样,就能操作更多字节,但仍受限于 JVM 可用的最大内存。
使用 bodyToMono() 从服务器获取 Mono<byte[]>:
public class LimitedFileDownloadWebClient { public static long fetch(WebClient client, String destination) { Mono<byte[]> mono = client.
1、概览 当我们在执行 Spring Boot JAR 文件时候,可能会遇到 “jar中没有主清单属性”(no main manifest attribute)错误。这是因为我们在 MANIFEST.MF 文件中缺少了 Main-Class 元数据属性的声明,该文件位于 META-INF 文件夹下。
在本教程中,我们将重点介绍造成这一问题的原因以及如何解决。
TL;DR
出现这个问题的原因,八成是你忘记在 pom.xml 中添加 spring-boot-maven-plugin 插件了,添加即可。如下:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> 2、问题的出现 一般来说,从 Spring Initializr 获取 pom.xml 不会有任何问题。不过,如果我们是手动创建项目,自己在 pom.xml 中添加 spring-boot-starter-parent 依赖,可能会遇到这个问题。
我们可以尝试以 mvn clean package 的方式来构建应用,以重现这个问题:
$ mvn clean package 运行 jar 时,我们会遇到错误:
$ java -jar target\spring-boot-artifacts-2.jar no main manifest attribute, in target\spring-boot-artifacts-2.jar 在此示例中,MANIFEST.MF 文件的内容为:
Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Created-By: Apache Maven 3.
1、概览 我们经常遇到的一个编码问题是,URI 变量中包含一个加号(+)。例如,如果我们的 URI 变量值为 http://localhost:8080/api/v1/plus+sign,那么加号将被编码为空格,这可能会导致意外的服务器响应。
在本教程中,我们将学习如何在 Spring 的 RestTemplate 上对 URI 变量进行编码。
让我们来看看解决这个问题的几种方法。
2、项目设置 创建一个使用 RestTemplate 进行 API 调用的小项目。
2.1、Spring Web 依赖 在 pom.xml 中添加 Spring Web Starter 依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> 或者,可以使用 Spring Initializr 生成项目并添加依赖。
2.2、RestTemplate Bean 创建一个 RestTemplate Bean:
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } } 3、 调用 API 创建一个 service 类,调用公共 API http://httpbin.org/get。
API 会返回一个包含请求参数的 JSON 响应。例如,如果我们在浏览器上访问 URL https://httpbin.
Spring Boot 默认使用 Tomcat 作为嵌入式 Servlet 服务器,它是由 Apache 软件基金会下 Jakarta 项目开发的 Servlet 容器,被广泛用于部署和运行 Java Web 应用程序。特点是轻量级、易于安装和配置,并且具有良好的可扩展性和性能
本文将会介绍另一款优秀的 Servlet 容器,Undertow。以及如何在 Spring Boot 中用它来替换 Tomcat 作为嵌入式服务器。
Undertow Undertow 是一个轻量级的、高性能的 Java Web 服务器,由 JBoss 开发并开源。它是基于非阻塞(non-blocking)的I/O模型,具有低资源消耗和高并发处理能力。
Undertown 的优势如下:
支持 HTTP/2:Undertow 开箱即支持 HTTP/2,无需重写启动类路径。 支持 HTTP Upgrade:允许通过 HTTP 端口复用多种协议。 支持 Web Socket:Undertow 提供对 Web Sockets 的全面支持,包括 JSR-356 支持。 Servlet 4.0:Undertow 支持 Servlet 4.0,包括对嵌入式 Servlet 的支持。还可以在同一部署中混合使用 Servlet 和原生 undertow 非阻塞 handler。 可嵌入式:只需几行代码,即可将 Undertow 嵌入应用程序或独立运行。 灵活性:Undertow 通过链式 handler 进行配置,可以根据需求灵活地添加功能。 在很多场景的测试下, Undertow 的性能都高于 Tomcat。天生适合作为 Spring Boot 应用的嵌入式服务器!