文章目录

  • 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.GetControllerstudy.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.PostControllerstudy.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等信息。