Feign 的实现原理

Feign 实现原理

Feign是申明式的 HTTP 客户端。代码中创建一个接口并加上@FeingClient 注解即可使用。其底层封装了 HTTP 客户端构建并发送的复杂逻辑。同时也可以整合注册中心及 Ribbon 为其提供负载均衡能力;通过整合 Histrix/sentinal 实现熔断限流功能。本期主要分享下 Feign 与 SpringCloud 的整合过程,及其底层 HTTP 调用的实现细节。

https://www.springcloud.cc/spring-cloud-greenwich.html#_spring_cloud_openfeign

使用

  1. pom.xml 中添加

    <dependency>
      <groupId>org.springframework.cloudgroupId>
      <artifactId>spring-cloud-starter-openfeignartifactId>
    dependency>
    
  2. 启动类添加 @EnableFeignClients注解

    @SpringBootApplication(scanBasePackages = {"com.xxx"})
    // 指定 FeiginClient 扫描路径
    @EnableFeignClients(basePackages = {"com.xxx.biz.feign"})
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    
  3. 申明 FeignClient

    @FeignClient(name = "inverter-data-service", url = "${feign.property.inverter-data-service.url}", configuration = {xxx})
    public interface InverterDataFeign {
        @PostMapping(value = "/api/inverter/get_first_date")
        FirstDataResponse firstData(@RequestBody String sn, @RequestHeader("token") String token);
    }
    
  4. 服务调用

    @Service
    public class DemoService {
      @Autowired
    	private InverterDataFeign inverterDataFeign;
    
    	public void remoteCall(String sn, String token) {
      	// ...
      	FirstDataResponse firstDataResponse = inverterDataFeign.firstData(sn, token);
      	// ...
    	}
    }
    

思考

  • @FeignClient注解是如何使普通的 Java 接口具有 HttpClient 能力的?
  • @FeignClient并非 @Component注解,为什么可以像其他Spring 组件一样注入到其他组件中(如 DemoService)?

实现流程图

Feign 的实现原理_第1张图片

实现细节

扫描 & 注册

启动类 Application 申明了注解 @EnableFeignClients,而@EnableFeignClients又申明了@Import(FeignClientsRegistrar.class)FeignClientsRegistrar实现了 ImportBeanDefinitionRegistrar

public interface ImportBeanDefinitionRegistrar {

	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
			BeanNameGenerator importBeanNameGenerator) {

		registerBeanDefinitions(importingClassMetadata, registry);
	}

  /**
  	* 子类实现
  	*/
	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	}

}

当 Spring IOC 容器启动时,会去扫描 @ComponentScan 指定包下所有 @Import 的注解,如果 @Import 的 value 参数是 ImportBeanDefinitionRegistrar 的实现类,就会调用 ImportBeanDefinitionRegistrar#registerBeanDefinitions() 方法,而 FeignClientsRegistrar 就是 ImportBeanDefinitionRegistrar 的实现类;故容器初始化过程中 FeignClientsRegistrar#registerBeanDefinitions() 会被执行,这个方法会做两件事情,1. 注册 @EnableFeignClients 中 configuration 参数指定的默认配置类;2. 扫描并注册子包下被 @FeignClient 注解的 feign 客户端

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
  @Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		// 注册默认配置类
    registerDefaultConfiguration(metadata, registry);
    // 扫描注册 feign 客户端至 spring 容器
		registerFeignClients(metadata, registry);
	}
}

