当前位置: 代码迷 >> 综合 >> Spring - WebClient RestTemplate
  详细解决方案

Spring - WebClient RestTemplate

热度:43   发布时间:2023-12-03 23:52:46.0

文章目录

    • 前言
    • 一、WebClient
      • 1.依赖
      • 2.创建
      • 3.使用
    • 二、RestTemplate
      • 1.小技巧
    • 2.示例
      • 2.1GET
      • 2.2 POST
      • 2.x其他请求类似
    • 三、代码地址

前言

在Web应用中我们需要对其他服务进行HTTP调用。WebClient和RestTemplate是两个由Spring提供的客户端工具。

一、WebClient

非阻塞式客户端。WebClient 使用 Spring Reactive Framework 所提供的异步非阻塞解决方案。
当 RestTemplate 为每个事件(HTTP 请求)创建一个新的线程时,WebClient 将为每个事件创建类似于“任务”。幕后,Reactive 框架将对这些 “任务” 进行排队,并仅在适当的响应可用时执行它们。
Reactive 框架使用事件驱动的体系结构。它提供了通过 Reactive Streams API 组合异步逻辑的方法。因此,与同步/阻塞方法相比,Reactive 可以使用更少的线程和系统资源来处理更多的逻辑。WebClient 是 Spring WebFlux 库的一部分。因此,我们还可以使用流畅的函数式 API 编写客户端代码,并将响应类型(Mono 和 Flux)作为声明来进行组合。

1.依赖

 <!--WebClient--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency>

