消费REST服务

发布时间:2024年01月04日

概述

Spring应用除了提供对外API之外,同时要对另一个应用的API发起请求。实际上,在微服务领域,这正变得越来越普遍。因此,花点时间研究一下如何使用Spring与RESTAPI交互是非常值得的。
Spring应用可以采用多种方式来消费RESTAPI

  • RestTemplatc:由Spring核心框架提供的简单、同步REST客户端。
  • Traverson:对 Spring RestTemplate 的包装,由Spring HATEOAS 提供的支持超链接、同步的 REST客户端,其灵感来源于同名的 JavaScript库。
  • WebClient:反应式、异步REST的客户端。

现在,我们主要关注使用 RestTemplate 创建客户端。
从客户端的角度来看,与 REST 资源进行交互涉及很多工作,而且大多数都是很单调乏味的样板式代码。如果使用较低层级HTTP 库,客户端需要创建一个客户端实例和请求对象,执行请求,解析响应,将响应映射为领域对象,还要处理这个过程中可能会抛出的所有异常。不管发送什么样的 HTTP 请求,这种样板代码都要不断重复。
为了避免这种样板代码,Spring 提供了 RestTemplate。就像JDBCTemplate 能够处理JDBC 中丑陋的那部分代码一样,RestTemplate 也能够将你从消费 REST 资源所面临的单调工作中解放出来。
RestTemplat 提供了 41个与 REST 资源交互的方法。我不会介绍它所提供的所有方法,而是只考虑独立的12个操作,这些操作的重载形式组成了完整的41个方法。

