欢迎访问shiker.tech

请允许在我们的网站上展示广告

您似乎使用了广告拦截器,请关闭广告拦截器。我们的网站依靠广告获取资金。

Open Feign如何运行的?
(last modified Dec 10, 2023, 3:45 PM )
by
侧边栏壁纸
  • 累计撰写 176 篇文章
  • 累计创建 61 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

Open Feign如何运行的?

橙序员
2023-09-25 / 0 评论 / 0 点赞 / 280 阅读 / 3,579 字 / 正在检测百度是否收录... 正在检测必应是否收录...
文章摘要(AI生成)

Open Feign是一个用于在Spring Boot应用中使用的轻量级HTTP客户端工具。在使用Open Feign时,我们需要在启动类上加上@EnableFeignClients注解,并导入FeignClientsRegistrar类。FeignClientsRegistrar类实现了ImportBeanDefinitionRegistrar、ResourceLoaderAware和EnvironmentAware这三个接口。ImportBeanDefinitionRegistrar接口用于将Feign客户端的bean定义添加到Spring容器的bean定义注册表中,ResourceLoaderAware接口用于加载Feign客户端,EnvironmentAware接口用于获取Feign客户端的相关配置。整个注册过程可以分为三个步骤:获取Feign相关的配置、使用加载器加载Feign客户端,并将其注册到容器中。在注册过程中,需要注册两个bean定义:我们自定义的Feign全局配置类和所有的Feign客户端的自定义配置类。通过这样的注册方式,我们可以方便地在Spring Boot应用中使用Open Feign来进行HTTP请求发送。

Open Feign如何运行

注册feign客户端

spring boot使用openfeign时,需要在启动类上加入@EnableFeignClients注解,如下所示:

@EnableFeignClients
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        // Run application
        SpringApplication.run(Application.class, args);
    }
}

而我们查看@EnableFeignClients注解,会发现该注解导入了一个bean注册器FeignClientsRegistrar.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    ....
}

FeignClientsRegistrar则实现了对feign client的注册,该类分别实现了ImportBeanDefinitionRegistrarResourceLoaderAwareResourceLoaderAware三个接口。其中ImportBeanDefinitionRegistrar负责添加bean定义到spring容器的bean定义注册表中,而ResourceLoaderAware则负责将进行feign client加载,最后EnvironmentAware获取feign client的相关配置。这也对应了我们注册的三个步骤:

  1. 获取feign相关的配置
  2. 获取feign client加载器
  3. 使用加载器加载client,并将其注册到容器中

注册BeanDefinition

有两个要注册:

  1. 我们自定义的Feign全局配置类
  2. feign客户端所有的自定义配置类以及feign客户端
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        //注册自定义配置类
		registerDefaultConfiguration(metadata, registry);
        //注册feign客户端
		registerFeignClients(metadata, registry);
	}

Feign全局配置类注册

@EnableFeignClients中自定义配置加载:

  1. 获取EnableFeignClients中的默认配置defaultConfiguration中定义的配置类,包括自定义的编码、解码器

    1. 配置类bean名称前缀获取:
      1. default.+EnableFeignClients注解所在类名称+配置类名称
  2. 将自定义的编码、解码器作为BeanDefinition注册到容器中

    private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
		//获取EnableFeignClients中的默认配置defaultConfiguration中定义的配置类,包括自定义的编码、解码器
        if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
           String name;
           if (metadata.hasEnclosingClass()) {
              name = "default." + metadata.getEnclosingClassName();
           }
           else {
              name = "default." + metadata.getClassName();
           }
           registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
        }
    }

	private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
		builder.addConstructorArgValue(name);
		builder.addConstructorArgValue(configuration);
        //将自定义的编码、解码器的bean定义注册到容器中
		registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
				builder.getBeanDefinition());
	}

自定义配置类以及feign客户端注册

