分布式应用环境下,免不了系统之间的相互调用;在使用 Spring 开发的应用中,在调用其它系统提供 HTTP 接口(Restful风格、非 RPC )时, 经常会使用到一个Spring提供的工具类 RestTemplate , RestTemplate 提供了调用接口的模板抽象及封装;它不仅能让我们随意选择 Http 调用客户端,还提供了定制扩展机制。下面就重点说一下它的扩展机制。
扩展机制 - 过滤器 可以为 RestTemplate 设置过滤器,过滤器可以在请求前 修改或设置 请求信息,在请求结束后,预处理请求结果。RestTemplate 继承了类 InterceptingHttpAccessor ,类 InterceptingHttpAccessor 提供了获取和设置过滤器( ClientHttpRequestInterceptor) 的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public abstract class InterceptingHttpAccessor extends HttpAccessor { private List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>(); public void setInterceptors (List<ClientHttpRequestInterceptor> interceptors) { this .interceptors = interceptors; } public List<ClientHttpRequestInterceptor> getInterceptors () { return interceptors; } }
ClientHttpRequestInterceptor 接口仅包含一个方法 ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)。HttpRequest 是请求信息的一个封装;body 是请求 body 体;ClientHttpRequestExecution 是请求执行器的一个抽象。可以实现此接口,处理请求信息及响应结果。
实现思路 在项目中,有个场景是公司里某个系统A采用的是 Oauth2 认证,每次调用业务,需要调用两次;第一次获取认证结果信息,第二次根据认证结果信息请求业务数据。由于多个项目都要调用系统A,因为认证复杂,并且系统A的响应结果中带有转义字符需要处理;为此每个系统、每个新来的开发人员编写这一块调用代码都会花比较长的时间。所以就在考虑能不能用一种封装的办法来解决此问题达到一劳永逸。主要就是实现过滤器,及相应的请求和响应结果的处理。
请求信息封装
封装认证信息。
认证信息缓存。认证信息有一定的失效时间,缓存起来,可以避免每次请求都要进行认证请求。
业务数据请求认证失败自动重新认证。
定期刷新认证结果信息。
响应结果封装
自动处理认证结果中的转义字符。避免开发人员每次都得特殊处理。
主要实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 public class CustomClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { private AuthInfo authInfo; public CustomClientHttpRequestInterceptor (AuthInfo authInfo) { this .authInfo = authInfo; } @Override public ClientHttpResponse intercept (HttpRequest request, byte [] body, ClientHttpRequestExecution execution) throws IOException { CustomHttpRequest httpRequest = new CustomHttpRequest(request, this .authInfo); ClientHttpResponse originalResponse = execution.execute(httpRequest, body); return new CustomClientHttpResponse(originalResponse); } }public class CustomHttpRequest extends HttpRequestWrapper { private AuthInfo authInfo; public CustomHttpRequest (HttpRequest request, AuthInfo authInfo) { super (request); this .authInfo = authInfo; } @Override public URI getURI () { URI originalURI = super .getURI(); URI newURI = null ; return uri; } @Override public HttpHeaders getHeaders () { HttpHeaders headers = super .getHeaders(); return headers; } }@CommonsLog public class CustomClientHttpResponse implements ClientHttpResponse { private ClientHttpResponse originalResponse; private InputStream handledInputStream; public CustomClientHttpResponse (ClientHttpResponse originalResponse) { this .originalResponse = originalResponse; } @Override public InputStream getBody () throws IOException { if (handledInputStream != null ) { return handledInputStream; } if (originalResponse != null ) { InputStream originalInputStream = originalResponse.getBody(); String content = IOUtils.toString(originalInputStream, RESPONSE_CHARSET); handledInputStream = IOUtils.toInputStream(content, RESPONSE_CHARSET); } return handledInputStream; } @Override public void close () { originalResponse.close(); if (handledInputStream != null ) { try { handledInputStream.close(); } catch (IOException e) { log.error("handledInputStream() close error: " + e.getMessage()); e.printStackTrace(); } } } }