微服务中如何使用RestTemplate优雅调用API(详细分析)
yuyutoo 2024-10-12 01:53 3 浏览 0 评论
关注我,可以获取最新知识、经典面试题以及微服务技术分享
在微服务中,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,返回包含一个对象的
- ResponseEntitypostForObject():POST 数据到一个URL,返回根据响应体匹配形成的对象
- headForHeaders():发送HTTP HEAD请求,返回包含特定资源URL的HTTP头
- optionsForAllow():发送HTTP OPTIONS请求,返回对特定URL的Allow头信息
- postForLocation():POST 数据到一个URL,返回新创建资源的URLput()PUT 资源到特定的URL
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,反之,返回InterceptingClientHttpRequestFactory的requestFactory,可以通过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等错误信息捕捉。如果想捕捉服务本身抛出的异常信息,需要通过自行实现RestTemplate的ErrorHandler。
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()
在默认调用链中, HttpMessageConverterExtractor的extractData中进行响应消息体封装为java对象,就需要使用message转换器,可以通过追加的方式增加自定义的messageConverter:先获取现有的messageConverter,再将自定义的messageConverter添加进去。
根据restTemplate的setMessageConverters的源码可得,使用追加的方式可防止原有的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之间的关系
在HttpMessageConverterExtractor的extractData方法中看出,会根据contentType与responseClass选择messageConverter是否可读、消息转换。关系如下:
- ByteArrayHttpMessageConverter:java对象byte[],contentType为application/octet-stream, */*String
- HttpMessageConverter: :java对象String,contentType为text/plain, */*
- ResourceHttpMessageConverter: Resource,contentType为*/*
- SourceHttpMessageConverter: :java对象Source ,contentType为application/xml, text/xml, application/*+xml
- AllEncompassingFormHttpMessageConverter::java对象Map<K, List<?>>,contentType为application/x-www-form-urlencoded, multipart/form-data
- MappingJackson2HttpMessageConverter::java对象Object ,contentType为application/json, application/*+json
- Jaxb2RootElementHttpMessageConverter::java对象Object ,contentType为 application/xml, text/xml, application/*+xml
- JavaSerializationConverter::java对象Serializable ,contentType为x-java-serialization;charset=UTF-8
- FastJsonHttpMessageConverter: :java对象Object ,contentType为 */*
根据上述源码的分析学习,可以轻松,简单地在项目进行对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参数,比如ConnectTimeout,ReadTimeout;
- 增加自定义的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,记录resttemplate的request和response信息,可进行追踪分析;
- 自定义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; } } <br>
自定义异常处理,可继承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 }
`}
相关推荐
- 史上最全的浏览器兼容性问题和解决方案
-
微信ID:WEB_wysj(点击关注)◎◎◎◎◎◎◎◎◎一┳═┻︻▄(页底留言开放,欢迎来吐槽)●●●...
-
- 平面设计基础知识_平面设计基础知识实验收获与总结
-
CSS构造颜色,背景与图像1.使用span更好的控制文本中局部区域的文本:文本;2.使用display属性提供区块转变:display:inline(是内联的...
-
2025-02-21 16:01 yuyutoo
- 写作排版简单三步就行-工具篇_作文排版模板
-
和我们工作中日常word排版内部交流不同,这篇教程介绍的写作排版主要是用于“微信公众号、头条号”网络展示。写作展现的是我的思考,排版是让写作在网格上更好地展现。在写作上花费时间是有累积复利优势的,在排...
- 写一个2048的游戏_2048小游戏功能实现
-
1.创建HTML文件1.打开一个文本编辑器,例如Notepad++、SublimeText、VisualStudioCode等。2.将以下HTML代码复制并粘贴到文本编辑器中:html...
- 今天你穿“短袖”了吗?青岛最高23℃!接下来几天气温更刺激……
-
最近的天气暖和得让很多小伙伴们喊“热”!!! 昨天的气温到底升得有多高呢?你家有没有榜上有名?...
- CSS不规则卡片,纯CSS制作优惠券样式,CSS实现锯齿样式
-
之前也有写过CSS优惠券样式《CSS3径向渐变实现优惠券波浪造型》,这次再来温习一遍,并且将更为详细的讲解,从布局到具体样式说明,最后定义CSS变量,自定义主题颜色。布局...
- 你的自我界限够强大吗?_你的自我界限够强大吗英文
-
我的结果:A、该设立新的界限...
- 行内元素与块级元素,以及区别_行内元素和块级元素有什么区别?
-
行内元素与块级元素首先,CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,分别为块级(block)、行内(inline)。块级元素:(以下列举比较常...
-
- 让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华
-
去年的两会期间,习近平总书记在参加人大会议四川代表团审议时,对治蜀兴川提出了明确要求,指明了前行方向,并带来了“祝四川人民的生活越来越安逸”的美好祝福。又是一年...
-
2025-02-21 16:00 yuyutoo
- 今年国家综合性消防救援队伍计划招录消防员15000名
-
记者24日从应急管理部获悉,国家综合性消防救援队伍2023年消防员招录工作已正式启动。今年共计划招录消防员15000名,其中高校应届毕业生5000名、退役士兵5000名、社会青年5000名。本次招录的...
- 一起盘点最新 Chrome v133 的5大主流特性 ?
-
1.CSS的高级attr()方法CSSattr()函数是CSSLevel5中用于检索DOM元素的属性值并将其用于CSS属性值,类似于var()函数替换自定义属性值的方式。...
- 竞走团体世锦赛5月太仓举行 世界冠军杨家玉担任形象大使
-
style="text-align:center;"data-mce-style="text-align:...
- 学物理能做什么?_学物理能做什么 卢昌海
-
作者:曹则贤中国科学院物理研究所原标题:《物理学:ASourceofPowerforMan》在2006年中央电视台《对话》栏目的某期节目中,主持人问过我一个的问题:“学物理的人,如果日后不...
-
- 你不知道的关于这只眯眼兔的6个小秘密
-
在你们忙着给熊本君做表情包的时候,要知道,最先在网络上引起轰动的可是这只脸上只有两条缝的兔子——兔斯基。今年,它更是迎来了自己的10岁生日。①关于德艺双馨“老艺...
-
2025-02-21 16:00 yuyutoo
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)