> 文章列表 > SpringCloud整合Feign基本使用及源码分析-02

SpringCloud整合Feign基本使用及源码分析-02

SpringCloud整合Feign基本使用及源码分析-02

又是美好的一天呀~
个人博客地址: huanghong.top

往下看看~

  • 服务调用源码分析
    • invoke
    • executeAndDecode
    • targetRequest
      • 根据扩展点进行功能扩展

服务调用源码分析

SpringCloud整合Feign基本使用及源码分析-02

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 12response = 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 {@Overridepublic 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