最近使用restTemplate实现了一个服务对另一个服务中的Rest接口的调用,这里总结下其用法,并分析下源码,话不多说这就开始。
一、RestTemplate简单使用
1》首先是RestTemplate的配置文件:
package cn.com.ctsi.busiobj.config; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; @Configuration public class RestTemplateConfig{ @Bean public RestTemplate restTemplate(@Qualifier("simpleClientHttpRequestFactory") ClientHttpRequestFactory factory){ return new RestTemplate(factory); } @Bean public ClientHttpRequestFactory simpleClientHttpRequestFactory(){ SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setReadTimeout(5000);//ms factory.setConnectTimeout(15000);//ms return factory; } }
1.使用@Configuration表名这是一个配置文件
2.使用RestTemplate的构造方法:
public RestTemplate(ClientHttpRequestFactory requestFactory) { this(); setRequestFactory(requestFactory); }
ClientHttpRequestFactory对超时时间等属性进行设置。
3.ClientHttpRequestFactory接口有4个实现类,分别是:
2》RestTemplate的调用
Service层调用方式如下:
@Service public class CdmApiUtil { @Autowired RestTemplate restTemplate; public CdmRes sendCdmReq(CdmReq cctReq)throws Exception{ try{ CdmRes res =restTemplate.postForObject(cdmBaseUrl+tableCreateUrl,cctReq,CdmRes.class); Assert.notNull(cdmCreateTemplateTableRes,"调用接口,返回为null"); int code= res.getStatus().getCode(); Assert.isTrue((code==Status.CODE_SUCCESS),"sendCreateCdmTableReq 创建业务数据表失败,"+ res.getStatus().getDesc());return res; }catch (Exception e){ logger.error("sendCdmReq,"+e.getMessage()); throw new Exception("调用cdm接口失败,"+e.getMessage()); } } }
这里我们使用了RestTemplate的postForObject方法发送请求,此方法中的参数:
1)调用rest接口的url路径,
2)拼接的请求实体对象,
3)返回的响应的class实体对象
至此就完成了对远程rest接口的简单调用。
二、RestTemplate使用详解
转自:https://blog.csdn.net/u012702547/article/details/77917939
GET请求
在RestTemplate中,发送一个GET请求,我们可以通过如下两种方式:
第一种:getForEntity
@Override public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); } @Override public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); } @Override public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); return execute(url, HttpMethod.GET, requestCallback, responseExtractor); }
getForEntity方法的返回值是一个ResponseEntity<T>
,ResponseEntity<T>
是Spring对HTTP请求响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体等。比如下面一个例子:
@RequestMapping("/gethello") public String getHello() { ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class); String body = responseEntity.getBody(); HttpStatus statusCode = responseEntity.getStatusCode(); int statusCodeValue = responseEntity.getStatusCodeValue(); HttpHeaders headers = responseEntity.getHeaders(); StringBuffer result = new StringBuffer(); result.append("responseEntity.getBody():").append(body).append("<hr>") .append("responseEntity.getStatusCode():").append(statusCode).append("<hr>") .append("responseEntity.getStatusCodeValue():").append(statusCodeValue).append("<hr>") .append("responseEntity.getHeaders():").append(headers).append("<hr>"); return result.toString(); }
关于这段代码:
- getForEntity的第一个参数为我要调用的服务的地址,这里我调用了服务提供者提供的/hello接口,注意这里是通过服务名调用而不是服务地址,如果写成服务地址就没法实现客户端负载均衡了。
- getForEntity第二个参数String.class表示我希望返回的body类型是String
- 拿到返回结果之后,将返回结果遍历打印出来
最终显示结果如下:
有时候我在调用服务提供者提供的接口时,可能需要传递参数,有两种不同的方式,如下:
@RequestMapping("/sayhello") public String sayHello() { ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={1}", String.class, "张三"); return responseEntity.getBody(); } @RequestMapping("/sayhello2") public String sayHello2() { Map<String, String> map = new HashMap<>(); map.put("name", "李四"); ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={name}", String.class, map); return responseEntity.getBody(); }
- 可以用一个数字做占位符,最后是一个可变长度的参数,来一一替换前面的占位符
- 也可以前面使用name={name}这种形式,最后一个参数是一个map,map的key即为前边占位符的名字,map的value为参数值
第一个调用地址也可以是一个URI而不是字符串,这个时候我们构建一个URI即可,参数神马的都包含在URI中了,如下:
@RequestMapping("/sayhello3") public String sayHello3() { UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://HELLO-SERVICE/sayhello?name={name}").build().expand("王五").encode(); URI uri = uriComponents.toUri(); ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class); return responseEntity.getBody(); }
通过Spring中提供的UriComponents来构建Uri即可。
当然,服务提供者不仅可以返回String,也可以返回一个自定义类型的对象,比如我的服务提供者中有如下方法:
@RequestMapping(value = "/getbook1", method = RequestMethod.GET) public Book book1() { return new Book("三国演义", 90, "罗贯中", "花城出版社"); }
对于该方法我可以在服务消费者中通过如下方式来调用:
@RequestMapping("/book1") public Book book1() { ResponseEntity<Book> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/getbook1", Book.class); return responseEntity.getBody(); }
运行结果如下:
第二种:getForObject
getForObject函数实际上是对getForEntity函数的进一步封装,如果你只关注返回的消息体的内容,对其他信息都不关注,此时可以使用getForObject,举一个简单的例子,如下:
@RequestMapping("/book2") public Book book2() { Book book = restTemplate.getForObject("http://HELLO-SERVICE/getbook1", Book.class); return book; }
getForObject也有几个重载方法,如下:
@Override public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); } @Override public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); } @Override public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.GET, requestCallback, responseExtractor); }
这几个重载方法参数的含义和getForEntity一致,我就不再赘述了。
在RestTemplate中,POST请求可以通过如下三个方法来发起:
第一种:postForEntity
该方法和get请求中的getForEntity方法类似,如下例子:
@RequestMapping("/book3") public Book book3() { Book book = new Book(); book.setName("红楼梦"); ResponseEntity<Book> responseEntity = restTemplate.postForEntity("http://HELLO-SERVICE/getbook2", book, Book.class); return responseEntity.getBody(); }
- 方法的第一参数表示要调用的服务的地址
- 方法的第二个参数表示上传的参数
- 方法的第三个参数表示返回的消息体的数据类型
我这里创建了一个Book对象,这个Book对象只有name属性有值,将之传递到服务提供者那里去,服务提供者代码如下:
@RequestMapping(value = "/getbook2", method = RequestMethod.POST) public Book book2(@RequestBody Book book) { System.out.println(book.getName()); book.setPrice(33); book.setAuthor("曹雪芹"); book.setPublisher("人民文学出版社"); return book; }
服务提供者接收到服务消费者传来的参数book,给其他属性设置上值再返回,调用结果如下:
postForEntity的其他重载方法如下:
@Override public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); } @Override public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); } @Override public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); return execute(url, HttpMethod.POST, requestCallback, responseExtractor); }
这些方法的参数含义和getForEntity参数的含义一致,不再赘述。
第二种:postForObject
如果你只关注,返回的消息体,可以直接使用postForObject。用法和getForObject一致。
@Override public <T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); } @Override public <T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); } @Override public <T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<T>(responseType, getMessageConverters()); return execute(url, HttpMethod.POST, requestCallback, responseExtractor); }
第三种:postForLocation
postForLocation也是提交新资源,提交成功之后,返回新资源的URI,postForLocation的参数和前面两种的参数基本一致,只不过该方法的返回值为Uri,这个只需要服务提供者返回一个Uri即可,该Uri表示新资源的位置。
PUT请求
在RestTemplate中,PUT请求可以通过put方法调用,put方法的参数和前面介绍的postForEntity方法的参数基本一致,只是put方法没有返回值而已。举一个简单的例子,如下:
@RequestMapping("/put") public void put() { Book book = new Book(); book.setName("红楼梦"); restTemplate.put("http://HELLO-SERVICE/getbook3/{1}", book, 99); }
book对象是我要提交的参数,最后的99用来替换前面的占位符{1}
DELETE请求
delete请求我们可以通过delete方法调用来实现,如下例子:
@RequestMapping("/delete") public void delete() { restTemplate.delete("http://HELLO-SERVICE/getbook4/{1}", 100); }
delete方法也有几个重载的方法,不过重载的参数和前面基本一致,不赘述。
三、源码分析
以postForObject为例进行源码分析:
@Override @Nullable public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType);//1. HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters());//2. return execute(url, HttpMethod.POST, requestCallback, responseExtractor);//3. }
httpEntityCallback(@Nullable Object requestBody, Type responseType)方法:
protected <T> RequestCallback httpEntityCallback(@Nullable Object requestBody, Type responseType) { return new HttpEntityRequestCallback(requestBody, responseType); }
private HttpEntityRequestCallback(@Nullable Object requestBody, @Nullable Type responseType) { super(responseType); if (requestBody instanceof HttpEntity) { this.requestEntity = (HttpEntity<?>) requestBody; } else if (requestBody != null) { this.requestEntity = new HttpEntity<>(requestBody); } else { this.requestEntity = HttpEntity.EMPTY; } }
1)如果传递进来的requestBody是HttpEntity类型则将其强制转型为HttpEntity<?>类型;
2)否则如果requestBody不为null,则将其转换成HttpEntity格式
3)否则如果requestBody为null,则创建一个没有请求头和请求体的空HttpEntity。
HttpEntity实体类如下:
public class HttpEntity<T> { /** * The empty {@code HttpEntity}, with no body or headers. */ public static final HttpEntity<?> EMPTY = new HttpEntity<>(); private final HttpHeaders headers; @Nullable private final T body; /** * Create a new, empty {@code HttpEntity}. */ protected HttpEntity() { this(null, null); } /** * Create a new {@code HttpEntity} with the given body and no headers. * @param body the entity body */ public HttpEntity(T body) { this(body, null); } /** * Create a new {@code HttpEntity} with the given headers and no body. * @param headers the entity headers */ public HttpEntity(MultiValueMap<String, String> headers) { this(null, headers); } /** * Create a new {@code HttpEntity} with the given body and headers. * @param body the entity body * @param headers the entity headers */ public HttpEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers) { this.body = body; HttpHeaders tempHeaders = new HttpHeaders(); if (headers != null) { tempHeaders.putAll(headers); } this.headers = HttpHeaders.readOnlyHttpHeaders(tempHeaders); } /** * Returns the headers of this entity. */ public HttpHeaders getHeaders() { return this.headers; } /** * Returns the body of this entity. */ @Nullable public T getBody() { return this.body; } /** * Indicates whether this entity has a body. */ public boolean hasBody() { return (this.body != null); }
....... }
即为将requestBody写成HttpEntity的请求头、请求体的形式。
下面我们来看第二步:
官方对HttpMessageConverterExtractor的解释是:
Response extractor that uses the given {@linkplain HttpMessageConverter entity converters}
* to convert the response into a type {@code T}
即能够将返回的相应转换为某个特定的类型responseType。
第三步:
首先将String类型的URI转换为URI类型,然后execute方法底层调用doExecute方法。
@Override public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor<T> responseExtractor, Map<String, ?> uriVariables) throws RestClientException { URI expanded = getUriTemplateHandler().expand(url, uriVariables); return doExecute(expanded, method, requestCallback, responseExtractor); }
/** * Execute the given method on the provided URI. * <p>The {@link ClientHttpRequest} is processed using the {@link RequestCallback}; * the response with the {@link ResponseExtractor}. * @param url the fully-expanded URL to connect to * @param method the HTTP method to execute (GET, POST, etc.) * @param requestCallback object that prepares the request (can be {@code null}) * @param responseExtractor object that extracts the return value from the response (can be {@code null}) * @return an arbitrary object, as returned by the {@link ResponseExtractor} */ protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor<T> responseExtractor) throws RestClientException { Assert.notNull(url, "'url' must not be null"); Assert.notNull(method, "'method' must not be null"); ClientHttpResponse response = null; try { ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { requestCallback.doWithRequest(request); } response = request.execute(); handleResponse(url, method, response); if (responseExtractor != null) { return responseExtractor.extractData(response); } else { return null; } } catch (IOException ex) { String resource = url.toString(); String query = url.getRawQuery(); resource = (query != null ? resource.substring(0, resource.indexOf(query) - 1) : resource); throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + ex.getMessage(), ex); } finally { if (response != null) { response.close(); } } }
doExecute方法:
1.首先使用请求的url和method(post 或者get)构造出一个ClientHttpRequest。
2.requestCallback.doWithRequest将之前的requestBody requestHeader放入此ClientHttpRequest中;
3.调用request的execute方法获得response,调用handleResponse方法处理response中存在的error;
4.使用ResponseExtractor的extraData方法将返回的response转换为某个特定的类型;
5.最后记得关闭ClientHttpResponse资源,这样就完成了发送请求并获得对应类型的返回值的全部过程。
四、另外一种方式:使用RestTemplateUtil使用restTemplate
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package ctsi.mts.common.mvc.util; import com.fasterxml.jackson.databind.DeserializationFeature; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.client.RestTemplate; public class RestTemplateUtil { private static Logger logger = LoggerFactory.getLogger(RestTemplateUtil.class); public RestTemplateUtil() { } private static RestTemplate getRestTemplate() { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.getObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); List<HttpMessageConverter<?>> lstConverters = new ArrayList(); lstConverters.add(new StringHttpMessageConverter(Charset.forName("utf-8"))); lstConverters.add(converter); RestTemplate rt = new RestTemplate(); rt.setMessageConverters(lstConverters); return rt; } public static String getForStringResult(String url) { return (String)get(url, String.class, (Map)null); } public static String getForStringResult(String url, Map<String, ?> urlVariables) { return (String)get(url, String.class, urlVariables); } public static <T> T get(String url, Class<T> responseType) { return get(url, responseType, (Map)null); } public static <T> T get(String url, Class<T> responseType, Map<String, ?> urlVariables) { logger.info("[rest get] {}", url); logger.info("[responseType] {}", responseType); Object result; if (urlVariables == null) { result = getRestTemplate().getForObject(url, responseType, new Object[0]); } else { Iterator var4 = urlVariables.entrySet().iterator(); while(var4.hasNext()) { Entry entry = (Entry)var4.next(); logger.info("[params] {} = {}", entry.getKey(), entry.getValue()); } result = getRestTemplate().getForObject(url, responseType, urlVariables); } logger.info("[result] {}", result); return result; } public static String postForStringResult(String url, Object request) { return (String)post(url, request, String.class, (Map)null); } public static String postForStringResult(String url, Object request, Map<String, ?> urlVariables) { return (String)post(url, request, String.class, urlVariables); } public static <T> T post(String url, Object request, Class<T> responseType) { return post(url, request, responseType, (Map)null); } public static <T> T post(String url, Object request, Class<T> responseType, Map<String, ?> urlVariables) { logger.info("[rest post] {}", url); logger.info("[request] {}", request); logger.info("[responseType] {}", responseType); Object result; if (urlVariables == null) { result = getRestTemplate().postForObject(url, request, responseType, new Object[0]); } else { Iterator var5 = urlVariables.entrySet().iterator(); while(var5.hasNext()) { Entry entry = (Entry)var5.next(); logger.info("[params] {} = {}", entry.getKey(), entry.getValue()); } result = getRestTemplate().postForObject(url, request, responseType, urlVariables); } logger.info("[result] {}", result); return result; } }
看了下,大致只有getForString getForObject postForString postForObject没有forEntity类型,对restTemplate的readTimeOut proxy等属性没有要求时可通过RestTemplateUtil来获取restTemplate对象。