Feign源码学习

Feign在Spring Cloud中主要用于封装Http请求细节,让微服务之间的调用同服务内调用一样便捷。
Open Feign的源码实现过程主要可以概括为以下几点

  1. 通过@EnableFeignClients引入FeignClientsRegistrar。
  2. FeignClientsRegistrar实现了ImportBeanDefinition接口,扫描对应路径下被@EnableFeign注解修饰的接口(必须是接口),生成对应的FeignClientSpecifition。
  3. FeignAutoConfiguration注入所有的FeignClientSpecification的实例,注入到FeignContext中。
  4. 当接口调用时,通过CGLIB动态代理的形式,与已有的RestTemplate整合请求参数,生成最后的RestTemplate。
  5. RestTemplate被转换为Request,使用了Ribbon还会被转换为RibbonRequest。
  6. 通过Ribbon进行负载均衡,将url中的应用名转化为ip。
  7. 调用Request内的Client执行真正的Http请求,client可以是ApacheHttpClient等实例。
  8. 返回结果response,使用了Ribbon还会转化为RibbonResponse。
  9. 根据返回结果的状态码使用Decoder或者ErrorDecoder进行解析。Decoder等组件都维护在FeignContext上下文中。
由此得到Feign的启用和请求调用的大体步骤,这些步骤又可以分为三个阶段:BeanDefinition的注册、实例的初始化、函数的调用和网络请求三个部分,分别以FeignClientRegistrar、FeignClientFactoryBean、SynchronousMethodHandler为链路起点。
1、BeanDefinition的注册 1.1、@EnableFeignClients注解启用Feign
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) // 引入FeignClientsRegistrar,用来处理@FeignClient public @interface EnableFeignClients { /** * 用来指定自动扫描的包 */ String[] value() default {}; String[] basePackages() default {}; Class[] basePackageClasses() default {}; /** * 自定义配置 */ Class[] defaultConfiguration() default {}; /** * 指定FeignClient注解修饰的类,如果不为空,将禁用FeignClient自动扫描 */ Class[] clients() default {}; }

1.2、FeignClientsRegistrar注册
@Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 处理@EnableFeignClients注解,从配置参数来构建Feign的自定义Configuration来注册 registerDefaultConfiguration(metadata, registry); // 扫描@FeignClient注解修饰的类,注册BeanDefinition到BeanDefinitionRegistrar中 registerFeignClients(metadata, registry); }

1.2.1、处理@EnableFeignClients注解,注册配置 @EnableFeignClients的自定义配置类是被@Configuration注解修饰的配置类,它会提供一系列组装FeignClient的各类组件实例。这些组件包括:Client、Targeter、Decoder、Encoder和Contract等。
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(); } // 注册BeanDefinition到Spring容器中 registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration")); } }

1.2.2、处理@FeignClient注解,注册FeignClient
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))); }// 通过scanner从扫描路径下扫描候选FeignClient 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 = https://www.it610.com/article/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")); // 注册BeanDefinition到Spring容器中 registerFeignClient(registry, annotationMetadata, attributes); } } } }

