Feign
Spring Cloud Feign是一套基于Netflix Feign实现的声明式服务调用客户端。我们只需要声明接口和一些简单的注解,就能像使用普通的Bean一样调用远程服务。它具备可插拔的注解支持,包括Feign注解、JAX-RS注解。它也支持可插拔的编码器和解码器。Spring Cloud Feign还扩展了对Spring MVC注解的支持,同时还整合了Ribbon和Eureka来提供均衡负载的HTTP客户端实现。
使用Feign需要增加的maven依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
主应用类增加@EnableFeignClients开启扫描Spring Cloud Feign客户端功能:
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaConsumerFeignApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaConsumerFeignApplication.class, args);
}
}
创建Feign Client定义, 使用@FeignClient注解来定义Feign Client并定义该Feign客户端需要调用的微服务名,之后定义各个方法是调用微服务的哪个接口:
@FeignClient("eureka-client")
public interface DcClient {
@GetMapping("/ls")
String consumer();
}
调用:
@RestController
public class FeignController {
@Autowired
DcClient dcClient;
@GetMapping("/feign/consumer")
public String ls() {
return dcClient.consumer();
}
}
通过@FeignClient定义的接口来统一的声明需要依赖的微服务接口。而在具体使用的时候就跟调用本地方法一样地进行调用即可。由于Feign是基于Ribbon实现的,所以它自带了客户端负载均衡功能,也可以通过Ribbon的IRule接口进行策略扩展。另外,Feign还整合的Hystrix来实现服务的容错保护,在Dalston版本中,Feign的Hystrix默认是关闭的。
@EnableFeignClients开启feign
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
这个注解导入了一个类FeignClientsRegistrar,这个类实现了ImportBeanDefinitionRegistrar接口,该接口用于向Bean容器中注册添加BeanDefinition。
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
public interface ImportBeanDefinitionRegistrar {
void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2);
}
跟进FeignClientsRegistrar的registerBeanDefinitions方法,看看它注册了哪些东西。
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 注册默认配置
this.registerDefaultConfiguration(metadata, registry);
// 注册FeignClient接口的Bean
this.registerFeignClients(metadata, registry);
}
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
// 获取扫描路径
// 扫描所有路径,默认情况下扫描启动类下的路径
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
// 遍历扫描到的FeignClient的Bean
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
// 注册FeignClient的配置
registerClientConfiguration(registry, name, attributes.get("configuration"));
// 注册FeignClient
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
方法中的核心逻辑就是扫描类路径,获取BeanDefinition(@FeignClient),然后遍历进行注册。
跟进registerFeignClient注册方法
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
// 生成FeignClientFactoryBean这个BeanDefinition构造器
// FeignClientFactoryBean.class
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
// 将@FeignClient的注解属性添加到builder
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = name + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
// 注册
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
这里值得注意的是genericBeanDefinition方法最终生成的其实是FeignClientFactoryBean,registerBeanDefinition方法注册进容器的也是FeignClientFactoryBean。
从名字就可以看出FeignClientFactoryBean是个Factorybean,实现了FactoryBean接口
FactoryBean接口是spring开放出来的,用于自定义Bean的生成过程。也就是说,spring将会通过调用FeignClientFactoryBean的getObject来获取@FeignClient注解的接口对应的Bean对象。
总结
openfeign的自动配置过程逻辑相对比较简单,就是扫描了一下@FeignClient注解的接口,然后生成FeignClientFactoryBean的BeanDefinition给注册到容器当中。而具体的Bean对象,将会通过调用FeignClientFactoryBean的getObject方法来获取。
生成proxy对象
从FeignClientFactoryBean的getObject方法开始,看看代理对象的生成。跟进getObject方法
@Override
public Object getObject() throws Exception {
// 获取一个上下文
FeignContext context = applicationContext.getBean(FeignContext.class);
// feign用于构造代理对象,builder将会构建feign
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
String url;
// 拼接URL地址,如:http://service-provider/
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
// ....... 省略
}
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
// 获取执行http请求的客户端
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
// 选择获取代理对象的实现类,默认是HystrixTargeter
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-ribbon?");
}
获取代理对象的实现由Targeter的实现类处理
interface Targeter {
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target);
}
默认是HystrixTargeter
class HystrixTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
// Feign实现了构造代理对象的过程,所以这里将会回调feign的构造过程方法
return feign.target(target);
}
// ...... 省略
return feign.target(target);
}
// ...... 省略
}
public <T> T target(Target<T> target) {
return this.build().newInstance(target);
}
public Feign build() {
// ......
return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory);
}
跟进newInstance方法,看看代理对象是如何被构建的
/**
* creates an api binding to the {@code target}. As this invokes reflection, care should be taken
* to cache the result.
*/
@SuppressWarnings("unchecked")
@Override
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>();
// 构建Method到MethodHandler的映射关系
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)));
}
}
// factory.create() 实现 invocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
// jdk的动态代理获取的代理对象
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
代理对象的构建主要由三块内容
1、构建Method到MethodHandler的映射关系,后面调用代理的对象的时候将会根据Method找到MethodHandler然后调用MethodHandler的invoke方法,而MethodHandler将包含发起http请求的实现。
2、jdk动态代理需要提供InvocationHandler,这个大家比较熟悉了。而InvocationHandler将由InvocationHandlerFactory的create方法实现
3、通过Proxy.newProxyInstance方法,生成proxy对象。
这里我们看看factory.create方法生成InvocationHandler的实现吧
static final class Default implements InvocationHandlerFactory {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
// 这是一个内部类的实现 FeignInvocationHandler
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
调用proxy对象发起http请求
我们知道,jdk的动态代理将会调用FeignInvocationHandler的invoke方法。所以,我们看看FeignInvocationHandler是怎么调用Method的
private final Map<Method, MethodHandler> dispatch;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// ...
return dispatch.get(method).invoke(args);
}
前面我们说到,构建proxy对象的时候会构建Method和MethodHandler的映射关系。而这里invoke代理对象的时候又会根据method来获取到MethodHandler,再调用其invoke方法。
MethodHandler的默认实现类是SynchronousMethodHandler,我们跟进它的invoke方法
// feign.SynchronousMethodHandler
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
// executeAndDecode
return executeAndDecode(template, options);
} catch (RetryableException e) {
// ...
}
}
}
熟悉的代码来了,executeAndDecode将会负责发起http请求
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template);
Response response;
try {
// 执行http请求
response = client.execute(request, options);
} catch (IOException e) {
}
try {
//...
// http请求成功
if (response.status() >= 200 && response.status() < 300) {
// 无需返回值
if (void.class == metadata.returnType()) {
return null;
} else {
// 解码结果
Object result = decode(response);
return result;
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
// ...
} else {
// ...
}
} catch (IOException e) {
// ...
} finally {
// ...
}
}
总结
openFeign生成@FeignClient注解的接口的代理对象是从FeignClientFactoryBean的getObject方法开始的,生成proxy对象主要由ReflectiveFeign对象来实现。动态代理方法由jdk原生的动态代理支持。
调用proxy对象,其实就是发起http请求,请求结果将被解码并返回。
所以,正如Feign本身的意义一样,http远程调用被伪装成了本地调用一样简单的代理对象,对于使用者来说就是调用本地接口一样简单。
ribbon如何集成在openfeign中使用
ribbon是springcloud封装的一个基于http客户端负载均衡的组件。springcloud的openfeign集成使用了ribbon。所以如果你使用openfeign,那么也会很轻易得使用到ribbon。ribbon是负载均衡的组件,所以使用在http的请求之中。
参考之前的executeAndDecode方法, client提交了一个http的request,然后获得了response响应对象,处理后并返回。ribbon的接入将从这里开始,我们看看client接口
public interface Client {
Response execute(Request request, Options options) throws IOException;
}

