RestTemplate.exchange用法

前言

在我们日常开发中,无论是内部服务之间的调用,还是调用第三方服务,都免不了发起Http请求,在Java中发起Http请求常见的方式大致有原生HttpURLConnection、Apache的HttpClient、Spring的RestTemplate等,如果您基于Spring框架,那么强烈推荐使用RestTemplate,理由很简单:非常符合我们发起http请求的习惯,就像使用postman,只需要关心具体的url、header、body等即可,对于繁琐的细节RestTemplate都帮我们安排(封装)的明明白白,无关的细节我们统统不用操心! 尤其是RestTemplate.exchange方法,可以称的上是单靠一招就可以吊打其它方式。。。 所以本文就来详细介绍一下RestTemplate.exchange各种用法,力求覆盖日常开发中的各种场景,Let’s start~~

1. Get请求

这里准备了5个常见场景: 1.1 返回基本类型 1.2 返回自定义对象类型 1.3 返回List类型 1.4 返回Map类型 1.5 返回自定义泛型类型

1.1 返回基本类型

我们先模拟一个最基础的API:根据用户id获取name 调用代码

    // 1.1 get请求返回基本类型
    @GetMapping("/name")
    public String getName(@RequestParam("id") Integer id) {
    
    
        String url = "http://localhost:8080/demo/name/mock?id=" + id;
        return restTemplate.exchange(url, HttpMethod.GET, null, String.class).getBody();
    }

被调用的mock代码

    @GetMapping("/name/mock")
    public String mockName(@RequestParam("id") Integer id) {
    
    
        return "天罡" + id;
    }

验证一下: 如期望一样,不得不说就应该这么简洁,good~~

请求 http://localhost:8080/demo/name?id=123 返回 天罡123

关于exchange的方法参数说明: 直接看源码的注释吧,非常清晰,再说就属于画蛇添足了哈

在这里插入图片描述

1.2 返回自定义对象类型

其实自定义的对象和String调用是一样的,只需要将返回类型String.class改成DTO.class即可,比如:根据用户id获取用户信息

新建一个UserDto对象:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserDto implements Serializable {
    
    
    private Integer id;
    private String name;
    private Integer age;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date birthday;
}

调用代码

    // 1.2 get请求返回对象类型
    @GetMapping("/user")
    public UserDto getUser(@RequestParam("id") Integer id) {
    
    
        String url = "http://localhost:8080/demo/user/mock?id=" + id;
        return restTemplate.exchange(url, HttpMethod.GET, null, UserDto.class).getBody();
    }

被调用的mock代码

    @GetMapping("/user/mock")
    public UserDto mockUser(@RequestParam("id") Integer id) {
    
    
        return UserDto.builder().id(id)
                .name("天罡" + id)
                .age(id + 18)
                .birthday(new Date()).build();
    }

验证一下: ok~~

请求 http://localhost:8080/demo/user?id=1 返回 { “id”: 1, “name”: “天罡”, “age”: 19, “birthday”: “2022-11-06 05:35:43” }

1.3 返回List 类型

对于泛型类型,我们需要使用exchange的另一个针对泛型的重载方法,即将responseType换成ParameterizedTypeReference,还是建议看源码注释:

在这里插入图片描述

接下来,我们继续模拟一个常见场景:根据用户name模糊检索所有匹配的用户,返回结果是多条,我们使用List类型。 调用代码通过ParameterizedTypeReference指定返回的List

    // 1.3 get请求返回List<T>类型
    @GetMapping("/user/list")
    public List<UserDto> getUserList(@RequestParam("name") String name) {
    
    
        String url = "http://localhost:8080/demo/user/list/mock?name=" + name;
        ParameterizedTypeReference<List<UserDto>> responseBodyType = new ParameterizedTypeReference<List<UserDto>>() {
    
    };
        return restTemplate.exchange(url, HttpMethod.GET, null, responseBodyType).getBody();
    }

被调用的mock代码

    @GetMapping("/user/list/mock")
    public List<UserDto> mockUserList(@RequestParam("name") String name) {
    
    
        List<UserDto> list = new ArrayList<>();
        for (int i = 1; i < 3; i++) {
    
    
            list.add(UserDto.builder().id(i)
                    .name(name + i)
                    .age(i + 10)
                    .birthday(new Date()).build());
        }
        return list;
    }

验证一下: ok~~

请求 http://localhost:8080/demo/user/list?name=天罡 返回 [ { “id”: 1, “name”: “天罡1”, “age”: 11, “birthday”: “2022-11-06 21:44:24” }, { “id”: 2, “name”: “天罡2”, “age”: 12, “birthday”: “2022-11-06 21:44:24” } ]

1.4 返回Map<K,V> 类型

