结合上一篇 如何配合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<T> target)
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
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<String, MethodHandler> apply(Target key) {
List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
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
的时候。
那么怎么才能让OpenFeign记录请求和响应日志呢。
我们看到封装完请求信息。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";
private Response response;
@Nullable
private byte[] body;
/**
* 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();
this.response = super.execute(request, options);
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 = null;
MediaType reqMediaType;
if (Objects.nonNull(reqMediaType = httpHeaders.getContentType())) {
if (reqMediaType.includes(MediaType.MULTIPART_FORM_DATA)) {
body = new byte[]{0};
}
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 repMap = null;
String resStr = null;
Collection<String> collection;
if (Objects.nonNull(this.response.headers()) && !CollectionUtils.isEmpty(collection =
this.response.headers().get(CONTENT_TYPE))) {
StringBuilder resBody = new StringBuilder();
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(getBody(), charset))) {
String line = bufferedReader.readLine();
while (line != null) {
resBody.append(line);
line = bufferedReader.readLine();
}
}
if (!collection.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) {
resStr = resBody.toString();
}
if (collection.contains(MediaType.APPLICATION_JSON_VALUE) || collection.contains(MediaType.APPLICATION_JSON)) {
try {
repMap = JSON.parseObject(reqStr);
resStr = null;
//no care this exception
} catch (JSONException e) {
}
}
}
RestLog.builder().costTime(stopWatch.getLastTaskTimeMillis()).headers(httpHeaders)
.method(request.method()).reqBody(reqStr).resJson(repMap).reqJson(reqMap).reqUrl(request.url())
.resBody(resStr).resStatus(this.response.status()).build().print();
Response response = this.response.toBuilder().body(getBody(), this.response.body().length()).build();
close();
return response;
}
private HttpHeaders convert(Map<String, Collection<String>> headers) {
HttpHeaders httpHeaders = new HttpHeaders();
if (Objects.nonNull(headers)) {
headers.forEach((k, v) -> {
httpHeaders.set(k, convert(v));
});
}
return httpHeaders;
}
private String convert(Collection<String> strings) {
StringBuilder builder = new StringBuilder();
strings.forEach(s -> {
builder.append(s).append(",");
});
//去除末尾逗号
if (builder.length() > 0) {
builder.delete(builder.length() - 2, builder.length() - 1);
}
return builder.toString();
}
private InputStream getBody() throws IOException {
if (this.body == null) {
this.body = StreamUtils.copyToByteArray(this.response.body().asInputStream());
}
return new ByteArrayInputStream(this.body);
}
private void close() {
if (this.response != null) {
this.response.close();
}
}
}