1、RestTemplate
以前用过RestTemplate,也记录了一下:
https://blog.csdn.net/zzhongcy/article/details/104674808
这里再介绍一下RestTemplate的异步兄弟AsyncRestTemplate。
在 Spring 3 时代,为了能更优雅地实现HTTP调用,引入了 RestTemplate
,其中提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。
在 Spring 4 时代,为了能实现异步地HTTP调用,引入了AsyncRestTemplate
,使得编写异步代码和同步代码一样简单。
2、AsyncRestTemplate
熟悉使用RestTemplate的,不会觉得AsyncRestTemplate很难,因为的确很相似。在Spring 看来,你只管写同步的代码,异步的执行交给它处理就可以了。
AsyncRestTemplate 是 Spring中提供异步的客户端HTTP访问的核心类。与RestTemplate类相似,它提供了一些类似的方法,只不过返回类型不是具体的结果,而是ListenableFuture包装类。
通过getRestOperations()方法,对外提供了一个同步的RestTemplate对象,并且通过这个RestTemplate对象来共享错误处理和消息转换。
注意:默认情况下,AsyncRestTemplate依靠标准JDK工具来创建HTTP链接。通过使用构造函数来接收AsyncClientHttpRequestFactory接口的具体实现类对象,你可以选用不同的HTTP库,例如Apache HttpComponents,Netty,以及OkHttp。
3、AsyncRestTemplate配置
3.1 设置调度器:
默认的有时难以满足多变的需求,于是配置自定义参数排上用场:
@Bean
public AsyncRestTemplate asyncRestTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
//设置链接超时时间
factory.setConnectTimeout(100);
//设置读取资料超时时间
factory.setReadTimeout(200);
//设置异步任务(线程不会重用,每次调用时都会重新启动一个新的线程)
factory.setTaskExecutor(new SimpleAsyncTaskExecutor());
return new AsyncRestTemplate(factory);
}
设置合理的超时时间对于HTTP请求至关重要,单位为毫秒
异步任务可能需要配置线程池
注意:这样设置factory.setTaskExecutor(new SimpleAsyncTaskExecutor())异步任务(线程不会重用,每次调用时都会重新启动一个新的线程),这样打大并发下可能导致线程过多而内存溢出。这个应该是它的一个缺点吧
3.2 设置线程池:
ThreadPoolTaskScheduler taskExecutor = new ThreadPoolTaskScheduler();
taskExecutor.setPoolSize(10);
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
simpleClientHttpRequestFactory.setTaskExecutor(taskExecutor);
return new AsyncRestTemplate(simpleClientHttpRequestFactory);
线程池参考:https://blog.csdn.net/Little_fxc/article/details/103583650
4 线程回调
如果需要对异步请求的返回值做处理,需要手动实现回调,而在回调里面实现逻辑看起来会很臃肿。
entity.addCallback(new SuccessCallback<ResponseEntity<String>>() {
@Override
public void onSuccess(ResponseEntity<String> result) {
log.info("A");
}
}, new FailureCallback() {
@Override
public void onFailure(Throwable ex) {
log.info("B");
}
});
这时如果能用上Java 8 的lambda表示式,代码可以如此简单:
public String demo3() {
String url = "http://localhost:8080/teacher/1";
log.info("Start");
ListenableFuture<ResponseEntity<Teacher>> entity = asyncRestTemplate.getForEntity(url, Teacher.class);
entity.addCallback(result -> log.info(result.getBody().getName()),(e) -> log.error(e.getMessage()));
log.info("C");
return "End";
}
一行代码可以替代之前的十多行,代码更整洁了,可读性可高了不少。
为了能证明之前设置的超时时间有效,我在需要调用的请求代码中加入一行:
TimeUnit.SECONDS.sleep(3); // 暂停3秒
调用demo3(),查看控制台:
22:36:29.844 INFO 2732 --- [nio-8080-exec-1] c.c.t.web.AsyncTeacherController : Start
22:36:29.926 INFO 2732 --- [nio-8080-exec-1] c.c.t.web.AsyncTeacherController : C
22:36:29.926 INFO 2732 --- [nio-8080-exec-1] c.c.t.web.AsyncTeacherController : End
22:36:30.132 ERROR 2732 --- [cTaskExecutor-1] c.c.t.web.AsyncTeacherController : Read timed out
回调函数的第一个参数是成功以后执行的代码,没有在控制台看到相关信息,说明没有执行。
最后一行的输出不是来自主线程,并且打印的是错误信息:超时。
5 异步发送数据
我已经实现了一个接受数据的HTTP接口:
@PostMapping("/save/teacher")
public Teacher save(@RequestBody Teacher teacher){
log.info(teacher.toString());
return teacher;
}
发送数据的逻辑,需要使用到 POST 请求:
public String demo4() {
String url = "http://localhost:8080/save/teacher";
//设置Header
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("Content-Type", "application/json;charset=UTF-8");
HttpEntity<Object> httpEntity = new HttpEntity<>(headers);
Teacher teacher = new Teacher().setId(4).setName("Kelly");
//异步发送
ListenableFuture<ResponseEntity<Teacher>> entity = asyncRestTemplate.postForEntity(url, httpEntity, Teacher.class, teacher);
entity.addCallback(result -> log.info(result.getBody().getName()),(e) -> log.error(e.getMessage()));
log.info("C");
return "End";
}
6 例子
6.1 简单例子1
/**
* 异步调用
* @return
*/
@RequestMapping("/async")
@ResponseBody
public String async(){
AsyncRestTemplate template = new AsyncRestTemplate();
String url = "http://localhost:8080/async/fivetime";//休眠5秒的服务
//调用完后立即返回(没有阻塞)
ListenableFuture<ResponseEntity<String>> forEntity = template.getForEntity(url, String.class);
//异步调用后的回调函数
forEntity.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
//调用失败
@Override
public void onFailure(Throwable ex) {
logger.error("=====rest response faliure======");
}
//调用成功
@Override
public void onSuccess(ResponseEntity<String> result) {
logger.info("--->async rest response success----, result = "+result.getBody());
}
});
return "异步调用结束";
}
6.2 简单例子2
private String result = "";
@Test
public void testAsyncPost() throws Exception {
String posturl = "http://xxxxxx";
String params = "xxxxxx";
MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>();
headers.add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
HttpEntity<Object> hpEntity = new HttpEntity<Object>(params, headers);
AsyncRestTemplate asyncRt = new AsyncRestTemplate();
ListenableFuture<ResponseEntity<String>> future = asyncRt.postForEntity(posturl, hpEntity, String.class);
future.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
public void onSuccess(ResponseEntity<String> resp) {
result = resp.getBody();
}
public void onFailure(Throwable t) {
System.out.println(t.getMessage());
}
});
System.out.println(result);
}
6.3 简单例子3,返回类型
在业务代码中,http://localhost:8080/student/1
返回的是一个对象,而不是字符串类型,getForEntity
的第二个参数可以指定返回值类型,于是对代码改造一下。
public String demo2() {
String url = "http://localhost:8080/student/1";
log.info("Start");
ListenableFuture<ResponseEntity<Student>> entity = asyncRestTemplate.getForEntity(url, Student.class);
entity.addCallback(new SuccessCallback<ResponseEntity<Student>>() {
@Override
public void onSuccess(ResponseEntity<Student> result) {
log.info(result.getBody().getName());
log.info("A");
}
}, new FailureCallback() {
@Override
public void onFailure(Throwable ex) {
log.info("B");
}
});
log.info("C");
return "End";
}
7 utf-8 的解析器
AsyncRestTemplate 默认缺少 utf-8 的解析器,如果不设置解析器可能出现乱码。
- 增加一个 UTF-8 解析器 (第一种)
private AsyncRestTemplate asyncRestTemplate ;
@PostConstruct
public void init(){
asyncRestTemplate = new AsyncRestTemplate() ;
// 增加一个 utf-8 解析器
asyncRestTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
}
- 删除原有的 StringHttpMessageConverter , 然后重新添加 UTF-8 解析器 (第二种)
private AsyncRestTemplate asyncRestTemplate ;
@PostConstruct
public void init(){
asyncRestTemplate = new AsyncRestTemplate() ;
// 删除所有的 StringHttpMessageConverter
Iterator<HttpMessageConverter<?>> iterator = asyncRestTemplate.getMessageConverters().iterator();
while (iterator.hasNext()) {
final HttpMessageConverter<?> converter = iterator.next();
if (converter instanceof StringHttpMessageConverter) {
iterator.remove();
}
}
// 添加 UTF-8 的解析器
asyncRestTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
}
注意 :增加解析器后,最好在 post
、put
、delete
请求增加contentType 头信息,类似如下
HttpHeaders headers = new HttpHeaders();
// 等价 ContentType: application/json;charset=utf-8
headers.add(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
asyncRestTemplate.postForEntity(url, new HttpEntity<>(data,headers), ResponseModel.class);
8 RestTemplate与WebClient性能对比
https://blog.csdn.net/get_set/article/details/79506373
这里说WebClient性能更佳:
- WebClient同样能够以少量而固定的线程数处理高并发的Http请求,在基于Http的服务间通信方面,可以取代RestTemplate以及AsyncRestTemplate。
目前还没有亲自验证,等后面项目需要在研究WebClient吧
9 参考:
https://blog.csdn.net/jiangchao858/article/details/86697630
https://blog.csdn.net/get_set/article/details/79506373
https://www.jianshu.com/p/91c0eaacde0b
请我喝咖啡
如果觉得文章写得不错,能对你有帮助,可以扫描我的微信二维码请我喝咖啡哦~~哈哈~~