方法描述
delete(…)在特定的URL上对资源执行HTTP DELETE请求
exchange(…)在URL上执行特定的 HTTP 方法,返回包含对象的 ResponseEntity个对象是从响应体中映射得到的
execute(…)在URL上执行特定的 HTTP方法,返回一个从响应体映射得到的对象
getForEntity(…)发送一个HTTP GET 请求,返回的ResponseEntity 包含了响应体所映射成的对象
getForObject(…)发送一个HTTP GET 请求,返回响应体所映射成的对象
headForHeaders(…)发送HTTP HEAD 请求,返回包含特定资源 URL 的 HTTP头信息
optionsForAllow(…)发送HTTP OPTIONS 请求,返回特定 URL的Allow头信息
patchForObject(…)发送HTTP PATCH请求,返回一个从响应体映射得到的对象
postForEntity(…POST数据到一个URL,返回包含一个对象的 ResponseEntity,这个对象是从响应体中映射得到的
postForLocation(…)POST数据到一个URL,返回新创建资源的URL
postForObject(…)POST数据到一个URL,返回根据响应体映射形成的对象
put(…)PUT资源到特定的URL

除了 TRACE,RestTemplate 对每种标准的HTTP 方法都提供了至少一个方法。除之外,execute()exchange()提供了较低层次的通用方法,以便使用任意的HTTP 操作
表中的大多数操作都以如下的3种方法形式进行了重载:

  • 使用String作为URL格式,并使用可变参数列表指明 URL 参数;
  • 使用String作为URL格式,并使用Map<String,String>指明 URL参数;
  • 使用java.net.URI作为URL格式,不支持参数化URL。

明确了 RestTemplate 所提供的 12 个操作以及各个变种如何工作之后,我们就能以自己的方式编写消费 REST资源的客户端了。
要使用 RestTemplate,可以在需要的地方创建一个实例:

RestTemplate rest = new RestTemplate();

也可以将其声明为一个 bean 并注入到需要的地方:

@Bean
public RestTemplate restTemplate() {
	return new RestTemplate();
}

我们从其支持的4个主要 HTTP 方法(也就是GETPUTDELETEPOST)入手来研究 RestTemplate 的操作。不妨从 GET方法的 getForObject()getForEntity()开始。

GET资源

假设我们现在想要通过 Taco Cloud API(访问该RestAPI可以得到对应的数据)获取某个配料。为了实现这一点,我们可以使用RestTemplategetForObject()方法来获取配料。例如如下的代码使用RestTemplate来根据ID来获Ingredient 对象:

public Ingredient getIngredientById(string ingredientId){
	return rest.getForObject("http://localhost;8080/ingredients/{id}",
	Ingredient.class, ingredientId);
}

在这里,我们使用了gctForObject()的变种形式,它接收一个String 类型的URL并使用可变列表来指定URL变量。传递给getForObject()IngredientId 参数会用来填充给定URL{id}占位符。尽管在本例中只有一个URL 变量,但是有很重要的一点需要我们注意:变量参数会按照它们出现的顺序被设置到占位符中。
getForObject()方法的第二个参数是响应应该绑定的类型。在本例中,响应数据(很可能是JSON格式)应该被反序列化为要返回的Igredient对象。另外一种替代方案是使用Map 来指定URL变量:

public Ingredient getIngredientById(String ingredientId) (
	Map<String,String> urlVariables = new HashMap<>();
	urlVariables.put("id", ingredientId);
	return rest.getForObject("http://localhost:8080/ingredients/{id}",
										Ingredient.class, urlVariables);

在本例中,ingredicntId 的值会映射到名为 idkey 上。当发起请求的时候,{id}占位符将会被替换成keyidMap条目。
使用 URI参数要稍微复杂一些,这种方式需要我们在调用 getForObject()之前构建URI对象。在其他方面,它与另外两个变种非常类似:

public Ingred1ent getIngredientById(String ingredientId) {
	Map<String,String> urlVariables = new HashMap<>();
	urlVariables.put("id", ingredientId);
	URI	url=UriComponentsBuilder.fromHttpUrl("http://localhost:8080/ingredients/{id}").
						build(urlVariables);
	return rest,getForObject(url, Ingredient.class);
}

在这里,URI对象是通过 String 规范定义的,它的占位符会被 Map 中的条目替换。这与我们之前看到的 getForObject()变种非常相似。getForObject()是获取资源的有效式。但是,如果客户端需要的不仅仅是载荷体,那么可以考虑使用 getForEntity()getForEntity()的工作方式和 getForObject()类似,但是它所返回的并不是代表响应载荷的领域对象,而是会包裹领域对象的 ResponseEntity 对象。借助 ResponseEntity 对象能够访问很多响应细节,比如响应头信息。
例如,假设我们除了想要获取配料数据,还想要从响应中探查 Date 头信息。借助getForEntity(),这个需求能够很容易实现:

public Ingredient getIngredientById(String ingredientId) {
ResponseEntity<Ingredient> responseEntity =rest.getForEntity("http://localhost:8080/ingredients/{id}",Ingredient.class, ingredientId);
log.info("Fetched time: {}",
responseEntity.getHeaders().getDate());
return responseEntity.getBody();
}

getForEntity()有着与 getForObject()方法相同参数的重载形式,所以我们可以按照变列表参数的形式提供 URL变量,也可以按照 URI对象的形式调用 getForEntity()

PUT资源

为了发送HTTP PUT 请求,RestTemplate 提供了 put()方法。put()方法的3个变种形式都会接收一个会被序列化并发送至给定 URLObject。就URL本身来讲,它可以按照URI对象或 String 的形式来指定。与 getForObject()getForEntity()类似,URL变量能够以可变参数列表或Map的形式提供。
假设我们想要使用一个新 Igredient 对象的数据来替换某个配料资源,那么如下的代码片段就能做到这一点:

public void updateIngredient(Ingredient ingredient) {
	rest.put("http://localhost:8080/ingredients/{id}",ingredient,ingredient.getId());
}

在这里,URL 是以 String 的形式指定的,该 URL 包含一个占位符,它会被给定Ingredientid 属性所替换。要发送的数据是 Ingredient 对象本身。put()方法返回 void 所以我们没有必要处理返回值。

DELETE资源

假设 Taco Cloud 不想再提供某种配料,因此我们要从可选列表中将其完全删除。为了实现这一点,可以使用 RestTemplate 来调用 delete() 方法:

public void deleteIngredlent(Ingredient ingredient) {
	rest.delete("http://1ocalhost;8080/ingredients/{id})",ingredient.getId())
}

在本例中,我们只为 delete()提供了 URL(以 String 的形式指定)和 URL变量值但是,和其他的 RestTemplate 方法类似,URL 能够以URI对象的方式来指定,URL参数也能够以 Map 的方式来声明。

POST资源

现在,我们假设要添加新的配料到 Taco Cloud 菜单中。为了实现这一点,我“.../ingredients”端点发送 HTTP POST 请求,并将配料数据放到请求体中。RestTemplate 有 3种发送 POST 请求的方,每种方法都有相同的重载变种来指定URL。如果希望在 POST请求之后得到新创建的 Ingredient 资源,可以按照如下的方式使用 postForObject():

public Ingredient createIngredient(Ingredient ingredient) {
	return rest.postForObject("http://localhost:8080/ingredients",
							ingredient,Ingredient.class);
}

postForObject()方法的这个变种形式接收 String类型的URL规范、要提交给服务器端的对象,以及响应体应该绑定的领域类型。尽管我们在这里没有用到,但是第 4个参数可以是URL变量值的Map 或可变参数的列表。它们能够替换到 URL之中。如果客户端还想要知道新创建资源的地址,那么可以调用 postForLocation()方法如下所示:

public java,net.URI createIngredient(Ingredient ingredient) {
	return rest.postForLocation("http://localhost:8080/ingredients",
			ingredient);
}

注意,postForLocation()有与 postForObject()类似的工作方式,只不过它返回的是新创建资源的 URI,而不是资源对象本身。这里返回的 URI是从响应的 Location 头信息中派生出来的。如果同时需要地址和响应载荷,可以使用 postForEntity()方法:

public Ingredient createIngredient(Ingredient ingredient) (
	ResponseEntity<Ingredient> responseEntity=rest.postForEntity("http://localhost:8080/ingredients",
	ingredient,Ingredient.class);
	log.Info("New resource created at {}",responseEntity.getHeaders().getLocation());
	return responseEntity.getBody();
}

尽管 RestTemplate 的方法可以实现不同的目的,但是用法非常相似。因此,我们很容易就可以精通 RestTemplate,并将其用到客户端代码中。

文章来源:https://blog.csdn.net/m0_62943934/article/details/135391838
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。