【Spring IN ACTION】-----REST概述及Spring对REST支持(二)

本文深入讲解Spring框架中的RestTemplate类,介绍了如何使用RestTemplate进行GET、POST、PUT和DELETE等HTTP请求,以及如何处理响应头信息和状态码。文章详细解释了RestTemplate的各种方法,包括getForObject、getForEntity、postForObject、postForEntity、postForLocation和exchange方法的使用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


-------------------------------笔记来自于《Spring IN ACTION》

1. 编写REST客户端

Spring支持提供一个Rest类型的服务端,又支持使用Rest客户端来访问Rest服务器端。Spring提供的访问REST API的方式有以下几种:

  • RestTemplate——由Spring提供的简单的,同步的REST客户端。
  • Traverson——由Spring HATEOAS提供的超链接感知(hyperlink-aware),同步的REST客户端。此方式灵感来于同名的JavaScript库。
  • WebClient——Spring 5中引入的一个反应式,异步的REST客户端。

本文仅介绍使用RestTemplate来访问Rest端点的部分。例如如下代码使用Apache HTTP Client来获取FaceBook中的个人信息。仔细看下代码可以发现fetchFaceBookProfile()方法中只有少量代码与获取FaceBook个人信息有关。如果编写另外一个方法来使用其他的Rest资源,很可能会有很多代码与fetchFaceBookProfile()相同的。另外还有一些地方抛出了IOException异常,因为IOException是检查型异常,所以要么捕获它,要么抛出它。鉴于在请求Rest资源上包含很多的重复代码,最好的方式就是封装通用代码并参数化可变部分。这正是RestTemplate所做的事情。

public Profile fetchFaceBookProfile(String id) {
  try {
    HttpClient client = HttpClients.createDefault();
    HttpGet request = new HttpGet("http://graph.facebook.com/"+id);
    request.setHeader("Accept","application/json");
    HttpResponse response = client.execute(request);
    HttpEntity entity = response.getEntity();
    ObjectMapper mapper = new ObjectMapper();
    return mapper.readValue(entity.getContent(), Profile.class);
  }catch(IOException e) {
    throw new RuntimeException(e);
  }
}

从客户端的角度看,与Rest服务器端交互包含客户端创建客户端实例和请求对象,执行请求,解析响应,将响应映射到对象,并且处理可能抛出的任何异常等过程涉及很多的底层HTTP库。不管发送什么类型的Http请求,所有这些样板文件都是重复的。为了避免重复,Spring提供了RestTemplate,如同JDBCTemplate处理JDBC中重复部分一样。

2. RestTemplate操作

RestTemplate定义了41种与REST资源交互的方法,其中大多数方法对应HTTP的方法。其中有11个方法有三种重载形式,另外一个方法重载了8次,因此共有12个重载方法组。除了TRACE以外,RestTemplate涵盖了所有的HTTP动作。其中execute()exchange()方法提供了较低级别的通用方法来使用任何HTTP方法。表中大多数操作都以三种形式进行了重载:

  • 一个使用java.net.URI作为URL格式,不支持参数化URL。
  • 一个使用String作为URL格式,并使用Map指明URL参数。
  • 一个使用String作为URL格式,并使用可变参数列表指明URL参数。
方法组描述
delete在特定的URL上对资源指定HTTP DELETE操作
exchange在URL上执行特定的HTTP方法,返回包含对象的ResponseEntity,这个对象是从响应体中映射得到的
execute在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象
getForEntity发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象
getForObject发送一个HTTP GET请求,返回的请求体将映射为一个对象
headForHeaders发送HTTP HEAD请求,返回包含特定资源的URL的HTTP头,需要注意的是JDK HttpURLConnection不支持PATCH,Apache HttpComponents和其他组件支持
optionsForAllow发送HTTP OPTIONS请求,返回对特定URL的Allow头信息
patchForObject发送HTTP PATCH请求,返回一个从响应体映射得到的对象
postForEntitypost数据到一个URL,返回包含一个对象的ResponseEntity,这个对象是从响应体中映射得到的
postForLocationpost数据到一个URL,返回新创建资源的URL
postForObjectpost数据到一个URL,返回根据响应提匹配形成的对象
putput资源到特定的URL

为了使用RestTemplate,可以在客户端创建一个RestTemplate实例对象:

