Feign是一个声明式的Web Service客户端,它使得编写Web Serivce客户端变得更加简单。我们只需要使用Feign来创建一个接口并用注解来配置它既可完成。

@FeignClient(value = "qrcodepay-dike-service")
public interface TestRoute {
@RequestMapping(value = "/dike/get", method = RequestMethod.GET)
HdResult get();
}

我们只需要在相应的接口上添加@FeignClient注解即可将他声明为一个web客户端。这其中的原理我们后续分析。我们首先先关注下feign暴露的几个配置。

  • value: 目标服务名,一般都是 application.name
  • fallback : 服务降级策略

@FeignClient(value = "qrcodepay-dike-service",fallback = TestRoute.TestRouteFaback.class)
public interface TestRoute {
@RequestMapping(value = "/dike/get", method = RequestMethod.GET)
HdResult get();
}
@Component
class TestRouteFaback implements TestRoute{
@Override
public HdResult get() {
return HdResult.makeFail("服务降级");
}
}
  • fallbackFactory :fallback的升级版,可以获取更加详细的异常信息
@FeignClient(value = "qrcodepay-dike-service",fallbackFactory = TestRoute.TestRouteFallbackFactory.class)
public interface TestRoute {
@RequestMapping(value = "/dike/get", method = RequestMethod.GET)
HdResult get(); @Component
class TestRouteFallbackFactory implements FallbackFactory<TestRoute>{
private static final Logger logger = LoggerFactory.getLogger(TestRouteFallbackFactory.class);
@Override
public TestRoute create(Throwable throwable) {
String msg = throwable == null ? "" : throwable.getMessage();
if (!StringUtils.isEmpty(msg)) {
logger.error("异常信息打印:{}",msg);
}
return new TestRoute() {
@Override
public HdResult get() {
return HdResult.makeFail(msg);
}
};
} }
}
  • configuration:重写feign的配置

具体哪些内容可以配置我们可以看  FeignClientsConfiguration和feign.Feign.Builder。

下面用两种方式重写feign的配置

覆盖原有的配置bean达到重写目的

@Configuration
public class FeignBreakerConfiguration {
@Bean
public ErrorDecoder errorDecoder() {
return new UserErrorDecoder();
}
/**
* 自定义错误解码器 只有返回http status 非200才会进入
*/
public class UserErrorDecoder implements ErrorDecoder {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public Exception decode(String methodKey, Response response) {
Exception exception = null;
try {
String json = Util.toString(response.body().asReader());
System.out.println("自定义解码:"+json);
exception = new RuntimeException(json);
HdResult result=HdResult.makeFail(json);
// 业务异常包装成 HystrixBadRequestException,不进入熔断逻辑
// if (!result.isSuccess()) {
// exception = new HystrixBadRequestException(result.getMessage());
// }
} catch (IOException ex) {
logger.error(ex.getMessage(), ex);
}
return exception;
}
}
}

自定义客户端达到重写的目的

@Import(FeignClientsConfiguration.class)
@RestController
public class DefaultController {
private FeignClientService feignClientService;
public DefaultController(Decoder decoder, Encoder encoder, Client client){
this.feignClientService = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.requestInterceptor(new BasicAuthRequestInterceptor("user","password"))
.target(FeignClientService.class,"http://eureka-client");
} @RequestMapping(name = "/default",method = RequestMethod.GET)
public String getInfo(){
return feignClientService.getValue("hello world!");
}
}

feignclient最常用的配置大致如上,接下来介绍下feign实现的原理。

先说结论,feign是通过动态代理的技术将一个interface变为Web Service客户端。那我们应该从哪里入手呢。在使用feign的时候,我们应该关注两个注解,一个就是我们上文所说的feignClient,但是仅仅只用这个注解feign是不会生效的,必须要在启动类上加上EnableFeignClients,feign才会自动扫描feignClient。所以我们的入口应该是 EnableFeignClients

EnableFeignClients 导入了FeignClientsRegistrar,这个注解真正的逻辑就在FeignClientsRegistrar中

