0


Spring Boot 的 WebClient 实践教程

什么是 WebClient?

在 Spring Boot 中,WebClient 是 Spring WebFlux 提供的一个非阻塞、响应式的 HTTP 客户端,用于与 RESTful 服务或其他 HTTP 服务交互。相比于传统的 RestTemplate,WebClient 更加现代化,具有异步和非阻塞的特点,适合高性能、高并发的应用场景。

WebClient 的特点

非阻塞 I/O:适用于响应式编程模型,能高效处理大量并发请求。

功能强大:支持同步和异步调用,处理复杂的 HTTP 请求和响应,包括流式数据。

灵活的配置:可自定义超时、请求拦截器、认证方式等。

响应式编程支持:返回 Mono 或 Flux,与 Spring WebFlux 的响应式编程模型无缝集成。

引入依赖

在使用 WebClient 之前,需要确保 Spring Boot 项目已包含相关依赖。

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-webflux</artifactId>
  4. </dependency>

配置及使用 WebClient

现在有以下服务

创建 WebClientConfig 配置类,为 service1 和 service2 配置独立的 WebClient。

  1. package com.example.common.config;
  2. import io.netty.channel.ChannelOption;
  3. import io.netty.handler.timeout.ReadTimeoutHandler;
  4. import io.netty.handler.timeout.WriteTimeoutHandler;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
  8. import org.springframework.web.reactive.function.client.ExchangeStrategies;
  9. import org.springframework.web.reactive.function.client.WebClient;
  10. import reactor.core.publisher.Mono;
  11. import reactor.netty.http.client.HttpClient;
  12. import reactor.netty.tcp.TcpClient;
  13. import org.springframework.http.client.reactive.ReactorClientHttpConnector;
  14. /**
  15. * 配置 WebClient,支持基础功能(独立 WebClient 实例)和高级特性(超时、拦截器、内存限制)。
  16. */
  17. @Configuration
  18. public class WebClientConfig {
  19. /**
  20. * 配置 WebClient,用于调用 service1(http://localhost:8081)
  21. *
  22. * @param builder WebClient.Builder 实例
  23. * @return 针对 service1 的 WebClient 实例
  24. */
  25. @Bean(name = "service1WebClient")
  26. public WebClient service1WebClient(WebClient.Builder builder) {
  27. return builder
  28. .baseUrl("http://localhost:8081") // 配置 service1 的基本 URL
  29. .defaultHeader("Content-Type", "application/json") // 设置默认请求头
  30. .exchangeStrategies(
  31. ExchangeStrategies.builder()
  32. .codecs(configurer -> configurer
  33. .defaultCodecs()
  34. .maxInMemorySize(16 * 1024 * 1024)) // 设置最大内存限制为 16MB
  35. .build())
  36. .filter(logRequest()) // 添加请求日志拦截器
  37. .filter(logResponse()) // 添加响应日志拦截器
  38. .build();
  39. }
  40. /**
  41. * 配置 WebClient,用于调用 service2(http://localhost:8082)
  42. *
  43. * @param builder WebClient.Builder 实例
  44. * @return 针对 service2 的 WebClient 实例
  45. */
  46. @Bean(name = "service2WebClient")
  47. public WebClient service2WebClient(WebClient.Builder builder) {
  48. return builder
  49. .baseUrl("http://localhost:8082") // 配置 service2 的基本 URL
  50. .defaultHeader("Content-Type", "application/json") // 设置默认请求头
  51. .filter(logRequest()) // 添加请求日志拦截器
  52. .filter(logResponse()) // 添加响应日志拦截器
  53. .build();
  54. }
  55. /**
  56. * 提供全局的 WebClient.Builder 配置,支持超时和高级功能。
  57. *
  58. * @return 配置好的 WebClient.Builder
  59. */
  60. @Bean
  61. public WebClient.Builder webClientBuilder() {
  62. // 配置 TCP 客户端,设置连接超时、读超时和写超时
  63. TcpClient tcpClient = TcpClient.create()
  64. .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 连接超时 5秒
  65. .doOnConnected(connection ->
  66. connection.addHandlerLast(new ReadTimeoutHandler(5)) // 读超时 5秒
  67. .addHandlerLast(new WriteTimeoutHandler(5))); // 写超时 5秒
  68. // 使用配置的 TcpClient 创建 HttpClient
  69. HttpClient httpClient = HttpClient.from(tcpClient);
  70. // 创建 WebClient.Builder 并配置 HttpClient 和拦截器
  71. return WebClient.builder()
  72. .clientConnector(new ReactorClientHttpConnector(httpClient)) // 配置 HttpClient
  73. .filter(logRequest()) // 请求日志拦截器
  74. .filter(logResponse()); // 响应日志拦截器
  75. }
  76. /**
  77. * 请求日志拦截器:记录请求的详细信息(方法和 URL)
  78. *
  79. * @return ExchangeFilterFunction 拦截器
  80. */
  81. private ExchangeFilterFunction logRequest() {
  82. return ExchangeFilterFunction.ofRequestProcessor(request -> {
  83. System.out.println("Request: " + request.method() + " " + request.url());
  84. return Mono.just(request);
  85. });
  86. }
  87. /**
  88. * 响应日志拦截器:记录响应的状态码
  89. *
  90. * @return ExchangeFilterFunction 拦截器
  91. */
  92. private ExchangeFilterFunction logResponse() {
  93. return ExchangeFilterFunction.ofResponseProcessor(response -> {
  94. System.out.println("Response status: " + response.statusCode());
  95. return Mono.just(response);
  96. });
  97. }
  98. }

