为什么要使用RestTemplate?

随着微服务的广泛使用,在实际的开发中,客户端代码中调用RESTful接口也越来越常见。在系统的遗留代码中,你可能会看见有一些代码是使用HttpURLConnection来调用RESTful接口的,类似于下面这样:

URL url = ...
// 打开连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
try {
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.setDoOutput(true);
conn.connect();
// 发送数据...
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(conn.getOutputStream(), "utf-8"));
bw.write(str);
// 接收数据 ...
BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "utf-8"));
String line = null;
while ((line = br.readLine()) != null) {
...
}
} finally {
conn.disconnect();
}

从上面的代码可以看出,使用HttpURLConnection调用RESTful接口是比较麻烦的,假如要调用30个接口,每个接口都使用类似于上面的代码 进行调用,那简直是一场灾难(写这么多无聊的样板代码,内心绝对是崩溃的)。有人可能会想,将常用的RESTful操作(例如GET、POST、DELETE)封装成工具类,再调用不是也可以吗!这样做确实可行,但是要封装成通用的工具类不是那么简单的(需要一定的经验)。调用RESTful接口,还有另外一种选择:Apache HttpComponents。关于如何调用,可以参考我之前写过的一篇文章:学习HttpClient,从两个小例子开始。虽然使用它发送HTTP请求确实挺方便的,但是使用它调用RESTful接口好像也挺麻烦的!

直到我遇到了RestTemplate,世界顿时都明亮了,腰也不酸了,腿也不疼了,一下子就对接好了10个RESTful接口。写的代码可能变成是这样子的:

RestTemplate template = ...
// 请求地址
String url = ...
// 请求参数
UserParams request = ...
// 执行POST请求
User u = template.postForObject(url, request, User.class);
...

上面的调用代码变的简洁了很多,是不是很爽啊!RestTemplate是spring提供用来调用RESTful接口的类,里面提供了大量便捷的方法,如下:

请求方式 RestTemplate中的方法
DELETE delete(java.lang.String, java.lang.Object...)
GET getForObject(java.lang.String, java.lang.Class, java.lang.Object...)
getForEntity(java.lang.String, java.lang.Class, java.lang.Object...)
HEAD headForHeaders(java.lang.String, java.lang.Object...)
OPTIONS optionsForAllow(java.lang.String, java.lang.Object...)
POST postForLocation(java.lang.String, java.lang.Object, java.lang.Object...)
postForObject(java.lang.String, java.lang.Object, java.lang.Class, java.lang.Object...)
PUT put(java.lang.String, java.lang.Object, java.lang.Object...)
any exchange(java.lang.String, org.springframework.http.HttpMethod,
org.springframework.http.HttpEntity,
java.lang.Class, java.lang.Object...)
execute(java.lang.String, org.springframework.http.HttpMethod,
org.springframework.web.client.RequestCallback,
org.springframework.web.client.ResponseExtractor, java.lang.Object...)

执行不同的请求,只需找到这种请求方式所对应的方法就行,上例中的postForObject就是发送的POST请求。如果上面的请求没有找到对应的方法,可以使用更加通用的exchangeexecute方法。

RestTemplate的可扩展性也很强(下面列出比较常用的几种方式):

  1. RestTemplate默认使用的是JDK中的HttpURLConnection实现的,如果你想要切换到其他的HTTP库,例如,Apache HttpComponents, Netty和OkHttp,只需要调用setRequestFactory方法来进行设置,甚至可以使用自己实现的类型,只需要继承自ClientHttpRequestFactory。(一般情况下,使用默认的实现就好)
  2. RestTemplate默认使用的是DefaultResponseErrorHandler响应错误处理器,你可以调用setErrorHandler来定制自己的响应错误处理器。
  3. 客户端请求拦截器:ClientHttpRequestInterceptor,实现这个接口,并在org.springframework.web.client.RestTemplate#setInterceptors(java.util.List)中进行注册,能对请求头和请求体的内容进行修改,并能对响应的内容进行修改。(下面将会讲解)
  4. RestTemplate中的doExecute方法,所有请求方式最终都会执行这个方法,重写此方法能完成一些必要的操作。

RestTemplate的妙用

考虑这样一个场景:假如说A部门开发的用户相关的微服务接口,提供给B部门来使用,在使用时需要在请求头中加入基本的认证头信息,认证头的格式如下:

Authorization=Basic {token}

token的格式是:base64(username:password)。假如username是zfx,密码是123,结果是先拼接用户名和密码:zfx:123,再用utf-8格式获取其字节码,进行base64编码的结果为:emZ4OjEyMw==

