Java】JSON踩坑02 -- RestTemplate使用总结

日常工作中肯定会遇到服务之间的调用,尤其是现在都是微服务的架构,所以总结一下restTemplate的最常用的用法以及自己踩过的坑。

restTemplate的使用

restTemplate底层调用的是Execute方法,而Execute底层调用的是doExecute,它是基于http协议的,底层还是httpClient 的使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
     * Execute the given method on the provided URI.
     * <p>The {@link ClientHttpRequest} is processed using the {@link RequestCallback};
     * the response with the {@link ResponseExtractor}.
     * @param url the fully-expanded URL to connect to
     * @param method the HTTP method to execute (GET, POST, etc.)
     * @param requestCallback object that prepares the request (can be {@code null})
     * @param responseExtractor object that extracts the return value from the response (can be {@code null})
     * @return an arbitrary object, as returned by the {@link ResponseExtractor}
     */
    @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;
        try {
            ClientHttpRequest request = createRequest(url, method);
            if (requestCallback != null) {
                requestCallback.doWithRequest(request);
            }
            response = request.execute();
            handleResponse(url, method, response);
            return (responseExtractor != null ? responseExtractor.extractData(response) : null);
        }
        catch (IOException ex) {
            String resource = url.toString();
            String query = url.getRawQuery();
            resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
            throw new ResourceAccessException("I/O error on " + method.name() +
                    " request for \"" + resource + "\": " + ex.getMessage(), ex);
        }
        finally {
            if (response != null) {
                response.close();
            }
        }
    }

 

我们一般都是用的restTepmlate的exchange方法,这个方法比较灵活,可以接受可变参数,重载方法也有很多。 当然 restTemplate还有其他很多方法,而且遵循restFul风格,像PUT POST GET PATCH DELETE 等都有对应的方法,按需使用。这里就不贴源码了。

然后就贴一个使用案例代码上来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public YourResponse sampleRestTepmlate (YourRequest request) throws Exception {
    UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(this.serviceUrl);
    builder.path("urlpath");
    log.info("url : {}, request : {}", builder.toUriString(), JsonUtils.toJson(request));
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    headers.set("headername","headervalue");
    headers.add("anotherway", "value");
    HttpEntity<YourRequest> requestEntity = new HttpEntity<>(request, headers);
    ResponseEntity<YourResponse> responseEntity = null;
    try {
        responseEntity = restTemplate.exchange(builder.toUriString(), HttpMethod.POST, requestEntity,
                    YourResponse.class);
        return responseEntity.getBody();
    } catch (Exception e) {
        log.error("exception:{}",e.getMessage());
    }
}

 

踩坑大道

这里就要说一下我遇到的坑了。 在使用restTemplate的时候,当你的call没有成功返回200的时候,比如返回400 500之类的,restTemplate里面有一个DefaultResponseErrorHandler,他会自动拦截住这些httpstatus 为400 500的response然后给你抛出一个异常。这就意味着,当你也想拿到带有错误信息的response的时候,他不会给你!它会给你抛出exception并且只是给你返回一个简单的类似500 Internal error! WTF!

贴上这段坑爹的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
     * Handle the error in the given response with the given resolved status code.
     * <p>This default implementation throws a {@link HttpClientErrorException} if the response status code
     * is {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR}, a {@link HttpServerErrorException}
     * if it is {@link org.springframework.http.HttpStatus.Series#SERVER_ERROR},
     * and a {@link RestClientException} in other cases.
     * @since 5.0
     */
    protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
        switch (statusCode.series()) {
            case CLIENT_ERROR:
                throw new HttpClientErrorException(statusCode, response.getStatusText(),
                        response.getHeaders(), getResponseBody(response), getCharset(response));
            case SERVER_ERROR:
                throw new HttpServerErrorException(statusCode, response.getStatusText(),
                        response.getHeaders(), getResponseBody(response), getCharset(response));
            default:
                throw new UnknownHttpStatusCodeException(statusCode.value(), response.getStatusText(),
                        response.getHeaders(), getResponseBody(response), getCharset(response));
        }
    }

脱坑之计

遇到了坑就不要害怕,这个问题可以这么解决:

1.不用restTemplate去请求,可以采用httpClient底层去实现

2.重写handleError方法,自定义ErrorHandle继承DefaultResponseErrorHandler

在已经写完实现之后,我选择方式2 : )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Builder
@Slf4j
public class MyErrorHandle extends DefaultResponseErrorHandler {
 
    @Override
    public void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
        int status = statusCode.value();
        if (status == 200 || status == 400 || status == 500) {
            //do what u want to do
        } else {
            super.handleError(response,statusCode);
        }
    }
}

然后在初始化restTemplate的时候调用setErrorHandle方法就可以了。

1
restTemplate.setErrorHandler(YourErrorHandle).

 

至于方式一这里不提了。

导入证书

有的时候当我们调用对方的server时,基于https 的协议是需要导入证书的,那我们该怎么把证书融入到restTemplate中呢?(又一个坑)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
    public RestTemplate buildRestTemplateWithinSSl(@Value("${service.connectTimeout}") int connectTimeout,
      @Value("${service.readTimeout}") int readTimeout,
      @Value("${service.sslFilePath}") String filePath,
      @Value("${service.sslpassword}") String sslPassword) throws Exception{
       
    RestTemplate template = restTemplateBuilder.setConnectTimeout(connectTimeout).setReadTimeout(readTimeout).build();
    String workingDirectory = BeanUtility.getWorkingDirectory();
    SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(new File(workingDirectory + "/" + filePath), sslPassword.toCharArray()).build();
    SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
    CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
    template.setRequestFactory(factory);
    return template;
    }

 

相当于重新给RequestFactory值,构造一个已经带有ssl证书的factory给他。

这里注意两个地方:

1
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);

 

这里有个参数是NoopHostnameVerifier.INSTANCE, 这里是可以无视ip的,也就是ip或者域名形式都可以。 (适用于对方给我提供证书和 ip地址,试了半天死活不通的情况。。)

第二个就是一个工具类的使用,我相信很多时候new file的时候很容易被路径绕晕。

1
String workingDirectory = BeanUtility.getWorkingDirectory();

这个工具类获得的路径不用你去担心,只要你的jks文件和你的jar包同级就行。管他什么环境什么路径,很方便。

贴上地址: github.com/AnsonCong/A…

本地调试证书导入jdk就行。

记录下导入证书的方法:

1
keytool -import -alias {别名} -file {路径\证书名}.cer -keystore "{jdk路径\jre\lib\security\cacerts}" -storepass {password} -trustcacerts

 删除证书:

1
keytool -delete -alias {别名}  -keystore "C:\Program Files\Java\jdk1.7.0_25\jre\lib\security\cacerts" -storepass {password}

 查看所有安装证书列表

1
keytool -list -v  -keystore "C:\Users\1580977\Downloads\jdk1.8.0_101\jre\lib\security\cacerts" -storepass {password} >> C:\Desktop\abcd.txt

 

生成jks文件 (没有默认生存,有就导入)

1
keytool -import -alias {别名} -file {证书名}.cer -keystore {命名}.jks

 

最后

RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。

更多restTemplate详细资料,可以参考: juejin.cn/post/684490… www.zifangsky.cn/1221.html

 

posted @   IT6889  阅读(610)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
点击右上角即可分享
微信分享提示