RestTemplate rest = new RestTemplate();

或者将RestTemplate声明为一个Spring bean,并将其注入来实现方法调用。

@bean
public RestTemplate restTemplate(){
	return new RestTemplate(); 
}
2.1 GET资源

指定GET请求的方式有两种:getForObject()getForEntity(),每个方法又有三种形式的重载。getForObject()方法的重载形式:

public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException
public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException

getForEntity()方法的重载形式:

public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)throws RestClientException
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)throws RestClientException 
public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException

两种类型的方法,除了返回类型,getForEntity()方法就是getForObject()方法的镜像。实际上它们的工作方式大同小异。它们都是根据URL检索资源的GET请求,都将资源根据responseType参数匹配为一定的类型。唯一的区别在于getForObject()只返回所请求类型的对象,而getForEntity()方法会返回请求的对象以及响应相关的额外信息。

————检索资源
getForObject()方法适合检索资源。请求一个资源按照所选择的Java类型接收该资源。如下:

public Profile fetchFaceBookProfile(String id) {
  RestTemplate rest = new RestTemplate();
  return rest.getForObject("http://graph.facebook.com/{id}", Profile.class, id);
}

现在 获取FaceBook个人信息通过一两行代码可以实现,fetchFaceBookProfile()方法首先创建了一个RestTemplate的实例(另外的方法是注入实例),它调用了getForObject()方法得到了FaceBook个人信息。为了做到这一点,它要求结果是Profile对象。接收到Profile对象后,该方法将其返回给调用者。
注意的是方法的URL并不是通过字符串连接来构建,而是利用了RestTemplate可以接收参数化的URL这一功能。URL中的{id}占位符最终将会用方法的id参数来填充。每个参数都会按出现顺序插入到指定的URL的占位符中。
另外一种替代方案是将id参数放到Map中,并以id作为key,然后将这个Map作为最后一个参数传递给getForObject()

  public Profile fetchFaceBookProfile(String id) {
    Map<String,String> urlVal = new HashMap<>();
    urlVal.put("id", id);
    RestTemplate rest = new RestTemplate();
    return rest.getForObject("http://graph.facebook.com/{id}", Profile.class, urlVal);
  }

这里没有任何形式的JSON解析和对象映射。getForObject()将响应体转换为对象。它实现了需要格式转换的HTTP消息转换器,与带有@ResponseBody注解的Spring MVC处理方法所使用的一样。
这个方法也没有使用任何的异常处理。这不是因为getForObject()不能抛出异常,而是因为它抛出的异常都是非检查型的。如果在getForObject()中有错误,将抛出非检查型RestClientException异常(或者它的子类)。编译器不会强制捕获它,可以选择捕获。

————抽取响应的元数据
作为getForObject()方法的替代方法,getForEntity()方法与getForObject()方法的工作很相似。getForObject()只返回了资源(通过HTTP信息转换器将其转换为Java对象),getForEntity()会在ResponseEntity中返回相同的对象,而且ResponseEntity包含了响应的额外信息,如HTTP状态码和响应头。
例如服务端在LastModified头部信息中提供了资源最后修改时间,通过ResponseEntity上调用getHeaders()方法来获取此时间信息。

Date lastModified = new Date(response.getHeaders().getLastModified());

getHeaders()方法返回一个HttpHeaders对象,该对象提供了多个便利的方法来查询响应头,包括getLastModified(),它将返回从1970年1月1日开始的毫秒数。HttpHeaders包含以下的方法来获取头信息:

public List<MediaType> getAccept() {}
public List<Charset> getAcceptCharset() {}
public Set<HttpMethod> getAllow() {}
public String getCacheControl() {}
public List<String> getConnection() {}
public long getContentLength() {}
public long getDate() {}
public String getETag() {}
public long getExpires() {}
public long getIfModifiedSince() {}
public List<String> getIfNoneMatch() {}
public long getLastModified() {}
public URI getLocation() {}
public String getOrigin() {}
public String getPragma() {}
public String getUpgrade() {}
...//省略其他方法

