为什么要使用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. Linux 播放网易云音乐(树莓派)

    环境安装sudo apt-get install python-pipsudo apt-get insyall python-dev mpg123sudo pip install Netease-Mu ...

  2. Kth MIN-MAX 反演

    MIN-MAX 反演 我们知道对于普通的 \(\min-\max\) 容斥有如下式子: \[ \max(S) = \sum_{T \subseteq S} (-1)^{|T| + 1} \min(T) ...

  3. 如何取消Paypal自动付款功能

    在国外在线服务消费肯定会常遇到PayPal的支付方式,有些人可能PayPal有些余额可能会用这个工具来支付,但付款后,可能服务因为不满意而退掉,但第二年却自动续约了?但明明服务已退掉,这该怎么处理呢? ...

  4. tf.reducemean()到底是什么意思?

    https://blog.csdn.net/he_min/article/details/78694383 在tensorflow中经常见到reducemean这个api,到底有什么用,到底是对谁求均 ...

  5. 20175209 《Java程序设计》第六周学习总结

    20175209 <Java程序设计>第六周学习总结 一.教材知识点总结 第七章 内部类与异常类 1.内部类 定义:在一个类中定义的另一个类称作内部类,包含内部类的类成为内部类的外嵌类. ...

  6. 微信小程序之动态获取元素宽高

    我以前一直以为微信小程序不能动态获取view元素的宽高.但是自从看到: wx.createSelectorQuery() 这个api接口,以前的某些问题就能得到解决了... 那么,这个api接口怎么用 ...

  7. HZNU第十二届校赛赛后补题

    愉快的校赛翻皮水! 题解 A 温暖的签到,注意用gets #include <map> #include <set> #include <ctime> #inclu ...

  8. 分布式监控系统开发【day38】:报警阈值程序逻辑解析(三)

    一.需求讨论 1.请问如何解决延迟问题 1000台机器,每1分钟循环一次但是刚好第一次循环第一秒刚处理完了,结果还没等到第二分钟又出问题,你那必须等到第二次循环,假如我这个服务很重要必须实时知道,每次 ...

  9. Entity Framework入门教程(8)---预先加载、延迟加载、显示加载

    1.预先加载 预先加载:在对一种类型的实体进行查询时,将相关的实体作为查询的一部分一起加载.预先加载可以使用Include()方法实现. 1.加载一个相关实体类型 栗子:使用Include()方法从数 ...

  10. 第八节:Task的各类Task<TResult>返回值以及通用线程的异常处理方案。

    一. Task的各种返回值-Task<TResult> PS: 在前面章节,我们介绍了Task类开启线程.线程等待.线程延续的方式,但我们并没有关注这些方式的返回值,其实他们都是有返回值的 ...