假如要调用A部门的接口,根据id来获取用户的信息,代码如下:

String userId = "11";
String url = "http://127.0.0.1:8080/v1/users/{id}"; RestTemplate restTemplate = new RestTemplate(); // 基本的认证头信息
String username = "zfx";
String password = "123";
String token = Base64Utils.encodeToString(
(username + ":" + password).getBytes(StandardCharsets.UTF_8));
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + token);
HttpEntity<Void> requestEntity = new HttpEntity<>(headers); ResponseEntity<User> exchange = restTemplate.exchange(url, HttpMethod.GET,
requestEntity, User.class, userId);
User result = exchange.getBody();

首先先创建RestTemplate的实例,在实际的开发中,最好不要每次都创建RestTemplate的实例,最好在spring中以单例的方式来配置,要使用的地方直接注入。用base64来编码了用户名和密码,然后在请求头中进行设置。restTemplate.exchange执行请求,并获取返回的结果。上面的代码其实并不难,但是这样写会不会有啥问题呢!假如说,叫你对接A部门的根据机构id来获取机构信息的接口,你岂不是又要把上面的代码再重新复制一下吗?这样不是很麻烦,代码会很臃肿。

spring提供了ClientHttpRequestInterceptor这个类,适合用来处理这种情况,加入基本的认证头信息,可以使用spring提供的这个类:BasicAuthorizationInterceptor。使用配置的方式来配置RestTemplate:

@Configuration
public class RestTemplateConfig { @Value("${zfx.username}")
private String username; @Value("${zfx.password}")
private String password; @Bean("basicAuthRestTemplate")
public RestTemplate createBasicAuthRestTemplate() {
RestTemplate restTemplate = new RestTemplate(); List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(new BasicAuthorizationInterceptor(username, password));
restTemplate.setInterceptors(interceptors);
return restTemplate;
} }

上面的代码在创建basicAuthRestTemplate时,会加入基本的认证头信息的拦截器,来设置基本认证头信息。

再次调用上面的接口时,代码可以这样写:

@Autowired
RestTemplate basicAuthRestTemplate;
...
String userId = "11";
String url = "http://127.0.0.1:8080/v1/users/{id}";
User result = basicAuthRestTemplate.getForObject(url, User.class, userId);

代码一下子简洁了这么多,假如说要调用根据机构id来获取机构信息的接口呢?如下:

@Autowired
RestTemplate basicAuthRestTemplate;
...
String orgId = "11";
String url = "http://127.0.0.1:8080/v1/orgs/{id}";
Org result = basicAuthRestTemplate.getForObject(url, Org.class, orgId);

代码一下子简洁了很多,对接这些接口的程序员不用再关心认证是怎么一回事,认证这件事对于他们完全是透明的,他们只需要专注于编写他们自己的逻辑代码就可以了。ClientHttpRequestInterceptor的实现也很简单,代码如下:

@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException { String token = Base64Utils.encodeToString(
(this.username + ":" + this.password).getBytes(StandardCharsets.UTF_8));
request.getHeaders().add("Authorization", "Basic " + token);
return execution.execute(request, body);
}

在intercept方法中加入了基本的认证头信息。

假如说,有一天认证方式变了,改成OAuth2.0了,那么我们只需要实现自己的请求拦截器就行了,如下:

public class BearerAuthorizationIntercept
implements ClientHttpRequestInterceptor { @Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
String token = 这些写获取token的逻辑;
request.getHeaders().add("Authorization", "Bearer " + token);
return execution.execute(request, body);
} }

然后配置restTemplate:

	@Bean("bearerAuthRestTemplate")
public RestTemplate createBearerAuthRestTemplate() {
RestTemplate restTemplate = new RestTemplate(); List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(new BearerAuthorizationIntercept());
restTemplate.setInterceptors(interceptors);
return restTemplate;
}

那么只需要注入bearerAuthRestTemplate,就能使用他了:

@Autowired
RestTemplate bearerAuthRestTemplate;
...
String userId = "11";
String url = "http://127.0.0.1:8080/v1/users/{id}";
User result = bearerAuthRestTemplate.getForObject(url, User.class, userId);

