此文为《Spring Cloud、Nginx高并发核心编程_尼恩 (作者) _机械工业出版社》 关于FEIGN部分的读书笔记,不太清楚的地方又在网上另外找了资料
一、简介
Feign是Spring Cloud 中用于 RPC调用的模块。
Feign的作用
在应用启动的初始化过程中:
(1)对于每一个RPC远程调用Java接口,Feign根据@FeignClient注解生成本地JDK动态代理实例。
(2)对于Java接口中的每一个RPC远程调用方法,Feign首先根据Spring MVC(如@GetMapping)类型注解生成方法处理器MethodHandler实例,该实例内部包含一个请求模板RequestTemplate实例。
在远程调用REST请求执行的过程中:
(1)Feign使用远程方法调用的实际参数替换掉RequestTemplate模板实例中的参数,生成最终的HTTP请求。
(2)将HTTP请求通过feign.Client客户端实例发送到Provider服务端。
Feign整体运行流程
1.通过应用启动类上的@EnableFeignClients注解开启Feign的装配和远程代理实例创建。
在@EnableFeignClients注解源码中可以看到导入了FeignClientsRegistrar类,该类用于扫描@FeignClient注解过的RPC接口。
2.通过对@FeignClient注解RPC接口扫描创建远程调用的动态代理实例。
FeignClientsRegistrar类会进行包扫描,扫描所有包下@FeignClient注解过的接口,创建RPC接口的FactoryBean工厂类实例,并将这些FactoryBean注入Spring IOC容器中。
如果应用某些地方需要注入RPC接口的实例(比如被@Resource引用),Spring就会通过注册的FactoryBean工厂类实例的getObject()方法获取RPC接口的动态代理实例。
在创建RPC接口的动态代理实例时,Feign会为每一个RPC接口创建一个调用处理器InvocationHandler,也会为接口的每一个RPC方法创建一个方法处理器MethodHandler,并且将方法处理器缓存在调用处理器InvocationHandler的dispatch映射成员中。
在创建动态代理实例时,Feign也会通过RPC方法的注解为每一个RPC方法生成一个RequesTemplate请求模板实例,RequestTemplate中包含请求的所有信息,如请求URL、请求类型(如GET)、请求参数等。
3.发生RPC调用时,通过动态代理实例类完成远程Provider的HTTP调用。
当动态代理实例类的方法被调用时,Feign会根据RPC方法的反射实例从调用处理器InvocationHandler的dispatch成员中取得方法处理器MethodHandler,然后由MethodHandler方法处理器开始HTTP请求处理。
MethodHandler会结合实际的调用参数,通过RequesTemplate模板实例生成Request请求实例。最后,将Request请求实例交给feign.Client客户端实例进一步完成HTTP请求处理。
4.在完成远程HTTP调用前需要进行客户端负载均衡的处理。
在Spring Cloud微服务架构中,同一个Provider微服务一般都会运行多个实例,所以说客户端的负载均衡能力其实是必选项,而不是可选项。
生产环境下,Feign必须和Ribbon结合在一起使用,所以方法处理器MethodHandler的客户端client成员必须是具备负载均衡能力的LoadBalancerFeignClient类型,而不是完成HTTP请求提交的ApacheHttpClient等类型。
只有在负载均衡计算出最佳的Provider实例之后,才能开始HTTP请求的提交。
在LoadBalancerFeignClient内部有一个delegate委托成员,其类型可能为feign.client.Default、ApacheHttpClient、OkHttpClient等,最终由该delegate客户端委托成员完成HTTP请求的提交。
Feign远程调用的完整流程
注意:Feign的远程调用是同步的
二、Feign运作流程的每一个环节详细介绍
1.启动、扫描、注册
springboot自动装配
用于测试的服务调用者假定叫一个order服务,它需要RPC调用Item服务。
order服务中有一个RPC远程调用Java接口
@FeignClient(value = "app-item",fallback = ItemServiceFallback.class)
public interface ItemFeignClient {
@RequestMapping(value = "/item/{id}", method = RequestMethod.GET)
Item queryItemById(@PathVariable("id") Long id);
@RequestMapping(value = "/item/hello", method = RequestMethod.GET)
String hello(@PathVariable("echo") String echo);
}
order项目的启动类
有一个@EnableFeignClients注解
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrix
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class, args);
}
/**
* 向Spring容器中定义RestTemplate对象
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
}
@EnableFeignClients注解
它里面@Import了FeignClientsRegistrar.class类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
......省略
}
FeignClientsRegistrar类
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
private ResourceLoader resourceLoader;
private Environment environment;
public FeignClientsRegistrar() {
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
......还有一些内部调用的方法
}
可以看到它 实现了ImportBeanDefinitionRegistrar类。
其中registerBeanDefinitions方法会在spring启动的时候被调用
具体该方法是在什么时候被调用呢?
AbstractApplicationContext.refresh()-------->
AbstractApplicationContext.invokeBeanFactoryPostProcessors()-------->
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()-------->
PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors()-------->
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry()-------->
ConfigurationClassPostProcessor.processConfigBeanDefinitions()-------->
ConfigurationClassBeanDefinitionReader.loadBeanDefinitions()-------->
ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass()-------->
ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars()-------->
FeignClientsRegistrar.registerBeanDefinitions()
FeignClientsRegistrar.registerBeanDefinitions方法的执行内容
1.扫描所有包下 @FeignClient注解过的接口
比如这里就是扫描ItemFeignClient接口
2.为扫描到的每一个RPC客户端接口注册一个beanDefinition实例(bean的),其中beanClass为FeignClientFactoryBean。有兴趣可以看一下这个类。
3.并将这些FactoryBean注入Spring IOC容器中。
此时已经把@FeignClient(value = "app-item",fallback = ItemServiceFallback.class)中的attributes解析出来了
2.创建代理
前置调用链:
当有地方@Resource注入这个远程调用接口ItemFeignClient时,会进行依赖注入,getBean()实例化这个ItemFeignClient接口
AbstractBeanFactory.getBean()-------->AbstractBeanFactory.doGetBean()-------->AbstractAutowireCapableBeanFactory.getObjectForBeanInstance()-------->
AbstractBeanFactory.getObjectForBeanInstance()-------->FactoryBeanRegistrySupport.getObjectFromFactoryBean()-------->
FactoryBeanRegistrySupport.doGetObjectFromFactoryBean()-------->FeignClientFactoryBean.getObject()
创建代理具体的方法调用链:
1.当FeignClientFactoryBean.getObject()方法被调用时
其调用的getTarget()方法首先从IOC容器获取配置好的Feign.Builder建造者容器实例
然后通过Feign.Builder.target()方法完成RPC动态代理实例的创建。
然而Feign.Builder的实例是怎么来的呢?
Feign.Builder建造者容器实例在自动配置类FeignClientsConfiguration中完成配置,通过其源码可以看到,配置类的feignBuilder(...)方法通过调用Feign.builder()静态方法创建了一个Feign.Builder建造者容器实例。
2.Feign.Builder.target()实例方法
首先调用内部的build()方法创建一个Feign实例(默认情况下是ReflectiveFeign(反射式Feign)实例)
然后通过ReflectiveFeign.newInstance(...)方法创建远程接口最终的JDK动态代理实例。
ReflectiveFeign.newInstance()方法内部创建代理的具体逻辑
源码:
@Override
public <T> T newInstance(Target<T> target) {
//1.方法解析和创建方法处理器MethodHandler
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
//2.创建方法反射实例和方法处理器映射
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)));
}
}
//3.创建一个JDK调用处理器FeignInvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
//4.创建一个动态代理对象
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
做了几件事:
1.方法解析和创建方法处理器MethodHandler
调用了ParseHandlersByName.apply()方法,解析RPC接口中的所有RPC方法配置(通过Contract解析),为每个RPC方法创建一个对应的MethodHandler方法处理器。
然后进行方法名称和方法处理器的Key-Value键值映射nameToHandler。
ParseHandlersByName是方法解析器,它是ReflectiveFeign类的一个内部类。
那么ParseHandlersByName是怎么创建方法处理器MethodHandler的呢?
方法解析器ParseHandlersByName创建方法处理器的过程是通过方法处理器工厂类实例factory的create()方法完成的。
而默认的方法处理器工厂类Factory定义在SynchronousMethodHandler类中。
这里创建的方法处理器就是同步方法处理器SynchronousMethodHandler的实例。
2.创建方法反射实例和方法处理器映射
通过方法名称和方法处理器的映射nameToHandler创建一个方法反射实例到方法处理器的Key-Value映射methodToHandler,
作为方法远程调用时的分发处理映射实例。
3.创建一个JDK调用处理器FeignInvocationHandler
主要以methodToHandler为参数,创建一个InvocationHandler调用处理器实例。
它的创建也是通过工厂模式完成的。默认的InvocationHandler实例是通过InvocationHandlerFactory工厂类完成的。
4.创建一个动态代理对象。
调用JDK的Proxy.newProxyInstance()方法创建一个动态代理实例,
它的参数有3个:RPC远程接口的类装载器、RPC远程接口的Class实例以及上一步创建的FeignInvocationHandler调用处理器实例。
流程图
Contract远程调用协议规则类
那么ParseHandlersByName是怎么解析方法及方法配置的呢?
ParseHandlersByName.apply()方法源码
public Map<String, MethodHandler> apply(Target key) {
//1.通过Contract协议规则类将远程调用Feign接口中的所有方法配置和注解解析成一个List<MethodMetadata>方法元数据列表
List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
//2.为每个方法生成对应的RequestTemplate.Factory
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder);
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder);
} else {
buildTemplate = new BuildTemplateByResolvingArgs(md);
}
result.put(md.configKey(),
factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
}
return result;
}
做了几件事
1.通过Contract协议规则类将远程调用Feign接口中的所有方法配置和注解解析成一个List<MethodMetadata>方法元数据列表
2.为每个方法生成对应的RequestTemplate.Factory
Contract协议规则类与方法解析器、调用处理器的关系图
关于RPC接口的配置解析类,Spring Cloud Feign中有两个协议规则解析类:
一个为Feign默认协议规则解析类(DefaultContract),用于解析使用Feign默认的配置注解比如@RequestLine注解。
另一个为SpringMvcContract协议规则解析类,用于解析使用了Spring MVC规则配置的RPC方法。
Feign 默认的协议规范
注解 | 接口Target | 使用说明 |
@RequestLine | 方法上 | 定义HttpMethod 和 UriTemplate. UriTemplate 中使用{} 包裹的表达式,可以通过在方法参数上使用@Param 自动注入 |
@Param | 方法参数 | 定义模板变量,模板变量的值可以使用名称的方式使用模板注入解析 |
@Headers | 类上或者方法上 | 定义头部模板变量,使用@Param 注解提供参数值的注入。如果该注解添加在接口类上,则所有的请求都会携带对应的Header信息;如果在方法上,则只会添加到对应的方法请求上 |
@QueryMap | 方法上 | 定义一个键值对或者 pojo,参数值将会被转换成URL上的 query 字符串上 |
@HeaderMap | 方法上 | 定义一个HeaderMap, 与 UrlTemplate 和HeaderTemplate 类型,可以使用@Param 注解提供参数值 |
3.RPC调用执行流程
1.与默认的调用处理器FeignInvocationHandler相关的RPC执行流程
FeignInvocationHandler是默认的调用处理器,如果没有进行特殊的配置,那么Feign将默认使用此调用处理器。
时序图
具体流程
(1)通过Spring IOC容器实例完成动态代理实例的装配。
前文讲到,Feign在启动时会为加上了@FeignClient注解的所有远程接口(包括DemoClient接口)创建一个FactoryBean工厂实例,并注册到Spring IOC容器。
然后在uaa-provider的DemoRPCController控制层类中,通过@Resource注解从Spring IOC容器找到FactoryBean工厂实例,通过其getObject()方法获取到动态代理实例,装配给DemoRPCController实例的成员变量demoClient。
在需要进行hello()远程调用时,直接通过demoClient成员变量调用JDK动态代理实例的hello()方法。
(2)执行InvocationHandler调用处理器的invoke(...)方法。
前面讲到,JDK动态代理实例的方法调用过程是通过委托给InvocationHandler调用处理器完成的,故在调用demoClient的hello()方法时,会调用到它的调用处理器FeignInvocationHandler实例的invoke(...)方法。
FeignInvocationHandler实例内部保持了一个远程调用方法反射实例和方法处理器的dispatch映射。
FeignInvocationHandle在它的invoke(...)方法中会根据hello()方法的Java反射实例在dispatch映射对象中找到对应的MethodHandler方法处理器,然后由后
者完成实际的HTTP请求和结果的处理。
(3)执行MethodHandler方法处理器的invoke(...)方法。
feign默认的方法处理器为SynchronousMethodHandler同步调用处理器,它的invoke(...)方法主要通过内部feign。
Client类型的client成员实例完成远程URL请求执行和获取远程结果。
feign.Client客户端有多种类型,不同的类型完成URL请求处理的具体方式不同。
(4)通过feign.Client客户端成员完成远程URL请求执行和获取远程结果。
如果MethodHandler方法处理器client成员实例是默认的feign.Client.Default实现类,就通过JDK自带的HttpURLConnnection类完成远程URL请求执行和获取远程结果。
如果MethodHandler方法处理器实例的client客户端是ApacheHttpClient客户端实现类,就使用ApacheHttpClient开源组件完成远程URL请求执行和获取远程结果。
如果MethodHandler方法处理器实例的client客户端是LoadBalancerFeignClient负载均衡客户端实现类,就使用Ribbon结算出最佳的Provider节点,然后由内部的delegate委托客户端成员去请求Provider服务,完成URL请求处理。
FeignInvocationHandler源码
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;
FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
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();
}
return dispatch.get(method).invoke(args);
}
}
其实就做了一件事:从dispatch中得到当前要远程调用的method对应的MethodHandler,然后调用MethodHandler的.invoke方法
SynchronousMethodHandler源码
final class SynchronousMethodHandler implements MethodHandler {
private static final long MAX_RESPONSE_BUFFER_SIZE = 8192L;
private final MethodMetadata metadata;
private final Target<?> target;
private final Client client;
private final Retryer retryer;
private final List<RequestInterceptor> requestInterceptors;
private final Logger logger;
private final Logger.Level logLevel;
private final RequestTemplate.Factory buildTemplateFromArgs;
private final Options options;
private final Decoder decoder;
private final ErrorDecoder errorDecoder;
private final boolean decode404;
@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) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
}
做了几件事:
1.使用该方法对应的RequestTemplate.Factory创建RequestTemplate
2.调用executeAndDecode方法执行远程调用请求并且对response解码
如果捕获到可重试的异常,则根据Retryer(Feign 内置的重试器)的重试规则重试
这里可以看到 SynchronousMethodHandler的invoke方法中是有retryer重试机制的。
重试器有如下几个控制参数:
重试参数 | 说明 | 默认值 |
period | 初始重试时间间隔,当请求失败后,重试器将会暂停 初始时间间隔(线程 sleep 的方式)后再开始,避免强刷请求,浪费性能 | 100ms |
maxPeriod | 当请求连续失败时,重试的时间间隔将按照:long interval = (long) (period * Math.pow(1.5, attempt - 1)); 计算,按照等比例方式延长,但是最大间隔时间为 maxPeriod, 设置此值能够避免 重试次数过多的情况下执行周期太长 | 1000ms |
maxAttempts | 最大重试次数 | 5 |
executeAndDecode(…) 请求执行和结果解码方法源码
Object executeAndDecode(RequestTemplate template) throws Throwable {
//1.通过 RequestTemplate 请求模板实例,生成远程URL请求实例 request;
Request request = targetRequest(template);
//在发送和接收请求的时候,Feign定义了统一的日志门面来输出日志信息
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
//2.用自己的 feign 客户端client成员,excecute(…) 执行请求,并且获取 response 响应;
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).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);
boolean shouldClose = true;
try {
if (logLevel != Logger.Level.NONE) {
response =
logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();
}
if (Response.class == metadata.returnType()) {
if (response.body() == null) {
return response;
}
if (response.body().length() == null ||
response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
shouldClose = false;
return response;
}
// Ensure the response body is disconnected
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();
}
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
return decode(response);
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
return decode(response);
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
}
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
该方法的工作步骤:
(1)targetRequest(RequestTemplate template)
通过 RequestTemplate 请求模板实例,生成远程URL请求实例 request;
这个方法里面会先把List<RequestInterceptor>每个请求连接器的apply方法都执行一遍(看起来是做请求鉴权和encode的)
(2)调用自己的 feign 客户端client成员,excecute(…) 执行请求,并且获取 response 响应;
这里我们细看一下LoadBalancerFeignClient.execute 生产上肯定是用这个,跟ribbon结合做负载的
(3)对response 响应进行结果解码。
日志的输出的四个等级
在发送和接收请求的时候,Feign定义了统一的日志门面来输出日志信息
级别 | 说明 |
NONE | 不做任何记录 |
BASIC | 只记录输出Http 方法名称、请求URL、返回状态码和执行时间 |
HEADERS | 记录输出Http 方法名称、请求URL、返回状态码和执行时间 和 Header 信息 |
FULL | 记录Request 和Response的Header,Body和一些请求元数据 |
编解码器
Encoder/ Decoder 实现 | 说明 |
JacksonEncoder,JacksonDecoder | 基于 Jackson 格式的持久化转换协议 |
GsonEncoder,GsonDecoder | 基于Google GSON 格式的持久化转换协议 |
SaxEncoder,SaxDecoder | 基于XML 格式的Sax 库持久化转换协议 |
JAXBEncoder,JAXBDecoder | 基于XML 格式的JAXB 库持久化转换协议 |
ResponseEntityEncoder,ResponseEntityDecoder | Spring MVC 基于 ResponseEntity< T > 返回格式的转换协议 |
SpringEncoder,SpringDecoder | 基于Spring MVC HttpMessageConverters 一套机制实现的转换协议 ,应用于Spring Cloud 体系中 |
LoadBalancerFeignClient.execute源码
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
做了几件事:
1.组装URI
2.新建FeignLoadBalancer.RibbonRequest,它里面持有真正的HTTPCLIENT,默认是JDK的HTTPCLIENT
3.组装请求配置,即ConnectTimeout、ReadTimeout
4.调用符合负载策略的服务提供方(这里另外一章讲ribbon的时候细说)
2.与Hystrix调用处理器HystrixInvocationHandler相关的RPC执行流程
它带有熔断的、隔离、回退的能力,看一下HystrixInvocationHandler源码中的Invoke方法吧
源码解读
final class HystrixInvocationHandler implements InvocationHandler {
private final Target<?> target;
private final Map<Method, MethodHandler> dispatch;
private final FallbackFactory<?> fallbackFactory; // Nullable
private final Map<Method, Method> fallbackMethodMap;
private final Map<Method, Setter> setterMethodMap;
HystrixInvocationHandler(Target<?> target, Map<Method, MethodHandler> dispatch,
SetterFactory setterFactory, FallbackFactory<?> fallbackFactory) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
this.fallbackFactory = fallbackFactory;
this.fallbackMethodMap = toFallbackMethod(dispatch);
this.setterMethodMap = toSetters(setterFactory, target, dispatch.keySet());
}
static Map<Method, Method> toFallbackMethod(Map<Method, MethodHandler> dispatch) {
Map<Method, Method> result = new LinkedHashMap<Method, Method>();
for (Method method : dispatch.keySet()) {
method.setAccessible(true);
result.put(method, method);
}
return result;
}
static Map<Method, Setter> toSetters(SetterFactory setterFactory, Target<?> target,
Set<Method> methods) {
Map<Method, Setter> result = new LinkedHashMap<Method, Setter>();
for (Method method : methods) {
method.setAccessible(true);
result.put(method, setterFactory.create(target, method));
}
return result;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
// early exit if the invoked method is from java.lang.Object
// code is the same as ReflectiveFeign.FeignInvocationHandler
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();
}
HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setterMethodMap.get(method)) {
@Override
protected Object run() throws Exception {
try {
return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
} catch (Exception e) {
throw e;
} catch (Throwable t) {
throw (Error) t;
}
}
@Override
protected Object getFallback() {
if (fallbackFactory == null) {
return super.getFallback();
}
try {
Object fallback = fallbackFactory.create(getExecutionException());
Object result = fallbackMethodMap.get(method).invoke(fallback, args);
if (isReturnsHystrixCommand(method)) {
return ((HystrixCommand) result).execute();
} else if (isReturnsObservable(method)) {
// Create a cold Observable
return ((Observable) result).toBlocking().first();
} else if (isReturnsSingle(method)) {
// Create a cold Observable as a Single
return ((Single) result).toObservable().toBlocking().first();
} else if (isReturnsCompletable(method)) {
((Completable) result).await();
return null;
} else {
return result;
}
} catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an interface
throw new AssertionError(e);
} catch (InvocationTargetException e) {
// Exceptions on fallback are tossed by Hystrix
throw new AssertionError(e.getCause());
}
}
};
if (isReturnsHystrixCommand(method)) {
return hystrixCommand;
} else if (isReturnsObservable(method)) {
// Create a cold Observable
return hystrixCommand.toObservable();
} else if (isReturnsSingle(method)) {
// Create a cold Observable as a Single
return hystrixCommand.toObservable().toSingle();
} else if (isReturnsCompletable(method)) {
return hystrixCommand.toObservable().toCompletable();
}
return hystrixCommand.execute();
}
}
invoke(...)方法会创建hystrixCommand命令实例,
对从dispatch获取的SynchronousMethodHandler实例进行封装,
然后对RPC方法实例method进行判断,判断是直接返回hystrixCommand命令实例,还是立即执行其execute()方法。
默认情况下,都是立即执行它的execute()方法。
如果MethodHandler内的RPC调用出现异常,比如远程server宕机、网络延迟太大而导致请求超时、远程server来不及响应等,hystrixCommand命令器就会调
用失败回调方法getFallback()返回回退结果。
而hystrixCommand的getFallback()方法最终会调用配置在RPC接口@FeignClient注解的fallback属性上的失败回退类中对应的回退方法,执行业务级别的失败回退处理。
时序图
总体来说,使用HystrixInvocationHandler处理器的执行流程与使用FeignInvocationHandler默认的调用处理器相比大致是相同的。
不同的是,HystrixInvocationHandler增加了RPC的保护机制。 至于是怎么增加保护机制的,这个另外一篇讲Hystrix的再细说