SpringBoot | RestTemplate异常处理器ErrorHandler使用详解

关注wx:CodingTechWork

引言

在代码开发过程中,发现很多地方通过RestTemplate调用了第三方接口,而第三方接口需要根据某些状态码或者异常进行重试调用,此时,要么在每个调用的地方进行异常捕获,然后重试;要么在封装的RestTemplate工具类中进行统一异常捕获和封装。当然,本文不走寻常路,将会通过RestTemplate的异常处理器进行操作。

RestTemplate异常处理器介绍

分类

异常处理器 功能描述
ResponseErrorHandler 异常处理器接口,是restTemplate所有异常处理器的实现接口
DefaultResponseErrorHandler 默认的异常处理器,处理客户端和服务端异常
ExtractingResponseErrorHandler 将HTTP错误响应转换RestClientException
NoOpResponseErrorHandler 不处理异常

RestTemplate异常处理器源码

ResponseErrorHandler

java 复制代码
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

java 复制代码
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 */
    @Deprecated
    protected 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];
        }
    }

    @Nullable
    protected Charset getCharset(ClientHttpResponse response) {
        HttpHeaders headers = response.getHeaders();
        MediaType contentType = headers.getContentType();
        return contentType != null ? contentType.getCharset() : null;
    }
}

ExtractingResponseErrorHandler

java 复制代码
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

java 复制代码
	//在TestRestTemplate类中
    private static class NoOpResponseErrorHandler extends DefaultResponseErrorHandler {
        private NoOpResponseErrorHandler() {
        }
		//不做错误处理
        public void handleError(ClientHttpResponse response) throws IOException {
        }
    }

RestTemplate异常处理器被触发源码

  1. 初始化errorHandler变量
  2. 执行doExecute()方法
java 复制代码
    @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;

        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;
    }
  1. 处理响应,调用handleResponse()方法
java 复制代码
    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);
        }

    }
  1. 获取异常处理器,调用getErrorHandler()方法
java 复制代码
    public ResponseErrorHandler getErrorHandler() {
    	//返回的就是RestTemplate中的成员变量errorHandler
        return this.errorHandler;
    }

RestTemplate异常处理器实践模板

定义一个自定义的errorHandler实现ResponseErrorHandler接口

  1. errorHandler
java 复制代码
/**
 * 继承默认错误处理器DefaultResponseErrorHandler,无需关注hasError和handlerError方法
 */
@Component
public class MyResponseErrorHandler implements ResponseErrorHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyResponseErrorHandler.class);

    /**
     * my service进行定制化处理
     */
    @Autowired
    private MyService myService;
    
    @Override
    public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
        return clientHttpResponse.getStatusCode().value() != 200 && clientHttpResponse.getStatusCode().value() != 201 && clientHttpResponse.getStatusCode().value() !=302;
    }

    @Override
    public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
        //遇到401进行单独处理
        if (HttpStatus.UNAUTHORIZED.value() == response.getStatusCode().value()) {
            myService.doSomething(url.getHost());
        } else {
        	//继续抛异常
            throw new RuntimeException(clientHttpResponse.getStatusText());
        }
    }
    @Override
    public 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("========================================================");
    }
}
  1. HttpClientUtils工具类
java 复制代码
@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协议register
            Registry<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);
			//构建CloseableHttpClient
            CloseableHttpClient closeableHttpClient = build.build();
            return closeableHttpClient;
        } catch (Exception e ) {
        	log.error("异常:{}", e.getLocalizedMessage());
        }
        return null;
    }

}
  1. restTemplate调用
java 复制代码
    /**
     * 获取远程连接template
     *
     * @return
     */
    public static RestTemplate getRestTempte() {
        try {
            HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(HttpClientUtils.newHttpClientForHttpsOrHttp());
            factory.setConnectTimeout(30000);
            //设置handler
            return new RestTemplate(factory).setErrorHandler(new MyResponseErrorHandler());
        } catch (Exception e) {
            return null;
        }
    }

定义一个自定义的errorHandler继承DefaultResponseErrorHandler类

java 复制代码
/**
 * 继承默认错误处理器DefaultResponseErrorHandler,无需关注hasError和handlerError方法
 */
@Component
public class MyResponseErrorHandler extends DefaultResponseErrorHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyResponseErrorHandler.class);

    /**
     * my service进行定制化处理
     */
    @Autowired
    private MyService myService;

    @Override
    public 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);
        }
    }
}
相关推荐
追逐时光者1 小时前
推荐 12 款开源美观、简单易用的 WPF UI 控件库,让 WPF 应用界面焕然一新!
后端·.net
Jagger_1 小时前
敏捷开发流程-精简版
前端·后端
苏打水com2 小时前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端
西瓜er3 小时前
JAVA:Spring Boot 集成 FFmpeg 实现多媒体处理
java·spring boot·ffmpeg
间彧3 小时前
Spring Cloud Gateway与Kong或Nginx等API网关相比有哪些优劣势?
后端
间彧3 小时前
如何基于Spring Cloud Gateway实现灰度发布的具体配置示例?
后端
间彧3 小时前
在实际项目中如何设计一个高可用的Spring Cloud Gateway集群?
后端
间彧3 小时前
如何为Spring Cloud Gateway配置具体的负载均衡策略?
后端
间彧3 小时前
Spring Cloud Gateway详解与应用实战
后端
EnCi Zheng4 小时前
SpringBoot 配置文件完全指南-从入门到精通
java·spring boot·后端