声明式服务调用:Spring Cloud Feign

来源:互联网 发布:爱名网域名过户步骤 编辑:程序博客网 时间:2024/05/16 13:43

1.使用方法

   1.消费方基于spring mvc 注解自定义访问接口,这样相当于消费方和服务提供方各自维护一套接口。

  2.基于继承的方法,服务提供方将对外接口打成单独的jar包提供出去,消费方依赖这个jar就可以了。缺点是服务提供方和消费方在构建期间就建立起依赖,修改接口的时候需要注意兼容性。

  例:服务提供方提供接口

public interface FeignController {    @RequestMapping("/hello")    String hello();    @RequestMapping(value = "/hello1", method = RequestMethod.GET)    String hello(@RequestParam("name") String name);    @RequestMapping(value = "/hello2", method = RequestMethod.PUT)    String hello(@RequestHeader("name") String name, @RequestHeader("age") Integer age);    @RequestMapping(value = "/hello3", method = RequestMethod.POST)    User hello(@RequestBody User user);}
   消费方继承接口,添加注解

@FeignClient(name = "feign-service", fallback = TestServiceFallback.class)public interface TestService extends FeignController {}

上面还设置了fallback,其定义如下:

@Componentpublic class TestServiceFallback implements TestService {    @Override    public String hello() {        return "error";    }    @Override    public String hello(String s) {        return "error";    }    @Override    public String hello(String s, Integer integer) {        return "未知";    }    @Override    public User hello(User user) {        return new User("未知", 0);    }}

2.配置

  Feign是基于ribbon的,所以其配置和ribbon一致。
  全局配置:ribbon.<key>=<value>   
  指定服务配置:<client>.ribbon.<key>=<value>
 
#全局配置ribbon.ConnectTimeout=1000ribbon.ReadTimeout=2000#断路器相关配置feign.hystrix.enabled=truehystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=100000#hystrix.command.default.execution.timeout.enabled=false 是否开启断路器的超时设置#如果要单独设置某个方法,则将default改为对应的方法名即可 如hystrix.command.hello.execution..#注意:相同方法名的Hystrix配置会公用#设置对应服务的配置feign-service.ribbon.ConnectTimeout=2feign-service.ribbon.ReadTimeout=10#错误重试相关配置spring.cloud.loadbalancer.retry.enabled=truefeign-service.ribbon.MaxAutoRetries=5feign-service.ribbon.OkToRetryOnAllOperations=truefeign-service.ribbon.maxAutoRetriesNextServer=1
  

3.日志

格式:logging.level.<FeignClient>=
logging.level.com.feiniu.feign.TestService=debug
只添加上面的配置还是无法实现debug日志的输出。这是由于Fegin客户端默认的Logger.Level 对象为NONE级别,该级别不会记录任何Feign调用过程信息,我们需要调整级别。
全局定义:
@BeanLogger.Level feignLoggerLevel() {    return Logger.Level.BASIC;}
局部定义:
@Configurationpublic class FeignFullLogConfiguration {    @Bean    Logger.Level feignLoggerLevel() {        return Logger.Level.FULL;    }}
指定配置
@FeignClient(name = "feign-service", fallback = TestServiceFallback.class,        configuration = FeignFullLogConfiguration.class)public interface TestService extends FeignController {}


NONE:不记录任何信息
BASIC:仅记录请求方法、URL以及响应状态码和执行时间
HEADERS:BASIC信息加上请求和响应的头信息
FULL:记录所有请求与响应的明细,包括头信息、请求体、元数据等。

4.原理

@EnableFeignClients 注解导入了FeignClientsRegistrar,看到registerFeignClients方法有如下代码

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);      }   }}
可以看到此方法找到有FeignClient 注解的接口,然后进行注册
继续看registerFeignClient方法

private void registerFeignClient(BeanDefinitionRegistry registry,      AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {   String className = annotationMetadata.getClassName();   BeanDefinitionBuilder definition = BeanDefinitionBuilder         .genericBeanDefinition(FeignClientFactoryBean.class);   validate(attributes);   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);}
我们看这句代码
BeanDefinitionBuilder definition = BeanDefinitionBuilder      .genericBeanDefinition(FeignClientFactoryBean.class);
FeignClientFactoryBean 很明显是一个FactoryBean实现,我们看它的 getObject()实现
if (!StringUtils.hasText(this.url)) {   String url;   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));}