feign客户端注册:

  1. 判断EnableFeignClients注解中clients是否配置了feign客户端

  2. 如果客户端没有在注解中指定,则创建一个注解扫描器,对FeignClient注解进行扫描,将扫描到的客户端添加到BeanDefinition结果集中

    扫描路径获取:

    1. EnableFeignClients注解中的value值
    2. EnableFeignClients注解中的basePackages值
    3. EnableFeignClients注解中的basePackageClasses值
    4. 如果以上三个属性都为空,则扫描EnableFeignClients注解所在包路径

    源码如下:

    	//获取扫描路径
    	protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
    		Map<String, Object> attributes = importingClassMetadata
    				.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());
    		//EnableFeignClients注解中的value值
    		Set<String> basePackages = new HashSet<>();
    		for (String pkg : (String[]) attributes.get("value")) {
    			if (StringUtils.hasText(pkg)) {
    				basePackages.add(pkg);
    			}
    		}
            //EnableFeignClients注解中的basePackages值
    		for (String pkg : (String[]) attributes.get("basePackages")) {
    			if (StringUtils.hasText(pkg)) {
    				basePackages.add(pkg);
    			}
    		}
            //EnableFeignClients注解中的basePackageClasses值
    		for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
    			basePackages.add(ClassUtils.getPackageName(clazz));
    		}
    		//如果以上三个属性都为空,则扫描EnableFeignClients注解所在路径
    		if (basePackages.isEmpty()) {
    			basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
    		}
    		return basePackages;
    	}
    
    
  3. 如果客户端在注解中指定,则直接添加到BeanDefinition结果集中

  4. 对BeanDefinition结果集进行遍历

    1. 如果FeignClient注解所属对象是否为接口,如果不为接口则报错

    2. 获取FeignClient注解中的属性

    3. 注册FeignClient注解中的configuration中定义的配置类,包括自定义的编码、解码器

      1. FeignClient配置类名称,BeanName前缀获取:

        1. 先获取FeignClient注解中的contextId值
        2. 如果名称为空,再获取value值
        3. 如果名称为空,再获取name值
        4. 如果名称为空,再获取serviceId值
        5. 如果名称不为空,则返回;为空则为异常

        源码如下:

        	//获取feign的bean名称
        	private String getClientName(Map<String, Object> client) {
        		if (client == null) {
        			return null;
        		}
                //先获取注解中的contextId值
        		String value = (String) client.get("contextId");
                //如果名称为空,再获取value值
        		if (!StringUtils.hasText(value)) {
        			value = (String) client.get("value");
        		}
                //如果名称为空,再获取name值
        		if (!StringUtils.hasText(value)) {
        			value = (String) client.get("name");
        		}
                //如果名称为空,再获取serviceId值
        		if (!StringUtils.hasText(value)) {
        			value = (String) client.get("serviceId");
        		}
                //如果名称不为空,则返回
        		if (StringUtils.hasText(value)) {
        			return value;
        		}
        		//否则抛异常
        		throw new IllegalStateException(
        				"Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName());
        	}
        
    4. 注册FeignClient

      1. FeignClient注册:

        1. 获取容器id
        2. 获取客户端名称
        3. 创建feign的工厂bean,设置bean相关属性
        4. 获取FeignClient注解中的url、path、decode404、fallback属性,设置到feign的工厂bean中
        5. 创建一个自动装配的BeanDefinition,其属性包含FeignClient工厂bean

        源码如下:

        private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
        			Map<String, Object> attributes) {
        		String className = annotationMetadata.getClassName();
        		Class clazz = ClassUtils.resolveClassName(className, null);
        		ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
        				? (ConfigurableBeanFactory) registry : null;
                //获取容器id
        		String contextId = getContextId(beanFactory, attributes);
                //获取客户端名称
        		String name = getName(attributes);
                //创建feign的工厂bean,设置bean相关属性
        		FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
        		factoryBean.setBeanFactory(beanFactory);
        		factoryBean.setName(name);
        		factoryBean.setContextId(contextId);
        		factoryBean.setType(clazz);
        		factoryBean.setRefreshableClient(isClientRefreshEnabled());
        		BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
                    //获取FeignClient注解中的url、path、decode404、fallback属性进行设置
                    //提供FeignClient bean的创建过程
        			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);
        
        		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" };
        		}
        		//注册feign bean
        		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
        		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
        
        		registerOptionsBeanDefinition(registry, contextId);
        	}
        

上述整体注册流程代码如下:

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		
		LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
		Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
    	//首先获取EnableFeignClients注解中clients是否配置了feign客户端
		final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
    	//如果客户端没有在注解中指定,则创建一个注解扫描器,进行注解扫描
		if (clients == null || clients.length == 0) {
            //创建一个注解扫描器,对注解FeignClient进行扫描
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			scanner.setResourceLoader(this.resourceLoader);
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            //获取扫描路径
			Set<String> basePackages = getBasePackages(metadata);
			for (String basePackage : basePackages) {
                //将FeignClient添加到结果中
				candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
			}
		}
    	//如果客户端在注解中指定,则直接添加
		else {
			for (Class<?> clazz : clients) {
                //将FeignClient添加到结果中
				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
			}
		}

		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");
				//获取FeignClient注解中的属性
				Map<String, Object> attributes = annotationMetadata
						.getAnnotationAttributes(FeignClient.class.getCanonicalName());
				//根据FeignClient注解中的属性,获取client名称
				String name = getClientName(attributes);
                //注册FeignClient注解中的configuration中定义的配置类,包括自定义的编码、解码器
				registerClientConfiguration(registry, name, attributes.get("configuration"));
				//注册FeignClient
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}