为了实现更通用的HTTP头部信息访问,HttpHeaders提供了get()方法和getFirst()方法。两个方法都接受String参数来标识所需要的头信息。get()将会返回一个String值的列表,其中的每个值都是赋给该头部信息的,而getFirst()方法只会返回第一个头信息的值。可以调用getStatusCode()方法来获取HTTP状态码。如下:

  public Spittle fetchSpittle(long id) {
    RestTemplate rest = new RestTemplate();
    ResponseEntity<Spittle> response = rest.getForEntity("http://localhost:8080/spittle/{id}", Spittle.class,id);
    if(response.getStatusCode()==HttpStatus.NOT_MODIFIED) {
      throw new NotModifiedException();
    }
    return response.getBody();
  }

这里服务器响应NOT_MODIFIED(304)状态,意味着服务器端的内容自从上一次请求之后再也没有修改。此时将会抛出一个自定义的NotModifiedException异常表明客户端应该检查他的缓存来获取Spittle对象。

2.2 PUT资源

RestTemplate提供了三种重载的put()方法。

public void put(String url, @Nullable Object request, Object... uriVariables)throws RestClientException 
public void put(String url, @Nullable Object request, Map<String, ?> uriVariables)throws RestClientException
public void put(URI url, @Nullable Object request) throws RestClientException

put()最简单的形式,接收一个java.net.URI,用来标识(及定位)要将资源发送到服务器上,另外还接收一个对象,这代表了资源的Java表述。如下是展现了使用基于URI版本的put()方法

public void updateSpittle(Spittle spittle) throws SpitterException{
  RestTemplate rest = new RestTemplate();
  rest.put("http:.//localhost:8080/spittles/{id}", spittle,spittle.getId());
}

现在URI使用简单的String模板来进行表示。当RestTemplate发送PUT请求时,URI模板将{id}部分使用spittle.getId()方法返回值来进行替换。就像getForObject()getForEntity()一样,这个版本的put()方法最后一个参数是大小可变的参数列表,每个值会按照顺序复制给占位符变量。还可以将模板参数作为Map传递进来。

public void updateSpittle(Spittle spittle) throws SpitterException{
  RestTemplate rest = new RestTemplate();
  Map<String,String> params = new HashMap<>();
  params.put("id", spittle.getId());
  rest.put("http://localhost:8080/spittles/{id}", spittle,params);
}

当使用Map来传递模板参数时,Map映射中的每个key与URI模板中占位符变量的名字相同。所有版本的put()中,第二个参数表示资源的java对象,它将按照指定的URI发送到服务器端。本例中是一个Spittle对象。RestTemplate使用适合的Http消息转换器将spittle对象转换为一种表述形式,并在请求体中将其发送到服务器端。
对象将被转换成什么样的内容类型很大程序取决于传递给put()方法的类型,如果给定一个String值,那么将会使用StringHttpMessageConverter:这个值直接被写到请求体中,内容类型设置为"text/plain"。如果给定一个MultiValueMap<String,String>,那么这个Map中的值将会被FormHttpMessageConverter"application/x-www-form-urlencoded"的格式写到请求体中。
因为传递进来的是Spittle对象,所以需要一个能够处理任意对象的信息转换器。如果在类路径下包含Jackson2库,那么MappingJacksonHttpMessageConverter将以application/json格式将Spittle写到请求中。

2.3 DELETE资源

当不需要在服务端保留某个资源时,那么可能需要调用RestTemplatedelete()方法。delete()的三个重载方法如下:

public void delete(String url, Object... uriVariables) throws RestClientException
public void delete(String url, Map<String, ?> uriVariables) throws RestClientException
public void delete(URI url) throws RestClientException

delete()方法是RestTemplate方法中最简单的。唯一要提供的是要删除资源的URI。例如为了删除指定的id的Spittle,调用的delete()方法可以如下:

  public void deleteSpittle(long id) {
    RestTemplate rest = new RestTemplate();
    rest.delete(URI.create("http://localhost:8080/spittles/"+id));
  }

上面URI依赖了字符串的连接来创建的,为避免麻烦,同样可以采用占位符的形式:

  public void deleteSpittle(long id) {
    RestTemplate rest = new RestTemplate();
    rest.delete(URI.create("http://localhost:8080/spittles/{id}",id));
  }
2.4 POST资源