2.3、FeignAutoConfiguration,将配置的实例放到FeignContext上下文
public class FeignAutoConfiguration {// 自动注入所有的FeignClientSpecification配置Bean @Autowired(required = false) private List configurations = new ArrayList<>(); @Bean public FeignContext feignContext() { FeignContext context = new FeignContext(); // 把这些配置Bean放到上下文中,创建FeignContext到Spring容器 context.setConfigurations(this.configurations); return context; } //... }

2、实例的初始化 前一步有FeignClientRegistrar完成BeanDefinition的注册后,FeignClientFactoryBean接过使命,进行Bean的初始化。
2.1、FeignClientFactoryBean
FeignClientFactoryBean实现了FactoryBean接口,作为工厂类,Spring容器通过调用它的getObject方法来获取对应的Bean实例。通过判断@FeignClient注解是否有url配置,决定生成的是LoadBalancerFeignClient还是普通的FeignClient,因为前者更常见,这里着重介绍LoadBalancerFeignClient。LoadBalancerFeignClient的负载均衡由Ribbon支持,在这里不做详细介绍。
@Override public Object getObject() throws Exception { return getTarget(); }// 获取FeignClient实例 T getTarget() { FeignContext context = this.applicationContext.getBean(FeignContext.class); // 从上下文中获取Encode,Contract等各种组件放到Feign.Builder中 Feign.Builder builder = feign(context); // 判断对应FeignClient是否有url // 有url会生成LoadBalancerFeignClient // 否则返回普通的FeignClient if (!StringUtils.hasText(this.url)) { if (!this.name.startsWith("http")) { this.url = "http://" + this.name; } else { this.url = this.name; } this.url += cleanPath(); // 生成LoadBalancerFeignClient,带有负载均衡 return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, this.url)); }// 没有配置url,返回普通的FeignClient // ... }// 获取LoadBalancerFeignClient // 这里会从上下文中获取Client和Targeter,如果尚未初始化会触发他们的实例化 // 在FeignClientAutoConfiguration中有Targeter的 protected T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget target) { // 从上下文中获取Client实例,默认为ApacheHttpClient Client client = getOptional(context, Client.class); if (client != null) { // 在Builder中放入Client builder.client(client); // 获取Client实例 Targeter targeter = get(context, Targeter.class); // target()调用Builder.build()方法,然后调用newInstance方法生成Proxy return targeter.target(this, builder, context, target); }// ... }

2.2、Targeter
【Feign源码学习】Targeter是一个接口,它的target方法生成对应的实例对象。他有两个实现类,分别为DefaultTargeter和HystrixTargeter。OpenFeign使用HystrixTargeter这一层抽象来封装关于Hystrix的实现。
DefaultTargeter DefaultTargeter只是调用了Feign.Builder的target方法。
class DefaultTargeter implements Targeter { @Override public T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget target) { return feign.target(target); } }