下面主要看registerFeignClients(metadata, registry)的实现

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

		LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
  	// 获取 @EnableFeignClients 中所有的参数
		Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
  	// 拿到 clients 参数
		final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
      /* 如果未设置 clients 参数,则去子包下扫描所有注解类型为 @FeignClient 的类 */
      // 获取扫描器
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			scanner.setResourceLoader(this.resourceLoader);
      // 设置扫描器的过滤条件,只扫描 @FeignClient 类型
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
      // 拿到 @FeignClient 的 basePackages 参数
			Set<String> basePackages = getBasePackages(metadata);
			for (String basePackage : basePackages) {
        // 执行扫描,构建并添加 BeanDefinition 实体
				candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
			}
		}
		else {
      // 如果 clients 参数不为空,则只去注册指定的 FeignClient
			for (Class<?> clazz : clients) {
				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
			}
		}

    /* 
    	遍历 BeanDefinitions(申明 @FeignClient 的类定义)
       1. 注册 @FeignClient 中 configuration 参数指定的配置类
       2. 注册具体的 FeignClient 
    */
		for (BeanDefinition candidateComponent : candidateComponents) {
      // 查看组件是否是包含注解的 BeanDefinition
			if (candidateComponent instanceof AnnotatedBeanDefinition) {
				// verify annotated class is an interface
				AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
        // 拿到类的注解元数据(包含 class 信息)
				AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
        // 校验,@FeignClient 只能申明在接口上
				Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");

        // 拿到 @FeignClient 的属性数据
				Map<String, Object> attributes = annotationMetadata
						.getAnnotationAttributes(FeignClient.class.getCanonicalName());

        // 获取客户端名称,依次查看 contextId、value、name、serviceId,只要有值就返回
				String name = getClientName(attributes);
        // 拿到 configuration 参数,注册配置类
				registerClientConfiguration(registry, name, attributes.get("configuration"));
				// 注册 FeignClient
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}

接着看registerFeignClient(registry, annotationMetadata, attributes)的逻辑:

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
			Map<String, Object> attributes) {
    // 拿到接口类名,构建 class 对象
		String className = annotationMetadata.getClassName();
		Class clazz = ClassUtils.resolveClassName(className, null);
		ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
				? (ConfigurableBeanFactory) registry : null;
		String contextId = getContextId(beanFactory, attributes);
		String name = getName(attributes);
    // 由于 FeignClient 是个接口,业务里没有实现类,所以这里将 feignClient 封装成 FactoryBean,后面对象初始化时,回调其 getObject() 方法,实现动态创建 FeignClient 实现类的 Bean 对象
		FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
		factoryBean.setBeanFactory(beanFactory);
		factoryBean.setName(name);
		factoryBean.setContextId(contextId);
		factoryBean.setType(clazz);
		factoryBean.setRefreshableClient(isClientRefreshEnabled());
    // 创建 BeanDefinitionBuilder, 会传入一个回调接口 instanceSupplier 的实现,后续在实例化该 BeanDefinition 对应的 Bean 时,会去回调这个接口中实现的方法
		BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
			factoryBean.setUrl(getUrl(beanFactory, attributes));
			factoryBean.setPath(getPath(beanFactory, attributes));
			factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
			Object fallback = attributes.get("fallback");
			if (fallback != null) {
				factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
						: ClassUtils.resolveClassName(fallback.toString(), null));
			}
			Object fallbackFactory = attributes.get("fallbackFactory");
			if (fallbackFactory != null) {
				factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
						: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
			}
			return factoryBean.getObject();
		});
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
		definition.setLazyInit(true);
		validate(attributes);

  	// 设置 BD 的一些属性,是否 primary,别名等...
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
		beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);

		// has a default, won't be null
		boolean primary = (Boolean) attributes.get("primary");

		beanDefinition.setPrimary(primary);

		String[] qualifiers = getQualifiers(attributes);
		if (ObjectUtils.isEmpty(qualifiers)) {
			qualifiers = new String[] { contextId + "FeignClient" };
		}

		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
    // 注册 FeignClient 对应的 BeanDefinition
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
		// 使用 RefreshScope 创建 Request.Options(请求超时时间等配置) BeanDefinition
		registerOptionsBeanDefinition(registry, contextId);
	}

至此,Spring 容器启动过程中,扫描 FeignClient 并注册到容器的流程已经结束

初始化代理类

注册到 Spring 容器中的 BeanDefinition 会存入一个 Map 中(BeanDefinitionMap);所有 BeanDefinition 注册完毕后,Spring 会对其进行初始化,即实例化并存入 Spring 容器;注册 FeignClient 时,FeignClient 被包装成 FeignClientFactoryBean(FactoryBean 实现类)存入 BeanDefinitionMap;故初始化 FeignClientFactoryBean 时,会回调其 getObject() 方法

再来看刚刚注册 FeignClient 的逻辑:

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
			Map<String, Object> attributes) {
    // 省略前面逻辑...
      
    // 由于 FeignClient 是个接口,业务里没有实现类,所以这里将 feignClient 封装成 FactoryBean,后面对象初始化时,回调其 getObject() 方法,实现动态创建 FeignClient 实现类的 Bean 对象
		FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
		factoryBean.setBeanFactory(beanFactory);
		factoryBean.setName(name);
		factoryBean.setContextId(contextId);
		factoryBean.setType(clazz);
		factoryBean.setRefreshableClient(isClientRefreshEnabled());
    // 创建 BeanDefinitionBuilder, 会传入一个回调接口 instanceSupplier 的实现,后续在实例化该 BeanDefinition 对应的 Bean 时,会去回调这个接口中实现的方法
		BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
      // 初始化参数
			factoryBean.setUrl(getUrl(beanFactory, attributes));
			factoryBean.setPath(getPath(beanFactory, attributes));
			factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
			Object fallback = attributes.get("fallback");
			if (fallback != null) {
				factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
						: ClassUtils.resolveClassName(fallback.toString(), null));
			}
			Object fallbackFactory = attributes.get("fallbackFactory");
			if (fallbackFactory != null) {
				factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
						: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
			}
      // 主要逻辑在这个方法里
			return factoryBean.getObject();
		});
  
		// 省略后面逻辑...
	}