2.创建

    @Testvoid webclient01Create(){
    WebClient webClient = WebClient.create();Mono<String> mono = webClient.get() //get请求.uri(BASE_URI + "/test/get01").retrieve() //获取响应体.bodyToMono(String.class); //格式化响应数据log.info("{}",mono.block());}
    @Testvoid webclient02Create(){
    WebClient webClient = WebClient.create(BASE_URI);Mono<String> mono = webClient.get().uri("/test/get01")// uri 会自动拼接.retrieve().bodyToMono(String.class);log.info("{}",mono.block());}
    @Testvoid webclient03Builder(){
    WebClient webClient = WebClient.builder().baseUrl(BASE_URI).build();Mono<String> mono = webClient.get().uri("/test/get01").retrieve().bodyToMono(String.class);log.info("{}",mono.block());}

3.使用

    @Testvoid webclient04(){
    WebClient webClient = WebClient.builder().baseUrl(BASE_URI).build();Mono<String> mono = webClient.get().uri(uriBuilder -> uriBuilder.path("/test/get02").queryParam("pageSize",10).queryParam("pageNum",1).build()).retrieve().bodyToMono(String.class);log.info("{}",mono.block());}
    @Testvoid webclient05(){
    WebClient webClient = WebClient.builder().baseUrl(BASE_URI).build();User user = User.builder().id("0000").password("0000").build();Mono<User> userMono = webClient.post().uri(uriBuilder -> uriBuilder.path("/test/post01").build()).contentType(MediaType.APPLICATION_JSON).body(Mono.just(user),User.class)
// .header().retrieve().bodyToMono(User.class);log.info("{}",userMono.block());}

二、RestTemplate

阻塞式客户端。RestTemplate 使用了基于每个请求对应一个线程模型(thread-per-request)的 Java Servlet API。这意味着,直到 Web 客户端收到响应之前,线程都将一直被阻塞下去。而阻塞代码带来的问题则是,每个线程都消耗了一定的内存和 CPU 周期。这些线程将耗尽线程池或占用所有可用内存。由于频繁的 CPU 上下文(线程)切换,我们还会遇到性能下降的问题。

1.小技巧

  • 方法名的第一部分表示HTTP请求类型,方法名的第二部分表示响应类型。例如:getForObject 表示执行GET请求并将响应转化成一个Object类型的对象。
  • 利用RestTemplate封装客户端发送HTTP请求时,如果出现异常就会抛出 RestClientException 类型的异常;可以通过在创建RestTemplate对象的时候指定一个ResponseErrorHandler类型的异常处理类来处理这个异常
  • exchange 和 excute 这两个方法是通用的HTTP请求方法,而且这两个方法还支持额外的HTTP请求类型
  • RestTemplate默认使用JDK提供的包去建立HTTP连接,当然,开发者也可以使用诸如 Apache HttpComponents, Netty, and OkHttp 去建立HTTP连
  • RestTemplate内部默认使用HttpMessageConverter来实现HTTTP messages 和 POJO 之间的转换,可以通过RestTemplate的成员方法 tMessageConverters(java.util.List<org.springframework.http.converter.HttpMessageConverter<?>>). 去修改默认的转换器
  • RestTemplate内部默认使用SimpleClientHttpRequestFactory创建HTTP连接可以通过HttpAccessor.setRequestFactory(org.springframework.http.client.ClientHttpRequestFactory)去做相应的修改
  • 每种方法都有3个重载方法,其中两个接收String类型的请求路径和响应类型、参数;另外一个接收URI类型的请求路径和响应类型
  • 使用String类型的请求路径时,RestTemplate会自动进行一次编码,所以为了避免重复编码问题最好使用URI类型的请求路径
  • getForObject 和 getForEntity 的区别:后者可以获取到更多的响应信息,前者这可以获取到响应体的数据

2.示例

使用@Configuration 提前注入的 RestTemplate

package com.dingwen.wcrtst.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;/*** RestTemplate 配置** @author dingwen* 2021.05.07 15:17*/
@Configuration
public class RestTemplateConfig {
    /** 注入 RestTemplate Bean* @return RestTemplate*/@Beanpublic RestTemplate restTemplate() {
    return new RestTemplate();}
}

2.1GET

  • 不带请求参数的GET请求

    • 服务端代码

      package com.dingwen.wcrtst.controller;import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;/*** Test** @author dingwen* 2021.05.07 15:53*/
      @RestController
      @RequestMapping("/test")
      public class TestController {
              /** 不带参数的get请求* @return String*/@GetMapping("/get01")public String get01() {
              return "get01() ok";}
      }
    • 客户端测试代码

package com.dingwen.wcrtst;import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.web.client.RestTemplate;import java.net.URI;@SpringBootTest
@Slf4j
class WcRtStApplicationTests {
    @Autowiredprivate RestTemplate restTemplate;@Testvoid contextLoads() {
    }@Testvoid get01() {
    URI uri = URI.create("http://192.168.0.49:8080/test/get01");String result = restTemplate.getForObject(uri, String.class);log.info("{}", result);}}
  • 带请求参数的GET请求

    • 服务端代码

          /** 带请求参数的get请求* @param pageSize* @param pageNum* @return String*/@GetMapping("/get02")public String get02(@RequestParam Integer pageSize, @RequestParam Integer pageNum) {
              return "get02() ok,请求参数为:" + pageSize + "/" + pageNum;}
      
    • 客户端测试代码

          @Testvoid get02() {
              String uri = "http://192.168.0.49:8080/test/get02?pageSize={1}&pageNum={2}";String result = restTemplate.getForObject(uri, String.class, 10, 1);log.info("{}",result);}
  • 带路径参数的GET请求

    • 服务端代码

          /** 带路径参数的get请求* @param id* @return String*/@GetMapping("/get03/{id}")public String get03(@PathVariable("id") String id) {
              return "get03() ok,路径参数为:" + id;}
      
    • 客户端测试代码

          @Testvoid get03(){
              URI uri = URI.create("http://192.168.0.49:8080/test/get03/8888");String result = restTemplate.getForObject(uri,String.class);log.info("{}",result);}
      
  • 带有请求参数和路径参数的GET请求

    • 服务端代码

          /** 带有请求参数和路径参数的get请求* @param score* @param id* @return String*/@GetMapping("/get04/{id}")public String get04(@RequestParam String score, @PathVariable("id") String id) {
              return "get04() ok,请求参数为:" + score + ",路径参数为:" + id;}
      
    • 客户端测试代码

          @Testvoid get04(){
              String uri = "http://192.168.0.49:8080/test/get04/8888/?score={1}";String result = restTemplate.getForObject(uri, String.class, 99);log.info("{}",result);}
      
  • 带请求参数的GET请求,Map传递

    • 服务端代码

          /** 带请求参数的get请求* @param pageSize* @param pageNum* @return String*/@GetMapping("/get02")public String get02(@RequestParam Integer pageSize, @RequestParam Integer pageNum) {
              return "get02() ok,请求参数为:" + pageSize + "/" + pageNum;}
      
    • 客户端测试代码

          @Testvoid get05(){
              Map<String,Object> params = new HashMap<>();params.put("pageSize",10);params.put("pageNum",1);String uri = BASE_URI +"/test/get02?pageSize={pageSize}&pageNum={pageNum}";String result = restTemplate.getForObject(uri,String.class,params);log.info("{}",result);}
      
  • 不带请求参数的GET请求返回ResponseEntity

    可以获取到完整的响应信息

    • 服务端代码

          /** 不带参数的get请求* @return String*/@GetMapping("/get01")public String get01() {
              return "get01() ok";}
      
    • 客户端测试代码

          @Testvoid get06(){
              String uri = BASE_URI + "/test/get01";ResponseEntity<String> result = restTemplate.getForEntity(uri,String.class);log.info("{}",result);}
      
  • 携带请求参数的GET请求,返回List<Entity>

    • 服务器端代码

       /** 模拟返回对象集合的GET请求,需要请求参数,携带token(过滤器实现:检查请求头)* @param pageSize* @param pageNum* @return User>*/@GetMapping("/get05")public List<User> get05(@RequestParam("pageSize") Integer pageSize,@RequestParam("pageNum") Integer pageNum) {
              List<User> userList = new ArrayList<>();userList.add(User.builder().id("1111").password("1111").build());userList.add(User.builder().id("2222").password("2222").build());log.info("pageSize={},pageNum={}",pageSize,pageNum);return userList;}
      }
      
    • 客户端测试代码

          @Testvoid get07(){
              String uri = BASE_URI + "/test/get05?pageSize={1}&pageNum={2}";ResponseEntity<List> result = restTemplate.getForEntity(uri,List.class,10,1);log.info("{}", result);}
      
  • 自定义请求头的GET请求

    • 服务端代码

       /** 模拟返回对象集合的GET请求,需要请求参数,携带token(过滤器实现:检查请求头)* @param pageSize* @param pageNum* @return User>*/@GetMapping("/get05")public List<User> get05(@RequestParam("pageSize") Integer pageSize,@RequestParam("pageNum") Integer pageNum, HttpServletRequest request) {
              List<User> userList = new ArrayList<>();userList.add(User.builder().id("1111").password("1111").build());userList.add(User.builder().id("2222").password("2222").build());String token = request.getHeader("token");log.info("token:{}",token);log.info("pageSize={},pageNum={}",pageSize,pageNum);return userList;}
      
    • 客户端测试代码

      @Testvoid get08(){
              String uri = BASE_URI + "/test/get05?pageSize={1}&pageNum={2}";HttpHeaders httpHeaders = new HttpHeaders();httpHeaders.add("token", UUID.randomUUID().toString());ResponseEntity<List> result = restTemplate.exchange(uri,HttpMethod.GET,new HttpEntity<>(httpHeaders),List.class,10,1);log.info("{}",result);}
      

2.2 POST

  • 没有请求参数只有请求体的POST请求

    • 服务端代码

          /** 只有请求体的POST请求* @param user**/@PostMapping("/post01")public User post01(@RequestBody User user){
              log.info("请求体:{}",user.toString());return user;}
      
    • 客户端测试代码

          @Testvoid post01(){
              String uri = BASE_URI + "/test/post01";User user = User.builder().id("9999").password("9999").build();ResponseEntity<User> resultUser = restTemplate.postForEntity(uri,user,User.class);
      // User user1 = restTemplate.postForObject(uri,user,User.class);log.info("{}",resultUser);}
      
  • 含有请求体、请求参数、路径参数的POST请求

    • 服务端代码

          /** 带有请求体参数、请求参数、路径参数的POST请求* @param user* @param status* @param type* @return String*/@PostMapping("/post02/{type}")public String post02(@RequestBody User user,@RequestParam("status") Integer status,@PathVariable("type") String type) {
              return "requestBody:" + user.toString() + ",requestParam:" + status + ",pathVariable:" + type + "";}
      
    • 客户端测试代码

          @Testvoid post02() {
              String uri = BASE_URI + "/test/post02/++++/?status={1}";String result = restTemplate.postForObject(uri,User.builder().id("***").password("....").build(),String.class,0);log.info("{}",result);}
      
  • 使用HttpEntity自定义请求体和请求头发起POST请求

    • 服务端代码

          /** 带有请求体参数、请求参数、路径参数的POST请求* @param user* @param status* @param type* @return String*/@PostMapping("/post02/{type}")public String post02(@RequestBody User user,@RequestParam("status") Integer status,@PathVariable("type") String type) {
              return "requestBody:" + user.toString() + ",requestParam:" + status + ",pathVariable:" + type + "";}
      
    • 客户端测试代码

          @Testvoid post03(){
              String uri =   BASE_URI + "/test/post02/788787/?status={1}";User user = User.builder().id("6666").password("7777").build();HttpHeaders httpHeaders = new HttpHeaders();httpHeaders.setContentType(MediaType.APPLICATION_JSON);HttpEntity<User> httpEntity = new HttpEntity<>(user,httpHeaders);String result = restTemplate.postForObject(uri,httpEntity,String.class,0);log.info("{}",result);}
      

2.x其他请求类似

三、代码地址

项目环境:SpringBoot 2.4.5

https://gitee.com/dingwen-gitee/wc-rt-st.git

  相关解决方案