1.1 简述

最近项目中需要使用http的形式进行调用第三方的服务,我们项目中使用的是RestTemplate进行交互调用。不像我们以前写的HttpClient,需要写很多的工具类,RestTemplate基本上是开箱即用,本文主要是介绍其在spring boot项目中的基本使用,对于日常的开发等,基本上是没有问题。

1.1.1 Rest

当谈论REST时,有一种常见的错误就是将其视为“基于URLWeb服务”——将REST作为另一种类型的远程过程调用(remote procedure callRPC)机制,就像SOAP一样,只不过是通过简单的HTTP URL来触发,而不是使用SOAP大量的XML命名空间

恰好相反,RESTRPC几乎没有任何关系。RPC是面向服务的,并关注于行为和动作;而REST是面向资源的,强调描述应用程序的事物和名词。

更简洁地讲,REST就是将资源的状态以最适合客户端或服务端的形式从服务器端转移到客户端(或者反过来)。

REST中,资源通过URL进行识别和定位。至于RESTful URL的结构并没有严格的规则,但是URL应该能够识别资源,而不是简单的发一条命令到服务器上。再次强调,关注的核心是事物,而不是行为。

1.1.2 Spring 中如何使用Rest资源

借助 RestTemplateSpring应用能够方便地使用REST资源
SpringRestTemplate访问使用了模版方法的设计模式

模版方法将过程中与特定实现相关的部分委托给接口,而这个接口的不同实现定义了接口的不同行为

RestTemplate定义了36个与REST资源交互的方法,其中的大多数都对应于HTTP的方法。
其实,这里面只有11个独立的方法,其中有十个有三种重载形式,而第十一个则重载了六次,这样一共形成了36个方法。

  • delete() 在特定的URL上对资源执行HTTP DELETE操作
  • exchange()URL上执行特定的HTTP方法,返回包含对象的ResponseEntity,这个对象是从响应体中映射得到的
  • execute()URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象
  • getForEntity() 发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象
  • getForObject() 发送一个HTTP GET请求,返回的请求体将映射为一个对象
  • postForEntity()POST 数据到一个URL,返回包含一个对象的ResponseEntity,这个对象是从响应体中映射得到的
  • postForObject() POST 数据到一个URL,返回根据响应体匹配形成的对象
  • headForHeaders() 发送HTTP HEAD请求,返回包含特定资源URLHTTP
  • optionsForAllow() 发送HTTP OPTIONS请求,返回对特定URLAllow头信息
  • postForLocation()POST 数据到一个URL,返回新创建资源的URL
  • put()PUT资源到特定的URL

实际上,由于Post 操作的非幂等性,它几乎可以代替其他的CRUD操作

1.2 Get请求

RestTemplateget方法有以上几个,可以分为两类: getForEntity() getForObject()

首先看 getForEntity() 的返回值类型 ResponseEntity

<T> ResponseEntity<T> getForEntity()

看一下 ResponseEntity 的文档描述:

可以看到 它继承了HttpEntity,封装了返回的响应信息,包括 响应状态,响应头 和 响应体

在测试之前我们首先 创建一个Rest服务,模拟提供Rest数据,这里给出Controller层代码:

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value = "getAll")
    public List<UserEntity> getUser() {
        List<UserEntity> list = userService.getAll();
        return list;
    }

    @RequestMapping("get/{id}")
    public UserEntity getById(@PathVariable(name = "id") String id) {
        return userService.getById(id);
    }
    
    @RequestMapping(value = "save")
    public String save(UserEntity userEntity) {
        return "保存成功";
    }

    @RequestMapping(value = "saveByType/{type}")
    public String saveByType(UserEntity userEntity,@PathVariable("type")String type) {
        return "保存成功,type="+type;
    }
}

1.2.1 测试: getForEntity

无参数的 getForEntity 方法

@RequestMapping("getForEntity")
    public List<UserEntity> getAll2() {
        ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://localhost/getAll", List.class);
        HttpHeaders headers = responseEntity.getHeaders();
        HttpStatus statusCode = responseEntity.getStatusCode();
        int code = statusCode.value();

        List<UserEntity> list = responseEntity.getBody();

        System.out.println(list.toString());
        return list;

    }

有参数的 getForEntity 请求,参数列表,可以使用{}进行url路径占位符

//有参数的 getForEntity 请求,参数列表
    @RequestMapping("getForEntity/{id}")
    public UserEntity getById2(@PathVariable(name = "id") String id) {

        ResponseEntity<UserEntity> responseEntity = restTemplate.getForEntity("http://localhost/get/{id}", UserEntity.class, id);
        UserEntity userEntity = responseEntity.getBody();
        return userEntity;
    }

有参数的 get 请求,使用map封装参数