创建 FeignClient 对象的逻辑均在 FeignClientFactoryBean#getObject() 中:

	@Override
	public Object getObject() {
		return getTarget();
	}

	/**
	 * @param  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(FeinContext 是在 FeignAutoConfiguration 中通过 @Bean 注入的)
    // 属于 ApplicationContext 的子容器
		FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
				: applicationContext.getBean(FeignContext.class);
    // 从容器中获取 feign builder 对象(该对象也是在 FeignAutoConfiguration 中通过 @Bean 注入的)
		Feign.Builder builder = feign(context);

		if (!StringUtils.hasText(url)) {
			if (url != null && LOG.isWarnEnabled()) {
				LOG.warn("The provided URL is empty. Will try picking an instance via load-balancing.");
			}
			else if (LOG.isDebugEnabled()) {
				LOG.debug("URL not provided. Will use LoadBalancer.");
			}
			if (!name.startsWith("http")) {
				url = "http://" + name;
			}
			else {
				url = name;
			}
			url += cleanPath();
      // 负载均衡,需结合 ribbon 使用
			return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
		}
		if (StringUtils.hasText(url) && !url.startsWith("http")) {
			url = "http://" + url;
		}
		String url = this.url + cleanPath();
		Client client = getOptional(context, Client.class);
		if (client != null) {
			if (client instanceof FeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
			}
			if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
			}
			builder.client(client);
		}
    // 容器中获取 Targeter 实例(DefaultTargeter)
		Targeter targeter = get(context, Targeter.class);
    // 获取代理对象
		return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
	}

DefaultTargeter#target 会去实例化 ReflectiveFeign, 并调用其 newInstance() 方法实例化代理对象,ReflectiveFeign#newInstance() 逻辑:

public <T> T newInstance(Target<T> target) {
    // feign 中调用的方法名 -> SynchronousMethodHandler映射;(SynchronousMethodHandler 是 InvocationHandler 的封装,包含 Feign 请求的配置信息) 
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    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)));
      }
    }
    // 创建 InvocationHandler 对象
    InvocationHandler handler = factory.create(target, methodToHandler);
    // JDK 动态代理,创建代理对象,
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

至此,代理对象创建结束.

发送请求

当 FeignClient 发起 http 请求时,会从容器中获取对应的代理类,并调用 FeignInvocationHandler#invoke() 方法,其最终实现在 SynchronousMethodHandler#invoke() 中:

public Object invoke(Object[] argv) throws Throwable {
    // 解析请求参数,封装 RequestTemplate
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    // 获取 Request.Options 对象,封装了请求的 connectTimeout/readTimeout 等信息
    Options options = findOptions(argv);
    // 重试机制
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        // 执行请求,并解析响应
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        // 重试逻辑
        try {
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

执行请求的逻辑主要在 executeAndDecode() 方法:

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
  Request request = targetRequest(template);

  // 打印日志
  if (logLevel != Logger.Level.NONE) {
    logger.logRequest(metadata.configKey(), logLevel, request);
  }

  Response response;
  long start = System.nanoTime();
  try {
    // 执行请求,拿到响应
    response = client.execute(request, options);
    response = response.toBuilder()
        .request(request)
        .requestTemplate(template)
        .build();
  } catch (IOException e) {
    // ...
  }
  // ...
}

如果未设置 http 客户端,则使用默认的客户端 Client.Default,即构建 java.net.HttpURLConnection 并发送请求:

 public Response execute(Request request, Options options) throws IOException {
 			// 构建 java.net.HttpURLConnection 并发送请求
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection, request);
    }

问题

实例化 FeignClient 时,Feign 为什么要使用 FeignContext 来给每个 FeignClient 创建一个子IOC容器,如果直接使用父容器会有什么问题?

你可能感兴趣的:(Java,java,开发语言,spring,cloud)