Map也属于泛型类型,并且是有K,V两个类型,我们继续模拟一个常见场景:根据关键字查找,不同类型返回不同字段,因为返回结果字段不固定,所以我们返回Map类型。 调用代码依然通过ParameterizedTypeReference指定返回的Map

    // 1.4 get请求返回Map类型
    @GetMapping("/user/map")
    public Map<String, Object> getUserMap(@RequestParam(value = "type", required = true) Integer type, @RequestParam("key") String key) {
    
    
        String url = "http://localhost:8080/demo/user/map/mock?type=" + type + "&key=" + key;
        ParameterizedTypeReference<Map<String, Object>> responseBodyType = new ParameterizedTypeReference<Map<String, Object>>() {
    
    };
        return restTemplate.exchange(url, HttpMethod.GET, null, responseBodyType).getBody();
    }

被调用的mock代码

    @GetMapping("/user/map/mock")
    public Map<String, Object> mockUserMap(@RequestParam(value = "type", required = true) Integer type, @RequestParam("key") String key) {
    
    
        Map<String, Object> map = new HashMap<>();
        if (type.equals(1)) {
    
    
            map.put("id", 1);
            map.put("name" + type, "hello" + key);
        } else {
    
    
            map.put("id", 2);
            map.put("name" + type, "hello" + key);
        }
        return map;
    }

验证一下: 根据不同类型返回不同字段了,漂亮~~

请求 http://localhost:8080/demo/user/map?type=1&key=123 返回 { “id”: 1, “name1”: “hello123” }

请求 http://localhost:8080/demo/user/map?type=2&key=456 返回 { “id”: 2, “name2”: “hello456” }

1.5 返回自定义泛型类型

我们在1.2 返回自定义对象类型模拟过根据用户id获取用户信息的场景,但未处理非法请求、异常等情况,所以接下来我们自定一个通用的自定义Code的泛型返回结果,对1.2做一下增强:根据用户id获取用户信息,根据不同情况返回不同编码

我们新建一个Result类:

@Data
public class Result<T extends Serializable> implements Serializable {
    
    
    private boolean success;
    private String code;
    private String message;
    private T data;

    public static <T extends Serializable> Result<T> success(String code, String message, T data) {
    
    
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMessage(message);
        result.setData(data);
        result.setSuccess(true);
        return result;
    }

    public static <T extends Serializable> Result<T> success(T data) {
    
    
        return success("200", "成功", data);
    }

    public static <T extends Serializable> Result<T> fail(String code, String message) {
    
    
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMessage(message);
        result.setSuccess(false);
        return result;
    }
}

调用代码依然通过ParameterizedTypeReference指定返回的Result<T>

    // 1.5 get请求返回自定义泛型类型
    @GetMapping("/user/result")
    public Result<UserDto> getUserResult(@RequestParam("id") Integer id) {
    
    
        String url = "http://localhost:8080/demo/user/result/mock?id=" + id;
        ParameterizedTypeReference<Result<UserDto>> responseBodyType = new ParameterizedTypeReference<Result<UserDto>>() {
    
    };
        return restTemplate.exchange(url, HttpMethod.GET, null, responseBodyType).getBody();
    }

被调用的mock代码

	@GetMapping("/user/result/mock")
    public Result<UserDto> mockUserResult(@RequestParam("id") Integer id) {
    
    
        if (id == null || id <= 0) {
    
    
            return Result.fail("400", "id不合法!");
        }
        if (id % 2 == 0) {
    
    
            // 这里只是模拟异常情况
            return Result.fail("500", "操作失败,访问量太大了!");
        }
        UserDto userDto = UserDto.builder().id(id)
                .name("天罡" + id)
                .age(id + 18)
                .birthday(new Date()).build();
        return Result.success("200", "成功", userDto);
    }

验证一下: 正是我们想要的,完全符合预期!

请求 http://localhost:8080/demo/user/result?id=0 返回 { “success”: false, “code”: “400”, “message”: “id不合法!”, “data”: null }

请求 http://localhost:8080/demo/user/result?id=1 返回 { “success”: true, “code”: “200”, “message”: “成功”, “data”: { “id”: 1, “name”: “天罡1”, “age”: 19, “birthday”: “2022-11-07 04:03:09” } }

请求 http://localhost:8080/demo/user/result?id=2 返回 { “success”: false, “code”: “500”, “message”: “操作失败,访问量太大了!”, “data”: null }


2.Post请求

实际上对于exchange来说,POST与GET的使用方式非常类似,所以这里只准备2个demo主要演示如何传header和body2.1 传header+body返回对象类型 2.2 传header+body返回自定义泛型类型

2.1 传header+body返回对象类型

调用代码

	@GetMapping("/user/body")
    public UserDto postUser(@RequestParam("id") Integer id) {
    
    
        String url = "http://localhost:8080/demo/user/body/mock";
        UserDto body = UserDto.builder().id(id)
                .name("body" + id)
                .age(id + 18)
                .birthday(new Date()).build();
        // header根据实际情况设置,没有就空着
        HttpHeaders headers = new HttpHeaders();
        headers.add("AccessKey", "自定义的API访问key");
        headers.add("Content-Type", "application/json");
        HttpEntity<?> requestEntity = new HttpEntity<>(body, headers);
        return restTemplate.exchange(url, HttpMethod.POST, requestEntity, UserDto.class).getBody();
    }