RestTemplate中有三种不同类型的方法来发送的POST请求。每类方法又有三种重载形式,因此就有九个方法可以POST数据到服务器端。其中postForObject()postForEntity()对POST请求的处理方式与发送GET请求的getForObject()getForEntity()方法是类似的。另外一个方法是getForLocation(),是POST请求所特有的。

————在POST请求中获取响应对象
假设使用RestTemplatePOST一个全新的Spitter对象到Spittr应用程序的REST API。因为这是一个全新的Spitter,服务器端还不知道它,因此它还不是真正的REST资源,也没有URL。另外在服务端创建之前,客户端并不知道Spitter的id。
POST资源到服务端的一种方式是使用RestTemplatepostForObject()方法。postForObject()方法的三种重载形式如下:

public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType,
			Object... uriVariables) throws RestClientException
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType,
			Map<String, ?> uriVariables) throws RestClientException
public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType)
			throws RestClientException

postForObject()方法所有重载形式中,第一个参数都是资源要POST的URL,第二个参数是要发送的对象,而第三个参数是预期返回的Java类型。在将URL作为String类型的两个版本中,第四个参数指定了URL变量(要么是可变参数列表,要么是一个Map)。
POST新的Spitter资源到Spitter REST API时,它们应该发送到http:/localhost:8080/spitters,这里会有一个应对POST请求处理请求方法来保存对象,所以可以使用任何版本的postForObject()。但为了保存简单,我们可以使用如下调用:

  public Spitter postSpitterForObject(Spitter spitter) {
    RestTemplate rest = new RestTemplate();
    return rest.postForObject("http://localhost:8080/spitters", spitter, Spitter.class);
  }

postSpitterForObject()方法给定一个新创建的Spitter对象,并使用postForObject()将其发送到服务器端。在响应中,它接收一个Spitter对象并将其返回个调用。就像getForEntity()方法一样,可能需要在请求中带回来一些元数据。在这种情况下,postForEntity()是更合适的方法。postForEntity()方法有着与postForObject()几乎相同的方法重载形式:

public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request,
			Class<T> responseType, Object... uriVariables) 
public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request,
			Class<T> responseType, Map<String, ?> uriVariables)
public <T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType)
			throws RestClientException

假设除了要获取返回的Spitter资源,并要查看响应中Location头信息的值。可以使用下面的方式来调用postForEntity()方法:

  public String postSpitterForObject(Spitter spiter) {
    RestTemplate rest = new RestTemplate();
    ResponseEntity<Spitter> response = rest.postForEntity("http://localhost:8080/spitters", spitter, Spitter.class);
    Spitter spitter =response.getBody();
    URI url= response.getHeaders().getLocation();
    return url.toString();
  }

getForEntity()方法一样,postForEntity()返回一个ResponseEntity<T>对象。可以调用这个对象的getBody()方法来获取资源对象。getHeaders()方法返回的是HttpHeaders对象,通过它可以访问响应中返回的各种HTTP头信息,这里使用getLocation()方法得到java.net.URI形式的Location头信息。

————在POST请求后获取资源位置
如果同时接收所发送的资源和响应头,postForEntity()方法比较适合。但通常来说POST请求是发送资源到服务器端,不需要再将资源从服务器端发送回来。仅仅为了得到Location头部信息的值,那么使用postForLocation会更加简单。
类似于其他的POST方法,POSTForLocation()会在POST请求的请求体中发送一个资源到服务器端。但是响应中不再是相同资源的对象,postForLocation()的响应是新创建资源的位置。它的三个重载方法如下:

public URI postForLocation(String url, @Nullable Object request, Object... uriVariables)throws RestClientException
public URI postForLocation(String url, @Nullable Object request, Map<String, ?> uriVariables)throws RestClientException
public URI postForLocation(URI url, @Nullable Object request) throws RestClientException

如下使用postForLocation()POST一个Spitter对象,并且方法返回中包含一个资源的URL。

  public String postSpitter(Spitter spitter) {
    RestTemplate rest = new RestTemplate();
    return rest.postForLocation("http://localhost:8080/spittles", spitter).toString();
  }

这里以String的形式将目标的URL传递进来,还有要POST的spitter对象。创建资源后,如果服务端在响应的Location头信息中返回新资源的URL,接下来postForLocation()会以String的格式返回该URL。

2.5 交换资源