这个类实现了三个接口,我们先关注 ImportBeanDefinitionRegistrar,这是spring动态注册bean的接口。所以spring在启动的时候会调用以下方法

public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}

将配置类纳入beandefinationMap管理 ,这一块更为详细的内容可以看  SpringIoc分析

private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true); if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}

扫描FeignClient注解,将interface纳入beanDefination

public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader); Set<String> basePackages; Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
else {
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
} for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
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);
registerClientConfiguration(registry, name,
attributes.get("configuration")); registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}

接下来,我们需要找到jdk代理的地方

我们在构建feign的地方发现如下方法

public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}

最终我们在SynchronousMethodHandler类中发现了真正拦截的代码

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;
}
}
}

真正执行的逻辑如下,这里也是feign最为关键的地方。这里我们主要关注下真正请求的那一行。如果想对feign做debug或者重写一些配置,参考这里会是一个很好的入口。

Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = targetRequest(template); if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
} Response response;
long start = System.nanoTime();
try {
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) {
return decoder.decode(response, metadata.returnType());
} 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());
}
}
}

这里的client是请求客户端,feign统一封装为LoadBalancerFeignClient

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignRibbonClientAutoConfiguration { @Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null),
cachingFactory, clientFactory);
} }

默认的Client 是HttpURLConnection,同时 feign也支持httpclient和okhhtp

@Configuration
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
protected static class HttpClientFeignConfiguration { @Autowired(required = false)
private HttpClient httpClient; @Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient() {
if (this.httpClient != null) {
return new ApacheHttpClient(this.httpClient);
}
return new ApacheHttpClient();
}
} @Configuration
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnProperty(value = "feign.okhttp.enabled", matchIfMissing = true)
protected static class OkHttpFeignConfiguration { @Autowired(required = false)
private okhttp3.OkHttpClient okHttpClient; @Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient() {
if (this.okHttpClient != null) {
return new OkHttpClient(this.okHttpClient);
}
return new OkHttpClient();
}
}

只要满足 配置条件,就可以将httpclient或okhhtp引入,这里举例说明怎么使用httpclient

在pom文件加上:

<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>RELEASE</version>
</dependency>

在配置文件上加上feign.httpclient.enabled为true(默认为true,可不写)

最后,我们再看看feign是怎么使用ribbon的,上文我们说过feign统一将client封装为LoadBalancerFeignClient,fein的请求最终都会到以下代码

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);
}
}

具体我们可以看下 executeWithLoadBalancer 

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) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
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);
}
} }

在submit方法里,发现了如下代码

// Use the load balancer
Observable<T> o =
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
}

这里的selectServer 最终会调用 ILoadBalancer 选择一个server