Feign.Builder#target()
public T target(Target target) { // newInstance()调用的是ReflectiveFeign的newInstance() return build().newInstance(target); }

2.3、 重要的ReflectiveFeign
2.3.1、 newInstance()的两件事 newInstance()主要做了两件事:
  1. 扫描了该FeignClient接口类中的函数信息,生成对应的MethodHandler。targetToHandlersByName.apply(target);
  2. 使用Proxy生成FeignClient的实例对象。
  • Contract
在生成对应的MethodHandler过程中,BaseContract的parseAndValidateMetadata方法会依次解析接口类的注解,函数注解和函数的参数注解,将这些注解包含的信息封装到MethodMetadata对象中,将@RequestMapping的uri拼接到函数的uri中等操作。
在MethodHandler中包含了SynchronizedMethodHandler,内含RestTemplate.Factory用来生成最后的请求RestTemplate。

@Override public T newInstance(Target target) { // 这里生成所有方法MethodHandler // nameToHandler由Contract解析target得到 // 在MethodHandler中存在对应方法的RestTemplate.Factory和SynchronizedMethodHandler Map nameToHandler = targetToHandlersByName.apply(target); Map methodToHandler = new LinkedHashMap(); List defaultMethodHandlers = new LinkedList(); for (Method method : target.type().getMethods()) { if (method.getDeclaringClass() == Object.class) { continue; } else if (Util.isDefault(method)) { DefaultMethodHandler handler = new DefaultMethodHandler(method); defaultMethodHandlers.add(handler); methodToHandler.put(method, handler); } else { methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); } } // 这里把target和MethodHandler的映射关系 // 创建出一个FeignInvocationHandler InvocationHandler handler = factory.create(target, methodToHandler); // 使用Proxy生成FeignClient的实例对象。 T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class[] {target.type()}, handler); // 把defaultMethodHandler绑定到proxy for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; }

2.3.2、第一件事:扫描了该FeignClient接口类中的函数信息 2.3.3、第二件事:使用Proxy生成FeignClient的实例对象 3、函数的调用和网络请求 Feign源码学习
文章图片
OpenFeign网络请求流程图 3.1、SynchronousMethodHandler#invoke
调用RestTemplate.Factory的create方法生成RestTemplate方法。
@Override public Object invoke(Object[] argv) throws Throwable { // 调用RestTemplate.Factory的create方法生成RestTemplate实例 RequestTemplate template = buildTemplateFromArgs.create(argv); Retryer retryer = this.retryer.clone(); while (true) { try { // Target将template转换为request实例 // client调用request,返回response return executeAndDecode(template); } catch (RetryableException e) { // ... } } }

3.2、RestTemplate.Factory#create
根据实际的调用参数和之前的RestTemplate结合,生成RestTemplate实例。
@Override public RequestTemplate create(Object[] argv) { RequestTemplate mutable = RequestTemplate.from(metadata.template()); if (metadata.urlIndex() != null) { int urlIndex = metadata.urlIndex(); checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex); mutable.target(String.valueOf(argv[urlIndex])); } Map varBuilder = new LinkedHashMap(); // 遍历MethodMetadata中所有关于参数的索引及其对应名称的配置信息。 for (Entry entry : metadata.indexToName().entrySet()) { int i = entry.getKey(); Object value = https://www.it610.com/article/argv[entry.getKey()]; if (value != null) { // Null values are skipped. if (indexToExpander.containsKey(i)) { value = expandElements(indexToExpander.get(i), value); } for (String name : entry.getValue()) { varBuilder.put(name, value); } } }RequestTemplate template = resolve(argv, mutable, varBuilder); // 设置queryMap参数 if (metadata.queryMapIndex() != null) { // add query map parameters after initial resolve so that they take // precedence over any predefined values Object value = argv[metadata.queryMapIndex()]; // 调用Encoder进行入参解析 Map queryMap = toQueryMap(value); template = addQueryMapQueryParameters(queryMap, template); }// 设置headerMap参数 if (metadata.headerMapIndex() != null) { template = addHeaderMapHeaders((Map) argv[metadata.headerMapIndex()], template); }return template; }

3.3、SynchronousMethodHandler#executeAndDecode
在这个流程里用到了Encoder、Decoder、ErrorDecoder组件;
Object executeAndDecode(RequestTemplate template) throws Throwable { // 1、通过RequestInterceptor机制, // 通过拦截器为每个请求增加固定的header信息 Request request = targetRequest(template); Response response; long start = System.nanoTime(); try { // 2、调用client的execute方法进行http请求 response = client.execute(request, options); } catch (IOException e) { throw errorExecuting(request, e); } boolean shouldClose = true; try { if (Response.class == metadata.returnType()) { // ... 省略进行一些body校验的代码 // Ensure the response body is disconnected byte[] bodyData = https://www.it610.com/article/Util.toByteArray(response.body().asInputStream()); return response.toBuilder().body(bodyData).build(); } // http请求状态码为在[200, 300)代表调用成功 if (response.status()>= 200 && response.status() < 300) { if (void.class == metadata.returnType()) { return null; } else { Object result = decode(response); shouldClose = closeAfterDecode; return result; } } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) { // 调用异常,且返回类型为Void Object result = decode(response); shouldClose = closeAfterDecode; return result; } else { // 调用失败,errorDecoder组件解析返回信息 throw errorDecoder.decode(metadata.configKey(), response); } } catch (IOException e) { throw errorReading(request, response, e); } finally { if (shouldClose) { ensureClosed(response.body()); } } }

RequestInterceptor OpenFeign也提供了RequestInterceptor机制,在由RestTemplate生成Request的过程中,会调用所有的RequestInterceptor对RestTemplate进行处理。
Request targetRequest(RequestTemplate template) { // 使用请求拦截器为每个请求增加固定的header信息 for (RequestInterceptor interceptor : requestInterceptors) { interceptor.apply(template); } return target.apply(template); }

Client Client用来进行真正的Http请求,前文提到当@FeignClient中有url时,getTarget()中会生成LoadBalancerFeignClient,Ribbon实现了负载均衡决定调用哪台机器。
// 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); // 将request封装成了RibbonRequest FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest( this.delegate, request, uriWithoutHost); IClientConfig requestConfig = getClientConfig(options, clientName); // 这里涉及到Ribbon如何实现负载均衡,具体内容将在Ribbon源码中展开。 return lbClient(clientName) .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse(); } catch (ClientException e) { // ... 不重要 } }

真正执行的Client有OkHttpClient和RibbonClient两个子类。OkhttpClient调用OkHttp的相关组件进行网络请求的发送。
参考 [Spring Cloud微服务架构进阶

    推荐阅读