service1相应的接口

  1. package cloud.service1.controller;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. import java.util.Map;
  6. /**
  7. * Service1 的控制器类,用于处理与API相关的请求.
  8. * 该类被Spring框架管理,作为处理HTTP请求的一部分.
  9. */
  10. @RestController
  11. @RequestMapping("/api/service1")
  12. public class Service1Controller {
  13. /**
  14. * 获取Service1的数据信息.
  15. *
  16. * @return 包含服务信息的映射,包括服务名称和问候消息.
  17. */
  18. @GetMapping("/data")
  19. public Map<String, String> getData() {
  20. // 返回一个不可变的映射,包含服务名称和问候消息
  21. return Map.of("service", "service1", "message", "Hello from Service1");
  22. }
  23. }

service2相应的接口

  1. package cloud.service2.controller;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. import java.util.Map;
  6. /**
  7. * Service2的控制器类,用于处理与Service2相关的HTTP请求.
  8. * 该类被Spring框架管理,作为处理RESTful请求的控制器.
  9. */
  10. @RestController
  11. @RequestMapping("/api/service2")
  12. public class Service2Controller {
  13. /**
  14. * 处理GET请求到/api/service2/info,返回Service2的信息.
  15. *
  16. * @return 包含服务信息的Map,包括服务名称和欢迎消息.
  17. */
  18. @GetMapping("/info")
  19. public Map<String, String> getInfo() {
  20. return Map.of("service", "service2", "message", "Hello from Service2");
  21. }
  22. }

