在开发的过程中,很多时候我们需要从当前服务端(当前项目)向外进行网络请求(其他项目)获取数据或传送数据,传统情况下,我们一般会使用JDK
自带的HttpURLConnection
、Apache
提供的HttpClient
、OkHttp
等客户端请求工具,但是这几种方式使用起来比较繁琐,对于初学者来说不太友好,而Spring
为我们提供了一个快捷方便的Restful
模板类请求工具类RestTemplate
。
RestTemplate
是一个执行HTTP
请求的同步阻塞式请求工具类,它是在JDK HttpURLConnection
,Apache HttpComponents
,OkHttp
等客户端的基础上,封装了更高级别的API,使在单行中调用REST端点变得容易。RestTemplate
默认是依赖JDK HttpURLConnection
,可以根据实际需要通过构造函数中的setRequestFactory
方法设置底层依赖请求客户端。
public RestTemplate(ClientHttpRequestFactory requestFactory) {
this();
this.setRequestFactory(requestFactory);//指定HTTP客户端
}
如果当前是非SpringBoot项目(非spring环境),我们需要导入Spring-web
依赖,即可引入RestTemplate
类:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>5.2.0.RELEASEversion>
dependency>
如果当前是SpringBoot项目(spring环境),我们只需要导入spring-boot-starter-web
依赖,即可引入RestTemplate
类:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>2.2.0.RELEASEversion>
dependency>
内置支持以下功能:
RestTemplate包含以下几个部分:
HttpMessageConverter
对象转换器ClientHttpRequestFactory
默认是JDK
的HttpURLConnection
ResponseErrorHandler
异常处理器ClientHttpRequestInterceptor
请求拦截器我们现在大多使用SpringBoot框架,为了后续方便使用,我们将RestTemplate
配置初始化为一个Bean
:
@Configuration
public class RestTemplateConfig {
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(getSimpleClientHttpRequestFactory());
return restTemplate;
}
/**
* 默认底层依赖JDK HttpURLConnection作为HTTP客户端
* @return
*/
public ClientHttpRequestFactory getSimpleClientHttpRequestFactory() {
int timeout = 5000;
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(timeout);
factory.setConnectTimeout(timeout);
return factory;
}
}
在项目启动的时候会默认加载当前配置类中的@Bean
注解方法,将restTemplate()
方法注入Spring容器中,注意: @Bean
注解的方法名最好是restTemplate
首字母小写,后续我们在需要用到的地方直接@Autowire
自动装载即可使用。
@Autowired
private RestTemplate restTemplate;
通过管方指导文档描述:
The default constructor uses java.net.HttpURLConnection to perform requests. You can switch to a different HTTP library with an implementation of ClientHttpRequestFactory. There is built-in support for the following:
释义:默认构造函数使用java.net.HttpURLConnection执行请求。您可以切换到具有`ClientHttpRequestFactory`实现的不同HTTP库。
//直接new RestTemplate();底层依赖的也是JDK HttpURLConnection
如果我们想切换到Apache HttpComponents
客户端,可以使用以下方式:
首先导入Apache HttpClient
依赖:
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
<version>4.5.6version>
dependency>
初始化:
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
但是通常情况下,我们切换客户端会预先做一些连接请求超时参数配置,如下:
@Configuration
public class RestTemplateConfig {
/**
* 使用Apache HttpClient作为底层客户端
* @return
*/
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(){
//RestTemplate restTemplate = new RestTemplate();
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
return restTemplate;
}
/**
* 使用Apache HttpClient作为底层客户端
* @return
*/
private ClientHttpRequestFactory getClientHttpRequestFactory(){
int timeout = 5000;
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(timeout)
.setConnectTimeout(timeout)
.setSocketTimeout(timeout)
.build();
CloseableHttpClient client = HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.build();
return new HttpComponentsClientHttpRequestFactory(client);
}
}
切换为OkHttp客户端:
先导入依赖:
<dependency>
<groupId>com.squareup.okhttp3groupId>
<artifactId>okhttpartifactId>
<version>4.7.2version>
dependency>
初始化:
RestTemplate template = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
预先做一些连接请求超时参数配置,如下:
@Configuration
public class RestTemplateConfig {
/**
* 使用OkHTTP作为底层客户端
* @return
*/
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(){
//RestTemplate restTemplate = new RestTemplate();
//RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
RestTemplate restTemplate = new RestTemplate(getOkHttp3ClientHttpRequestFactory());
return restTemplate;
}
/**
* 使用OkHTTP作为底层客户端
* @return
*/
private OkHttp3ClientHttpRequestFactory getOkHttp3ClientHttpRequestFactory(){
int timeout = 5000;
OkHttpClient okHttpClient = new OkHttpClient();
OkHttp3ClientHttpRequestFactory okHttp3ClientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(okHttpClient){{{
setConnectTimeout(timeout);
setReadTimeout(timeout);
setWriteTimeout(timeout);
}}};
return okHttp3ClientHttpRequestFactory;
}
}
具网上不完全统计,上述Http客户端的效率:
OkHttp > Apache HttpClient > JDK HttpURLConnection
所以下述我们以OkHttp作为底层客户端依赖。
扩展:
@ConditionalOnMissingBean(RestTemplate.class)
当注入多个相同类型的
Bean
时,会报异常,此注解就是保证当前类型Bean
只能有一个实例化对象。
@ConditionalOnBean // 当给定的在bean存在时,则实例化当前Bean
@ConditionalOnMissingBean // 当给定的在bean不存在时,则实例化当前Bean
@ConditionalOnClass // 当给定的类名在类路径上存在,则实例化当前Bean
@ConditionalOnMissingClass // 当给定的类名在类路径上不存在,则实例化当前Bean
Spring之RestTemplate官方指导文档。
它公开了以下几组重载方法:
方法 | 描述 |
---|---|
getForObject |
通过GET检索一个表示。 |
getForEntity |
通过使用GET检索一个ResponseEntity (即状态、标题和主体)。 |
headForHeaders |
通过使用HEAD检索资源的所有标头。 |
postForLocation |
使用POST创建一个新资源,并从响应中返回Location 报头。 |
postForObject |
使用POST创建新资源,并从响应返回表示。 |
postForEntity |
使用POST创建新资源,并从响应返回表示。 |
put |
通过使用PUT创建或更新资源。 |
patchForObject |
使用PATCH更新资源,并从响应返回表示。注意,JDK HttpURLConnection 不支持PATCH ,但Apache HttpComponents 和其他组件支持。 |
delete |
使用DELETE删除指定URI处的资源。 |
optionsForAllow |
使用ALLOW为资源检索允许的HTTP方法。 |
exchange |
在需要时提供额外灵活性的前面方法的更一般化(且不那么自以为是)版本。它接受一个’RequestEntity (包括HTTP方法、URL、报头和主体作为输入),并返回一个ResponseEntity 。这些方法允许使用ParameterizedTypeReference 代替Class 来指定具有泛型的响应类型。 |
execute |
执行请求的最通用方法,通过回调接口完全控制请求准备和响应提取。 |
将上述方法大致归为三类:
常用的Rest API(GET、POST、PUT、DELETE):
getForObject
getForEntity
headForHeaders
postForLocation
postForObject
postForEntity
put
patchForObject
delete
optionsForAllow
接收一个 RequestEntity
参数,自定义设置HTTP method
,URL
,headers
和body
,返回 ResponseEntity:
exchange
通过 callback 接口,可以对请求和返回做更加全面的自定义控制:
execute
创建一个响应实体类ResponseBean
:
@Data
@ToString
public class ResponseBean<T> {
/**
* 状态码 200 成功 其他失败
*/
private String code;
/**
* 响应数据
*/
private T data;
/**
* 异常信息
*/
private String message;
}
创建一个请求实体类RequestBean
:
@Data
public class RequestBean {
/**
* 用户id
*/
private String userCode;
/**
*
*/
private String userName;
}
通过RestTemplate
发送HTTP GET
协议请求,经常会用到以下两个方法:
getForObject()
:返回值是HTTP
协议的响应体内容getForEntity()
:返回的是ResponseEntity
,ResponseEntity
是对HTTP
响应的封装,除了包含响应体,还包含HTTP
状态码、contentType、contentLength、Header
等信息getForObject()
重载方法:
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {}
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {}
@Nullable
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 {}
创建一个无参get请求接口:
@RestController
@RequestMapping("/rest")
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/no/param/test/get")
public ResponseBean<String> testGet1(){
ResponseBean<String> responseBean = new ResponseBean<String>();
responseBean.setCode("200");
responseBean.setData("无参get请求!");
responseBean.setMessage(null);
return responseBean;
}
}
单元测试无参get请求:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testGet1(){
String url = "http://localhost:6625/rest/no/param/test/get";
ResponseBean response = restTemplate.getForObject(url, ResponseBean.class);
System.out.println(response);
}
}
打印结果:
{"code":"200","data":"å“应æˆåŠŸäº†ï¼","message":null}
发现出现了乱码,原因在于RestTemplate中对字符串默认使用的转换器是StringHttpMessageConverter
,而StringHttpMessageConverter
默认的字符集编码是ISO_8859_1
:
public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
public static final Charset DEFAULT_CHARSET;
public StringHttpMessageConverter() {
this(DEFAULT_CHARSET);
}
static {
//默认字符集
DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;
}
}
ISO_8859_1
编码格下,中文是乱码的。因此我们需要将编码格式设置为UTF-8
的格式才能支持中文:
@Configuration
public class RestTemplateConfig {
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(getOkHttp3ClientHttpRequestFactory());
//设置RestTemplate的字符集为UTF-8
setRestTemplateEncode(restTemplate);
return restTemplate;
}
/**
* 使用OkHTTP作为底层客户端
* @return
*/
private OkHttp3ClientHttpRequestFactory getOkHttp3ClientHttpRequestFactory(){
int timeout = 5000;
OkHttpClient okHttpClient = new OkHttpClient();
OkHttp3ClientHttpRequestFactory okHttp3ClientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(okHttpClient){{{
setConnectTimeout(timeout);
setReadTimeout(timeout);
setWriteTimeout(timeout);
}}};
return okHttp3ClientHttpRequestFactory;
}
/**
* 指定设置RestTemplate的字符转换器编码为UTF_8
* @param restTemplate
*/
public static void setRestTemplateEncode(RestTemplate restTemplate) {
if (ObjectUtils.isEmpty(restTemplate) || ObjectUtils.isEmpty(restTemplate.getMessageConverters())){
return;
}
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
for (int i = 0; i < messageConverters.size(); i++) {
HttpMessageConverter<?> httpMessageConverter = messageConverters.get(i);
if (StringHttpMessageConverter.class.equals(httpMessageConverter.getClass())){
messageConverters.set(i,new StringHttpMessageConverter(StandardCharsets.UTF_8));
}
}
}
}
再次重启,打印结果:
ResponseBean(code=200, data=无参get请求!, message=null)
问题解决!
创建一个占位符传参get请求接口:
@RestController
@RequestMapping("/rest")
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/path/param/test/get/{code}/{data}")
public ResponseBean<String> testGet2(@PathVariable("code") String code,@PathVariable("data") String data){
ResponseBean<String> responseBean = new ResponseBean<String>();
responseBean.setCode(code);
responseBean.setData(data);
responseBean.setMessage(null);
return responseBean;
}
}
单元测试占位符传参get请求:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testGet2(){
String url = "http://localhost:6625/rest/path/param/test/get/{code}/{data}";
ResponseBean response = restTemplate.getForObject(url, ResponseBean.class,"200","占位符传参get请求!");
System.out.println(response);
}
}
打印结果:
ResponseBean(code=200, data=占位符传参get请求!, message=null)
创建一个带参RestFul请求方法:
@RestController
@RequestMapping("/rest")
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/restful/param/test/get")
public ResponseBean<String> testGet3(@RequestParam("userCode") String userCode,@RequestParam("userName") String userName){
ResponseBean<String> responseBean = new ResponseBean<String>();
responseBean.setCode("200");
responseBean.setData("userCode = " + userCode + ",userName = " + userName);
responseBean.setMessage(null);
return responseBean;
}
}
单元测试Restful传参get请求:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testGet3(){
String url = "http://localhost:6625/rest/restful/param/test/get?userCode={userCode}&userName={userName}";
Map<String,String> paramMap = new HashMap<String,String>();
paramMap.put("userCode","123456");
paramMap.put("userName","张三三");
ResponseBean response = restTemplate.getForObject(url, ResponseBean.class,paramMap);
System.out.println(response);
}
}
打印结果:
ResponseBean(code=200, data=userCode = 123456,userName = 张三三, message=null)
getForEntity
方法的应用和上述getForObject
类似,三种重载方法参数一致,只是返回的结果略有差异,下面我们只演示Restful传参get请求请求:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testGet4(){
String url = "http://localhost:6625/rest/restful/param/test/get?userCode={userCode}&userName={userName}";
Map<String,String> paramMap = new HashMap<String,String>();
paramMap.put("userCode","123456");
paramMap.put("userName","张三三");
ResponseEntity<ResponseBean> response = restTemplate.getForEntity(url, ResponseBean.class, paramMap);
System.out.println(response);
}
}
打印结果:
HTTP 响应结果体:<200,ResponseBean(code=200, data=userCode = 123456,userName = 张三三, message=null),[Content-Type:"application/json", Date:"Sat, 29 Oct 2022 02:55:35 GMT", Transfer-Encoding:"chunked"]>
HTTP 响应状态:200 OK
HTTP 响应状态码:200
HTTP 响应headers信息:[Content-Type:"application/json", Date:"Sat, 29 Oct 2022 02:55:35 GMT", Transfer-Encoding:"chunked"]
HTTP 响应headers信息内容类型:application/json
HTTP 响应headers信息内容长度:-1
如果我们想要完整的HTTP响应体,那推荐使用getForEntity
,可以根据响应码判断是否响应成功!
我们在平时开发的过程中可以了解到,其实POST
请求方法和GET
请求方法上大同小异,RestTemplate
的POST
请求也主要包含以下几个方法,比POST
多了一个postForLocation
方法:
postForObject()
:返回值是HTTP
协议的响应体body
内容postForEntity()
:返回的是ResponseEntity
,ResponseEntity
是对HTTP
响应的封装,除了包含响应体,还包含HTTP
状态码、contentType、contentLength、Header
等信息postForLocation
:使用POST创建一个新资源,返回UTI
对象,可以从响应中返回Location
报头。getForObject()
重载方法:
@Nullable
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException {}
@Nullable
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {}
@Nullable
public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException {}
getForEntity()
重载方法:
public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException {}
public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {}
public <T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException {}
getForLocation()
重载方法:
@Nullable
public URI postForLocation(String url, @Nullable Object request, Object... uriVariables) throws RestClientException {}
@Nullable
public URI postForLocation(String url, @Nullable Object request, Map<String, ?> uriVariables) throws RestClientException {}
@Nullable
public URI postForLocation(URI url, @Nullable Object request) throws RestClientException {}
创建一个post无参请求方法:
@RestController
@RequestMapping("/rest")
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
@PostMapping("/no/param/test/post")
public ResponseBean<String> testPost1(){
ResponseBean<String> responseBean = new ResponseBean<String>();
responseBean.setCode("200");
responseBean.setData("无参post请求!");
responseBean.setMessage(null);
return responseBean;
}
}
单元测试无参post请求:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testPost1(){
String url = "http://localhost:6625/rest/no/param/test/post";
ResponseBean response = restTemplate.postForObject(url, null, ResponseBean.class);
System.out.println(response);
}
}
打印结果:
ResponseBean(code=200, data=无参post请求!, message=null)
创建一个post无参请求方法:
@RestController
@RequestMapping("/rest")
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
@PostMapping("/path/param/test/post/{code}/{data}")
public ResponseBean<String> testPost2(@PathVariable("code") String code,
@PathVariable("data") String data){
ResponseBean<String> responseBean = new ResponseBean<String>();
responseBean.setCode(code);
responseBean.setData(data);
responseBean.setMessage(null);
return responseBean;
}
}
单元测试无参post请求:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testPost2(){
String url = "http://localhost:6625/rest/path/param/test/post/{code}/{data}";
ResponseBean response = restTemplate.postForObject(url, null, ResponseBean.class,"200","占位符post请求");
System.out.println(response);
}
}
打印结果:
ResponseBean(code=200, data=占位符post请求, message=null)
创建一个post表单提交请求方法:
@RestController
@RequestMapping("/rest")
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
@PostMapping("/form/param/test/post")
public ResponseBean<String> testPost3(@RequestParam("userCode") String userCode,
@RequestParam("userName") String userName){
ResponseBean<String> responseBean = new ResponseBean<String>();
responseBean.setCode("200");
responseBean.setData("userCode = " + userCode + ",userName = " + userName);
responseBean.setMessage(null);
return responseBean;
}
}
单元测试表单提交post请求:
/**
* postForObject 表单form传参(restful)
*/
@Test
public void testPost3(){
String url = "http://localhost:6625/rest/form/param/test/post";
Map<String,String> paramMap = new HashMap<String,String>();
paramMap.put("userCode","123456");
paramMap.put("userName","李四四");
ResponseBean response = restTemplate.postForObject(url, null, ResponseBean.class,paramMap);
System.out.println(response);
}
这时候发现上述请求会报错,因为post提交表单请求,需要将参数封装在请求体中,还要指定请求头信息,也就是postForObject
方法的第二个参数位,如下所示:
源码请求头信息ContentType
:
public static final MediaType APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded");
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testPost3(){
String url = "http://localhost:6625/rest/form/param/test/post";
//请求头设为application/x-www-form-urlencoded
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
//表单参数(专用map)
MultiValueMap<String,String> paramMap = new LinkedMultiValueMap<String,String>();
paramMap.add("userCode","123456");
paramMap.add("userName","李四四");
//组装请求体
HttpEntity<MultiValueMap<String,String>> requestEntity = new HttpEntity<>(paramMap,httpHeaders);
ResponseBean response = restTemplate.postForObject(url, requestEntity, ResponseBean.class);
System.out.println(response);
}
}
打印结果:
ResponseBean(code=200, data=userCode = 123456,userName = 李四四, message=null)
上述传递的表单参数,是用@RequestParam
接收的:
@RequestParam("userCode") String userCode,
@RequestParam("userName") String userName
还可以通过实体类对象映射接收,将上述post表单提交请求方法的参数列表改为实体类RequestBean
:
@RestController
@RequestMapping("/rest")
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
@PostMapping("/form/param/test/post")
public ResponseBean<String> testPost3(RequestBean requestBean){
ResponseBean<String> responseBean = new ResponseBean<String>();
responseBean.setCode("200");
responseBean.setData("userCode = " + requestBean.getUserCode() + ",userName = " + requestBean.getUserName());
responseBean.setMessage(null);
return responseBean;
}
}
再次请求,效果一样一样的!
将上述post表单提交请求方法的参数列表改为实体类RequestBean
:
@RestController
@RequestMapping("/rest")
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
@PostMapping("/json/param/test/post")
public ResponseBean<String> testPost4(@RequestBody RequestBean requestBean){
ResponseBean<String> responseBean = new ResponseBean<String>();
responseBean.setCode("200");
responseBean.setData("userCode = " + requestBean.getUserCode() + ",userName = " + requestBean.getUserName());
responseBean.setMessage(null);
return responseBean;
}
}
单元测试传递json数据提交post请求:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testPost4(){
String url = "http://localhost:6625/rest/json/param/test/post";
RequestBean requestBean = new RequestBean();
requestBean.setUserCode("123456");
requestBean.setUserName("张五五");
ResponseBean response = restTemplate.postForObject(url, requestBean, ResponseBean.class);
System.out.println(response);
}
}
打印结果:
ResponseBean(code=200, data=userCode = 123456,userName = 张五五, message=null)
POST请求postForEntity()
方法和postForObject()
重载方法用法一样,只是返回结果不同!!!
将上述post表单提交请求方法的参数列表改为实体类RequestBean
:
@RestController
@RequestMapping("/rest")
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
@PostMapping("/json/param/test/post/entity")
public ResponseBean<String> testPost5(@RequestBody RequestBean requestBean){
ResponseBean<String> responseBean = new ResponseBean<String>();
responseBean.setCode("200");
responseBean.setData("userCode = " + requestBean.getUserCode() + ",userName = " + requestBean.getUserName());
responseBean.setMessage(null);
return responseBean;
}
}
单元测试传递json数据提交post请求:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testPost5(){
String url = "http://localhost:6625/rest/json/param/test/post/entity";
//请求头请求头设为application/json
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
RequestBean requestBean = new RequestBean();
requestBean.setUserCode("123456");
requestBean.setUserName("马七七");
//组装请求体(直接传实体类requestBean,也可以识别为json对象数据)
HttpEntity<RequestBean> requestEntity = new HttpEntity<>(requestBean,httpHeaders);
ResponseEntity<ResponseBean> response = restTemplate.postForEntity(url, requestEntity, ResponseBean.class);
System.out.println("HTTP 响应结果体:" + response);
System.out.println("HTTP 响应状态:" + response.getStatusCode());
System.out.println("HTTP 响应状态码:" + response.getStatusCodeValue());
System.out.println("HTTP 响应headers信息:" + response.getHeaders());
System.out.println("HTTP 响应headers信息内容类型:" + response.getHeaders().getContentType());
System.out.println("HTTP 响应headers信息内容长度:" + response.getHeaders().getContentLength());
}
}
打印结果:
HTTP 响应结果体:<200,ResponseBean(code=200, data=userCode = 123456,userName = 马七七, message=null),[Content-Type:"application/json", Date:"Sat, 29 Oct 2022 09:51:53 GMT", Transfer-Encoding:"chunked"]>
HTTP 响应状态:200 OK
HTTP 响应状态码:200
HTTP 响应headers信息:[Content-Type:"application/json", Date:"Sat, 29 Oct 2022 09:51:53 GMT", Transfer-Encoding:"chunked"]
HTTP 响应headers信息内容类型:application/json
HTTP 响应headers信息内容长度:-1
postForLocation
方法的返回值是一个URI
对象,POST
请求一般用来新增数据,举个例子:用户注册之后,一半情况下会自动带着用户注册信息跳转到登录界面,这个时候就可以用到postForLocation
,也就是第一次请求之后,会返回一个请求资源URI
,这个资源可以是重定向,也可以是其他操作。
注意:@RestController 要换成@Controller,需要返回json数据的方法,标注@ResponseBody
创建请求方法:
@Controller
@RequestMapping("/rest")
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
/**
* 当做注册请求
* @param requestBean
* @return
*/
@PostMapping("/redirect/json/param/test/post")
public String testPostLocation(@RequestBody RequestBean requestBean){
//重定向到登录请求
return "redirect:/rest/testPostByLocation";
}
/**
* 当做登录请求
* @param requestBean
* @return
*/
@GetMapping("/testPostByLocation")
@ResponseBody
public String testPostByLocation(RequestBean requestBean){
return "hello postByLocation";
}
}
单元测试:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testPostLocation(){
String url = "http://localhost:6625/rest/redirect/json/param/test/post";
RequestBean requestBean = new RequestBean();
requestBean.setUserCode("123456");
requestBean.setUserName("张五五");
URI uri = restTemplate.postForLocation(url, requestBean);
System.out.println(uri);
}
}
打印结果:
http://localhost:6625/rest/testPostByLocation
POST请求postForLocation()
方法和前两种方法postForObject()
、postForEntity()
重载方法用法一样,只是返回结果不同!!!
PUT
请求一般是用来新增或者修改资源,重载方法的用法和GET
、POST
也基本一样,只是没有返回值。
重载方法:
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 {}
创建请求方法:
@RestController
@RequestMapping("/rest")
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
@PutMapping("/json/param/test/put")
public void testPut(@RequestBody RequestBean requestBean){
System.out.println("put请求更新成功了!userCode = " + requestBean.getUserCode() + ",userName = " + requestBean.getUserName());
}
}
单元测试:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testPut(){
String url = "http://localhost:6625/rest/json/param/test/put";
RequestBean requestBean = new RequestBean();
requestBean.setUserCode("123456");
requestBean.setUserName("张五五");
restTemplate.put(url,requestBean);
}
}
打印结果:
put请求更新成功了!userCode = 123456,userName = 张五五
DELETE请求一般是用来删除资源,重载方法的用法和
GET、
POST、
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 {}
创建请求方法:
@RestController
@RequestMapping("/rest")
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
@DeleteMapping("/restful/param/test/delete")
public void testDelete(@RequestParam("userCode") String userCode,
@RequestParam("userName") String userName){
System.out.println("delete请求删除成功了!userCode = " + userCode + ",userName = " + userName);
}
}
单元测试:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testDelete(){
String url = "http://localhost:6625/rest/restful/param/test/delete?userCode={userCode}&userName={userName}";
Map<String,String> paramMap = new HashMap<String,String>();
paramMap.put("userCode","123456");
paramMap.put("userName","张三三");
restTemplate.delete(url,paramMap);
}
}
打印结果:
delete请求删除成功了!userCode = 123456,userName = 张三三
如果觉得上述常用请求方法还不够灵活,可以使用exchange
通用协议请求方法,可以根据自己的需要,自由配置请求头、请求体等参数,常见的GET、POST、DELETE、PUT、OPTIONS、PATCH
,这些请求都可以通过exchange
实现,看下源码重载方法:
<T> ResponseEntity<T> exchange(String var1, HttpMethod var2, @Nullable HttpEntity<?> var3, Class<T> var4, Object... var5) throws RestClientException;
<T> ResponseEntity<T> exchange(String var1, HttpMethod var2, @Nullable HttpEntity<?> var3, Class<T> var4, Map<String, ?> var5) throws RestClientException;
<T> ResponseEntity<T> exchange(URI var1, HttpMethod var2, @Nullable HttpEntity<?> var3, Class<T> var4) throws RestClientException;
<T> ResponseEntity<T> exchange(String var1, HttpMethod var2, @Nullable HttpEntity<?> var3, ParameterizedTypeReference<T> var4, Object... var5) throws RestClientException;
<T> ResponseEntity<T> exchange(String var1, HttpMethod var2, @Nullable HttpEntity<?> var3, ParameterizedTypeReference<T> var4, Map<String, ?> var5) throws RestClientException;
<T> ResponseEntity<T> exchange(URI var1, HttpMethod var2, @Nullable HttpEntity<?> var3, ParameterizedTypeReference<T> var4) throws RestClientException;
<T> ResponseEntity<T> exchange(RequestEntity<?> var1, Class<T> var2) throws RestClientException;
<T> ResponseEntity<T> exchange(RequestEntity<?> var1, ParameterizedTypeReference<T> var2) throws RestClientException;
解释其一:
public <T> ResponseEntity<T> exchange(URI url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {}
向
PUT
、DELETE
没有返回值,也要默认指定一个类型,直接Object.class
即可!
使用上述方法单元测试,模拟DELETE
请求方法:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testExchangeDelete(){
String url = "http://localhost:6625/rest/restful/param/test/delete?userCode={userCode}&userName={userName}";
//请求参数
Map<String,String> paramMap = new HashMap<String,String>();
paramMap.put("userCode","123456");
paramMap.put("userName","张三三");
//请求头
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
//组装请求体
HttpEntity<MultiValueMap<String,String>> httpEntity = new HttpEntity<>(httpHeaders);
restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, Object.class,paramMap);
}
}
打印结果:
delete请求删除成功了!userCode = 123456,userName = 张三三
和使用delete
请求方法效果一样一样的!并不是更佳麻烦,而是更佳灵活!!!
使用上述方法单元测试,模拟POST
请求方法postForEntity
:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testExchangePostForObject(){
String url = "http://localhost:6625/rest/json/param/test/post/entity";
//请求头请求头设为application/json
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
RequestBean requestBean = new RequestBean();
requestBean.setUserCode("123456");
requestBean.setUserName("马七七");
//组装请求体(直接传实体类requestBean,也可以识别为json对象数据)
HttpEntity<RequestBean> requestEntity = new HttpEntity<>(requestBean,httpHeaders);
//postForEntity方法
//ResponseEntity response = restTemplate.postForEntity(url, requestEntity, ResponseBean.class);
//exchange方法
ResponseEntity<ResponseBean> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, ResponseBean.class);
System.out.println("HTTP 响应结果体:" + response);
System.out.println("HTTP 响应状态:" + response.getStatusCode());
System.out.println("HTTP 响应状态码:" + response.getStatusCodeValue());
System.out.println("HTTP 响应headers信息:" + response.getHeaders());
System.out.println("HTTP 响应headers信息内容类型:" + response.getHeaders().getContentType());
System.out.println("HTTP 响应headers信息内容长度:" + response.getHeaders().getContentLength());
}
}
打印结果:
HTTP 响应结果体:<200,ResponseBean(code=200, data=userCode = 123456,userName = 马七七, message=null),[Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Sun, 30 Oct 2022 05:12:38 GMT"]>
HTTP 响应状态:200 OK
HTTP 响应状态码:200
HTTP 响应headers信息:[Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Sun, 30 Oct 2022 05:12:38 GMT"]
HTTP 响应headers信息内容类型:application/json
HTTP 响应headers信息内容长度:-1
和使用postForEntity
请求方法效果一样一样的!!!
只要方法掌握了,所有的请求类型基本都可以兼容,兄弟们,方便又哇塞哈!!!
public enum HttpMethod {
GET,
HEAD,
POST,
PUT,
PATCH,
DELETE,
OPTIONS,
TRACE;
}
RestTemplate
常用于请求资源或添加资源,也可以用来上传和下载资源!
创建文件上传请求:
@RestController
@RequestMapping("/rest")
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
@PostMapping("/file/test/post/upload")
public ResponseBean testPostUpload(@RequestParam("multipartFile") MultipartFile multipartFile,
@RequestParam("userCode") String userCode,
@RequestParam("userName") String userName) throws Exception {
//上传存放路径
File folder = new File(UPLOAD_PATH + userCode + "/" + userName);
if (!folder.isDirectory()){
//如果不是文件夹,则创建为一个多级目录文件夹
boolean mkdirs = folder.mkdirs();
if (BooleanUtils.isNotTrue(mkdirs)){
throw new Exception("创建文件目录异常!");
}
}
//对上传文件重命名
String originalFilename = multipartFile.getOriginalFilename();
String newFilename = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
ResponseBean responseBean = new ResponseBean();
try {
//文件保存
multipartFile.transferTo(new File(folder,newFilename));
responseBean.setCode("200");
responseBean.setData("文件名:" + newFilename);
responseBean.setMessage("文件上传成功!");
}catch (IOException e){
LOGGER.error("上传文件异常!",e.getMessage());
responseBean.setCode("500");
responseBean.setData("文件名:" + newFilename);
responseBean.setMessage("文件上传失败!");
}
return responseBean;
}
}
单元测试,上传图片文件到指定文件目录:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testPostUpload(){
//需要上传的文件
String filePath = "/D:/Workspace/壁纸/傍晚雪山.jpg";
//请求地址
String url = "http://localhost:6625/rest/file/test/post/upload";
// 请求头设置,multipart/form-data格式的数据
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
//提交参数设置
MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
param.add("multipartFile", new FileSystemResource(new File(filePath)));
param.add("userCode", "anightmonarch");
param.add("userName", "一宿君");
// 组装请求体
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(param, headers);
//发起请求
ResponseBean responseBean = restTemplate.postForObject(url, request, ResponseBean.class);
System.out.println(responseBean);
}
}
打印结果:
ResponseBean(code=200, data=文件名:aa4da235-43ef-4687-a16c-1911ca0ab3ba.jpg, message=文件上传成功!)
创建文件下载请求:
@RestController
@RequestMapping("/rest")
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/file/test/get/download")
public void testGetDownload(@RequestParam("fileName") String fileName,
@RequestParam("userCode") String userCode,
@RequestParam("userName") String userName,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
//组装要下载文件所在的路径
File file = new File(UPLOAD_PATH + userCode + "/" + userName + File.separator + fileName);
if (!file.exists()){
throw new Exception("文件路径不存在!");
}
//获取文件流
FileInputStream fileInputStream = new FileInputStream(file);
//获取文件扩展名
String extendFilename = fileName.substring(fileName.lastIndexOf("."));
//动态设置文件响应类型
response.setContentType(request.getSession().getServletContext().getMimeType(extendFilename));
//设置响应头,attachment表示以附件的形式下载,inline表示在线打开
response.setHeader("content-disposition","attachment;fileName=" + URLEncoder.encode(fileName,"UTF-8"));
//获取输出流(用于写文件)
OutputStream outputStream = response.getOutputStream();
//使用spring框架中的FileCopyUtils工具,下载文件
FileCopyUtils.copy(fileInputStream,outputStream);
}
}
单元测试,下载文件到指定文件目录:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testGetDownload() throws IOException {
//需要下载的文件
String fileName = "aa4da235-43ef-4687-a16c-1911ca0ab3ba.jpg";
//用户信息
String userCode = "anightmonarch";
String userName = "一宿君";
//请求地址
String url = "http://localhost:6625/rest/file/test/get/download?fileName={fileName}&userCode={userCode}&userName={userName}";
//提交参数设置
Map<String,Object> paramMap = new HashMap<String,Object>();
paramMap.put("fileName",fileName);
paramMap.put("userCode",userCode);
paramMap.put("userName",userName);
//发起请求(响应内容为字节文件)
ResponseEntity<byte[]> response = restTemplate.getForEntity(url, byte[].class, paramMap);
System.out.println("HTTP 响应状态:" + response.getStatusCode());
//下载文件保存路径
File savePath = new File("D:\\Workspace\\download\\img\\");
if (!savePath.isDirectory()){
savePath.mkdirs();
}
Path path = Paths.get(savePath.getPath() + File.separator + fileName);
Files.write(path, Objects.requireNonNull(response.getBody(),"下载文件失败!"));
}
}
打印结果:
HTTP 响应状态:200 OK
上述文件下载的方式,是一次性将响应内容加载到客户端内存中,然后再从内存中将文件写入到磁盘中,这种方式适用于一些小文件的下载,比如:图片等。
但是在实际的工作场景中,如果下载一些比较大的文件,比如:excel、pdf、zip等文件,一次性将响应内容加载到内存中,会占用大量内存,严重影响系统的运行效率。
解决办法,设置请求头接收文件类型为MediaType.APPLICATION_OCTET_STREAM
,表示可以将响应内容以流的形式加载到内存,同时保证接收到一部分内容,就向磁盘中写入一部分内容,而不是等全部加载一次性加载,这样整体效率运行起来就非常哇塞,流畅丝滑!!!
创建文件下载请求:和上述请求保持一致。
单元测试,下载大文件到指定文件目录:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testGetDownloadBigFile() throws IOException {
//需要下载的文件
String fileName = "aa4da235-43ef-4687-a16c-1911ca0ab3ba.jpg";
//用户信息
String userCode = "anightmonarch";
String userName = "一宿君";
//请求地址
String url = "http://localhost:6625/rest/file/test/get/download?fileName={fileName}&userCode={userCode}&userName={userName}";
//提交参数设置
Map<String,Object> paramMap = new HashMap<String,Object>();
paramMap.put("fileName",fileName);
paramMap.put("userCode",userCode);
paramMap.put("userName",userName);
//定义请求头的接收类型
RequestCallback requestCallback = clientHttpRequest
-> clientHttpRequest.getHeaders()
.setAccept(Lists.newArrayList(MediaType.APPLICATION_OCTET_STREAM,MediaType.ALL));
//下载文件保存路径
File savePath = new File("D:\\Workspace\\download\\img\\");
if (!savePath.isDirectory()){
savePath.mkdirs();
}
Path path = Paths.get(savePath.getPath() + File.separator + fileName);
try {
//对响应结果进行流式处理,而不是一次性将响应结果全部加载到内存
restTemplate.execute(url, HttpMethod.GET, requestCallback,clientHttpResponse
-> {
//每响应一次就写入磁盘一次,不在内存中逗留
Files.copy(Objects.requireNonNull(clientHttpResponse.getBody(),"下载文件失败!"), path);
return null;
}, paramMap);
}catch (Exception e){
LOGGER.error("文件下载异常或已经存在!{}",e.getMessage());
throw new Exception("文件下载异常或已经存在!");
}
}
}
如果指定目录下已经存在要下载的文件,会报异常!!!
细心的小伙伴可能早就发现,前文我们用过的所有请求方法POST、GET、DELETE、PUT、EXCHANGE
等方法,底层基本都调用了excute
方法,那excute
是干啥的,请看下文。
源码重载方法:
@Nullable
<T> T execute(String var1, HttpMethod var2, @Nullable RequestCallback var3, @Nullable ResponseExtractor<T> var4, Object... var5) throws RestClientException;
@Nullable
<T> T execute(String var1, HttpMethod var2, @Nullable RequestCallback var3, @Nullable ResponseExtractor<T> var4, Map<String, ?> var5) throws RestClientException;
@Nullable
<T> T execute(URI var1, HttpMethod var2, @Nullable RequestCallback var3, @Nullable ResponseExtractor<T> var4) throws RestClientException;
上述下载大文件的执行方法使用的是第二个重载方法:
@Nullable
public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor, Map<String, ?> uriVariables) throws RestClientException {
URI expanded = this.getUriTemplateHandler().expand(url, uriVariables);
return this.doExecute(expanded, method, requestCallback, responseExtractor);
}
到这会发现,excute
方法将String
格式的URI
转成了java.net.URI
,然后有调用了方法doExecute
,这又是干啥的,再往里扒拉:
@Nullable
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
Object var14;
try {
//1.生成请求
ClientHttpRequest request = this.createRequest(url, method);
if (requestCallback != null) {
//2.设置header
requestCallback.doWithRequest(request);
}
//3.执行请求
response = request.execute();
//4.处理响应
this.handleResponse(url, method, response);
//5.返回执行结果
var14 = responseExtractor != null ? responseExtractor.extractData(response) : null;
} catch (IOException var12) {
String resource = url.toString();
String query = url.getRawQuery();
resource = query != null ? resource.substring(0, resource.indexOf(63)) : resource;
throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + var12.getMessage(), var12);
} finally {
if (response != null) {
response.close();
}
}
return var14;
}
此方法并没有对外暴露,只能通过继承调用。可以看到我们每次请求的时候内部会发生这几个步骤:
此处我们需要注意两个关键接口:RequestCallback
、ResponseExtractor
。
RequestCallback官方指导文档。
RequestCallback
用于操作ClientHttpRequest的代码的回调接口。允许操作请求头,并写入请求体。
在RestTemplate内部使用,但对应用程序代码也很有用。有以下两个实现类:
请求头
回调,用于restTemplate.getXXX()
方法;请求头
和body
,用于restTemplate.putXXX()、restTemplate.postXXX()和restTemplate.exchange()
等方法。响应提取器,主要就是用来从Response
中提取数据。RestTemplate
请求完成后,都是通过它来从ClientHttpResponse
提取出指定内容,比如:请求头、请求body等。
ResponseExtractor
有三个实现类:
HeadersExtractor
:用于提取请求头HttpMessageConverterExtractor
:用于提取响应body
体内容ResponseEntityResponseExtractor
:内部借助HttpMessageConverterExtractor
提取body
体(委托模式),然后将body和响应头、状态封装成ResponseEntity
对象返回调用者。最后,上述方法总结的比较浅显,仅限于熟练调用,底层实现原理还需进一步深究!