微服务中如何使用RestTemplate优雅调用API拦截器异常处理消息转换

33次阅读

共计 12990 个字符,预计需要花费 33 分钟才能阅读完成。

关注我,可以获取最新知识、经典面试题以及微服务技术分享

  在微服务中,rest服务互相调用是很普遍的,我们该如何优雅地调用,其实在 Spring 框架使用 RestTemplate 类可以优雅地进行 rest 服务互相调用,它简化了与 http 服务的通信方式,统一了 RESTful 的标准,封装了 http 链接,操作使用简便,还可以自定义 RestTemplate 所需的模式。其中:

  • RestTemplate默认使用 HttpMessageConverter 实例将 HTTP 消息转换成 POJO 或者从 POJO 转换成 HTTP 消息。默认情况下会注册主 mime 类型的转换器,但也可以通过 setMessageConverters 注册自定义转换器。
  • RestTemplate使用了默认的 DefaultResponseErrorHandler,对 40X Bad Request 或 50X internal异常 error 等错误信息捕捉。
  • RestTemplate还可以使用拦截器interceptor,进行对请求链接跟踪,以及统一 head 的设置。

其中,RestTemplate还定义了很多的 REST 资源交互的方法,其中的大多数都对应于 HTTP 的方法,如下:

方法 解析
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 请求,返回包含特定资源 URL 的 HTTP 头
optionsForAllow() 发送 HTTP OPTIONS 请求,返回对特定 URL 的 Allow 头信息
postForLocation() POST 数据到一个 URL,返回新创建资源的 URL
put() PUT 资源到特定的 URL

1. RestTemplate 源码

1.1 默认调用链路

restTemplate进行 API 调用时,默认调用链:

###########1. 使用 createRequest 创建请求 ########
resttemplate->execute()->doExecute()
HttpAccessor->createRequest()
// 获取拦截器 Interceptor,InterceptingClientHttpRequestFactory,SimpleClientHttpRequestFactory
InterceptingHttpAccessor->getRequestFactory() 
// 获取默认的 SimpleBufferingClientHttpRequest
SimpleClientHttpRequestFactory->createRequest()

#######2. 获取响应 response 进行处理 ###########
AbstractClientHttpRequest->execute()->executeInternal()
AbstractBufferingClientHttpRequest->executeInternal()

###########3. 异常处理 #####################
resttemplate->handleResponse()

##########4. 响应消息体封装为 java 对象 #######
HttpMessageConverterExtractor->extractData()

1.2 restTemplate->doExecute()

在默认调用链中,restTemplate 进行 API 调用都会调用 doExecute 方法,此方法主要可以进行如下步骤:

1)使用 createRequest 创建请求,获取响应
2) 判断响应是否异常,处理异常
3) 将响应消息体封装为 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;
    try {
        // 使用 createRequest 创建请求
        ClientHttpRequest request = createRequest(url, method);
        if (requestCallback != null) {requestCallback.doWithRequest(request);
        }
        // 获取响应 response 进行处理
        response = request.execute();
        // 异常处理
        handleResponse(url, method, response);
        // 响应消息体封装为 java 对象
        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();
        }
    }
}

1.3 InterceptingHttpAccessor->getRequestFactory()

在默认调用链中,InterceptingHttpAccessor 的 getRequestFactory()方法中,如果没有设置 interceptor 拦截器,就返回默认的 SimpleClientHttpRequestFactory,反之,返回InterceptingClientHttpRequestFactoryrequestFactory,可以通过 resttemplate.setInterceptors 设置自定义拦截器interceptor

//Return the request factory that this accessor uses for obtaining client request handles.
public ClientHttpRequestFactory getRequestFactory() {// 获取拦截器 interceptor(自定义的)
        List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
        if (!CollectionUtils.isEmpty(interceptors)) {
            ClientHttpRequestFactory factory = this.interceptingRequestFactory;
            if (factory == null) {factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
                this.interceptingRequestFactory = factory;
            }
            return factory;
        }
        else {return super.getRequestFactory();
        }
    }

然后再调用 SimpleClientHttpRequestFactory 的 createRequest 创建连接:

@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
    prepareConnection(connection, httpMethod.name());

    if (this.bufferRequestBody) {return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
    }
    else {return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
    }
}

1.4 resttemplate->handleResponse()

在默认调用链中,resttemplate 的 handleResponse,响应处理,包括异常处理,而且异常处理可以通过调用 setErrorHandler 方法设置自定义的 ErrorHandler,实现对请求响应异常的判别和处理。自定义的ErrorHandler 需实现 ResponseErrorHandler 接口,同时 Spring boot 也提供了默认实现DefaultResponseErrorHandler,因此也可以通过继承该类来实现自己的ErrorHandler

DefaultResponseErrorHandler默认对 40X Bad Request或 50X internal异常 error 等错误信息捕捉。如果想捕捉服务本身抛出的异常信息,需要通过自行实现 RestTemplateErrorHandler