ILoadBalancer lb = getLoadBalancer();
if (host == null) {
// Partial URI or no URI Case
// well we have to just get the right instances from lb - or we fall back
if (lb != null){
Server svc = lb.chooseServer(loadBalancerKey);

关于这方面的具体内容,请参考 SpringCloud Ribbon的分析

以上,就是对feign的具体分析

SpringCloud Feign的分析的更多相关文章

  1. SpringCloud+Feign环境下文件上传与form-data同时存在的解决办法(2)

    书接上文. 上文中描述了如何在 SpringCloud+Feign环境下上传文件与form-data同时存在的解决办法,实践证明基本可行,但却会引入其他问题. 主要导致的后果是: 1. 无法与普通Fe ...

  2. SpringCloud Feign 之 Fallback初体验

    SpringCloud Feign 之 Fallback初体验 在微服务框架SpringCloud中,Feign是其中非常重要且常用的组件.Feign是声明式,模板化的HTTP客户端,可以帮助我们更方 ...

  3. SpringCloud Feign 之 超时重试次数探究

    SpringCloud Feign 之 超时重试次数探究 上篇文章,我们对Feign的fallback有一个初步的体验,在这里我们回顾一下,Fallback主要是用来解决依赖的服务不可用或者调用服务失 ...

  4. SpringCloud Feign通过FallbackFactory显示异常信息

    SpringCloud Feign可以进行服务消费,而且内置了Hystrix,能够进行熔断. Feign可以通过fallback指定熔断回调的类.代码示例及讲解可见: https://www.cnbl ...

  5. SpringCloud Feign重试详解

    摘要: 今天在生产环境发生了数据库进程卡死的现象,除了sql因为全量更新,没加索引的原因,最主要还是我们的接口的服务器端接口出现问题了.忽视了更新接口的幂等性,以及调用方feign client的重试 ...

  6. 分享一个 SpringCloud Feign 中所埋藏的坑

    背景 前段时间同事碰到一个问题,需要在 SpringCloud 的 Feign 调用中使用自定义的 URL:通常情况下是没有这个需求的:毕竟都用了 SpringCloud 的了,那服务之间的调用都是走 ...

  7. springCloud feign使用/优化总结

    基于springCloud Dalston.SR3版本 1.当接口参数是多个的时候 需要指定@RequestParam 中的value来明确一下. /** * 用户互扫 * @param uid 被扫 ...

  8. SpringCloud Feign

    ⒈Feign是什么? Feign是Netflix开发的声明式.模板化的HTTP客户端, Feign可以帮助我们更快捷.优雅地调用HTTP API. SpringCloud微服务项目之间调用是通过Res ...

  9. SpringCloud Feign context-path踩到的坑

    最近在使用SpringCloud的context-path时,遇到了一些坑,记录一下. server.context-path(上下文) 服务提供者的application配置文件中有一个属性叫ser ...

随机推荐

  1. MYSQL可调用执行自定义SQL的代码

    DELIMITER $$ USE `mysql_wispeed01`$$ DROP PROCEDURE IF EXISTS `sp_execSQL`$$ CREATE DEFINER=`sa`@`%` ...

  2. [SCOI2015]小凸玩矩阵

    Description: 给你一个n*m的网格,每个格子有一个数字,每行每列只能选一个数字,问所选数字中第k大的数字的最小值是多少 Hint: \(n \le 250\) Solution: 显然是二 ...

  3. Blocks [POJ3734] [矩阵快速幂]

    题意: 有长度为n的一排格子,每个格子里面可以任意填入1,2,3,4四个数字,问1,2都为偶数个的方案 T组数据,每组数据一个n(<=1e9) 样例输入 2 1 2 样例输出 2 6 分析 设d ...

  4. 解决iPhone Safari 兼容性CSS背景显示不全问题

    https://jingyan.baidu.com/article/ca2d939d014ccbeb6c31ceb7.html 看到了这个文章解决的.中心部分小于980的时候回出现.苹果手机中的saf ...

  5. Vue中scoped css和css module比较

    scoped css 官方文档 scoped css可以直接在能跑起来的vue项目中使用. 使用方法: <style scoped> h1 { color: #f00; } </st ...

  6. java中的异常类

    Java中的异常: 1. Throwable是所有异常的根,java.lang.Throwable Throwable包含了错误(Error)和异常(Exception),Exception又包含了运 ...

  7. echarts-for-react 从新渲染数据

    <ReactEcharts option={option} notMerge={true}  style={{height: '600px', width: '100%'}} className ...

  8. go语言的for循环

    for循环是一个循环控制结构,可以执行指定次数的循环. 三种循环方式 第一种,常见的 for 循环,支持初始化语句 for init; condition; post { } init: 一般为赋值表 ...

  9. 最新版Navicat Premium12 中文破解版 安装激活

    对于PHPer 来说 Navicat Premium  简直就是神器有木有,反正我是这样觉得的,昨天刚更新了最新版本 Navicat Premium 12 ,官网是免费试用14 天的,肿么能行呢,我们 ...

  10. [运维工具]linux下远程桌面rdesktop安装和使用

    依然是解压 configure make make install 这些步骤 rdesktop -f 16 192.168.16.90 -f是全屏,退出全屏是CRTL+ALT+ENTER 记录一个li ...