继续看loadBalance方法

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,      HardCodedTarget<T> target) {   Client client = getOptional(context, Client.class);   if (client != null) {      builder.client(client);      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是HystrixTargeter,我们继续看HystrixTargeter的target方法

@Overridepublic <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,               Target.HardCodedTarget<T> target) {   if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {      return feign.target(target);   }   feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;   SetterFactory setterFactory = getOptional(factory.getName(), context,      SetterFactory.class);   if (setterFactory != null) {      builder.setterFactory(setterFactory);   }   Class<?> fallback = factory.getFallback();   if (fallback != void.class) {      return targetWithFallback(factory.getName(), context, target, builder, fallback);   }   Class<?> fallbackFactory = factory.getFallbackFactory();   if (fallbackFactory != void.class) {      return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);   }   return feign.target(target);}


看其中任意一个分支会看到InvocationHandler的一个实现最终都会调用

return dispatch.get(method).invoke(args);
打开断点,会看到使用的是SynchronousMethodHandler 我们看invoke方法

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

继续看executeAndDecode方法

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

其中client是LoadBalancerFeignClient,继续看execute方法

@Overridepublic 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方法

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);    }}
reconstructURIWithServer将服务名换成真实的服务地址。最终我们看到调用的是这个方法
AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig)
实际调用的是RetryableFeignLoadBalancer的execute方法
return retryTemplate.execute(new RetryCallback<RibbonResponse, IOException>() {   @Override   public RibbonResponse doWithRetry(RetryContext retryContext) throws IOException {      Request feignRequest = null;      //on retries the policy will choose the server and set it in the context      //extract the server and update the request being made      if(retryContext instanceof LoadBalancedRetryContext) {         ServiceInstance service = ((LoadBalancedRetryContext)retryContext).getServiceInstance();         if(service != null) {            feignRequest = ((RibbonRequest)request.replaceUri(reconstructURIWithServer(new Server(service.getHost(), service.getPort()), request.getUri()))).toRequest();         }      }      if(feignRequest == null) {         feignRequest = request.toRequest();      }      Response response = request.client().execute(feignRequest, options);      if(retryPolicy.retryableStatusCode(response.status())) {         response.close();         throw new RetryableStatusCodeException(RetryableFeignLoadBalancer.this.getClientName(), response.status());      }      return new RibbonResponse(request.getUri(), response);   }});
我们看此段代码
 Response response = request.client().execute(feignRequest, options);
request是RibbonRequest,它的client是什么呢,我们看默认的配置类
@Configurationclass DefaultFeignLoadBalancedConfiguration {   @Bean   @ConditionalOnMissingBean   public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,                       SpringClientFactory clientFactory) {      return new LoadBalancerFeignClient(new Client.Default(null, null),            cachingFactory, clientFactory);   }}

new Client.Default(null, null)

所以最终是通过Client.Default的execute方法进行请求的

@Overridepublic Response execute(Request request, Options options) throws IOException {  HttpURLConnection connection = convertAndSend(request, options);  return convertResponse(connection).toBuilder().request(request).build();}

最后我们还看到几个Client的扩展。

基于ApacheHttpClient

@Configuration@ConditionalOnClass(ApacheHttpClient.class)@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)class HttpClientFeignLoadBalancedConfiguration {   @Autowired(required = false)   private HttpClient httpClient;   @Bean   @ConditionalOnMissingBean(Client.class)   public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,                       SpringClientFactory clientFactory) {      ApacheHttpClient delegate;      if (this.httpClient != null) {         delegate = new ApacheHttpClient(this.httpClient);      } else {         delegate = new ApacheHttpClient();      }      return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);   }}
基于OkHttpClient

@Configuration@ConditionalOnClass(OkHttpClient.class)@ConditionalOnProperty(value = "feign.okhttp.enabled", matchIfMissing = true)class OkHttpFeignLoadBalancedConfiguration {   @Autowired(required = false)   private okhttp3.OkHttpClient okHttpClient;   @Bean   @ConditionalOnMissingBean(Client.class)   public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,                       SpringClientFactory clientFactory) {      OkHttpClient delegate;      if (this.okHttpClient != null) {         delegate = new OkHttpClient(this.okHttpClient);      } else {         delegate = new OkHttpClient();      }      return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);   }}

阅读全文
0 0
原创粉丝点击