在Java Web开发中,我们通常需要通过GET、POST请求其他系统提供的服务。其中,JDK自带的HttpURLConnection、Apache HttpClient等方式都可以实现。当然,这些方式都有一个很明显的缺陷,那就是代码很繁琐。而Spring提供的RestTemplate封装了这些库的实现,可以让我们的HTTP请求更加简洁、直观。
在RestTemplate中定义了11个独立的操作,它们分别是:
方法 | 描述 |
delete() | 在特定的URL上对资源执行HTTP DELETE操作 |
exchange() | 在URL上执行特定的HTTP方法,返回的ResponseEntity包含了响应体所映射成的对象 |
execute() | 在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象 |
getForEntity() | 发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象 |
getForObject() | 发送一个HTTP GET请求,返回根据响应体映射形成的对象 |
postForEntity() | POST数据到一个URL,返回的ResponseEntity包含了响应体所映射成的对象 |
postForLocation() | POST数据到一个URL,返回新创建资源的URL |
postForObject() | POST数据到一个URL,返回根据响应体映射形成的对象 |
put() | PUT资源到特定的URL |
headForHeaders() | 发送HTTP HEAD请求,返回包含特定资源URL的HTTP头 |
optionsForAllow() | 发送HTTP OPTIONS请求,返回对特定URL的Allow头信息 |
接下来,我将对常用的几个方法分别介绍。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <!-- Jackson对自动解析JSON和XML格式的支持 --> <dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-json-provider</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency> <!-- HttpClient --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency> <!-- Fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.46</version> </dependency> |
在注入RestTemplate的bean的时候,可以通过ClientHttpRequestFactory指定RestTemplate发起HTTP请求的底层实现所采用的类库。对此,ClientHttpRequestFactory接口主要提供了以下两种实现方法:
也就是底层使用java.net包提供的方式创建Http连接请求。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | package cn.zifangsky.springbootdemo.config; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; 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.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.web.client.RestTemplate; @Configuration public class RestTemplateConfig { /** * 返回RestTemplate * @param factory * @return */ @Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory){ //消息转换器,一般情况下可以省略,只需要添加相关依赖即可 // List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(); // messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); // messageConverters.add(new FormHttpMessageConverter()); // messageConverters.add(new MappingJackson2XmlHttpMessageConverter()); // messageConverters.add(new MappingJackson2HttpMessageConverter());
RestTemplate restTemplate = new RestTemplate(factory); // restTemplate.setMessageConverters(messageConverters);
return restTemplate; }
/** * ClientHttpRequestFactory接口的第一种实现方式,即: * SimpleClientHttpRequestFactory:底层使用java.net包提供的方式创建Http连接请求 * @return */ @Bean public SimpleClientHttpRequestFactory simpleClientHttpRequestFactory(){ SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setReadTimeout(5000); requestFactory.setConnectTimeout(5000);
return requestFactory; } } |
也就是底层使用Httpclient连接池的方式创建Http连接请求。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | package cn.zifangsky.springbootdemo.config; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.http.Header; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy; import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicHeader; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.web.client.RestTemplate; @Configuration public class RestTemplateConfig { /** * 返回RestTemplate * @param factory * @return */ @Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory){ //消息转换器,Spring Boot环境可省略,只需要添加相关依赖即可 // List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(); // messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); // messageConverters.add(new FormHttpMessageConverter()); // messageConverters.add(new MappingJackson2XmlHttpMessageConverter()); // messageConverters.add(new MappingJackson2HttpMessageConverter());
RestTemplate restTemplate = new RestTemplate(factory); // restTemplate.setMessageConverters(messageConverters);
return restTemplate; }
/** * ClientHttpRequestFactory接口的另一种实现方式(推荐使用),即: * HttpComponentsClientHttpRequestFactory:底层使用Httpclient连接池的方式创建Http连接请求 * @return */ @Bean public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory(){ //Httpclient连接池,长连接保持30秒 PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
//设置总连接数 connectionManager.setMaxTotal(1000); //设置同路由的并发数 connectionManager.setDefaultMaxPerRoute(1000);
//设置header List<Header> headers = new ArrayList<Header>(); headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.04")); headers.add(new BasicHeader("Accept-Encoding", "gzip, deflate")); headers.add(new BasicHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3")); headers.add(new BasicHeader("Connection", "keep-alive"));
//创建HttpClient HttpClient httpClient = HttpClientBuilder.create() .setConnectionManager(connectionManager) .setDefaultHeaders(headers) .setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)) //设置重试次数 .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) //设置保持长连接 .build();
//创建HttpComponentsClientHttpRequestFactory实例 HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
//设置客户端和服务端建立连接的超时时间 requestFactory.setConnectTimeout(5000); //设置客户端从服务端读取数据的超时时间 requestFactory.setReadTimeout(5000); //设置从连接池获取连接的超时时间,不宜过长 requestFactory.setConnectionRequestTimeout(200); //缓冲请求数据,默认为true。通过POST或者PUT大量发送数据时,建议将此更改为false,以免耗尽内存 requestFactory.setBufferRequestBody(false);
return requestFactory; }
} |
getForObject()方法实际上是对getForEntity()方法的进一步封装,二者用法类似。 唯一的区别在于getForObject()方法只返回所请求类型的对象, 而getForEntity()方法会返回请求的对象以及响应的Header、响应状态码等额外信息。
三个getForObject()方法的签名如下:
1 2 3 4 5 | <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException; <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException; <T> T getForObject(URI url, Class<T> responseType) throws RestClientException; |
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | package cn.zifangsky.SpringBootDemo.controller; import java.util.HashMap; import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.web.client.RestTemplate; import cn.zifangsky.springbootdemo.config.RestTemplateConfig; import cn.zifangsky.springbootdemo.config.WebMvcConfig; import cn.zifangsky.springbootdemo.model.DemoObj; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes={WebMvcConfig.class,RestTemplateConfig.class}) @WebAppConfiguration("src/main/resources") public class TestRestTemplate { @Autowired private RestTemplate restTemplate;
/** * 测试最基本的Get请求 */ @Test public void testGetMethod1(){ DemoObj obj = restTemplate.getForObject("http://127.0.0.1:9090/rest/testJson2?id={1}&name={2}" , DemoObj.class , 1,"Tom");
System.out.println(obj); } } |
上面代码设置请求参数使用了数字占位符,同时getForObject()方法的最后一个参数是一个可变长度的参数,用于一一替换前面的占位符。当然,除了这种方式之外,还可以使用Map来设置参数,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /** * 测试Get请求另一种设置参数的方式 */ @Test public void testGetMethod2(){ Map<String, String> uriVariables = new HashMap<String, String>(); uriVariables.put("var_id", "1"); uriVariables.put("var_name", "Tom");
DemoObj obj = restTemplate.getForObject("http://127.0.0.1:9090/rest/testJson2?id={var_id}&name={var_name}" , DemoObj.class , uriVariables);
System.out.println(obj); } |
运行单元测试之后,最后输出如下:
DemoObj [id=2, name=Tom Ret]
此外需要注意的是,由于上面代码只是简单的单元测试,因此请求URL就直接硬编码在代码中了。实际开发则需要将之配置到配置文件或者Zookeeper、Redis中。比如这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package cn.zifangsky.springbootdemo.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import cn.zifangsky.springbootdemo.model.DemoObj; @RestController @RequestMapping("/restTemplate") public class RestTemplateController { @Value("${SERVER_URL}") private String SERVER_URL;
@Autowired private RestTemplate restTemplate;
@RequestMapping(path="/getDemoObj",produces={MediaType.APPLICATION_JSON_UTF8_VALUE}) public DemoObj getDemoObj(){ DemoObj obj = restTemplate.getForObject(SERVER_URL + "/rest/testXML?id={1}&name={2}" , DemoObj.class , 1,"Tom");
return obj; }
} |
三个getForEntity()方法的签名如下:
1 2 3 4 5 | <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException; <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException; <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException; |
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /** * 测试Get请求返回详细信息,包括:响应正文、响应状态码、响应Header等 */ @Test public void testGetMethod3(){ ResponseEntity<DemoObj> responseEntity = restTemplate.getForEntity("http://127.0.0.1:9090/rest/testJson2?id={1}&name={2}" , DemoObj.class , 1,"Tom");
DemoObj body = responseEntity.getBody(); int statusCodeValue = responseEntity.getStatusCodeValue(); HttpHeaders headers = responseEntity.getHeaders();
System.out.println("responseEntity.getBody():" + body); System.out.println("responseEntity.getStatusCodeValue():" + statusCodeValue); System.out.println("responseEntity.getHeaders():" + headers); } |
运行单元测试之后,最后输出如下:
responseEntity.getBody():DemoObj [id=2, name=Tom Ret]
responseEntity.getStatusCodeValue():200
responseEntity.getHeaders():{Date=[Fri, 09 Feb 2018 06:22:28 GMT], Content-Type=[application/json;charset=utf-8], Transfer-Encoding=[chunked]}
在RestTemplate中,POST请求跟GET请求类似,也可以使用如下三个方法来请求:
1 2 3 4 5 6 7 | <T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables) throws RestClientException; <T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException; <T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException; |
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 | /** * 测试最基本的Post请求 */ @Test public void testPostMethod1(){ DemoObj request = new DemoObj(1l, "Tim");
DemoObj obj = restTemplate.postForObject("http://127.0.0.1:9090/rest/testJson1" , request, DemoObj.class);
System.out.println(obj); } |
运行单元测试之后,最后输出如下:
DemoObj [id=2, name=Tim Ret]
三个postForEntity()方法的签名如下:
1 2 3 4 5 6 7 | <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables) throws RestClientException; <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException; <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException; |
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /** * 测试Post请求返回详细信息,包括:响应正文、响应状态码、响应Header等 */ @Test public void testPostMethod2(){ DemoObj request = new DemoObj(1l, "Tim");
ResponseEntity<DemoObj> responseEntity = restTemplate.postForEntity("http://127.0.0.1:9090/rest/testJson1" , request, DemoObj.class);
DemoObj body = responseEntity.getBody(); int statusCodeValue = responseEntity.getStatusCodeValue(); HttpHeaders headers = responseEntity.getHeaders();
System.out.println("responseEntity.getBody():" + body); System.out.println("responseEntity.getStatusCodeValue():" + statusCodeValue); System.out.println("responseEntity.getHeaders():" + headers); } |
运行单元测试之后,最后输出如下:
responseEntity.getBody():DemoObj [id=2, name=Tim Ret]
responseEntity.getStatusCodeValue():200
responseEntity.getHeaders():{Date=[Fri, 09 Feb 2018 06:32:02 GMT], Content-Type=[application/json;charset=utf-8], Transfer-Encoding=[chunked]}
exchange()方法跟上面的getForObject()、getForEntity()、postForObject()、postForEntity()等方法不同之处在于它可以指定请求的HTTP类型。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /** * 测试Exchange请求 */ @Test public void testExchange(){ //设置header HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", "application/x-zifangsky");
//设置参数 String requestBody = "1#Converter"; HttpEntity<String> requestEntity = new HttpEntity<String>(requestBody,headers); ResponseEntity<String> responseEntity = restTemplate.exchange("http://127.0.0.1:9090/convert" , HttpMethod.POST, requestEntity, String.class);
System.out.println("responseEntity.getBody():" + responseEntity.getBody()); System.out.println("responseEntity.getHeaders():" + responseEntity.getHeaders()); } |
运行单元测试之后,最后输出如下:
responseEntity.getBody():{“id”:2,”name”:”Converter Ret”}
responseEntity.getHeaders():{Date=[Fri, 09 Feb 2018 06:42:29 GMT], Content-Type=[application/x-zifangsky], Transfer-Encoding=[chunked]}