Http客户端创建和Targeter创建

在Feign的jar包中,我们可以看到其配置的spring.factories配置文件:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration

客户端的初始化在工厂beanFeignClientFactoryBean中完成,当spring容器根据BeanDefinition创建对象时,由于FeignClientFactoryBean是自动创建的,因此spring容器中会自动创建FeignClientFactoryBean对象。当spring扫描到Feign的自动配置类FeignAutoConfiguration时,会触发客户端的创建。

而其中其他的自动配置会创建其他配置bean,例如支持解析HAL_JSON请求的FeignHalAutoConfiguration,支持Accept Gzip的FeignAcceptGzipEncodingAutoConfiguration和Content Gzip的FeignContentGzipEncodingAutoConfiguration。以及支持负载均衡的FeignLoadBalancerAutoConfiguration。

这里我们主要看FeignAutoConfiguration,这个类中声明了如下bean:

FeignAutoConfiguration声明列表

这里我们主要看DefaultFeignTargeterConfiguration的DefaultTargeter,这个Targeter提供了初始化Feign客户端的方法:

class DefaultTargeter implements Targeter {

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
			Target.HardCodedTarget<T> target) {
		return feign.target(target);
	}

}

这个target方法在我们使用feign工厂类初始化feign客户端时会用到,他通过调用Feign中的target方法,实现了动态代理完成了feignClient的创建:

    public <T> T target(Target<T> target) {
      return build().newInstance(target);
    }
  1. 首先会创建一个Feign构造器,构造器中包含了Feign的四大组件:Contract、请求参数、编码器、解码器

        public Feign build() {
          Client client = Capability.enrich(this.client, capabilities);
          Retryer retryer = Capability.enrich(this.retryer, capabilities);
          List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
              .map(ri -> Capability.enrich(ri, capabilities))
              .collect(Collectors.toList());
          //日志
          Logger logger = Capability.enrich(this.logger, capabilities);
          Contract contract = Capability.enrich(this.contract, capabilities);
          //请求参数
          Options options = Capability.enrich(this.options, capabilities);
          //编码器
          Encoder encoder = Capability.enrich(this.encoder, capabilities);
          //解码器
          Decoder decoder = Capability.enrich(this.decoder, capabilities);
          InvocationHandlerFactory invocationHandlerFactory =
              Capability.enrich(this.invocationHandlerFactory, capabilities);
          QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);
    
          SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
              new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
                  logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
          ParseHandlersByName handlersByName =
              new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
                  errorDecoder, synchronousMethodHandlerFactory);
          //创建Feign构建器
          return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
        }
    
  2. 构造器通过动态代理,创建我们所需要的feign客户端:

     //动态代理feign客户端
      @Override
      public <T> T newInstance(Target<T> target) {
        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);
        T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
            new Class<?>[] {target.type()}, handler);
    
        for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
          defaultMethodHandler.bindTo(proxy);
        }
        return proxy;
      }
    

初始化客户端

当spring容器初始化我们的feign客户端时,会调用Feign工厂的getObject方法创建feign客户端:

feign客户端创建-debug

feign工厂会创建对feign客户端,流程为:

  1. 获取feign构造器
  2. 解析feignClient的请求路径
  3. 创建对应的http客户端
  4. 通过动态代理生成对应的feign客户端
	/**
	 * @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 = beanFactory != null ? beanFactory.getBean(FeignContext.class)
				: applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);
		//如果FeignClient url为空
		if (!StringUtils.hasText(url)) {

			if (LOG.isInfoEnabled()) {
				LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");
			}
      		// 属性name中不含http则需要进行url拼接
			if (!name.startsWith("http")) {
				url = "http://" + name;
			}
			else {
				url = name;
			}
			url += cleanPath();
            //获取httpClient,根据httpClient通信后通过动态代理将结果映射到FeignClient所属的接口实例中
        	//HardCodedTarget: 包含了对应的feign client类、client名称、client url
			return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
		}
        //如果FeignClient url不为空且属性url中不含http则需要进行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);
		}
        //获取feign Targeter
		Targeter targeter = get(context, Targeter.class);
        //通过feignTargeter创建feign http client
        //HardCodedTarget: 包含了对应的feign client类、client名称、client url
		return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
	}
0

评论区