RestTemplate是Spring框架提供用于调用Rest接口的一个应用,它简化了与http服务通信方式。RestTemplate统一Restfull调用的标准,封装HTTP链接,只要需提供URL及返回值类型即可完成调用。相比传统的HttpClient与Okhttp,RestTemplate是一种优雅,简洁调用RESTfull服务的方式。
RestTemplate默认依赖JDK提供Http连接的能力(HttpURLConnection),如果有需要的话也可以通过SetRequestFactory方法替换为如:Apache HttpComponents、Netty或OKHttp等其他HTTP库。
RestTemplate template=new RestTemplate();
ClientHttpRequest request;
try {
request = createRequest(url, method);
}catch (IOException ex) {
ResourceAccessException exception = createResourceAccessException(url, method, ex);
throw exception;
}
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
handleResponse(url, method, response);
handleResponse方法代码
ResponseErrorHandler errorHandler = getErrorHandler();
boolean hasError = errorHandler.hasError(response);
if (logger.isDebugEnabled()) {
try {
HttpStatusCode statusCode = response.getStatusCode();
logger.debug("Response " + statusCode);
}catch (IOException ex) {
logger.debug("Failed to obtain response status code", ex);
}
}
if (hasError) {
errorHandler.handleError(url, method, response);
}
在HttpAccessor中
private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
public ClientHttpRequestFactory getRequestFactory() {
return this.requestFactory;
}
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
ClientHttpRequest request = getRequestFactory().createRequest(url, method);
initialize(request);
if (logger.isDebugEnabled()) {
logger.debug("HTTP " + method.name() + " " + url);
}
return request;
}
在RestTemplate可以设置是否使用缓存流,默认设置:bufferRequestBody =true,缺点是当发送大量数据时,比如put/post的保存和修改,那么可能内存消耗严重。所以这时候可以设置 RestTemplate.setBufferRequestBody(false);
即使用 SimpleStreamingClientHttpRequest 来实现
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
//创建java.net.HttpURLConnection
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
//创建connection属性,同时设置了setDoInput
prepareConnection(connection, httpMethod.name());
if (this.bufferRequestBody) {
return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
}
else {
return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
}
}
openConnection源码:
protected HttpURLConnection openConnection(URL url, @Nullable Proxy proxy) throws IOException {
URLConnection urlConnection = (proxy != null ? url.openConnection(proxy) : url.openConnection());
if (!(urlConnection instanceof HttpURLConnection)) {
throw new IllegalStateException(
"HttpURLConnection required for [" + url + "] but got: " + urlConnection);
}
return (HttpURLConnection) urlConnection;
}
prepareConnection方法:
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
//设置连接超时时间:connectTimeout
if (this.connectTimeout >= 0) {
connection.setConnectTimeout(this.connectTimeout);
}
//设置读超时时间:readTimeout
if (this.readTimeout >= 0) {
connection.setReadTimeout(this.readTimeout);
}
Boolean mayWrite =("POST".equals(httpMethod) ||
"PUT".equals(httpMethod) ||
"PATCH".equals(httpMethod) ||
"DELETE".equals(httpMethod));
//设置URL允许输入:connection.setDoInput(true);//默认true
//URL连接可用于输入和/或输出。 如果您打算使用URL连接进行输入,请将DoInput标志设置为true;
//否则,设置为false。 默认值是true。
//HttpUrlConnection中方法setDoInput(true);以后就可以使用conn.getInputStream().read();
connection.setDoInput(true);
connection.setInstanceFollowRedirects("GET".equals(httpMethod));
//URL连接可用于输入和/或输出。 如果您打算将URL连接用于输出,请将DoOutput标志设置为true,
//如果不是,则为false 默认值是false。
//如get请求,用不到conn.getOutputStream(),因为参数直接追加在地址后面,因此默认是false
//post、put、patch、delete请求会将setDoOutput设置为true
//设置setDoOutput(true);以后就可以使用conn.getOutputStream().write()
connection.setDoOutput(mayWrite);
connection.setRequestMethod(httpMethod);
}
RequestCallback 封装了请求体和请求头对象,既在RequstCallback可以输入需要传输的head数据
在执行 doWithRequest 时,与Connection发送请求体有着密切关系,请求头就是 SimpleBufferingClientHttpRequest.addHeaders 方法,那么请求体 bufferedOutput 是如何赋值的呢?就是在 doWithRequest 里面,如下 StringHttpMessageConverter (其他 MessageConvert 也一样,这里也是经常乱码的原因;
RequestCallback 用于操作请求头和body,在请求发出前执行。以下是RequestCallback的 两个实现类
调用接口ClientHttpRequest
public interface ClientHttpRequest extends HttpRequest, HttpOutputMessage {
ClientHttpResponse execute() throws IOException;
}
然后使用实例 SimpleBufferingClientHttpRequest 封装请求体和请求头
@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
addHeaders(this.connection, headers);
// JDK <1.8 doesn't support getOutputStream with HTTP DELETE
if (getMethod() == HttpMethod.DELETE && bufferedOutput.length == 0) {
this.connection.setDoOutput(false);
}
if (this.connection.getDoOutput() && this.outputStreaming) {
this.connection.setFixedLengthStreamingMode(bufferedOutput.length);
}
this.connection.connect();
if (this.connection.getDoOutput()) {
FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
}
else {
// Immediately trigger the request in a no-output scenario as well
this.connection.getResponseCode();
}
return new SimpleClientHttpResponse(this.connection);
}
Delete通过请求方式和是否有请求体对象来判断是否需要发送请求体如果是delete请求,首先设置 DoOutput = true,然后根据是否有请求体数据,然后封装请求体FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
接着就是 response 的解析了,主要还是 Error 的解析。this.handleResponse(url, method, response);
内部处理多是解析error处理
protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
ResponseErrorHandler errorHandler = getErrorHandler();
boolean hasError = errorHandler.hasError(response);
if (logger.isDebugEnabled()) {
try {
HttpStatusCode statusCode = response.getStatusCode();
logger.debug("Response " + statusCode);
}
catch (IOException ex) {
logger.debug("Failed to obtain response status code", ex);
}
}
if (hasError) {
errorHandler.handleError(url, method, response);
}
}
@Test
public void TestRestGetMethod() throws Exception {
RestTemplate restTemplate = new RestTemplate();
URI url = URI.create("http://api.goyeer.com:8888/user");
String response = restTemplate.getForObject(url, String.class);
}
@Test
public void TestGetForObjcetByArgument() throws Exception {
RestTemplate restTemplate = new RestTemplate();
String appkey="Goy_20200034_MES";
String appSecret="GY202305-0801-41d4-a716-9998880";
String url = "http://api.goyeer.com:8888/getToken?appkey={1}&appsecret={2}";
String responseToken = restTemplate.getForObject(url,String.class, appkey,appSecret);
}
@Test
public void TestGetForObjectByMap() throws Exception{
Map<String,Object> map=new HashMap();
map.put("appkey","Goy_20200034_MES");
map.put("appSecret","GY202305-0801-41d4-a716-9998880");
RestTemplate restTemplate = new RestTemplate();
String url = "http://api.goyeer.com:8888/user?appkey={appkey}&appsecret={appSecret}";
String response = restTemplate.getForObject(url,String.class, map);
}
@Test
public void TestGetForEntity() throws Exception {
RestTemplate restTemplate = new RestTemplate();
URI url = URI.create("http://api.goyeer.com:8888/user");
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
int httpCode =response.getStatusCode().value();
String resp= response.getBody();
System.out.println(httpCode);
System.out.println(resp);
}
@Test
public void TestGetForEntityByArgument() throws Exception {
RestTemplate restTemplate = new RestTemplate();
String appkey="Goy_20200034_MES";
String appSecret="GY202305-0801-41d4-a716-9998880";
String url = "http://api.goyeer.com:8888/getToken?appkey={1}&appsecret={2}";
ResponseEntity<String> response = restTemplate.getForEntity(url,String.class, appkey,appSecret);
int httpCode =response.getStatusCode().value();
String resp= response.getBody();
System.out.println(httpCode);
System.out.println(resp);
}
@Tes
public void TestGetForEntityByMap() throws Exception {
RestTemplate restTemplate = new RestTemplate();
Map<String,Object> map=new HashMap();
map.put("appkey","Goy_20200034_MES");
map.put("appSecret","GY202305-0801-41d4-a716-9998880");
String url = "http://api.goyeer.com:8888/getToken?appkey={appkey}&appsecret={appsecret}";
ResponseEntity<String> response = restTemplate.getForEntity(url,String.class, map);
int httpCode =response.getStatusCode().value();
String resp= response.getBody();
System.out.println(httpCode);
System.out.println(resp);
}
GetForEntity和GetForObject用法几乎完全一致,区别在于前者可以查看请求状态码,请求头信息。
getForEntity返回的是一个ResponseEntity,而getForObject返回的就只是返回内容。getForObject的返回相当于只返回http的body部份而getForEntity的返回是返回全部信息
@Test
public void TestPostForObject() {
try {
RestTemplate restTemplate = new RestTemplate();
URI url = URI.create("http://api.goyeer.com:7777/user/create");
User user = new User();
user.setId(1110L);
user.setLoginName("Goy");
user.setDepartment("Department");
User respUser = restTemplate.postForObject(url, user, User.class);
System.out.println(respUser.getLoginName());
} catch (Exception exception) {
throw exception;
}
}
@Test
public void TestPostForObejectByArgument()throws Exception{
RestTemplate restTemplate = new RestTemplate();
try {
String uri="http://api.goyeer.com:7777/{1}/{2}";
String controllerName="user";
String operateName="create";
User user = new User();
user.setId(1110L);
user.setLoginName("Goy");
user.setDepartment("Department");
User respUser = restTemplate.postForObject(uri, user, User.class,controllerName,operateName);
System.out.println(respUser.getLoginName());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Test
public void TestPostForObjectByMap() {
RestTemplate restTemplate = new RestTemplate();
try {
String uri="http://localhost:7777/{controller}/{operate}";
Map map=new HashMap();
map.put("controller","user");
map.put("operate","create");
User user = new User();
user.setId(1110L);
user.setLoginName("Goy");
user.setDepartment("Department");
User respUser = restTemplate.postForObject(uri, user, User.class,map);
System.out.println(respUser.getLoginName());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
其它提供POST的方法有postForLocation、postForEntity用法如postForObject。
相比于postForObject()方法, postForEntity() 返回响应体为 ResponseEntity 类型,其他两个方法功能一致。
与postForObject 和 postForEntity 方法类型, postForLocation 也发送post请求至特定uri并创建新的对象。唯一的差异是返回值为Location头信息。
headForHeaders方法 是HTTP中请求的一种。HEAD方法跟GET方法相同,只不过服务器响应时不会返回消息体。一个HEAD请求的响应中,HTTP头中包含的元信息应该和一个GET请求的响应消息相同。这种方法可以用来获取请求中隐含的元信息,而不用传输实体本身。也经常用来测试超链接的有效性、可用性和最近的修改。
一个HEAD请求的响应可被缓存,也就是说,响应中的信息可能用来更新之前缓存的实体。如果当前实体跟缓存实体的阈值不同(可通过Content-Length、Content-MD5、ETag或Last-Modified的变化来表明),那么这个缓存就被视为过期了。
以上三个方法使用方法同getForObject类似
PUT请求是向服务器端发送数据的,从而改变信息,该请求就像数据库的update操作一样,用来修改数据的内容,但是不会增加数据的种类等,也就是说无论进行多少次PUT操作,其结果并没有不同。
以上三个方法使用方法同getForObject类似
HTTP中为了提高交互操作性与防止错误,确实需要一种新的修改方法,而PUT方法已经被定义为用一个请求体去修改一个完整的资源。并且不能重复做部分更改,否则代理和缓存、甚至服务器或者客户端都会得到有问题的操作结果。
至此,PATCH方法有了被完全定义的必要。
PATCH在请求中定义了一个描述修改的实体集合,如果被请求修改的资源不存在,服务器可能会创建一个新的资源。
以上三个方法使用方法同getForObject类似
向服务器端提交数据,请求数据在报文body里;发送一个删除数据的请求。
以上三个方法使用方法同getForObject类似
RestTemplate是Spring自带的一个调用rest服务的客户端,它提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。在一般项目中完全可以替代HttpClient和OkHttp。
在后续Spring cloud系列文章中会多次使用到。