ResponseErrorHandler errorHandler = getErrorHandler();
               // 判断响应是否有异常
    boolean hasError = errorHandler.hasError(response);
    if (logger.isDebugEnabled()) {
        try {int code = response.getRawStatusCode();
            HttpStatus status = HttpStatus.resolve(code);
            logger.debug("Response" + (status != null ? status : code));
        }catch (IOException ex) {// ignore}
    }
    // 有异常进行异常处理
    if (hasError) {errorHandler.handleError(url, method, response);
    }
}

1.5 HttpMessageConverterExtractor->extractData()

在默认调用链中,HttpMessageConverterExtractorextractData 中进行响应消息体封装为 java 对象,就需要使用 message 转换器,可以通过追加的方式增加自定义的 messageConverter:先获取现有的messageConverter,再将自定义的messageConverter 添加进去。

根据 restTemplatesetMessageConverters的源码可得,使用追加的方式可防止原有的 messageConverter 丢失,源码:

public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        // 检验
        validateConverters(messageConverters);
        // Take getMessageConverters() List as-is when passed in here
        if (this.messageConverters != messageConverters) {
            // 先清除原有的 messageConverter
            this.messageConverters.clear();
            // 后加载重新定义的 messageConverter
            this.messageConverters.addAll(messageConverters);
        }
    }

HttpMessageConverterExtractor 的 extractData源码:

MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
    if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {return null;}
    // 获取到 response 的 ContentType 类型
    MediaType contentType = getContentType(responseWrapper);

    try {
        // 依次循环 messageConverter 进行判断是否符合转换条件,进行转换 java 对象
        for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
        // 会根据设置的返回类型 responseType 和 contentType 参数进行匹配,选择合适的 MessageConverter
            if (messageConverter instanceof GenericHttpMessageConverter) {
                GenericHttpMessageConverter<?> genericMessageConverter =
                        (GenericHttpMessageConverter<?>) messageConverter;
                if (genericMessageConverter.canRead(this.responseType, null, contentType)) {if (logger.isDebugEnabled()) {ResolvableType resolvableType = ResolvableType.forType(this.responseType);
                        logger.debug("Reading to [" + resolvableType + "]");
                    }
                    return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
                }
            }
            if (this.responseClass != null) {if (messageConverter.canRead(this.responseClass, contentType)) {if (logger.isDebugEnabled()) {String className = this.responseClass.getName();
                        logger.debug("Reading to [" + className + "] as \"" + contentType + "\"");
                    }
                    return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
                }
            }
        }
    }
    .....
}

1.6 contentType 与 messageConverter 之间的关系

HttpMessageConverterExtractorextractData方法中看出,会根据 contentTyperesponseClass选择 messageConverter 是否可读、消息转换。关系如下:

类名 支持的 JavaType 支持的 MediaType
ByteArrayHttpMessageConverter byte[] application/octet-stream, */*
StringHttpMessageConverter String text/plain, */*
ResourceHttpMessageConverter Resource */*
SourceHttpMessageConverter Source application/xml, text/xml, application/*+xml
AllEncompassingFormHttpMessageConverter Map<K, List<?>> application/x-www-form-urlencoded, multipart/form-data
MappingJackson2HttpMessageConverter Object application/json, application/*+json
Jaxb2RootElementHttpMessageConverter Object application/xml, text/xml, application/*+xml
JavaSerializationConverter Serializable x-java-serialization;charset=UTF-8
FastJsonHttpMessageConverter Object */*

2. springboot 集成 RestTemplate

  根据上述源码的分析学习,可以轻松,简单地在项目进行对 RestTemplate 进行优雅地使用,比如增加自定义的异常处理、MessageConverter以及拦截器interceptor。本文使用示例demo,详情请查看接下来的内容。

2.1. 导入依赖:(RestTemplate 集成在 Web Start 中)

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>2.2.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.10</version>
  <scope>provided</scope>
</dependency>

2.2. RestTemplat 配置:

  • 使用 ClientHttpRequestFactory 属性配置 RestTemplat 参数,比如ConnectTimeoutReadTimeout;
  • 增加自定义的 interceptor 拦截器和异常处理;
  • 追加 message 转换器;
  • 配置自定义的异常处理.


 @Configuration
public class RestTemplateConfig {@Value("${resttemplate.connection.timeout}")
    private int restTemplateConnectionTimeout;
    @Value("${resttemplate.read.timeout}")
    private int restTemplateReadTimeout;

    @Bean
    //@LoadBalanced
    public RestTemplate restTemplate(ClientHttpRequestFactory simleClientHttpRequestFactory) {RestTemplate restTemplate = new RestTemplate();
        // 配置自定义的 message 转换器
        List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        messageConverters.add(new CustomMappingJackson2HttpMessageConverter());
        restTemplate.setMessageConverters(messageConverters);
        // 配置自定义的 interceptor 拦截器
        List<ClientHttpRequestInterceptor> interceptors=new ArrayList<ClientHttpRequestInterceptor>();
        interceptors.add(new HeadClientHttpRequestInterceptor());
        interceptors.add(new TrackLogClientHttpRequestInterceptor());
        restTemplate.setInterceptors(interceptors);
        // 配置自定义的异常处理
        restTemplate.setErrorHandler(new CustomResponseErrorHandler());
        restTemplate.setRequestFactory(simleClientHttpRequestFactory);

        return restTemplate;
    }