还不知道spring的RestTemplate的妙用吗的更多相关文章

  1. 听说你还不知道Spring是如何解决循环依赖问题的?

    Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题. 其实笔者本人对这类框架源码题还是持一定的怀疑态度的. 如果笔者作为面试官,可能会问一些诸如"如果注入的属性为null, ...

  2. 难道你还不知道Spring之事务的回滚和提交的原理吗,这篇文章带你走进源码级别的解读。

    上一篇文章讲解了获取事务,并通过获取的connection设置只读,隔离级别等:这篇文章讲事务剩下的回滚和提交. 事务的回滚处理 之前已经完成了目标方法运行前的事务准备工作.而这些准备工作的最大目的无 ...

  3. 对Spring 的RestTemplate进行包装

    Spring的RestTemplate及大地简化了REST Client的开发,但每次还要编写大量的模板代码,代码不够简洁.我对他进行了一次包装,采用接口来声明REST接口,使用Annotation对 ...

  4. Spring’s RestTemplate

    Spring’s RestTemplate /** * After the word document is generated in memory we can upload it to the s ...

  5. 你还不知道Vue的生命周期吗?带你从Vue源码了解Vue2.x的生命周期(初始化阶段)

    作者:小土豆biubiubiu 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.im/user/58c61b4361ff4b005d9e8 ...

  6. JDK15就要来了,你却还不知道JDK8的新特性!

    微信搜「烟雨星空」,白嫖更多好文. 现在 Oracle 官方每隔半年就会出一个 JDK 新版本.按时间来算的话,这个月就要出 JDK15 了.然而,大部分公司还是在使用 JDK7 和 8 . 之前去我 ...

  7. Spring AOP应用场景你还不知道?这篇一定要看!

    回顾一下Spring AOP的知识 为什么会有面向切面编程(AOP)? 我们知道Java是一个面向对象(OOP)的语言,但它有一些弊端,比如当我们需要为多个不具有继承关系的对象引入一个公共行为,例如日 ...

  8. Spring 最常用的 7 大类注解,哪些你还不知道?

    随着技术的更新迭代,Java5.0开始支持注解.而作为java中的领军框架spring,自从更新了2.5版本之后也开始慢慢舍弃xml配置,更多使用注解来控制spring框架. 而spring的的注解那 ...

  9. Spring中RestTemplate的使用方法

    一.REST 在互联网中,我们会通过请求url来对网络上的资源做增删改查等动作,这里的请求包含两部分:动词,主要包括增.删.改.查:名词,就是网络中的各种资源.传统的非REST风格的请求方式是把动词和 ...

随机推荐

  1. PHP之道 - php各方面的知识汇总

    看到一个PHP的知识各方面的汇总,写的很有借鉴意义,搬过来了 转自: https://laravel-china.github.io/php-the-right-way/ 欢迎阅读 其他语言版本 参与 ...

  2. dubbo和dubboX与微服务架构(dubbo一)

    一.传统三层架构模式的缺陷 三层架构(3-tier architecture) 通常意义上的三层架构就是将整个业务应用划分为:界面层(User Interface layer)web.业务逻辑层(Bu ...

  3. Spark源码剖析 - SparkContext的初始化(一)

    1. SparkContext概述 注意:SparkContext的初始化剖析是基于Spark2.1.0版本的 Spark Driver用于提交用户应用程序,实际可以看作Spark的客户端.了解Spa ...

  4. [物理学与PDEs]第1章第1节 引言

    1. 电动力学研究的对象是电磁场, 研究电磁场的基本属性---运动规律及它和带电物质的相互作用. 2. 场, 物质的一种存在方式. 3. Maxwell 方程组是电动力学中的基本方程, 是一切有关电磁 ...

  5. 在vue中引入scss

    先npm安装stylus和stylus-loader  (我安装后,报错提示缺少stylus,所以第一步安装stylus,如果没有提示可省略第一步) 1.到 package.json ----- de ...

  6. v-model

    仅用于以下控件: <input> <select> <textarea> 组件 v-model以Vue 实例的数据作为数据来源,应当在组件的 data 选项中声明初 ...

  7. Java 计算两个日期相差月数、天数

    package com.myjava; import java.text.ParseException; import java.text.SimpleDateFormat; import java. ...

  8. [Kubernetes]编排其实很简单

    什么是编排 Kubernetes中,我们总是在说一个概念:编排. 在[Kubernetes]谈谈Kubernetes的本质这篇文章中,关于"编排"的概念介绍了一下:过去很多集群管理 ...

  9. anaconda3安装cv2模块(python3.6)

  10. Python-Django-BMS-增删改查

    无名分组 有名分组 反向解析 更改路由后不影响,动态传值 locals()传参 queryset.update() 自定义过滤器 {{forloop.counter}} new.authors.add ...