Spring 异步HTTP AsyncRestTemplate介绍

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 的解析器,如果不设置解析器可能出现乱码。

  1. 增加一个 UTF-8 解析器 (第一种)
    private AsyncRestTemplate asyncRestTemplate ;

    @PostConstruct
    public void init(){
        asyncRestTemplate = new AsyncRestTemplate() ;
        // 增加一个 utf-8 解析器
        asyncRestTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
    }
  1. 删除原有的 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")));
    }

注意 :增加解析器后,最好在 postputdelete 请求增加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性能更佳:

  1. 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

 

请我喝咖啡

如果觉得文章写得不错,能对你有帮助,可以扫描我的微信二维码请我喝咖啡哦~~哈哈~~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值