    @Bean
    public ClientHttpRequestFactory simleClientHttpRequestFactory(){SimpleClientHttpRequestFactory reqFactory= new SimpleClientHttpRequestFactory();
        reqFactory.setConnectTimeout(restTemplateConnectionTimeout);
        reqFactory.setReadTimeout(restTemplateReadTimeout);
        return reqFactory;
    }
}

2.3. 组件(自定义异常处理、interceptor 拦截器、message 转化器)

自定义 interceptor 拦截器,实现 ClientHttpRequestInterceptor 接口

  • 自定义 TrackLogClientHttpRequestInterceptor,记录resttemplaterequestresponse 信息,可进行追踪分析;
  • 自定义 HeadClientHttpRequestInterceptor,设置请求头的参数。API 发送各种请求,很多请求都需要用到相似或者相同的 Http Header。如果在每次请求之前都把Header 填入HttpEntity/RequestEntity,这样的代码会显得十分冗余,可以在拦截器统一设置。

TrackLogClientHttpRequestInterceptor:

/**
 * @Auther: ccww
 * @Date: 2019/10/25 22:48,记录 resttemplate 访问信息
 * @Description:   记录 resttemplate 访问信息
 */
@Slf4j
public class TrackLogClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {trackRequest(request,body);
        ClientHttpResponse httpResponse = execution.execute(request, body);
        trackResponse(httpResponse);
        return httpResponse;
    }

    private void trackResponse(ClientHttpResponse httpResponse)throws IOException {log.info("============================response begin==========================================");
        log.info("Status code  : {}", httpResponse.getStatusCode());
        log.info("Status text  : {}", httpResponse.getStatusText());
        log.info("Headers      : {}", httpResponse.getHeaders());
        log.info("=======================response end=================================================");
    }

    private void trackRequest(HttpRequest request, byte[] body)throws UnsupportedEncodingException {log.info("======= request begin ========");
        log.info("uri : {}", request.getURI());
        log.info("method : {}", request.getMethod());
        log.info("headers : {}", request.getHeaders());
        log.info("request body : {}", new String(body, "UTF-8"));
        log.info("======= request end ========");
    }
}

HeadClientHttpRequestInterceptor:


@Slf4j
public class HeadClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {log.info("#####head handle########");
        HttpHeaders headers = httpRequest.getHeaders();
        headers.add("Accept", "application/json");
        headers.add("Accept-Encoding", "gzip");
        headers.add("Content-Encoding", "UTF-8");
        headers.add("Content-Type", "application/json; charset=UTF-8");
        ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, bytes);
        HttpHeaders headersResponse = response.getHeaders();
        headersResponse.add("Accept", "application/json");
        return  response;
    }
}

自定义异常处理,可继承 DefaultResponseErrorHandler 或者实现 ResponseErrorHandler 接口:

  • 实现自定义 ErrorHandler 的思路是根据响应消息体进行相应的异常处理策略,对于其他异常情况由父类 DefaultResponseErrorHandler 来进行处理。
  • 自定义 CustomResponseErrorHandler 进行 30x 异常处理

CustomResponseErrorHandler:

/**
 * @Auther: Ccww
 * @Date: 2019/10/28 17:00
 * @Description:  30X 的异常处理
 */
@Slf4j
public class CustomResponseErrorHandler extends DefaultResponseErrorHandler {
    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {HttpStatus statusCode = response.getStatusCode();
        if(statusCode.is3xxRedirection()){return true;}
        return super.hasError(response);
    }

    @Override
    public void handleError(ClientHttpResponse response) throws IOException {HttpStatus statusCode = response.getStatusCode();
        if(statusCode.is3xxRedirection()){log.info("########30X 错误,需要重定向!##########");
            return;
        }
        super.handleError(response);
    }

}

自定义 message 转化器

/**
 * @Auther: Ccww
 * @Date: 2019/10/29 21:15
 * @Description: 将 Content-Type:"text/html" 转换为 Map 类型格式
 */
public class CustomMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {public CustomMappingJackson2HttpMessageConverter() {List<MediaType> mediaTypes = new ArrayList<MediaType>();
        mediaTypes.add(MediaType.TEXT_PLAIN);
        mediaTypes.add(MediaType.TEXT_HTML);  // 加入 text/html 类型的支持
        setSupportedMediaTypes(mediaTypes);// tag6
    }

}

最后可关注公众号【Ccww 笔记】,一起学习。加群,每天会分享干货,还有学习视频领取!

正文完
 0