Spring Openfeign作为一个声明式的REST Client,可以为应用中,尤其是微服务之间的调用上节省很多工作量,同时跟同为Netflix体系的Ribbon和Hystrix整合使用,可以为系统提供客户端负载均衡以及熔断保障。
以下内容参阅 spring-boot-2.5.x/Hystrix-2.0.x/spring-cloud-openfeign-2.0.x/feign-feign-11
配置
代码语言:txt复制通过META/spring.factories加载的配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,
org.springframework.cloud.openfeign.FeignAutoConfiguration,
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration
openfeign META-INF/spring.factories有上述配置(spring ioc在初始化过程中会自动加载这些配置,其原理和Java SPI机制基本一致,你可以参阅SpringApllication#getSpringFactoriesInstances)。
1.FeignRibbonClientAutoConfiguration Ribbon配置
代码语言:txt复制@ConditionalOnClass({ ILoadBalancer.class, Feign.class }) //引入Feign和Ribbon依赖时本配置生效
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class) //在FeignAutoConfiguration之前加载
@EnableConfigurationProperties({ FeignHttpClientProperties.class }) //让FeignHttpClientProperties.class生效
//Order is important here, last should be the default, first should be optional
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
- @AutoConfigureBefore(FeignAutoConfiguration.class)说明这个配置将在FeignAutoConfiguration之前加载。
- @EnableConfigurationProperties ({ FeignHttpClientProperties.class }),让FeignHttpClientProperties.class生效,该类读取feign.httpclient开头的相关配置
- @Import{...} 加载FeignLoadBanlancer相关配置,除了默认配置,其他两个是当我们的项目中引入了ApacheHttpClient或OkHttpClient才会生效的配置。核心是这些配置中都包含了feignClient方法,讲fegen.client注册到上下文中
2.FeignAutoConfiguration Feign配置
- 加载FeignClientProperties.class,和FeignHttpClientProperties.class
- 创建一个feignContext注册到当前上下文中,feignContext扩展自NamedContextFactory,是一个context工厂,会根据feignclient的名称创建context,也就是说,每个不同名称的feignclient都会创建一个独有的context。该context是AnnotationConfigApplicationContext,feignclient相关的配置和bean也会注册到这个context中。FeignClientsConfiguration作为这个context的默认配置,将在创建context时加载。FeignClientsConfiguration里处理关于feign encoder,decoder,Feign.Builder等关键配置Bean。
- 如果有引入hystrix,这里还会注册HystrixTargeter到applicationcontext中,如果没有hystix依赖,则会使用默认的DefaultTargeter。
- 如果没有loacbalancer相关的依赖(没有ribbon相关依赖),且有ApacheHttpClient或OkHttpClient依赖,会配置ApacheHttpClient或OkHttpClient作为feign.client(ApacheHttpClient是默认配置,OkHttpClient需要在配置中enable)
3.Gzip相关配置
@AutoConfigureAfter(FeignAutoConfiguration.class),均是在FeignAutoConfiguration之后配置,加载和压缩相关的配置信息并注册相关的拦截器
配置基本流程
Import BeanDefinition
在应用类上添加Enablexxx以启用相关功能
@EnableFeignClients和FeignClientsRegistrar.class
EnableFeignClients注解 @Import(FeignClientsRegistrar.class),FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,这个接口的registerBeanDefinitions方法将在ConfigurationClassPostProcessor处理时调用,你可以参阅ConfigurationClassPostProcessor#processConfigBeanDefinitions和ConfigurationClassBeanDefinitionReader#ConfigurationClassBeanDefinitionReader
FeignClientsRegistrar的registerBeanDefinitions主要是加载了Feign的默认配置以及通过FeignClientFactoryBean为每个应用中的feignclient注册了FactoryBean的BeanDefinition。
FeignClientFactoryBean.class
FeignClientFactoryBean实现了FactoryBean<Object>接口,在spring ioc refresh流程中,对于FactoryBean类型的类将会调用getObject方法获取实例Bean,也就是feignclient。FeignClientFactoryBean中主要有和feignclient bean相关的type,name,url,path,decode,fallback,fallbackfactory等属性,而这些属性就是在配置中已加载到feigncontext中的内容。
代码语言:txt复制 <T> T getTarget() {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
...
}
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on
configureFeign(context, builder);
return builder;
}
protected <T> T get(FeignContext context, Class<T> type) {
T instance = context.getInstance(this.name, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type " type " for "
this.name);
}
return instance;
}
主要流程就是从context中获取已配置好的相关target,loadbalancer,encoder,decoder等对feignclient进行配置。而get(FeignContext context, Class<T> type) 则确认相关配置都会对应feign name的context中获取或创建。
Import配置信息
创建FeignClient
调用一次feign client方法时的流程简述
假设我们用HelloFeign调用一次helloWorld方法,同时已经引入hystrix和ribbon并做了相关配置。
代码语言:txt复制@FeignClient(value = "remote-server", fallback = HelloFeignFallback.class)
public interface HelloFeign {
@GetMapping(value = "/helloworld", method = RequestMethod.POST)
String helloWorld();
}
@Autowired
HelloFeign helloFeign;
res = helloFeign.helloWorld();
创建FeignClient
前文中已经对FeignClientFactoryBean进行了说明,在getObject方法中,将创建feignclient,且未为每一个feignclient根据client名称创建一个feigncontext,加载相关配置。feign是一种声明式得REST client,他本身并不处理网络请求,负载均衡,熔断等等相关得事务,feignclient主要是保存相关得配置和代理信息,在实际调用方法时实际通过代理得方式将网络请求代理给像ribbon或者hystrix处理。
- feign.Builder 前文中提到每个feignclient都会创建一个feign context,feigncontext通过FeignClientsConfiguration加载 encoder,decoder,Feign.Builder等配置Bean,其中feign.builder在有hystrix依赖时,将返回HystrixFeign.builder()。在FeignClientFactoryBean#configureFeign中,将对builder配置相关得decoder,encoder等信息
<T> T getTarget() {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
//省略...
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
在FeignClientFactoryBean#getTarget方法中,在实例化feignclient时,设置了LoadBalancerFeignClient到builder中。之后通过HystrixTargeter.target方法创建feignclient实例
- feign.Target FeignAutoConfiguration#HystrixFeignTargeterConfiguration将HystrixTargeter做为feignTarget。
- HystrixTargeter HystrixTargeter实现了了feign.Targeter接口,实际对外只有一个target方法。通过判断是否有fallback和fallbackfactory,分别调用HystrixFeign.builder对应得target方法
- HystrixFeign HystrixFeign是feign中得一个项目,用于集成hystrix。HystrixFeign.Buidler扩展自Feign.Builder,target方法中,对fallback使用FallbackFactory封装,并调用Feign.Builder的invocationHandlerFactory将HystrixInvocationHandler设置为InvacationHandler。最后通过Feign.Builder.build方法返回Feign对象
- Feign
public Feign build() {
//省略Client,logger,Encoder等等配置信息...
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
}
Feign.build方法设置SynchronousMethodHandler和ParseHandlersByName等一系列配置信息后,返回一个ReflectiveFeign对象,ReflectiveFeign的newInstance将在HystrixFeign的target方法中调用。
- ReflectiveFeign
//ReflectiveFeign
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;
}
newInstance实际就是将我们声明式的FeignClient中的方法,通过Proxy进行代理,所有我们定义的方法都将由InvocationHandler进行处理,就是HystrixInvocationHandler。
自此,一个FeignClient就创建结束了,实际我们定义的方法都是由Proxy的InvocationHandler进行处理。
调用流程
代码语言:txt复制res = helloFeign.helloWorld();
- InvocationHandler.invoke 当调用FeignClient方法时,由前文可知,helloWorld()这个方法,实际是被代理的,实际会调用InvocationHandler的invoke方法。
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
if ("equals".equals(method.getName())) {
//...
} //...
HystrixCommand<Object> hystrixCommand =
new HystrixCommand<Object>(setterMethodMap.get(method)) {
@Override
protected Object run() throws Exception {
try {
return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
} //...
}
@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();
} //...
}
}
};
if (Util.isDefault(method)) {
return hystrixCommand.execute();
} //..
return hystrixCommand.execute();
}
invoke中对调用做了HystrixCommand封装,并做了fallback处理。根据hystrix isolation策略,可能会在隔离的线程或同样的线程调用run方法。就是HystrixInvocationHandler.this.dispatch.get(method).invoke(args)。这里的dispatch就是ReflectiveFeign调用create方法创建InvocationHandler时传入的Map<Method, MethodHandler> methodToHandler,而对于非default方法的handler,主要由SynchronousMethodHandler.invoke处理
- SynchronousMethodHandler.invoke
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
continue;
}
}
}
invoke方法是执行调用的处理位置,这里对retryer重试逻辑进行了处理,同时executeAndDecode中会执行client的execute方法,在本例中就是ribbon的execute方法,最终返回结果。
调用流程
spring在onfresh流程中,解决Bean的Autowired注解,将helloFeign注入的service中。