文章目录
- 1 RestTemplate
- 1.1 简述RestTemplate
- 1.2 入门案例
- 1.2.1 httpclient-provider中提供一个Rest接口
- 1.2.2 httpclient-consumer中消费该接口
- 1.2.3 RestTemplate构造方法
- 1.3 RestTemplate API使用
- 1.4 Get请求
- 1.4.1 请求没有参数
- 1.4.2 带请求参数的
- 参数是在请求路径上的
- 参数在请求路径后面
- 1.5 POST请求
- 1.5.1 postForObject
- url路径带参数
- 参数只放在body里面
1 RestTemplate
1.1 简述RestTemplate
是Spring用于同步client端的核心类,简化了与http服务的通信,并满足RestFul原则,程序代码可以给它提供URL,并提取结果。默认情况下,
RestTemplate默认依赖jdk的HTTP连接工具。当然你也可以 通过setRequestFactory属性切换到不同的HTTP源,
比如Apache HttpComponents、Netty和OkHttp。
RestTemplate能大幅简化了提交表单数据的难度,并且附带了自动转换JSON数据的功能,但只有理解了HttpEntity的组成结构(header与body),且理解了与uriVariables之间的差异,才能真正掌握其用法。这一点在Post请求更加突出,下面会介绍到。
该类的入口主要是根据HTTP的六个方法制定:
HttpMethod | RestTemplate Method |
Delete | Delete |
get | getForObject |
getForEbtity | |
Head | headForheaders |
options | optionsForAllow |
post | postForLocation |
postForObject | |
put | put |
any | exchange |
execute |
此外,exchange和excute可以通用上述方法。
在内部,RestTemplate默认使用HttpMessageConverter实例将HTTP消息转换成POJO或者从POJO转换成HTTP消息。默认情况下会注册主mime类型的转换器,
但也可以通过setMessageConverters注册其他的转换器。
其实这点在使用的时候是察觉不到的,很多方法有一个responseType 参数,它让你传入一个响应体所映射成的对象,
然后底层用HttpMessageConverter将其做映射
`
HttpMessageConverterExtractor responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
`
HttpMessageConverter.java源码:
public interface HttpMessageConverter<T> {
//指示此转换器是否可以读取给定的类。
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
//指示此转换器是否可以写给定的类。
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
//返回List<MediaType>
List<MediaType> getSupportedMediaTypes();
//读取一个inputMessage
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
//往output message写一个Object
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
在内部,RestTemplate默认使用SimpleClientHttpRequestFactory和DefaultResponseErrorHandler来分别处理HTTP的创建和错误,
但也可以通过setRequestFactory和setErrorHandler来覆盖。
1.2 入门案例
1.2.1 httpclient-provider中提供一个Rest接口
package study.wyy.httpclient.provider.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @author wyaoyao
* @data 2019-10-29 10:45
*/
@RestController
public class HelloWordController {
@GetMapping("provider/hello/{name}")
public String hello(@PathVariable String name){
return "Hello" + name;
}
}
1.2.2 httpclient-consumer中消费该接口
- 配置RestTemplate
package study.wyy.httpclient.customer.config;
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;
/**
* @author wyaoyao
* @data 2019-09-11 11:09
*/
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory){
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(15000);
factory.setReadTimeout(5000);
return factory;
}
}
- 写一个Controller消费该接口
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* @author wyaoyao
* @data 2019-10-29 10:49
*/
@RestController
@Slf4j
public class HelloController {
@Autowired
RestTemplate restTemplate;
@ApiOperation("入门测试")
@GetMapping("/consumer/hello")
public String hello(){
String name = "wyy";
String response = restTemplate.getForObject("http://127.0.0.1:9999/provider/hello/"+name, String.class);
log.info("HTTP接口返回参数:{}",response);
return response;
}
}
1.2.3 RestTemplate构造方法
RestTemplate有3个构造方法,一个无参构造,两个有参构造
- 无参构造
/**
* Create a new instance of the {@link RestTemplate} using default settings.
* Default {@link HttpMessageConverter}s are initialized.
* 使用默认配置创建一个RestTemplate实例
* 默认的HttpMessageConverter集合被初始化
*/
public RestTemplate() {
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter());
this.messageConverters.add(new SourceHttpMessageConverter<Source>());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (romePresent) {
this.messageConverters.add(new AtomFeedHttpMessageConverter());
this.messageConverters.add(new RssChannelHttpMessageConverter());
}
if (jackson2XmlPresent) {
this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
}
else if (jaxb2Present) {
this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
/**
* 如果类路径下包含com.fasterxml.jackson.databind.ObjectMapper 和 com.fasterxml.jackson.core.JsonGenerator
* 使用jackson做http请求、响应的json转换
*/
if (jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
this.messageConverters.add(new GsonHttpMessageConverter());
}
}
- 参数为ClientHttpRequestFactory的构造
/**
* Create a new instance of the {@link RestTemplate} based on the given {@link ClientHttpRequestFactory}.
* @param requestFactory HTTP request factory to use
* @see org.springframework.http.client.SimpleClientHttpRequestFactory
* @see org.springframework.http.client.HttpComponentsClientHttpRequestFactory
* 使用指定的ClientHttpRequestFactory创建一个RestTemplate实例
* requestFactory是用于创建HTTP请求的工厂,默认的实现有
* SimpleClientHttpRequestFactory、HttpComponentsClientHttpRequestFactory
* 如果没有设置默认是SimpleClientHttpRequestFactory
*/
public RestTemplate(ClientHttpRequestFactory requestFactory) {
this(); //也会调用无参构造初始化默认的messageConverters
setRequestFactory(requestFactory);
}
- 参数为messageConverters的构造
/**
* Create a new instance of the {@link RestTemplate} using the given list of
* {@link HttpMessageConverter} to use
* @param messageConverters the list of {@link HttpMessageConverter} to use
* @since 3.2.7
* 传入自定义的HttpMessageConverter集合,并赋值给messageConverters,之后使用自定义的HttpMessageConverter
*/
public RestTemplate(List<HttpMessageConverter<?>> messageConverters) {
Assert.notEmpty(messageConverters, "At least one HttpMessageConverter required");
this.messageConverters.addAll(messageConverters);
}
1.3 RestTemplate API使用
- RestTemplate的方法名遵循一定的命名规范,第一部分表示用哪种HTTP方法调用(get,post),第二部分表示返回类型
- getForObject() – 发送GET请求,将HTTP response转换成一个指定的object对象
- postForEntity() – 发送POST请求,将给定的对象封装到HTTP请求体,返回类型是一个HttpEntity对象
- 每个HTTP方法对应的RestTemplate方法都有3种。其中2种的url参数为字符串,URI参数变量分别是Object数组和Map,第3种使用URI类型作为参数
- exchange 和execute 方法比上面列出的其它方法(如getForObject、postForEntity等)使用范围更广,允许调用者指定HTTP请求的方法(GET、POST、PUT等),并且可以支持像HTTP PATCH(部分更新),但需要底层的HTTP库支持,
JDK自带的HttpURLConnection不支持PATCH方法,Apache的HTTPClient 4.2及以后版本支持
1.4 Get请求
完整的代码在
study.wyy.httpclient.customer.web.GetController
和study.wyy.httpclient.provider.web.GetController
1.4.1 请求没有参数
provider中提供一个没有请求参数的请求
@GetMapping(UrlString.GET_METHOD_NO_REQUEST)
public List<User> queryList(){
List list = new ArrayList<User>();
list.add(new User(1L,"john",new Department(1L,"财务部")));
list.add(new User(2L,"kobe",new Department(1L,"财务部")));
log.info("list: {}",list);
return list;
}
consumer调用该接口
- getForObject方法
@GetMapping("consumer/getForObject/user")
@ApiOperation("get请求-没有请求参数-getForObject")
public List<User> queryList1(){
String url = urlConfig.getAddress()+ UrlString.GET_METHOD_NO_REQUEST;
Logging.logurl(log,url);
/**
* 参数一:请求路径
* 参数二:请求返回的结果类型
*/
List response = restTemplate.getForObject(url, List.class);
Logging.logResponse(log,response);
return response;
}
- getForEntity方法
@GetMapping("consumer/getForEntity/user")
@ApiOperation("get请求-没有请求参数-getForEntity")
public ResponseEntity queryList2(){
String url = urlConfig.getAddress()+ UrlString.GET_METHOD_NO_REQUEST;
Logging.logurl(log,url);
ResponseEntity<List> response = restTemplate.getForEntity(url, List.class);
Logging.logResponse(log,response);
/**
* HTTP接口返回结果:<200 OK,[{id=1, username=john, department={id=1, name=财务部}},
* {id=2, username=kobe, department={id=1, name=财务部}}],{Content-Type=[application/json;charset=UTF-8],
* Transfer-Encoding=[chunked], Date=[Tue, 29 Oct 2019 07:02:27 GMT]}>
*/
return response;
}
getForEntity与getForObject最大的区别就在于返回内容不一样:
getForEntity返回的是一个ResponseEntity,而getForObject返回的就只是返回内容。
getForObject的返回相当于只返回http的body部份而getForEntity的返回是返回全部信息
1.4.2 带请求参数的
对于get方法,如果有请求参数的话,一般是2种传参数的方式:
- 参数是url路径上
- 参数跟在url路径后面
参数是在请求路径上的
provider/get/user/{id}/{name}
- provider中提供一个请求
@GetMapping(UrlString.GET_METHOD_WITH_REQUEST_PARAM_URL)
public User queryListWithParam(@PathVariable Long id,@PathVariable String name){
log.info("请求参数id:{}",id);
log.info("请求参数name:{}",name);
User user = new User(id, name, new Department(1L, "财务部"));
return user;
}
- consumer中提供一个请求来消费该rest请求
@GetMapping("consumer/getObject/withParam")
@ApiOperation("get请求-url带请求参数-getForObject")
public User queryList3(){
String url = urlConfig.getAddress()+ UrlString.GET_METHOD_WITH_REQUEST_PARAM_URL;
Logging.logurl(log,url);
// 可以这么写
//User response = restTemplate.getForObject(url, User.class, 1L, "wyy");
// 或者
Map<String, Object> requestMap = Maps.newHashMap();
requestMap.put("id",1L);
requestMap.put("name","wyy");
User response = restTemplate.getForObject(url, User.class, requestMap);
Logging.logResponse(log,response);
/**
* HTTP接口返回结果:<200 OK,[{id=1, username=john, department={id=1, name=财务部}},
* {id=2, username=kobe, department={id=1, name=财务部}}],{Content-Type=[application/json;charset=UTF-8],
* Transfer-Encoding=[chunked], Date=[Tue, 29 Oct 2019 07:02:27 GMT]}>
*/
return response;
}
参数在请求路径后面
provider/get/user?id=1L&username=“wyy”
- provider中提供一个请求
@GetMapping(UrlString.GET_METHOD_WITH_REQUEST_PARAM_URL_AFTER)
public User queryListWithParam(User user){
log.info("请求参数-- {}", user);
return user;
}
- consumer中提供一个请求来消费该请求
@GetMapping("consumer/getObject/withParamAfter")
@ApiOperation("get请求-url后面带请求参数-getForObject")
public User queryList4(){
String url = urlConfig.getAddress()+ UrlString.GET_METHOD_WITH_REQUEST_PARAM_URL_AFTER;
// 注意这里需要拼接参数
url = url + "?id={id}&username={username}";
Logging.logurl(log,url);
Map<String, Object> requestMap = Maps.newHashMap();
requestMap.put("id",1L);
requestMap.put("username","wyy");
User response = restTemplate.getForObject(url, User.class, requestMap);
Logging.logResponse(log,response);
return response;
}
1.5 POST请求
完整的代码在
study.wyy.httpclient.customer.web.PostController
和study.wyy.httpclient.provider.web.PostController
再接下来是POST请求。POST请求的9个方法,3个一组,postForLocation、postForObject、postForEntity
1.5.1 postForObject
主要接受四个参数,和get请求就是多了一个Object request参数
- 请求路径:可以是字符串类型,也可以是
java.net.URI
- Object request: 可以用此来传递请求头中设置的参数
- 请求返回结果的泛型
- 请求参数:可变参数或者Map类型
对于post方法来说,请求参数可以放到请求url里面(uriVariables),也可以放到http的body里面,当然一般来说post的数据放到body里面比较正规,
也比较好,因为这样数据相对不会暴露。但是有些比较简单的无关紧要的数据放到url里面传也不是不可以。所以下面重点说一下postForObject的传参方式:
url路径带参数
- provider中提供一个请求
@PostMapping(UrlString.POST_METHOD_WITH_REQUEST_PARAM_URL_AFTER)
public User queryListWithParam(User user){
log.info("请求参数-- {}", user);
return user;
}
- consumer中提供一个请求来消费该请求
@GetMapping("consumer/postObject/withParamAfter")
@ApiOperation("post请求-url后面带请求参数-postForObject")
public User queryList4(){
String url = urlConfig.getAddress()+ UrlString.POST_METHOD_WITH_REQUEST_PARAM_URL_AFTER;
// 注意这里需要拼接参数
url = url + "?id={id}&username={username}";
// http://127.0.0.1:9999/provider/post/user/after/?id={id}&username={username}
Logging.logurl(log,url);
Map<String, Object> requestMap = Maps.newHashMap();
requestMap.put("id",1L);
requestMap.put("username","wyy");
/**
* 参数一:请求路径
* 参数二:这里是路径传参数,设置为null即可
* 参数三:请求返回值的泛性
* 参数四:请参数
*/
User response = restTemplate.postForObject(url,null, User.class, requestMap);
Logging.logResponse(log,response);
return response;
}
参数只放在body里面
这种情况请求的参数一般为json格式,所以需要在请求头里进行设置,第二个参数就用到了
- Provider
@PostMapping(UrlString.POST_METHOD_WITH_REQUEST_PARAM_IN_BODY)
public User queryUserWithParamInBody(User user){
log.info("请求参数-- {}", user);
user.setDepartment(new Department(1L,"财务部"));
return user;
}
- Consumer
@GetMapping("consumer/postObject/withParamINBody")
@ApiOperation("post请求-请求体携带参数-postForObject")
public User queryList5() throws JsonProcessingException {
String url = urlConfig.getAddress()+ UrlString.POST_METHOD_WITH_REQUEST_PARAM_IN_BODY;
// 设置请求体
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
// 构建请求参数
HashMap<String, Object> requestParam = Maps.newHashMap();
requestParam.put("id",1L);
requestParam.put("username","wyy");
// HttpEntity的泛型就是请求体的类型,所以这里直接构建了Map,应该也可以直接构建一个User对象或者user对象
// json串也是可以的,无论何种形式最终都是以json格式的字符串传递过去,一会可以实验一下
HttpEntity<Map<String,Object>> requestEntity = new HttpEntity<Map<String, Object>>(requestParam,httpHeaders);
User response = restTemplate.postForObject(url, requestEntity, User.class);
Logging.logResponse(log,response);
return response;
}
注释提及的测试,在代码中也有,具体这里不展示了
study.wyy.httpclient.customer.web.PostController
与上面的postForObject的传参方法是一模一样的,唯一不同的就是postForObject返回的是自己指定的数据,相当于只返回body,
而postForEntity返回的是一个ResponseEntity对象,对象包括了http响应的headers、body等信息。