服务调用实现

  1. package com.example.common.service;
  2. import org.springframework.beans.factory.annotation.Qualifier;
  3. import org.springframework.stereotype.Service;
  4. import org.springframework.web.reactive.function.client.WebClient;
  5. import reactor.core.publisher.Mono;
  6. /**
  7. * CommonService 类提供了对其他服务进行调用的方法
  8. * 它通过 WebClient 实例与 service1 和 service2 进行通信
  9. */
  10. @Service
  11. public class CommonService {
  12. // 用于与 service1 通信的 WebClient 实例
  13. private final WebClient service1WebClient;
  14. // 用于与 service2 通信的 WebClient 实例
  15. private final WebClient service2WebClient;
  16. /**
  17. * 构造函数注入两个 WebClient 实例
  18. *
  19. * @param service1WebClient 用于 service1 的 WebClient
  20. * @param service2WebClient 用于 service2 的 WebClient
  21. */
  22. public CommonService(
  23. @Qualifier("service1WebClient") WebClient service1WebClient,
  24. @Qualifier("service2WebClient") WebClient service2WebClient) {
  25. this.service1WebClient = service1WebClient;
  26. this.service2WebClient = service2WebClient;
  27. }
  28. /**
  29. * 调用 service1 的接口
  30. *
  31. * @return 来自 service1 的数据
  32. */
  33. public Mono<String> callService1() {
  34. // 通过 service1WebClient 调用 service1 的 API,并处理可能的错误
  35. return service1WebClient.get()
  36. .uri("/api/service1/data")
  37. .retrieve()
  38. .bodyToMono(String.class)
  39. .onErrorResume(e -> {
  40. // 错误处理:打印错误信息并返回错误提示
  41. System.err.println("Error calling service1: " + e.getMessage());
  42. return Mono.just("Error calling service1");
  43. });
  44. }
  45. /**
  46. * 调用 service2 的接口
  47. *
  48. * @return 来自 service2 的数据
  49. */
  50. public Mono<String> callService2() {
  51. // 通过 service2WebClient 调用 service2 的 API,并处理可能的错误
  52. return service2WebClient.get()
  53. .uri("/api/service2/info")
  54. .retrieve()
  55. .bodyToMono(String.class)
  56. .onErrorResume(e -> {
  57. // 错误处理:打印错误信息并返回错误提示
  58. System.err.println("Error calling service2: " + e.getMessage());
  59. return Mono.just("Error calling service2");
  60. });
  61. }
  62. }
  1. package com.example.common.controller;
  2. import com.example.common.service.CommonService;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. import reactor.core.publisher.Mono;
  7. /**
  8. * 通用控制器类,处理与通用服务相关的API请求
  9. */
  10. @RestController
  11. @RequestMapping("/api/common")
  12. public class CommonController {
  13. // 注入通用服务接口,用于调用具体的服务方法
  14. private final CommonService commonService;
  15. /**
  16. * 构造函数注入CommonService实例
  17. *
  18. * @param commonService 通用服务接口实例
  19. */
  20. public CommonController(CommonService commonService) {
  21. this.commonService = commonService;
  22. }
  23. /**
  24. * 调用 service1 的接口
  25. *
  26. * @return service1 的响应数据
  27. */
  28. @GetMapping("/service1")
  29. public Mono<String> getService1Data() {
  30. return commonService.callService1();
  31. }
  32. /**
  33. * 调用 service2 的接口
  34. *
  35. * @return service2 的响应数据
  36. */
  37. @GetMapping("/service2")
  38. public Mono<String> getService2Info() {
  39. return commonService.callService2();
  40. }
  41. }

测试接口

优化实践

