技巧分享丨spring的RestTemplate的妙用,你知道吗?

时间:2019-09-09 07:00:01 来源:今日理财 当前位置:星爱宠乐 > 国际 > 手机阅读

为什么要使用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。虽然使用它发送HTTP请求确实挺方便的,但是使用它调用RESTful接口好像也挺麻烦的!

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

RestTemplate template = ...

// 请求地址

String url = ...

// 请求参数

UserParams request = ...

// 执行POST请求

User u = template.postForObject(url, request, User.class);

...

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

技巧分享丨spring的RestTemplate的妙用,你知道吗?

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

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 requestEntity = new HttpEntity<>(headers);

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

写在最后

看到这里,点个赞,评论,转入,关注吧!

国际本月排行

国际精选