//有参数的 get 请求,使用map封装参数
    @RequestMapping("getForEntity/{id}")
    public UserEntity getById4(@PathVariable(name = "id") String id) {
        HashMap<String, String> map = new HashMap<>();
        map.put("id",id);

        ResponseEntity<UserEntity> responseEntity = restTemplate.getForEntity("http://localhost/get/{id}", UserEntity.class, map);
        UserEntity userEntity = responseEntity.getBody();

        return userEntity;
    }

因此我们可以获取Http请求的全部信息.

但是,通常情况下我们并不想要Http请求的全部信息,只需要相应体即可,对于这种情况,RestTemplate提供了 getForObject() 方法用来只获取 响应体信息
getForObjectgetForEntity 用法几乎相同,指示返回值返回的是 响应体,省去了我们 再去 getBody()

1.2.2 测试: getForObject

无参数的 getForObject 请求

//无参数的 getForObject 请求
    @RequestMapping("getAll2")
    public List<UserEntity> getAll() {
        List<UserEntity> list = restTemplate.getForObject("http://localhost/getAll", List.class);
        System.out.println(list.toString());
        return list;
    }

有参数的 getForObject 请求,使用参数列表

//有参数的 getForObject 请求
    @RequestMapping("get2/{id}")
    public UserEntity getById(@PathVariable(name = "id") String id) {
        UserEntity userEntity = restTemplate.getForObject("http://localhost/get/{id}", UserEntity.class, id);
        return userEntity;
    }

有参数的 get 请求,使用map封装请求参数

//有参数的 get 请求,使用map封装请求参数
    @RequestMapping("get3/{id}")
    public UserEntity getById3(@PathVariable(name = "id") String id) {
        HashMap<String, String> map = new HashMap<>();
        map.put("id",id);
        UserEntity userEntity = restTemplate.getForObject("http://localhost/get/{id}", UserEntity.class, map);
        return userEntity;
    }

1.3 Post请求

了解了get请求后,Post请求就变得很简单了,我们可以看到post有如下方法:

1.3.1 测试:postForEntity

post 请求,保存UserEntity对像

//post 请求,提交 UserEntity 对像

    @RequestMapping("saveUser")
    public String save(UserEntity userEntity) {
        ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://localhost/save", userEntity, String.class);
        String body = responseEntity.getBody();
        return body;
    }

浏览器访问: http://localhost/saveUser?username=itguang&password=123456&age=20&email=123@123.com 我们再次断点调试,查看 responseEntity 中的信息

有参数的 postForEntity 请求

// 有参数的 postForEntity 请求
    @RequestMapping("saveUserByType/{type}")
    public String save2(UserEntity userEntity,@PathVariable("type")String type) {
        ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://localhost/saveByType/{type}", userEntity, String.class, type);
        String body = responseEntity.getBody();
        return body;
    }

    // 有参数的 postForEntity 请求,使用map封装
    @RequestMapping("saveUserByType2/{type}")
    public String save3(UserEntity userEntity,@PathVariable("type")String type) {
        HashMap<String, String> map = new HashMap<>();
         map.put("type", type);
        ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://localhost/saveByType/{type}", userEntity, String.class,map);
        String body = responseEntity.getBody();
        return body;
    }

我们浏览器访问: localhost/saveUserByType/120?username=itguang&password=123456&age=20&email=123@123.com

就会返回:保存成功,type=120

对与其它请求方式,由于不常使用,所以这里就不再讲述

1.4 踩坑记录:restTemplateexchange方法get请求报400 Bad request【restTemplate Bug】的解决办法

1.4.1 填坑记录

如下代码,url=http://www.baidu.com时请求报400

HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.add("Authorization", authorization);
        requestHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
        MultiValueMap<String, Object> requestBody = new LinkedMultiValueMap<>();
        if (map != null) {
            map.forEach((k, v) -> {
                if (StringUtils.isNotBlank(v)) {
                    requestBody.add(k,v);
                }
            });
        }
        HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(requestBody, requestHeaders);
        try {
            ResponseEntity<String> res = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
            return new HttpResDTO(res.getStatusCodeValue(), res.getBody());
        } catch (HttpClientErrorException ex) {
            log.error("rest template for face exception, error code: {}, error message: {}", ex.getRawStatusCode(), ex.getMessage());
            return new HttpResDTO(ex.getRawStatusCode(), ex.getResponseBodyAsString());
        } catch (UnknownHttpStatusCodeException e) {
            String responseBodyAsString = e.getResponseBodyAsString();
            log.warn("UnknownHttpStatusCodeException:{}",responseBodyAsString);
            JSONObject parse = JSONObject.parseObject(responseBodyAsString);
            throw new RuntimeException(parse.get("message").toString());
        }

1.4.2 解决方案

get请求直接在url上拼参数,如?type=1&name=xxString uri =tempUrl + “?keys={keys}”;通过map传递keys

Map<String, String> map = new HashMap<>(1);
map.put(“keys”, “xdfdf”);
ResponseEntity res = restTemplate.getForEntity(url, String.class, map);