SpringCloud整合Feign基本使用及源码分析-02
又是美好的一天呀~
个人博客地址: huanghong.top
往下看看~
- 服务调用源码分析
- invoke
- executeAndDecode
- targetRequest
- 根据扩展点进行功能扩展
服务调用源码分析
FeignClient实例为动态代理创建的对象,当进行服务调用FeignClient的接口方法就会被FeignInvocationHandler的invoke方法拦截。
//feign.ReflectiveFeign.FeignInvocationHandler#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//equals、hashCode、toString方法调用的处理
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
//根据调用方法名获取对应MethodHandler来执行请求
return dispatch.get(method).invoke(args);
}
invoke
//feign.SynchronousMethodHandler#invoke
public Object invoke(Object[] argv) throws Throwable {
//根据方法入参及接口方法对应处理器属性构建一个Http请求模版
RequestTemplate template = buildTemplateFromArgs.create(argv);
//获取请求相关超时参数
Options options = findOptions(argv);
//获取请求重试器
Retryer retryer = this.retryer.clone();
//循环请求
while (true) {
try {
//执行请求并解码响应内容
return executeAndDecode(template, options);
} catch (RetryableException e) {
//503响应值会触发返回ErrorDecoder
//请求抛出RetryableException异常才会触发重试,非RetryableException会抛出异常不进行重试
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;
}
}
}
executeAndDecode
//feign.SynchronousMethodHandler#executeAndDecode
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
//request进行拦截处理,创建一个全新的request对象
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
//执行http请求
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 12
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
//decoder不为空则对响应内容进行解码直接返回
if (decoder != null)
return decoder.decode(response, metadata.returnType());
//decoder为空,则使用feign.optionals.OptionalDecoder进行异步解码返回
CompletableFuture<Object> resultFuture = new CompletableFuture<>();
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
metadata.returnType(),
elapsedTime);
try {
if (!resultFuture.isDone())
throw new IllegalStateException("Response handling not done");
return resultFuture.join();
} catch (CompletionException e) {
Throwable cause = e.getCause();
if (cause != null)
throw cause;
throw e;
}
}
targetRequest
RequestInterceptor是Feign提供的扩展点,当实际开发过程中远程服务调用有设置请求头相关参数时,由于Feign的远程调用会构建一个全新的Request对象,导致原请求相关信息会被移除,这个时候可以通过RequestInterceptor来手动添加原请求相关参数,避免请求信息丢失的情况。
Request targetRequest(RequestTemplate template) {
//request扩展,对请求进行拦截处理
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
//获取全新的Request对象
return target.apply(template);
}
根据扩展点进行功能扩展
解决Feign远程调用请求头丢失问题
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
requestTemplate.header(name, values);
}
}
}
}
}
Feign异步情况丢失上下文问题
//问题主要出在 RequestContextHolder.getRequestAttributes()
public static RequestAttributes getRequestAttributes() {
//它是从requestAttributesHolder这里面取出来的
RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get();
if (attributes == null) {
attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();
}
return attributes;
}
//requestAttributesHolder是一个NamedThreadLocal对象
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");
//NamedThreadLocal继承自ThreadLocal
//而ThreadLocal是一个线程局部变量,在不同线程之间是独立的所以我们获取不到原先主线程的请求属性,即给请求头添加cookie失败
public class NamedThreadLocal<T> extends ThreadLocal<T> {
private final String name;
public NamedThreadLocal(String name) {
Assert.hasText(name, "Name must not be empty");
this.name = name;
}
public String toString() {
return this.name;
}
}
//解决方案
// 1.获取之前的请求头数据
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
//2.每一个线程都共享之前的请求数据
RequestContextHolder.setRequestAttributes(requestAttributes);
//远程查询
....
}, executor);
感谢阅读完本篇文章!!!
个人博客地址: huanghong.top