将上述代码进一步优化和整合以确保代码可维护性和高效性。

  1. package com.example.common.config;
  2. import io.netty.channel.ChannelOption;
  3. import io.netty.handler.timeout.ReadTimeoutHandler;
  4. import io.netty.handler.timeout.WriteTimeoutHandler;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.http.client.reactive.ReactorClientHttpConnector;
  8. import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
  9. import org.springframework.web.reactive.function.client.ExchangeStrategies;
  10. import org.springframework.web.reactive.function.client.WebClient;
  11. import reactor.core.publisher.Mono;
  12. import reactor.netty.http.client.HttpClient;
  13. import reactor.netty.tcp.TcpClient;
  14. /**
  15. * 配置 WebClient 的各类设置和日志记录
  16. */
  17. @Configuration
  18. public class WebClientConfig {
  19. /**
  20. * 全局 WebClient.Builder 配置
  21. *
  22. * @return 配置好的 WebClient.Builder
  23. */
  24. @Bean
  25. public WebClient.Builder webClientBuilder() {
  26. // 配置 TCP 客户端的连接、读取、写入超时时间
  27. TcpClient tcpClient = TcpClient.create()
  28. .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 连接超时
  29. .doOnConnected(conn -> conn
  30. .addHandlerLast(new ReadTimeoutHandler(5)) // 读超时
  31. .addHandlerLast(new WriteTimeoutHandler(5))); // 写超时
  32. // 将 TCP 客户端配置应用到 HTTP 客户端
  33. HttpClient httpClient = HttpClient.from(tcpClient);
  34. // 配置 WebClient 构建器,包括 HTTP 连接器、交换策略、请求和响应日志
  35. return WebClient.builder()
  36. .clientConnector(new ReactorClientHttpConnector(httpClient))
  37. .exchangeStrategies(ExchangeStrategies.builder()
  38. .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024)) // 内存限制
  39. .build())
  40. .filter(logRequest()) // 请求日志
  41. .filter(logResponse()); // 响应日志
  42. }
  43. /**
  44. * 针对 service1 的 WebClient 配置
  45. *
  46. * @param builder 全局配置的 WebClient.Builder
  47. * @return 配置好的 WebClient 实例
  48. */
  49. @Bean(name = "service1WebClient")
  50. public WebClient service1WebClient(WebClient.Builder builder) {
  51. // 为 service1 配置特定的 base URL 和默认头部
  52. return builder
  53. .baseUrl("http://localhost:8081")
  54. .defaultHeader("Content-Type", "application/json")
  55. .build();
  56. }
  57. /**
  58. * 针对 service2 的 WebClient 配置
  59. *
  60. * @param builder 全局配置的 WebClient.Builder
  61. * @return 配置好的 WebClient 实例
  62. */
  63. @Bean(name = "service2WebClient")
  64. public WebClient service2WebClient(WebClient.Builder builder) {
  65. // 为 service2 配置特定的 base URL 和默认头部
  66. return builder
  67. .baseUrl("http://localhost:8082")
  68. .defaultHeader("Content-Type", "application/json")
  69. .build();
  70. }
  71. /**
  72. * 请求日志拦截器
  73. *
  74. * @return 记录请求日志的 ExchangeFilterFunction
  75. */
  76. private ExchangeFilterFunction logRequest() {
  77. // 拦截请求并打印请求方法和URL
  78. return ExchangeFilterFunction.ofRequestProcessor(request -> {
  79. System.out.println("Request: " + request.method() + " " + request.url());
  80. return Mono.just(request);
  81. });
  82. }
  83. /**
  84. * 响应日志拦截器
  85. *
  86. * @return 记录响应日志的 ExchangeFilterFunction
  87. */
  88. private ExchangeFilterFunction logResponse() {
  89. // 拦截响应并打印响应状态码
  90. return ExchangeFilterFunction.ofResponseProcessor(response -> {
  91. System.out.println("Response status: " + response.statusCode());
  92. return Mono.just(response);
  93. });
  94. }
  95. }
  1. package com.example.common.service;
  2. import org.springframework.core.ParameterizedTypeReference;
  3. import org.springframework.stereotype.Service;
  4. import org.springframework.web.reactive.function.client.WebClient;
  5. import reactor.core.publisher.Mono;
  6. import java.util.Map;
  7. /**
  8. * CommonService 类提供了调用两个不同服务的公共方法,并合并其结果
  9. */
  10. @Service
  11. public class CommonService {
  12. // service1 的 WebClient 实例
  13. private final WebClient service1WebClient;
  14. // service2 的 WebClient 实例
  15. private final WebClient service2WebClient;
  16. /**
  17. * 构造函数注入 WebClient 实例
  18. *
  19. * @param service1WebClient service1 的 WebClient
  20. * @param service2WebClient service2 的 WebClient
  21. */
  22. public CommonService(WebClient service1WebClient, WebClient service2WebClient) {
  23. this.service1WebClient = service1WebClient;
  24. this.service2WebClient = service2WebClient;
  25. }
  26. /**
  27. * 异步调用 service1 和 service2,并返回合并结果(JSON 格式)
  28. *
  29. * @return 包含两个服务响应的 Mono 对象
  30. */
  31. public Mono<Map<String, Map<String, String>>> callServicesAsync() {
  32. // 调用 service1,返回 Map 响应
  33. Mono<Map<String, String>> service1Response = service1WebClient.get()
  34. // 设置请求的URI
  35. .uri("/api/service1/data")
  36. // 检索响应
  37. .retrieve()
  38. // 处理错误状态
  39. .onStatus(
  40. // 检查状态是否为4xx或5xx
  41. status -> status.is4xxClientError() || status.is5xxServerError(),
  42. // 如果是,创建一个运行时异常
  43. response -> Mono.error(new RuntimeException("Service1 Error: " + response.statusCode()))
  44. )
  45. // 将响应体转换为Mono<Map<String, String>>
  46. .bodyToMono(new ParameterizedTypeReference<Map<String, String>>() {})
  47. // 处理错误
  48. .onErrorResume(e -> {
  49. // 打印错误信息
  50. System.err.println("Error calling service1: " + e.getMessage());
  51. // 返回一个包含错误信息的Map
  52. return Mono.just(Map.of("error", "Fallback response for service1"));
  53. });
  54. // 调用 service2,返回 Map 响应
  55. Mono<Map<String, String>> service2Response = service2WebClient.get()
  56. // 设置请求的URI
  57. .uri("/api/service2/info")
  58. // 检索响应
  59. .retrieve()
  60. // 处理错误状态
  61. .onStatus(
  62. // 检查状态是否为4xx或5xx
  63. status -> status.is4xxClientError() || status.is5xxServerError(),
  64. // 如果是,创建一个运行时异常
  65. response -> Mono.error(new RuntimeException("Service2 Error: " + response.statusCode()))
  66. )
  67. // 将响应体转换为Mono<Map<String, String>>
  68. .bodyToMono(new ParameterizedTypeReference<Map<String, String>>() {})
  69. // 处理错误
  70. .onErrorResume(e -> {
  71. // 打印错误信息
  72. System.err.println("Error calling service2: " + e.getMessage());
  73. // 返回一个包含错误信息的Map
  74. return Mono.just(Map.of("error", "Fallback response for service2"));
  75. });
  76. // 合并两个响应
  77. return Mono.zip(service1Response, service2Response, (response1, response2) -> Map.of(
  78. "service1", response1,
  79. "service2", response2
  80. ))
  81. // 处理合并过程中的错误
  82. .onErrorResume(e -> {
  83. // 打印错误信息
  84. System.err.println("Error combining responses: " + e.getMessage());
  85. // 返回一个包含错误信息的Map
  86. return Mono.just(Map.of(
  87. "error", Map.of(
  88. "status", "error",
  89. "message", e.getMessage() // 捕获异常并输出信息
  90. )
  91. ));
  92. });
  93. }
  94. }
  1. package com.example.common.controller;
  2. import com.example.common.service.CommonService;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. import reactor.core.publisher.Mono;
  7. import java.util.Map;
  8. @RestController
  9. @RequestMapping("/api/common")
  10. public class CommonController {
  11. private final CommonService commonService;
  12. public CommonController(CommonService commonService) {
  13. this.commonService = commonService;
  14. }
  15. /**
  16. * 提供异步调用的 REST 接口,返回 JSON 格式的数据
  17. */
  18. @GetMapping("/service")
  19. public Mono<Map<String, Map<String, String>>> getServicesData() {
  20. System.out.println("Received request for combined service data");
  21. return commonService.callServicesAsync()
  22. .doOnSuccess(response -> System.out.println("Successfully retrieved data: " + response))
  23. .doOnError(error -> System.err.println("Error occurred while fetching service data: " + error.getMessage()));
  24. }
  25. }

测试接口

结语

WebClient 是一个功能强大且灵活的非阻塞 HTTP 客户端,特别适合在高并发和响应式编程场景下使用,是替代传统 RestTemplate 的优秀选择。在实际项目中,通过合理配置(如超时、连接池)和优化(如负载均衡、重试机制),可以显著提高服务间通信的效率和可靠性,降低延迟和资源消耗。

同时,结合 Spring WebFlux 提供的响应式编程支持,WebClient 能够更好地应对微服务架构中复杂的通信需求,成为开发现代分布式系统的重要工具。

标签: spring boot 后端 java

本文转载自: https://blog.csdn.net/m0_74293254/article/details/143961724
版权归原作者 Onlooker﹒ 所有, 如有侵权,请联系我们删除。

“Spring Boot 的 WebClient 实践教程”的评论:

还没有评论