被调用的mock代码

    @PostMapping("/user/body/mock")
    public UserDto mockPostUser(@RequestBody UserDto userParam) {
    
    
        return userParam;
    }

验证一下: ok~~

请求 http://localhost:8080/demo/user/body?id=1 返回 { “id”: 1, “name”: “body1”, “age”: 19, “birthday”: “2022-11-06 21:20:41” }

2.2 传header+body返回自定义泛型类型

和返回普通类型的区别还是将responseType换成ParameterizedTypeReference 调用代码

@GetMapping("/user/result/body")
public Result<UserDto> postUserResult(@RequestParam("id") Integer id) {
    
    
    String url = "http://localhost:8080/demo/user/result/body/mock";
    UserDto body = UserDto.builder().id(id)
            .name("body" + id)
            .age(id + 10)
            .birthday(new Date()).build();
    // header根据实际情况设置,没有就空着
    HttpHeaders headers = new HttpHeaders();
    headers.add("AccessKey", "自定义的API访问key");
    headers.add("Content-Type", "application/json");
    HttpEntity<?> requestEntity = new HttpEntity<>(body, headers);
    ParameterizedTypeReference<Result<UserDto>> responseBodyType = new ParameterizedTypeReference<Result<UserDto>>(){
    
    };
    return restTemplate.exchange(url, HttpMethod.POST, requestEntity, responseBodyType).getBody();
}

被调用的mock代码

    @PostMapping("/user/result/body/mock")
    public Result<UserDto> mockPostUserResult(@RequestBody UserDto userParam) {
    
    
        return Result.success("200", "成功", userParam);
    }

验证一下: ok~~

请求 http://localhost:8080/demo/user/body?id=1 返回 { “success”: true, “code”: “200”, “message”: “成功”, “data”: { “id”: 1, “name”: “body1”, “age”: 11, “birthday”: “2022-11-06 21:25:25” } }


3. 异常情况处理

上面写的均未处理异常情况,我们通常会处理两种异常情况:

  1. 本身抛出的 throws RestClientException
  2. 返回的ResponseEntity的Code不等于200

普通类型:

public <T> T restForEntity(HttpMethod httpMethod, String url, HttpHeaders headers, Object body
			, Class<T> responseType) {
    
    
    HttpEntity<?> requestEntity = null;
    if (headers != null || body != null) {
    
    
        requestEntity = new HttpEntity<>(body, headers);
    }
    try {
    
    
        ResponseEntity<T> responseEntity = restTemplate.exchange(url, httpMethod, requestEntity, responseType);
        if (responseEntity.getStatusCode().equals(HttpStatus.OK)) {
    
    
            return responseEntity.getBody();
        } else {
    
    
            // 处理Code不等于200的情况
            System.out.println("返回结果不等于200:code=" + responseEntity.getStatusCode().value()
                    + " reason=" + responseEntity.getStatusCode().getReasonPhrase());
        }
    } catch (RestClientException e) {
    
    
        // 处理RestClientException
        e.printStackTrace();
    }
    return null;
}

泛型类型: 只需要将普通类型的入参Class<T>改成 ParameterizedTypeReference<T>

public <T> T restForWarpEntity(HttpMethod httpMethod, String url, HttpHeaders headers, Object body
				, ParameterizedTypeReference<T> responseBodyType) {
    
    
    HttpEntity<?> requestEntity = null;
    if (headers != null || body != null) {
    
    
        requestEntity = new HttpEntity<>(body, headers);
    }
    try {
    
    
        ResponseEntity<T> responseEntity = restTemplate.exchange(url, httpMethod, requestEntity, responseBodyType);
        if (responseEntity.getStatusCode().equals(HttpStatus.OK)) {
    
    
            return responseEntity.getBody();
        } else {
    
    
            // 处理Code不等于200的情况, 这里只简单打印
            System.out.println("返回结果不等于200:code=" + responseEntity.getStatusCode().value()
                    + " reason=" + responseEntity.getStatusCode().getReasonPhrase());
        }
    } catch (RestClientException e) {
    
    
        // 处理RestClientException, 这里只简单打印
        e.printStackTrace();
    }
    return null;
}

4. RestTemplate配置@Bean

@Configuration
public class RestTemplateConfig {
    
    
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
    
    
        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
        restTemplate.getMessageConverters()
                .stream()
                .filter(MappingJackson2HttpMessageConverter.class::isInstance)
                .map(MappingJackson2HttpMessageConverter.class::cast)
                .findFirst()
                .map(MappingJackson2HttpMessageConverter::getObjectMapper)
                .ifPresent(objectMapper -> {
    
    
                    // 去掉默认的时间戳格式
                    objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
                    // 设置为东八区
                    objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
                    // 序列化时,日期的统一格式
                    objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
                    // 忽略大小写
                    objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
                });

        return restTemplate;
    }

    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory(){
    
    
        // 如果使用okHttpClient需要引入jar包:okhttp
        // OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory();
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setConnectTimeout(15000);
        factory.setReadTimeout(30000);
        return factory;
    }
}

猜你喜欢

转载自blog.csdn.net/ximaiyao1984/article/details/132257881