博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringCloud Feign的分析
阅读量:4678 次
发布时间:2019-06-09

本文共 16416 字,大约阅读时间需要 54 分钟。

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
{ 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达到重写目的

@Configurationpublic 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)@RestControllerpublic 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管理 ,这一块更为详细的内容可以看 

private void registerDefaultConfiguration(AnnotationMetadata metadata,            BeanDefinitionRegistry registry) {        Map
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
basePackages; Map
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
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
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
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文件加上:

com.netflix.feign
feign-httpclient
RELEASE

 在配置文件上加上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
command = LoadBalancerCommand.
builder() .withLoadBalancerContext(this) .withRetryHandler(handler) .withLoadBalancerURI(request.getUri()) .build(); try { return command.submit( new ServerOperation
() { @Override public Observable
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
o = (server == null ? selectServer() : Observable.just(server)) .concatMap(new Func1
>() {}

 

这里的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);

 关于这方面的具体内容,请参考 

 

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

 

转载于:https://www.cnblogs.com/xmzJava/p/9612988.html

你可能感兴趣的文章
node启动时, listen EADDRINUSE 报错;
查看>>
vue学习链接
查看>>
Systemd 初始化进程
查看>>
【C#学习笔记】文本复制到粘贴板
查看>>
Windows store 验证你的 URL http:// 和 https:// ms-appx:/// ms-appdata:///local
查看>>
python全栈开发_day7_字符编码,以及文件的基本读取
查看>>
js 验证码 倒计时60秒
查看>>
C#基础
查看>>
ASP.NET Core MVC 2.x 全面教程_ASP.NET Core MVC 15. 用户管理
查看>>
杭电3466————DP之01背包(对状态转移方程的更新理解)
查看>>
算法分析常用记号
查看>>
spring mvc 返回类型
查看>>
[js高手之路] html5 canvas动画教程 - 匀速运动
查看>>
11.VS2015常用快捷键大全
查看>>
js学习总结----less常用的方法
查看>>
需求分析问卷调查及反馈结果
查看>>
当深度学习遇见自动文本摘要
查看>>
心随手动,驱动短视频热潮的引擎
查看>>
Servlet深入之初始化
查看>>
python中出现IndentationError:unindent does not match any outer indentation level错误
查看>>