声明式服务调用: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方法
request是RibbonRequest,它的client是什么呢,我们看默认的配置类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);@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
- Spring Cloud 声明式服务调用 Feign
- 声明式服务调用:Spring Cloud Feign
- spring cloud Feign(声明式服务调用)
- Spring Cloud入门教程(三):声明式服务调用(Feign)
- Spring Cloud中声明式服务调用Feign
- 06.Spring Cloud学习笔记之声明式服务调用组件Feign
- Spring Cloud学习--声明式调用(Feign)
- spring cloud feign 调用服务注意问题
- Spring Cloud学习系列第二章:使用Feign调用服务
- 【Spring Cloud】Feign接口调用
- Spring Cloud(五):服务消费者Feign
- 使用Spring Cloud Feign远程调用
- Spring Cloud之Feign调用token丢失
- 使用Spring Cloud Feign作为HTTP客户端调用远程HTTP服务
- 使用Spring Cloud Feign作为HTTP客户端调用远程HTTP服务
- 使用Spring Cloud Feign作为HTTP客户端调用远程HTTP服务
- 使用Spring Cloud Feign作为HTTP客户端调用远程HTTP服务
- 使用Spring Cloud Feign作为HTTP客户端调用远程HTTP服务
- springboot Configuration 获取不到@value配置问题
- arm编程中__packed的使用
- 《多移动机器人协同原理与技术》读书笔记(一、绪论)
- poj2018 Best Cow Fences(求至少k个连续数 使得平均值最大)
- 后缀自动机 求不同的子串数目
- 声明式服务调用:Spring Cloud Feign
- 循环输入二维数组的方法
- 【错误解决】Spring JPA的错误及其解决方案
- 连十分钟都坚持不了,学长你不行啊!
- Exclipse快捷键迅速打开文件
- 关于C语言和Javascript函数参数的传递
- Windows Git+TortoiseGit简易使用教程
- iOS 直接改变控件x.y.width.height
- 初识Makefile