关注wx:CodingTechWork
引言
在代码开发过程中,发现很多地方通过RestTemplate
调用了第三方接口,而第三方接口需要根据某些状态码或者异常进行重试调用,此时,要么在每个调用的地方进行异常捕获,然后重试;要么在封装的RestTemplate
工具类中进行统一异常捕获和封装。当然,本文不走寻常路,将会通过RestTemplate
的异常处理器进行操作。
RestTemplate异常处理器介绍
分类
异常处理器 | 功能描述 |
---|---|
ResponseErrorHandler | 异常处理器接口,是restTemplate所有异常处理器的实现接口 |
DefaultResponseErrorHandler | 默认的异常处理器,处理客户端和服务端异常 |
ExtractingResponseErrorHandler | 将HTTP错误响应转换RestClientException |
NoOpResponseErrorHandler | 不处理异常 |
RestTemplate异常处理器源码
ResponseErrorHandler
public interface ResponseErrorHandler {/*** 判断请求是否异常* false: 请求返回无错误* true: 请求返回有错误* 可定制化根据某一些status的值进行返回,如根据2xx返回false,非2xx返回true* 同时,可根据ClientHttpResponse的返回结果来定制化判断*/boolean hasError(ClientHttpResponse var1) throws IOException;/*** 处理错误*/void handleError(ClientHttpResponse var1) throws IOException;/*** 默认的异常处理方法*/default void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {//默认调用handleError(response)方法,也可以重写该方法this.handleError(response);}
}
DefaultResponseErrorHandler
package org.springframework.web.client;import java.io.IOException;
import java.nio.charset.Charset;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.HttpStatus.Series;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.FileCopyUtils;public class DefaultResponseErrorHandler implements ResponseErrorHandler {public DefaultResponseErrorHandler() {}/*** 判断请求是否异常*/public boolean hasError(ClientHttpResponse response) throws IOException {HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode());//statusCode不为空并调用受保护的方法hasError()方法return statusCode != null && this.hasError(statusCode);}protected boolean hasError(HttpStatus statusCode) {//遇到客户端错误4xx或服务端错误5xx,就返回true表示有错误return statusCode.series() == Series.CLIENT_ERROR || statusCode.series() == Series.SERVER_ERROR;}/*** 处理错误*/public void handleError(ClientHttpResponse response) throws IOException {//获取状态码HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode());if (statusCode == null) {throw new UnknownHttpStatusCodeException(response.getRawStatusCode(), response.getStatusText(), response.getHeaders(), this.getResponseBody(response), this.getCharset(response));} else {//状态码不为空,则处理错误(主要是4xx和5xx错误)this.handleError(response, statusCode);}}/*** 处理错误*/protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {String statusText = response.getStatusText();HttpHeaders headers = response.getHeaders();byte[] body = this.getResponseBody(response);Charset charset = this.getCharset(response);switch(statusCode.series()) {case CLIENT_ERROR://http客户端错误throw HttpClientErrorException.create(statusCode, statusText, headers, body, charset);case SERVER_ERROR://http服务端错误throw HttpServerErrorException.create(statusCode, statusText, headers, body, charset);default:throw new UnknownHttpStatusCodeException(statusCode.value(), statusText, headers, body, charset);}}/** @deprecated */@Deprecatedprotected HttpStatus getHttpStatusCode(ClientHttpResponse response) throws IOException {HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode());if (statusCode == null) {throw new UnknownHttpStatusCodeException(response.getRawStatusCode(), response.getStatusText(), response.getHeaders(), this.getResponseBody(response), this.getCharset(response));} else {return statusCode;}}protected byte[] getResponseBody(ClientHttpResponse response) {try {return FileCopyUtils.copyToByteArray(response.getBody());} catch (IOException var3) {return new byte[0];}}@Nullableprotected Charset getCharset(ClientHttpResponse response) {HttpHeaders headers = response.getHeaders();MediaType contentType = headers.getContentType();return contentType != null ? contentType.getCharset() : null;}
}
ExtractingResponseErrorHandler
package org.springframework.web.client;import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatus.Series;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;public class ExtractingResponseErrorHandler extends DefaultResponseErrorHandler {//定义HttpMessageConverter对象列表private List<HttpMessageConverter<?>> messageConverters = Collections.emptyList();private final Map<HttpStatus, Class<? extends RestClientException>> statusMapping = new LinkedHashMap();private final Map<Series, Class<? extends RestClientException>> seriesMapping = new LinkedHashMap();public ExtractingResponseErrorHandler() {}public ExtractingResponseErrorHandler(List<HttpMessageConverter<?>> messageConverters) {this.messageConverters = messageConverters;}public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {this.messageConverters = messageConverters;}public void setStatusMapping(Map<HttpStatus, Class<? extends RestClientException>> statusMapping) {if (!CollectionUtils.isEmpty(statusMapping)) {this.statusMapping.putAll(statusMapping);}}public void setSeriesMapping(Map<Series, Class<? extends RestClientException>> seriesMapping) {if (!CollectionUtils.isEmpty(seriesMapping)) {this.seriesMapping.putAll(seriesMapping);}}protected boolean hasError(HttpStatus statusCode) {if (this.statusMapping.containsKey(statusCode)) {return this.statusMapping.get(statusCode) != null;} else if (this.seriesMapping.containsKey(statusCode.series())) {return this.seriesMapping.get(statusCode.series()) != null;} else {return super.hasError(statusCode);}}public void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {if (this.statusMapping.containsKey(statusCode)) {this.extract((Class)this.statusMapping.get(statusCode), response);} else if (this.seriesMapping.containsKey(statusCode.series())) {this.extract((Class)this.seriesMapping.get(statusCode.series()), response);} else {super.handleError(response, statusCode);}}//转换抽取为RestClientException异常private void extract(@Nullable Class<? extends RestClientException> exceptionClass, ClientHttpResponse response) throws IOException {if (exceptionClass != null) {HttpMessageConverterExtractor<? extends RestClientException> extractor = new HttpMessageConverterExtractor(exceptionClass, this.messageConverters);RestClientException exception = (RestClientException)extractor.extractData(response);if (exception != null) {throw exception;}}}
}
NoOpResponseErrorHandler
//在TestRestTemplate类中private static class NoOpResponseErrorHandler extends DefaultResponseErrorHandler {private NoOpResponseErrorHandler() {}//不做错误处理public void handleError(ClientHttpResponse response) throws IOException {}}
RestTemplate异常处理器被触发源码
- 初始化errorHandler变量
- 执行doExecute()方法
@Nullableprotected <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;Object var14;try {ClientHttpRequest request = this.createRequest(url, method);if (requestCallback != null) {requestCallback.doWithRequest(request);}response = request.execute();//处理响应this.handleResponse(url, method, response);var14 = responseExtractor != null ? responseExtractor.extractData(response) : null;} catch (IOException var12) {String resource = url.toString();String query = url.getRawQuery();resource = query != null ? resource.substring(0, resource.indexOf(63)) : resource;throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + var12.getMessage(), var12);} finally {if (response != null) {response.close();}}return var14;}
- 处理响应,调用handleResponse()方法
protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {//获取异常处理器ResponseErrorHandler errorHandler = this.getErrorHandler();boolean hasError = errorHandler.hasError(response);if (this.logger.isDebugEnabled()) {try {int code = response.getRawStatusCode();HttpStatus status = HttpStatus.resolve(code);this.logger.debug("Response " + (status != null ? status : code));} catch (IOException var8) {}}if (hasError) {errorHandler.handleError(url, method, response);}}
- 获取异常处理器,调用getErrorHandler()方法
public ResponseErrorHandler getErrorHandler() {//返回的就是RestTemplate中的成员变量errorHandlerreturn this.errorHandler;}
RestTemplate异常处理器实践模板
定义一个自定义的errorHandler实现ResponseErrorHandler接口
- errorHandler
/*** 继承默认错误处理器DefaultResponseErrorHandler,无需关注hasError和handlerError方法*/
@Component
public class MyResponseErrorHandler implements ResponseErrorHandler {private static final Logger LOGGER = LoggerFactory.getLogger(MyResponseErrorHandler.class);/*** my service进行定制化处理*/@Autowiredprivate MyService myService;@Overridepublic boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {return clientHttpResponse.getStatusCode().value() != 200 && clientHttpResponse.getStatusCode().value() != 201 && clientHttpResponse.getStatusCode().value() !=302;}@Overridepublic void handleError(ClientHttpResponse clientHttpResponse) throws IOException {//遇到401进行单独处理if (HttpStatus.UNAUTHORIZED.value() == response.getStatusCode().value()) {myService.doSomething(url.getHost());} else {//继续抛异常throw new RuntimeException(clientHttpResponse.getStatusText());}}@Overridepublic void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {LOGGER.error("=======================ERROR HANDLER============================");LOGGER.error("DateTime:{}", PoolUtil.currentTime());LOGGER.error("HOST:{},URI:{}", url.getHost(), url.getPath());LOGGER.error("Method:{}", method.name());LOGGER.error("Exception:{}", response.getStatusCode());LOGGER.error("StatusText: {}", response.getStatusText());LOGGER.error("========================================================");}
}
- HttpClientUtils工具类
@Slf4j
public class HttpClientUtils {//http协议private static final String HTTP_PROTOCOL = "http";//https协议private static final String HTTPS_PROTOCAL = "https";//最大连接数private static final int MAX_CONNECT = 300;//默认连接数private static final int DEFAULT_CONNECT = 200;public HttpClientUtils(){}/*** new一个http client*/public static CloseableHttpClient newHttpClientForHttpsOrHttp() {HttpClientBuilder build = HttpClientBuilder.create();try {SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {public boolean isTrusted(X509Certificate[] arg0, String arg1){return true;}}).build();build.setSslcontext(sslContext);//X509HostnameVerifier校验X509HostnameVerifier hostnameVerifier = new X509HostnameVerifier() {public boolean verify(String arg0, SSLSession arg1) {return true;}public void verify(String arg0, SSLSocket arg1){}public void verify(String arg0, String[] arg1, String[] arg2){}public void verify(String arg0, X509Certificate arg1){}};//SSL连接socket工厂SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);//http和https协议registerRegistry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create().register(HTTP_PROTOCOL, PlainConnectionSocketFactory.getSocketFactory()).register(HTTPS_PROTOCAL, sslSocketFactory).build();//连接池PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);connMgr.setDefaultMaxPerRoute(DEFAULT_CONNECT_NUM);connMgr.setMaxTotal(MAX_CONNECT_NUM);build.setConnectionManager(poolingHttpClientConnectionManager);//构建CloseableHttpClientCloseableHttpClient closeableHttpClient = build.build();return closeableHttpClient;} catch (Exception e ) {log.error("异常:{}", e.getLocalizedMessage());}return null;}}
- restTemplate调用
/*** 获取远程连接template** @return*/public static RestTemplate getRestTempte() {try {HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(HttpClientUtils.newHttpClientForHttpsOrHttp());factory.setConnectTimeout(30000);//设置handlerreturn new RestTemplate(factory).setErrorHandler(new MyResponseErrorHandler());} catch (Exception e) {return null;}}
定义一个自定义的errorHandler继承DefaultResponseErrorHandler类
/*** 继承默认错误处理器DefaultResponseErrorHandler,无需关注hasError和handlerError方法*/
@Component
public class MyResponseErrorHandler extends DefaultResponseErrorHandler {private static final Logger LOGGER = LoggerFactory.getLogger(MyResponseErrorHandler.class);/*** my service进行定制化处理*/@Autowiredprivate MyService myService;@Overridepublic void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {LOGGER.error("=======================ERROR HANDLER============================");LOGGER.error("DateTime:{}", PoolUtil.currentTime());LOGGER.error("HOST:{},URI:{}", url.getHost(), url.getPath());LOGGER.error("Method:{}", method.name());LOGGER.error("Exception:{}", response.getStatusCode());LOGGER.error("StatusText: {}", response.getStatusText());LOGGER.error("========================================================");//遇到401进行单独处理if (HttpStatus.UNAUTHORIZED.value() == response.getStatusCode().value()) {myService.doSomething(url.getHost());} else {this.handleError(response);}}
}