Spring - WebClient & RestTemplate
Posted dingwen_blog
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring - WebClient & RestTemplate相关的知识,希望对你有一定的参考价值。
前言
在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.创建
@Test
void webclient01Create(){
WebClient webClient = WebClient.create();
Mono<String> mono = webClient
.get() //get请求
.uri(BASE_URI + "/test/get01")
.retrieve() //获取响应体
.bodyToMono(String.class); //格式化响应数据
log.info("{}",mono.block());
}
@Test
void webclient02Create(){
WebClient webClient = WebClient.create(BASE_URI);
Mono<String> mono = webClient
.get()
.uri("/test/get01")// uri 会自动拼接
.retrieve()
.bodyToMono(String.class);
log.info("{}",mono.block());
}
@Test
void 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.使用
@Test
void 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());
}
@Test
void 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
*/
@Bean
public 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 {
@Autowired
private RestTemplate restTemplate;
@Test
void contextLoads() {
}
@Test
void 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; }
-
客户端测试代码
@Test void 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; }
-
客户端测试代码
@Test void 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; }
-
客户端测试代码
@Test void 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; }
-
客户端测试代码
@Test void 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"; }
-
客户端测试代码
@Test void 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; } }
-
客户端测试代码
@Test void 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; }
-
客户端测试代码
@Test void 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; }
-
客户端测试代码
@Test void 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(使用 ssl 的 Spring 5 WebClient
-