Spring 的异步HTTP请求调用AsyncRestTemplate

业务场景

基于HTTP的请求调用是一种常见的数据交互方式,Java也有很多类库可以实现HTTP请求调用,在大多数时候,它们是都同步调用。如果请求响应比较慢,甚至请求超时,程序就必须等到请求返回以后才能继续执行,在某些场合下,我并不需要等待请求的结果,或者我不关心请求是否执行成功,需要继续执行之后的逻辑,就需要通过异步处理。

在 Spring 3 时代,为了能更优雅地实现HTTP调用,引入了 RestTemplate,其中提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。
在 Spring 4 时代,为了能实现异步地HTTP调用,引入了AsyncRestTemplate,使得编写异步代码和同步代码一样简单。

认识 AsyncRestTemplate

在认识 AsyncRestTemplate 之前,先回顾一下 RestTemplate 中的一个经典方法:
public T getForObject(String url, Class responseType, Object... urlVariables) throws RestClientException

  • url 指的是请求的地址
  • responseType 指的是请求返回类型
  • urlVariables 指的是url参数,变长参数意味着它可有可无

而在 AsyncRestTemplate 中,可以看到如下方法签名:
public ListenableFuture> getForEntity(String url, Class responseType, Object... uriVariables) throws RestClientException

对比发现,只有返回值不同。这里我仅仅展示了其中的一个方法,其它的方法基本都遵循这样的规律,足以见得Spring在易用性上作出的努力。那为什么会出现这种似曾相识的感觉呢?是因为AsyncRestTemplate 底层是基于RestTemplate+异步线程池 实现的。

ListenableFuture是实现异步请求的关键,作为一个接口, 它继承自并发包下的另一个接口Future,并且巧妙地利用回调来处理异步请求。

ListenableFuture的两个方法签名如下,处理异步请求需要我们手动实现这两个接口(其中的一个)。

void addCallback(ListenableFutureCallback callback);

void addCallback(SuccessCallback successCallback, FailureCallback failureCallback);

RestTemplate 相同,AsyncRestTemplate 中最重要的两个方法是exchangeexecute。它们需要传入较多的参数,灵活度高,自由度大,自然掌握起来难度就大一些。大部分时候,调用getForEntitypostForLocationheadForHeaders等方法足以应对。

理解 AsyncRestTemplate

初始化

Spring 提供了 AsyncRestTemplate,但是没有提供默认的实现,不能直接注入,需要手动初始化:

    @Bean
	public AsyncRestTemplate asyncRestTemplate() {
		return new AsyncRestTemplate();
	}

在初始化逻辑中,可以指定线程池,指定错误处理器,设置超时时间等。

注入模板

    @Autowired
	private AsyncRestTemplate asyncRestTemplate;

有很多种方式注入,最简单的就是使用@Autowired,当然这是不推荐的一种注入方式,在最新版的idea工具中,这样的代码会有警告提示,建议你使用构造器注入。为什么不建议@Autowired,因为它使用的反射的机制,存在性能问题,当然还有其他因素考虑。

异步方法调用

我使用其中的getForEntity演示,这里和使用RestTemplate一模一样。

asyncRestTemplate.getForEntity(url, String.class);

我模拟的url来自当前项目的另一个rest地址,String.class代表返回值类型,注意String.class是一个泛指,因为任意一个HTTP请求的返回值都可以认为是字符串类型的。

处理回调

在大多数情况下,你需要处理回调,查看请求是否成功(响应为200),对返回值进一步操作。但是这个过程不是必须的,比如我只想发个类似通知一样的信息到另一个系统,不在乎是否成功,不需要处理返回值。

案例分析

	public String demo1() {
		String url = "http://localhost:8080/student/1";
		log.info("Start");
		ListenableFuture<ResponseEntity<String>> entity = asyncRestTemplate.getForEntity(url, String.class);
		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");
			}
		});
		log.info("C");
		return "End";
	}

这段代码记录了这个异步调用过程,我在其中关键节点打印了几个信息,在另个一方法中调用该方法。

    public String call1() {
		log.info(demo1());
		return "success";
	}

按照同步代码执行的规则,我们得到的结果应是这样的(假设请求成功):

Start
A
C
END

下面是我执行代码后,控制台信息片段:

23:14:53.751  INFO 7552 --- [nio-8080-exec-1] c.c.t.web.AsyncCallController            : Start
23:14:53.761  INFO 7552 --- [nio-8080-exec-1] c.c.t.web.AsyncCallController            : C
23:14:53.761  INFO 7552 --- [nio-8080-exec-1] c.c.t.web.AsyncCallController            : End
23:14:53.815  INFO 7552 --- [cTaskExecutor-1] c.c.t.web.AsyncCallController            : A

顺序当然和同步的代码不一致,还有一个关键的信息时间节点,从方法开始,到方法结束看起来“没有耗时”。而HTTP请求根本不可能这么快返回数据,最后一行的打印结果也证明了请求是通过另外一个线程执行的,并且耗时40+ms。

在业务代码中,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";
	}

这里直接使用定义好的领域模型来接收请求结果,在请求成功以后,输出对象的属性值,测试发现,控制台打印了正确结果:

23:31:47.389  INFO 7552 --- [nio-8080-exec-5] c.c.t.web.AsyncCallController            : Start
23:31:47.389  INFO 7552 --- [nio-8080-exec-5] c.c.t.web.AsyncCallController            : C
23:31:47.389  INFO 7552 --- [nio-8080-exec-5] c.c.t.web.AsyncCallController            : End
23:31:47.400  INFO 7552 --- [cTaskExecutor-2] c.c.t.web.AsyncCallController            : Tom
23:31:47.400  INFO 7552 --- [cTaskExecutor-2] c.c.t.web.AsyncCallController            : A

你可能感兴趣的:(Java)