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,其定义如下:
@Component public 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=1000 ribbon.ReadTimeout=2000 #断路器相关配置 feign.hystrix.enabled=true hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=100000 #hystrix.command.default.execution.timeout.enabled=false 是否开启断路器的超时设置 #如果要单独设置某个方法,则将default改为对应的方法名即可 如hystrix.command.hello.execution.. #注意:相同方法名的Hystrix配置会公用 #设置对应服务的配置 feign-service.ribbon.ConnectTimeout=2 feign-service.ribbon.ReadTimeout=10 #错误重试相关配置 spring.cloud.loadbalancer.retry.enabled=true feign-service.ribbon.MaxAutoRetries=5 feign-service.ribbon.OkToRetryOnAllOperations=true feign-service.ribbon.maxAutoRetriesNextServer=1
3.日志
格式:logging.level.<FeignClient>=
logging.level.com.feiniu.feign.TestService=debug只添加上面的配置还是无法实现debug日志的输出。这是由于Fegin客户端默认的Logger.Level 对象为NONE级别,该级别不会记录任何Feign调用过程信息,我们需要调整级别。
全局定义:
@Bean Logger.Level feignLoggerLevel() { return Logger.Level.BASIC; }局部定义:
@Configuration public 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方法
@Override public <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方法@Override 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; } } }继续看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方法
@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); 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);@Configuration class 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方法进行请求的
@Override public 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); } }