文档章节

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

c
 ccww_
发布于 11/13 10:23
字数 2385
阅读 1354
收藏 30

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

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

  在微服务中,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的设置。

<br>

其中,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](https://my.oschina.net/u/2896689)
protected <T> T doExecute(URI url, [@Nullable](https://my.oschina.net/u/2896689) HttpMethod method, [@Nullable](https://my.oschina.net/u/2896689) RequestCallback requestCallback,
		[@Nullable](https://my.oschina.net/u/2896689) 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](https://my.oschina.net/u/1162528)
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转换器;
  • 配置自定义的异常处理.

<br>

 @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,这样的代码会显得十分冗余,可以在拦截器统一设置。

<br>

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;
    }
}

<br>

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

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

<br>

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);
    }

}

<br>

自定义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
    }

}

最后麻烦各位看官点个赞,谢谢支持!!!

© 著作权归作者所有

c
粉丝 16
博文 30
码字总数 92052
作品 0
深圳
私信 提问
加载中

评论(2)

向北庆庆炒能力
写的好详细,我反手就一个赞
疯狂Spring Cloud连载(9)——RestTemplate的负载均衡原理

本文节选自《疯狂Spring Cloud微服务架构实战》 京东购买地址:https://item.jd.com/12256011.html 当当网购买地址:http://product.dangdang.com/25201393.html Spring Cloud教学视频:htt...

杨大仙的程序空间
2017/10/18
974
2
SpringCloud中任务调度器使用FeginClient被拦截

场景:微服务下使用定时任务进行数据统计,涉及到跨服务调用,由于服务中加入全局拦截器,跨服务调用过程中一直提示用户未登录 原因分析: 一:由于外部请求是通过ZUUL分配服务调用,经过外层...

WALK_MAN
2018/12/21
45
0
集成源码深度剖析:Fescar x Spring Cloud

Fescar 简介 常见的分布式事务方式有基于 2PC 的 XA (e.g. atomikos),从业务层入手的 TCC( e.g. byteTCC)、事务消息 ( e.g. RocketMQ Half Message) 等等。XA 是需要本地数据库支持的分布式...

中间件小哥
03/06
0
0
聊一聊 RestTemplate

最近这段时间用了下 RestTemplate 这个类,抽点时间总结下一些东西,希望对大家有所帮助。 从 3.0 版本开始,Spring 提供了 RestTemplate 作为用于访问 Rest 服务的客户端,RestTemplate 提供...

glmapper
2018/10/28
0
0
揭秘最新版本Spring Cloud 负载均衡之迷-Ribbon(一)

Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现,它可以通过客户端配置的ribbonServerList服务端列表去轮训访问达到负载均衡的作用。当Ribbon跟Eur...

魔都太阳神
2018/07/20
0
0

没有更多内容

加载失败,请刷新页面

加载更多

JVM性能调优的6大步骤,及关键调优参数详解

JVM内存调优 对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数。 1.Full GC 会对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比较慢,...

一只会编程的狼
8分钟前
4
0
并发和并行性有什么区别?

并发和并行性有什么区别? 示例被赞赏。 #1楼 并发性:具有共享资源潜力的多个执行流 例如:两个线程争用一个I / O端口。 平行主义:将问题分成多个相似的块。 例如:通过在文件的每半部分上...

javail
10分钟前
4
0
(推荐使用)提高开发效率工具集合

提高开发效率工具集合(推荐使用) 一、Hutool工具类 官网地址:https://www.hutool.cn/ Github地址:https://github.com/looly/hutool/ Gitee 地址:https://gitee.com/loolly/hutool/ 文档参...

明德先生
14分钟前
4
0
java并发-缓存一致性协议和内存屏障的思考和理解

################这是之前的思考 内存屏障只是保证清空流水线,如何保证高速缓存的内容更新到最新或刷新到主存呢?这个问题突然想到了,不知道这个需要怎回答。 内存屏障保证的CPU执行执行序...

萧默
17分钟前
5
0
类型名称后面的括号是否与new有所不同?

如果“测试”是普通类,则之间是否有任何区别: Test* test = new Test; 和 Test* test = new Test(); #1楼 new Thing(); 很明显,您想要一个构造函数,而new Thing; 表示您不介意是否未调...

技术盛宴
41分钟前
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部