结合上一篇 如何配合RestTemplate优雅的记录请求以及返回的信息 我们可以很方便的利用restTemplate提供的Interceptor记录信息,出于经验的问题我们是不是也可以通过OpenFeign找到它的Interceptor然后这么实现的呢?其实不然。
我们可以通过@EnableFeignClients
看到@Import(FeignClientsRegistrar.class)
@Import注解将指定的类作为Bean注入到Spring容器中。FeignClientsRegistrar
主要是根据定义路径加载扫描被FeignClient
相应的类,注入bean之后,通过jdk的代理,当请求Feign Client的方法时会被拦截
本文主要分析为什么不能利用openFeign提供的
RequestInterceptor
进行处理,因为这个Interceptor
和RestTemplate#Interceptor
区别很大,他只能做请求前的处理(eg: 接口签名、统一标示、认证信息等等)。OpenFeign原理 原理不作为本文的主题。
代码 ReflectiveFeign.class#newInstance(Target
public T newInstance(Target target) {
Map nameToHandler = targetToHandlersByName.apply(target);
Map methodToHandler = new LinkedHashMap();
List defaultMethodHandlers = new LinkedList();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
// ParseHandlersByName.class#apply(Target key)
public Map apply(Target key) {
List metadata = contract.parseAndValidatateMetadata(key.type());
Map result = new LinkedHashMap();
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else {
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
}
result.put(md.configKey(),
factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
}
return result;
}
可以看出为每个方法生成一个代理类 factory.create...
每当目标方法调用时都会被SynchronousMethodHandler
进行处理根据参数生成RequestTemplate
对象。SynchronousMethodHandler.class#invoke(Object[] argv)
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
response = client.execute(request, options);
...
}
Request targetRequest(RequestTemplate template) {
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
return target.apply(template);
}
看到RequestInterceptor
被调用的地方,所以Feign
的RequestInterceptor
是在请求前在创建RequestTemplate
的时候。
我们看到封装完请求信息。response = client.execute(request, options);
才是执行request请求以及接收response响应。Client.java
。重写这个client,spring 容器启动的时候创建我们重写的client 便可以实现。那么是没有大问题。但是为什么要自己重写的呢?毕竟不一定都会有这种需求。官方也没给出解释,只是建议重写client。根据上一篇restTemplate记录信息,我们是不是可以按照restTemplate写法(责任链模式)进行封装扩展造轮子呢?结果可能让你很失望。因为我们没有类似BufferingClientHttpRequestFactory
东西进行流copy,因为feign提供的Response.class
是final类型的,我们没有办法通过自己进行流copy,这个准备提个issues问问。自己重写Client 代码如下:
/**
* @author liweigao
* @date 2019/8/26 上午10:17
*/
@Slf4j
public class SuperClient extends Client.Default {
private static final String CONTENT_TYPE = "Content-Type";
/**
* Null parameters imply platform defaults.
*
* @param sslContextFactory
* @param hostnameVerifier
*/
public SuperClient(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
super(sslContextFactory, hostnameVerifier);
}
@Override
public Response execute(Request request, Request.Options options) throws IOException {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
String errorMsg = null;
BufferingFeignClientResponse bufferingFeignClientResponse = null;
try {
bufferingFeignClientResponse = new BufferingFeignClientResponse(super.execute(request, options));
} catch (Exception e) {
log.error(e.getMessage(), e);
errorMsg = e.getMessage();
throw e;
} finally {
stopWatch.stop();
// request
Map reqMap = null;
byte[] body = request.body();
Charset charset = Objects.isNull(request.charset()) ? Charset.defaultCharset() : request.charset();
HttpHeaders httpHeaders = convert(request.headers());
String reqStr = Strings.EMPTY;
MediaType reqMediaType;
if (Objects.nonNull(reqMediaType = httpHeaders.getContentType())) {
if (reqMediaType.includes(MediaType.MULTIPART_FORM_DATA)) {
body = new byte[]{0};
}
if (Objects.nonNull(body)) {
reqStr = new String(body, charset);
}
if ((reqMediaType.includes(MediaType.APPLICATION_JSON_UTF8) || reqMediaType.includes(MediaType.APPLICATION_JSON))) {
//json format paramters
try {
reqMap = JSON.parseObject(reqStr);
reqStr = null;
//no care this exception
} catch (JSONException e) {
}
}
}
//response
Map respMap = null;
String respStr = null;
int resStatus;
Collection collection;
if (Objects.nonNull(bufferingFeignClientResponse)) {
if (Objects.nonNull(bufferingFeignClientResponse.getHeaders()) && !CollectionUtils.isEmpty(collection =
bufferingFeignClientResponse.getHeaders().get(CONTENT_TYPE))) {
StringBuilder resBody = new StringBuilder();
try (BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(bufferingFeignClientResponse.getBody(),
charset))) {
String line = bufferedReader.readLine();
while (line != null) {
resBody.append(line);
line = bufferedReader.readLine();
}
}
if (!collection.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) {
respStr = resBody.toString();
}
if (collection.contains(MediaType.APPLICATION_JSON_VALUE) || collection.contains(MediaType.APPLICATION_JSON)) {
try {
respMap = JSON.parseObject(reqStr);
respStr = null;
//no care this exception
} catch (JSONException e) {
}
}
}
resStatus = bufferingFeignClientResponse.getRawStatusCode();
} else {
resStatus = HttpStatus.INTERNAL_SERVER_ERROR.value();
respStr = errorMsg;
}
RestLog.builder().costTime(stopWatch.getLastTaskTimeMillis()).headers(httpHeaders)
.method(request.method()).reqBody(reqStr).resJson(respMap).reqJson(reqMap).reqUrl(request.url())
.resBody(respStr).resStatus(resStatus).build().print();
}
Response response = bufferingFeignClientResponse.getResponse().toBuilder()
.body(bufferingFeignClientResponse.getBody(),
bufferingFeignClientResponse.getResponse().body().length()).build();
bufferingFeignClientResponse.close();
return response;
}
private HttpHeaders convert(Map> headers) {
HttpHeaders httpHeaders = new HttpHeaders();
if (Objects.nonNull(headers)) {
headers.forEach((k, v) -> {
httpHeaders.set(k, convert(v));
});
}
return httpHeaders;
}
private String convert(Collection strings) {
StringBuilder builder = new StringBuilder();
strings.forEach(s -> {
builder.append(s).append(",");
});
//去除末尾逗号
if (builder.length() > 0) {
builder.delete(builder.length() - 1, builder.length());
}
return builder.toString();
}
final class BufferingFeignClientResponse implements Closeable {
private Response response;
@Nullable
private byte[] body;
public BufferingFeignClientResponse(Response response) {
this.response = response;
}
public HttpStatus getStatusCode() {
return HttpStatus.valueOf(this.response.status());
}
public Response getResponse() {
return this.response;
}
public int getRawStatusCode() {
return this.response.status();
}
public String getStatusText() {
return HttpStatus.valueOf(this.response.status()).name();
}
public Map> getHeaders() {
return this.response.headers();
}
public InputStream getBody() throws IOException {
if (this.body == null) {
this.body = StreamUtils.copyToByteArray(this.response.body().asInputStream());
}
return new ByteArrayInputStream(this.body);
}
@Override
public void close() {
this.response.close();
}
}