上面使用了RestTemplate的各个方法来GET、PUT、DELETE以及POST资源。其中有个两个特殊的方法:getForEntity()postForEntity(),这两个方法将结果资源包含一个ResponseEntity对象中,通过这个对象可以响应头和状态码。
从响应中读取头信息是很有用的,但是如果想在发送给服务端的请求中设置头信息时,就需要使用RestTemplateexchange()方法。像RestTemplate的其他方法一样,exchange()方法有八种重载形式。

public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
			@Nullable HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables)
			throws RestClientException
public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
			@Nullable HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables)
			throws RestClientException
public <T> ResponseEntity<T> exchange(URI url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
			Class<T> responseType) throws RestClientException
public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
			ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException
public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
			ParameterizedTypeReference<T> responseType, Map<String, ?> uriVariables) throws RestClientException 
public <T> ResponseEntity<T> exchange(URI url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
			ParameterizedTypeReference<T> responseType) throws RestClientException
public <T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, Class<T> responseType)
			throws RestClientException
public <T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, ParameterizedTypeReference<T> responseType)
			throws RestClientException 

其中参数HttpMethod表明了要使用的HTTP动作。根据这个参数的值,exchange()能够执行与其他RestTemplate方法一样的工作。如从服务器端获取Spitter资源的一种方法是使用RestTemplategetForEntity()方法,如下所示:

    RestTemplate rest = new RestTemplate();
    ResponseEntity<Spitter> response = rest.getForEntity("http://localhost:8080/spitters/{id}", Spitter.class,id);
    Spitter spitter = response.getBody();

下面的代码片段使用exchange()也可以完成这项任务。

    RestTemplate rest = new RestTemplate();
    ResponseEntity<Spitter> response = rest.exchange("http://localhost:8080/spitters/{id}",HttpMethod.GET,null,Spitter.class,id);
    Spitter spitter = response.getBody();

通过传入HttpMethod.GET作为HTTP动作,exchange()会发送一个GET请求。第三个参数用于请求中发送资源的,但因为这是一个GET请求,它可以是null。下一个参数表明将响应转换为Spitter对象。最后一个参数用于替换URL模板中{id}占位符的值。
按这种方式,exchange()与之前使用的getForEntity()是几乎相同的。但是不同于getForEntity()或者getForObject()方法的是exchange()方法允许在请求中设置头信息。接下来不再给exchange()传递null,而是传入带有请求信息的HttpEntity。如果不指明头信息,exchange()对Spitter的GET请求会带有如下的头信息。

GET/Spitter/spitters/habuma HTTP/1.1
Accept:application/xml,text/xml,application/*+xml,aplication/json
Content-Length:0
User-Agent:Java/1.6.0_20
Host:Localhost:8080
Connnection:keep-alive

Accept头信息表明它能够接受多种不同的XML内容类型以及application/json。这样服务器端在决定采用哪种格式返回资源时,就有很大的可选空间。假设希望服务端以JSON格式发送资源,需要将"application/json"设置为Accept头信息的唯一值。设置请求头信息只需要构造发送给exchange()方法的HttpEntity对象即可,HttpEntity中包含承载头信息的MultiValueMap;如下创建一个LinkedMultiValueMap并添加值为"application/json"的Accept头信息。接下来构建一个HttpEntity,将MultiValueMap作为构造参数传入。如果这是一个PUT或者POST请求,需要将HttpEntity设置在请求体中发送的对象。对于GET请求来说,这个参数无意义。最后传入HttpEntity来调用exchange()方法。

MultiValueMap<String,String> headers = new LinkedHashMap<>();
headers.add("Accept", "application/json");
HttpEntity<Object> requestEntity = new HttpEntity<>(headers);
ResponseEntity<Spitter> response = rest.exchange("http://localhost:8080/spitters/{id}",HttpMethod.GET,requestEntity,Spitter.class,id);
Spitter spitter = response.getBody();

此时的请求报文如下面,头信息发生变化。

GET/Spitter/spitters/habuma HTTP/1.1
Accept:application/json
Content-Length:0
User-Agent:Java/1.6.0_20
Host:Localhost:8080
Connnection:keep-alive

假设服务器端能够将Spitter序列化为JSON,响应体将会以JSON格式来进行表述。

3. 参考

《Spring IN ACTION》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值