LoadBalancerFeignClient作为Client在负载均衡方面的实现类
@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);
// 构造ribbon的request对象
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
// 执行ribbon的request对象
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
requestConfig).toResponse();
}
catch (ClientException e) {
// ......
}
}
可以看到,关于负载均衡方面openfeign直接构造了ribbon的请求,并执行。
构造ribbon的IClient
lbClient(clientName)构造了一个ribbon的http客户端实现,打开该方法
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}
一个简单工厂模式,跟进create方法
public FeignLoadBalancer create(String clientName) {
if (this.cache.containsKey(clientName)) {
return this.cache.get(clientName);
}
IClientConfig config = this.factory.getClientConfig(clientName);
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
// 默认返回FeignLoadBalancer
FeignLoadBalancer client = enableRetry ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
loadBalancedRetryPolicyFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client);
return client;
}
看一下FeignLoadBalancer的继承结构
FeignLoadBalancer实现了IClient接口,所以它会负责提交并执行Ribbon的request请求
lbClient方法构建了FeignLoadBalancer,下面该调用它的executeWithLoadBalancer方法了,跟进方法(方法在父类AbstractLoadBalancerAwareClient中)
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);
LoadBalancerCommand<T> command = LoadBalancerCommand.<T>builder()
.withLoadBalancerContext(this)
.withRetryHandler(handler)
.withLoadBalancerURI(request.getUri())
.build();
try {
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
// 回调返回选择好的Server对象,并重新构造uri地址
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
// 执行ribbon的request请求
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}
}
如果不关心负载均衡的情况,这里其实就是直接执行ribbon的request请求了。也就是IClient这个接口类定义的内容。不过FeignLoadBalancer需要进行一次负载选择Server,然后才回调这里的call来发起请求。
跟进submit方法看看,submit方法先是进行了一次选择获得了一个Server对象,然后回调了上面说的ribbon的request执行
public Observable<T> submit(final ServerOperation<T> operation) {
// ...
// Use the load balancer
Observable<T> o =
// 选择Server
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
@Override
// Called for each server being selected
public Observable<T> call(Server server) {
context.setServer(server);
//
// Called for each attempt and retry
Observable<T> o = Observable
.just(server)
.concatMap(new Func1<Server, Observable<T>>() {
@Override
public Observable<T> call(final Server server) {
// ...
// 回调ribbon的request请求
return operation.call(server).doOnEach(
// ...
);
}
});
if (maxRetrysSame > 0)
o = o.retry(retryPolicy(maxRetrysSame, true));
return o;
}
});
// ...
}
ILoadBalancer负载均衡器
继续跟进selectServer,看看如何选择服务的
private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
// 从上下文中获取
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}
托付给了getServerFromLoadBalancer来实现,继续跟进
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
// ...
ILoadBalancer lb = getLoadBalancer();
if (host == null) {
if (lb != null){
Server svc = lb.chooseServer(loadBalancerKey);
// ...
return svc;
} else {
// ...
}
} else {
// ...
}
// ...
}
getLoadBalancer方法先是获取了一个ILoadBalancer接口的实现,然后调用了chooseServer来选择一个Server。
先跟进getLoadBalancer方法,直接返回了上下文中的设置的ILoadBalancer负载均衡器
private ILoadBalancer lb;
public ILoadBalancer getLoadBalancer() {
return lb;
}
我们看一下ILoadBalancer的的类图,RibbonClientConfiguration配置ILoadBalancer的时候配置的是ZoneAwareLoadBalancer的Bean
IRule负载均衡算法
有了ILoadBalancer负载均衡器,再看看chooseServer方法。这里忽略一些细节,直接看BaseLoadBalancer的chooseServer这个核心的实现
// com.netflix.loadbalancer.BaseLoadBalancer
protected IRule rule = DEFAULT_RULE;
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
可以看看,直接调用了IRule接口的choose方法。IRule接口则负责相关的负载均衡算法实现,我们看看IRule接口有哪些实现吧

常见的随机算法、轮询算法...等
如何定制给Ribbon配置算法,或者定制算法呢??
到这里,本文就结束了。我们再回顾一下文章的接口和流程
1、先是openfeign开放了一个Client接口用于http请求,并且LoadBalancerFeignClient作为负载均衡的实现类
2、LoadBalancerFeignClient则直接构造了一个ribbon的IClient接口的实现FeignLoadBalancer
3、执行ribbon的request之前,先委托ILoadBalancer负载均衡器选择一个Server,然后回调执行request请求
4、ILoadBalancer会选择IRule实现的负载均衡算法来获取一个Server,并返回。
总体逻辑比较简单,本文忽略了一些细节内容,比如一些自动配置的东西、如果从Eureka中获取服务列表等,有兴趣可以自己看看。

浙公网安备 33010602011771号