(基于o)
什么是Feign
Feign是由Retrofit,JAXRS-2.0和WebSocket启发的一个java到http客户端绑定。 Feign的主要目标是将Java Http Clients变得简单。Feign的源码地址:
写一个Feign
简单的实现一个Feign客户端,首先通过@FeignClient,客户端,其中value为调用其他服务的名称,FeignCon为FeignClient的配置文件,代码如下:
@FeignClient(value = "service-hi",configuration = FeignCon) public interface SchedualServiceHi { @GetMapping(value = "/hi") String sayHiFromClientOne(@requestParam(value = "name") String name); }
其配置文件如下:
@Configuration public class FeignConfig { @bean public Retryer feignRetryer() { return new Re(100, SECONDS.toMillis(1), 5); } }
查看FeignClient的源码,其代码如下:
@Targe) @Retention) @Documented public @interface FeignClient { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; @AliasFor("value") String name() default ""; String url() default ""; Boolean decode404() default false; Class<?>[] configuration() default {}; Class<?> fallback() default void.class; Class<?> fallbackFactory() default void.class; } String path() default ""; boolean primary() default true;
feign 用于声明具有该接口的REST客户端的接口的注释应该是创建(例如用于自动连接到另一个组件。 如果功能区可用,那将是用于负载平衡后端请求,并且可以配置负载平衡器使用与伪装客户端相同名称(即值)@RibbonClient 。
其中value()和name()一样,是被调用的 service的名称。url(),直接填写硬编码的url,decode404()即404是否被解码,还是抛异常;configuration(),标明FeignClient的配置类,默认的配置类为FeignClientsConfiguration类,可以覆盖Decoder、Encoder和Contract等信息,进行自定义配置。fallback(),填写熔断器的信息类。
FeignClient的配置
默认的配置类为FeignClientsConfiguration,这个类在spring-cloud-netflix-core的jar包下,打开这个类,可以发现它是一个配置类,注入了很多的相关配置的bean,包括feignRetryer、FeignLoggerFactory、FormattingConversionService等,其中还包括了Decoder、Encoder、Contract,如果这三个bean在没有注入的情况下,会自动注入默认的配置。
- Decoder feignDecoder: ResponseEntityDecoder(这是对SpringDecoder的封装)
- Encoder feignEncoder: SpringEncoder
- Logger feignLogger: Slf4jLogger
- Contract feignContract: SpringMvcContract
- Feign.Builder feignBuilder: Hy
代码如下:
@Configuration public class FeignClientsConfiguration { //省略代码 @Bean @ConditionalOnMissingBean public Decoder feignDecoder() { return new ResponseEntityDecoder(new SpringDecoder)); } @Bean @ConditionalOnMissingBean public Encoder feignEncoder() { return new SpringEncoder); } @Bean @ConditionalOnMissingBean public Contract feignContract(ConversionService feignConversionService) { return new SpringMvcContrac, feignConversionService); } //省略代码 }
重写配置:
你可以重写FeignClientsConfiguration中的bean,从而达到自定义配置的目的,比如FeignClientsConfiguration的默认重试次数为Re,即不重试,那么希望做到重写,代码如下:
@Configuration public class FeignConfig { @Bean public Retryer feignRetryer() { return new Re(100, SECONDS.toMillis(1), 5); } }
在上述代码更改了该FeignClient的重试次数,重试间隔为100ms,最大重试时间为1s,重试次数为5次。
Feign的工作原理
feign是一个伪客户端,即它不做任何的请求处理。Feign通过处理注解生成request,从而实现简化HTTP API开发的目的,即开发人员可以使用注解的方式定制request api模板,在发送http request请求之前,feign通过处理注解的方式替换掉request模板中的参数,这种实现方式显得更为直接、可理解。
@Retention) @Targe) @Documented @Impor) public @interface EnableFeignClients { // ...... }
通过包扫描注入FeignClient的bean,该源码在FeignClientsRegistrar类:首先在启动配置上检查是否有@EnableFeignClients注解,如果有该注解,则开启包扫描,扫描被@FeignClient注解接口。代码如下:
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { **registerDefaultConfiguration**(metadata, registry); **registerFeignClients**(metadata, registry); } private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 获取@**EnableFeignClients的属性值** Map<String, Object> defaultAttrs = metadata .getAnnotationAttributes(**EnableFeignClients**.cla(), true); if (defaultAttrs != null && de("defaultConfiguration")) { String name; if ()) { name = "default." + me(); } else { name = "default." + me(); } registerClientConfiguration(registry, name, de("defaultConfiguration")); } } private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) { BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition); builder.addConstructorArgValue(name); builder.addConstructorArgValue(configuration); regi( name + "." + FeignClien(), builder.getBeanDefinition()); } }
扫描到FeignClient后,将信息取出,以Bean的形式注入到ioc容器中,每一个FeignClient为FeignClientFactoryBean,源码如下:
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 路径扫描器,使用ClassLoader扫描包下的所有注解class文件 ClassPathScanningCandidateComponentProvider scanner = getScanner(); ); Set<String> basePackages; // 获取@**EnableFeignClients**的属性值 Map<String, Object> attrs = metadata .getAnnotationAttributes(EnableFeignClients.cla()); AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter( FeignClient.class); // 获取clients属性的属性值 final Class<?>[] clients = attrs == null ? null : (Class<?>[]) a("clients"); if (clients == null || clien == 0) { (annotationTypeFilter); basePackages = getBasePackages(metadata); } else { final Set<String> clientClasses = new HashSet<>(); basePackages = new HashSet<>(); for (Class<?> clazz : clients) { **basePackages**.add(clazz)); clien()); } AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() { @Override protected boolean match(ClassMetadata metadata) { String cleaned = me().replaceAll("\\\\$", "."); return clien(cleaned); } }; ( new AllTypeFilter(filter, annotationTypeFilter))); } for (String basePackage : basePackages) { // 包扫描,寻找候选组件,ClassLoader扫描包下的所有注解class文件 Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage); for (BeanDefinition candidateComponent : candidateComponents) { // 是注解类 if (candidateComponent instanceof AnnotatedBeanDefinition) { // verify annotated class is an interface AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; // beanDefinition的元数据 AnnotationMetadata annotationMetadata = beanDe(); // 判断beanDefinition是否为接口 A(), "@FeignClient can only be specified on an interface"); // 获取@FeignClient注解的所有属性,如value、url等键值 Map<String, Object> attributes = annotationMetadata .getAnnotationAttributes( FeignClient.cla()); String name = getClientName(attributes); registerClientConfiguration(registry, name, a("configuration")); // 注册注解了@FeignClient的接口的BeanDefinition **registerFeignClient**(registry, annotationMetadata, attributes); } } } } // 注册注解了@FeignClient的接口的beanDefinition private void **registerFeignClient**(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMe(); // 设置**beanClass**为FeignClientFactoryBean BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(**FeignClientFactoryBean**.class); validate(attributes); // 为FeignClient设置相关属性 de("url", getUrl(attributes)); de("path", getPath(attributes)); String name = getName(attributes); de("name", name); de("type", className); de("decode404", a("decode404")); de("fallback", a("fallback")); de("fallbackFactory", a("fallbackFactory")); de); String alias = name + "FeignClient"; AbstractBeanDefinition beanDefinition = de(); boolean primary = (Boolean)a("primary"); // has a default, won't be null beanDe(primary); String qualifier = getQualifier(attributes); if (qualifier)) { alias = qualifier; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); // BeanDefinition注册工具类 BeanDe(holder, registry); }
FeignClientFactoryBean:FeignClient实例化调用的是其getObject()
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware { @Override public Object getObject() throws Exception { return getTarget(); } /** * @param <T> the target type of the Feign client * @return a {@link Feign} client created with the specified data and the context * information */ <T> T getTarget() { FeignContext context = ); // **feign():** 设置Encoder、Decoder、Contract Feign.Builder builder = **feign**(context); // FeignClient的url为空, name不为空,即serverName if (!S)) { if (!("http")) { = "http://" + ; } else { = ; } += cleanPath(); return (T) **loadBalance**(builder, context, new HardCodedTarget<>, , )); } // FeignClient的url有值,且不以http开头 if ) && !.startsWith("http")) { = "http://" + ; } String url = + cleanPath(); Client client = getOptional(context, Client.class); if (client != null) { if (client instanceof LoadBalancerFeignClient) { // not load balancing because we have a url, // but ribbon is on the classpath, so unwrap client = ((LoadBalancerFeignClient) client).getDelegate(); } builder.client(client); } Targeter targeter = get(context, Targe); return (T) targeter.**target**(this, builder, context, new HardCodedTarget<>, , url)); } protected <T> T loadBalance builder, FeignContext context, HardCodedTarget<T> target) { Client client = getOptional(context, Client.class); if (client != null) { builder.client(client); Targeter targeter = get(context, Targe); return targeter.**target**(this, builder, context, target); } } class DefaultTargeter implements Targeter { @Override public <T> T **target**(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target) { return (target); } }
De()实际调用的是Feign.Builder.target()
public abstract class Feign { public static class Builder { public <T> T **target**(Target<T> target) { return build().**newInstance**(target); } public Feign **build**() { // 同步方法处理器 **SynchronousMethodHandler**.Factory synchronousMethodHandlerFactory = new Sync(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode, propagationPolicy); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, errorDecoder, synchronousMethodHandlerFactory); return new **ReflectiveFeign**(handlersByName, invocationHandlerFactory, queryMapEncoder); } } }
注入bean之后,通过jdk的代理,当请求Feign Client的方法时会被拦截,代码在ReflectiveFeign类,代码如下:
public <T> T newInstance(Target<T> target) { // targetToHandlersByName为ParseHandlersByName,Par()会 // 调用Sync,create(),为每个方法创建SynchronousMethodHandler Map<String, MethodHandler> nameToHandler = (target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); List<DefaultMethodHandler> **defaultMethodHandlers** = new LinkedList<DefaultMethodHandler>(); for (Method method : ().getMethods()) { if () == Object.class) { continue; } else i(method)) { // 默认方法处理器 DefaultMethodHandler handler = new DefaultMethodHandler(method); de(handler); me(method, handler); } else { // 遍历target的每个方法,为每个method设置一个SynchronousMethodHandler me(method, nameToHandler.ge((), method))); } } // factory为Default实现了InvocationHandlerFactory // create()返回的是FeignInvocationHandler,调用代理对象proxy的方法时会被 // FeignInvoca()拦截 InvocationHandler handler = (target, methodToHandler); // JDK动态代理 T proxy = (T) Proxy.newProxyInstance(().getClassLoader(), new Class<?>[]{()}, handler); for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { de(proxy); } return proxy; }
@FeignClient注解接口的方法会被**FeignInvocationHandler#invoke()**拦截
static class FeignInvocationHandler implements InvocationHandler { // MethodHandler为**SynchronousMethodHandler** private final Map<Method, MethodHandler> dispatch; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // ... return **dispatch**.get(method).invoke(args); } }
在SynchronousMethodHandler类进行拦截处理,当被拦截会根据参数生成RequestTemplate对象,该对象就是http请求的模板,代码如下:
@Override public Object invoke(Object[] argv) throws Throwable { // 将请求参数封装成RequestTemplate RequestTemplate template = buildTem(argv); // 重试器 Retryer retryer = (); while (true) { try { return **executeAndDecode**(template); } catch (RetryableException e) { re(e); if (logLevel != Logger.Level.NONE) { logger.logRetry(), logLevel); } continue; } } }
其中有个executeAndDecode()方法,该方法是通RequestTemplate生成Request请求对象,然后根据用client获取response。
Object executeAndDecode(RequestTemplate template) throws Throwable { Request request = targetRequest(template); ...//省略代码 response = client.execute(request, options); if () >= 200 && re() < 300) { if == me()) { return null; } else { // 解码器解码:decoder.decode(response) Object result = decode(response); shouldClose = closeAfterDecode; return result; } } ...//省略代码 }
Client组件
其中Client组件是一个非常重要的组件,Feign最终发送request请求以及接收response响应,都是由Client组件完成的,其中Client的实现类,只要有Client.Default,该类由HttpURLConnnection实现网络请求,另外还支持HttpClient、Okhttp.
首先来看以下在FeignRibbonClient的自动配置类,FeignRibbonClientAutoConfiguration ,主要在工程启动的时候注入一些bean,其代码如下:
package org..**openfeign**.ribbon; @ConditionalOnClass({ ILoadBalancer.class, Feign.class }) @ConditionalOnProperty(value = ";, matchIfMissing = true) @Configuration(proxyBeanMethods = false) @AutoConfigureBefore) @EnableConfigurationProperties({ FeignH }) @Import({ **HttpClientFeignLoadBalancedConfiguration**.class, **OkHttpFeignLoadBalancedConfiguration**.class, **DefaultFeignLoadBalancedConfiguration**.class }) public class FeignRibbonClientAutoConfiguration { @Bean @Primary @ConditionalOnMissingBean @ConditionalOnMissingClass("org.;) public CachingSpringLoadBalancerFactory cachingLBClientFactory( SpringClientFactory factory) { return new CachingSpringLoadBalancerFactory(factory); } @Configuration(proxyBeanMethods = false) class DefaultFeignLoadBalancedConfiguration { @Bean @ConditionalOnMissingBean public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) { return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory, clientFactory); } } }
在缺失配置feignClient的情况下,会自动注入new Client.Default(),跟踪Client.Default()源码,它使用的网络请求框架为HttpURLConnection,代码如下:
@Override public Response execute(Request request, Options options) throws IOException { HttpURLConnection connection = convertAndSend(request, options); return convertResponse(connection).toBuilder().request(request).build(); }
怎么在feign中使用HttpClient,查看FeignRibbonClientAutoConfiguration引入了HttpClientFeignLoadBalancedConfiguration
@Configuration(proxyBeanMethods = false) @**ConditionalOnClass**(**ApacheHttpClient**.class) @ConditionalOnProperty(value = ";, matchIfMissing = true) @Impor) class HttpClientFeignLoadBalancedConfiguration { @Bean @ConditionalOnMissingBean) public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory, HttpClient httpClient) { ApacheHttpClient delegate = new ApacheHttpClient(httpClient); return new **LoadBalancerFeignClient**(delegate, cachingFactory, clientFactory); } }
从代码@ConditionalOnCla)注解可知道,只需要在pom文件加上HttpClient的classpath就行了,另外需要在配置文件上加上为true,从@ConditionalOnProperty注解可知,这个可以不写,在默认的情况下就为true.
在pom文件加上:
<dependency> <groupId>com.ne;/groupId> <artifactId>feign-httpclient</artifactId> <version>RELEASE</version> </dependency>
同理,如果想要feign使用Okhttp,则只需要在pom文件上加上feign-okhttp的依赖:
<dependency> <groupId>com.ne;/groupId> <artifactId>feign-okhttp</artifactId> <version>RELEASE</version> </dependency>
HttpClient、Okhttp最终会封装成LoadBalancerFeignClient。
feign的负载均衡是怎么样实现的呢?
通过上述的FeignRibbonClientAutoConfiguration类配置Client的类型(httpurlconnection,okhttp和httpclient)时候,可知最终向容器注入的是LoadBalancerFeignClient,即负载均衡客户端。现在来看下LoadBalancerFeignClient的代码:
public class LoadBalancerFeignClient implements Client { @Override public Response execute(Request request, Reque options) throws IOException { try { URI asUri = URI.create()); String clientName = a(); URI uriWithoutHost = cleanUrl(), clientName); FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest( , request, uriWithoutHost); IClientConfig requestConfig = getClientConfig(options, clientName); // lbClient()创建FeignLoadBalancer // 使用FeignLoadBalancer执行请求 return lbClient(clientName).**executeWithLoadBalancer**(ribbonRequest, requestConfig).toResponse(); } catch (ClientException e) { IOException io = findIOException(e); if (io != null) { throw io; } throw new RuntimeException(e); } } }
FeignLoadBalancer继承了AbstractLoadBalancerAwareClient和LoadBalancerContext,其中有个executeWithLoadBalancer()方法,即通过负载均衡的方式请求。
public abstract class AbstractLoadBalancerAwareClient<S extends ClientRequest, T extends IResponse> extends LoadBalancerContext implements IClient<S, T>, IClientConfigAware { 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()) .build(); try { return command.**submit**( new ServerOperation<T>() { @Override public Observable<T> call(Server server) { URI finalUri = reconstructURIWithServer(server, reque()); S requestForServer = (S) reque(finalUri); try { return Ob(requestForServer, requestConfig)); } catch (Exception e) { return Ob(e); } } }) .toBlocking() .single(); } catch (Exception e) { Throwable t = e.getCause(); if (t instanceof ClientException) { throw (ClientException) t; } else { throw new ClientException(e); } } } }
其中服务在submit()方法上,点击submit进入具体的方法,这个方法是LoadBalancerCommand的方法:
public class LoadBalancerCommand<T> { public Observable<T> submit(final ServerOperation<T> operation) { Observable<T> o = (server == null ? **selectServer**() : Ob(server)) .concatMap(new Func1<Server, Observable<T>>() { @Override // Called for each server being selected public Observable<T> call(Server server) { con(server); } } } }
上述代码中有个selectServer(),该方法是选择服务的进行负载均衡的方法,代码如下:
private Observable<Server> selectServer() { return Ob(new OnSubscribe<Server>() { @Override public void call(Subscriber<? super Server> next) { try { Server server = loadBalancerContext.**getServerFromLoadBalancer**(loadBalancerURI, loadBalancerKey); next.onNext(server); next.onCompleted(); } catch (Exception e) { next.onError(e); } } }); }
最终负载均衡交给loadBalancerContext的**getServerFromLoadBalancer()**来处理
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException { // ... **ILoadBalancer** lb = getLoadBalancer(); Server svc = lb.chooseServer(loadBalancerKey); // ... }
总结
总到来说,Feign的源码实现的过程如下:
- 首先通过@EnableFeignCleints注解开启FeignCleint
- 根据Feign的规则实现接口,并加@FeignCleint注解
- 程序启动后,会进行包扫描,扫描所有的@FeignCleint的注解的类,并将这些信息以FeignClientFactoryBean的形式注入到ioc容器中。
- 当接口的方法被调用,通过jdk的代理,来生成具体的RequesTemplate
- RequesTemplate在生成Request
- Request交给Client去处理,其中Client可以是HttpUrlConnection、HttpClient、Okhttp。
- 最终Client会封装成LoadBalancerFeignClient,使用ILoadBalancer来selectServer(),这个类结合类Ribbon做到了负载均衡。