@EnableFeignClients为什么会生效呢?

欣喜 Spring Cloud 发布时间:2024-11-27 10:01:07 阅读数:3510 1
下文笔者讲述@EnableFeignClients生效的原理简介说明,如下所示
在日常开发中,我们经常涉及多个服务之间的调用,那么为什么EnableFeignClients生效了呢?
如:
一些SpringBoot应用程序,都采用这种方式使用
    @SpringBootApplication
    @EnableFeignClients
    public class TestApplication {
        public static void main(String[] args) {
            SpringApplication.run(TestApplication.class, args);
        }
    }

@FeignClient注解定义客户端

例

定义一个feign客户端
将远程服务http://system/test/hello映射为一个本地Java方法调用。

@FeignClient(name = "system", path = "/test")
public interface TestService {
	@RequestMapping(value = "/test", method = RequestMethod.GET)
	TestModel hello(@RequestParam("parameter") String parameter);
}
 

客户端中使用 feigin

 
    @Autowired   
    TestService testService;

    public void run()
    {
        // 这里的使用本地Java API的方式调用远程的Restful接口
        Model pojo = testService.hello("Hello,你好!");
        log.info("hello : {}", pojo);
     }
从上文的示例中
    我们可以得出只需以上的两个步骤
	 就可以将我们的feign客户端应用到系统中
    那么到底是一个什么原理,使我们的使用变得如此方便呢? 
	下文笔者将一一道来,如下所示
首先 testService 这个bean对象
  会变Spring转换为一个代理类
  通过代理类的增强,实现一系列的调用操作

feign代理类的生成方式

当我们在程序中引入 @EnableFeignClients时
会 通过 @Import注解
 导入  FeignClientsRegistrar 注册器
  
由于 FeignClientsRegistrar实现接口 ImportBeanDefinitionRegistrar

//ImportBeanDefinitionRegistrar 接口简介说明
public interface ImportBeanDefinitionRegistrar {

   /**
    * Register bean definitions as necessary based on the given annotation metadata of
    * the importing @Configuration class.
    * 根据使用者配置类的注解元数据注册bean定义
    * @param importingClassMetadata 使用者配置类的注解元数据
    * @param registry 当前bean定义注册表,一般指当前Spring应用上下文对象,当前Spring容器
    */
   public void registerBeanDefinitions(
   		AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

}
 

#registerBeanDefinitions  
 注册feign客户端配置和feign客户端

方法FeignClientsRegistrar#registerBeanDefinitions实现如下:

   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
   	// 注册缺省配置到容器 registry
   	registerDefaultConfiguration(metadata, registry);
   	// 注册所发现的各个 feign 客户端到到容器 registry
   	registerFeignClients(metadata, registry);
   }

 
#registerDefaultConfiguration– 注册feign客户端缺省配置

	// 注册feign客户端的缺省配置,缺省配置信息来自注解元数据的属性 defaultConfiguration    
    private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
		// 获取注解@EnableFeignClients的注解属性     
		Map<String, Object> defaultAttrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

		if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
			String name;
			// 下面是对所注册的缺省配置的的命名,格式如下 :
			// default.xxx.TestApplication
			if (metadata.hasEnclosingClass()) {
				//  针对注解元数据metadata对应一个内部类或者方法返回的方法本地类的情形
				name = "default." + metadata.getEnclosingClassName();
			}
			else {        
				// name 举例 : default.xxx.TestApplication
				// 这里 xxx.TestApplication 是注解@EnableFeignClients所在配置类的长名称			
				name = "default." + metadata.getClassName();
			}
			// 各种信息准备就绪,现在执行注册
			registerClientConfiguration(registry, name,
					defaultAttrs.get("defaultConfiguration"));
		}
	}
 
#registerDefaultConfiguration方法最终注册客户端缺省配置的动作交给方法#registerClientConfiguration执行。
#registerClientConfiguration – 注册feign客户端配置

    // 将指定feign客户端配置configuration作为一个bean定义注册到容器:
    // bean 定义对象类型 : GenericBeanDefinition
    // bean class : FeignClientSpecification    
    // bean name : default.xxx.TestApplication.FeignClientSpecification (缺省配置)
    // bean name : test-service.FeignClientSpecification (针对某个feign client 的配置)
	private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
			Object configuration) {
		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientSpecification.class);
		// 设置构造函数参数		
		builder.addConstructorArgValue(name);
		builder.addConstructorArgValue(configuration);
		// 从bean定义构建器构造bean定义并注册到容器
		registry.registerBeanDefinition(
				name + "." + FeignClientSpecification.class.getSimpleName(),
				builder.getBeanDefinition());
	}

 

#registerClientConfiguration方法
     用于注册一个feign客户端配置bean
	  可以用于注册针对所有feign客户端的缺省配置的注册
	   也可以用于针对每个feign客户端的专有配置的注册。

针对所有feign客户端的缺省配置的bean名称类似于 : default.xxx.TestApplication.FeignClientSpecification,
针对某个名称为test-service的feign客户端的配置的bean名称类似于:test-service.FeignClientSpecification。
#registerFeignClients – 注册各个feign客户端及其配置

	// 参数 metadata : 注解@EnableFeignClients所在配置类的注解元数据
	public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
		// 定义一个基于classpath的组件扫描器,它会根据指定的扫描位置和@EnableFeignClients注解属性   
		// 找出开发人员定义的所有feign客户端,也就是那些使用了注解@FeignClient的所有接口定义
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;

		// attrs 用于表示注解@EnableFeignClients所在配置类的注解元数据中注解@EnableFeignClients
		// 的部分
		Map<String, Object> 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) {
           // @EnableFeignClients 中没有指定 clients 属性的情况
			scanner.addIncludeFilter(annotationTypeFilter);
			basePackages = getBasePackages(metadata);
		}
		else {
           // @EnableFeignClients 中指定了 clients 属性的情况
			final Set<String> 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 扫描每一个 basePackage, 获取其中的 feign 客户端定义, 
		// 也就是 @FeignClient 定义的那些接口
		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");

					// 获取所定义的feign客户端接口上的注解@FeignClient属性
					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());

					String name = getClientName(attributes);
					// 将所定义的feign客户端上的配置属性作为一个bean注册到容器   
					// 此方法的逻辑我们上面已经分析过
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));

					// 将所定义的feign客户端作为一个bean注册到容器:
					// bean 定义类型 : GenericBeanDefinition
					//  bean class : FeignClientFactoryBean
					//  autowire 模式 : 根据类型绑定
					// @FeignClient注解中的url,path,fallback等属性会设置为bean定义的属性
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}   

	// 辅助工具类,从@EnableFeignClients注解属性中获取basePackages属性:
	// 参考以下@EnableFeignClients注解属性 :
	// 1. value
	// 2. basePackages
	// 3. basePackageClasses
	// 4. 配置类所在的包
	// 参数 importingClassMetadata : 使用注解@EnableFeignClients的配置类的元数据
	protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
		// 注解@EnableFeignClients的属性
		Map<String, Object> attributes = importingClassMetadata
				.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());

		Set<String> basePackages = new HashSet<>();
		for (String pkg : (String[]) attributes.get("value")) {
			if (StringUtils.hasText(pkg)) {
				basePackages.add(pkg);
			}
		}
		for (String pkg : (String[]) attributes.get("basePackages")) {
			if (StringUtils.hasText(pkg)) {
				basePackages.add(pkg);
			}
		}
		for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
			basePackages.add(ClassUtils.getPackageName(clazz));
		}

		if (basePackages.isEmpty()) {
			basePackages.add(
					ClassUtils.getPackageName(importingClassMetadata.getClassName()));
		}
		return basePackages;
	}

 

#registerFeignClients 最终注册feign客户端配置的动作交给#registerClientConfiguration完成
   而注册feign客户端的动作交给#registerFeignClient方法完成。
#registerFeignClient – 注册一个feign客户端

	// 将所定义的feign客户端作为一个bean注册到容器:
	// bean 定义类型 : GenericBeanDefinition
	//  bean class : FeignClientFactoryBean -- 这是一个工厂bean,而不是最终bean实例的class
	//  autowire 模式 : 根据类型绑定
	// @FeignClient注解中的url,path,fallback等属性会设置为bean定义的属性
	// 参数 registry : Spring 容器
	// 参数 annotationMetadata : @FeignClient所注解的接口上的注解元数据
	// 参数 attributes : @FeignClient 注解属性信息
	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);
   }

 

从上面的代码分析可知,FeignClientsRegistrar的主要作用如下 :

    注册缺省feign客户端配置bean定义;
    对于每个@FeignClient注解的feign客户端定义 :
        注册一个针对该feign客户端的配置bean定义;
        注册该feign客户端bean定义,指定生成bean实例采用工厂类FeignClientFactoryBean;

而且,上述功能实现在类方法FeignClientsRegistrar#registerBeanDefinitions中
    这是接口ImportBeanDefinitionRegistrar所定义的方法
	   该方法会在@EnableFeignClients注解被处理时执行
	      具体的执行时调用栈如下所示:

    AbstractApplicationContext#invokeBeanFactoryPostProcessors
    => PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
    => foreach BeanDefinitionRegistryPostProcessor : #postProcessBeanDefinitionRegistry
    => ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
    => #processConfigBeanDefinitions
    => ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
    => foreach ConfigurationClass : #loadBeanDefinitionsForConfigurationClass
    => #loadBeanDefinitionsFromRegistrars
    => foreach ImportBeanDefinitionRegistrar : #registerBeanDefinitions
    => FeignClientsRegistrar#registerBeanDefinitions

 

FeignClientFactoryBean生成feign客户端代理对象

代码中所定义feign客户端和相关配置会以bean定义的形式注册到bean容器中
 这样当使用@Autowired注入一个feign客户端时
  容器会使用工厂类FeignClientFactoryBean为其生成一个实例
  
FeignClientFactoryBean#getObject生成feign客户端代理对象

    // 该方法由接口FactoryBean约定
   	@Override
	public Object getObject() throws Exception {
		return getTarget();
	}
	
	
	<T> T getTarget() {
        //  从应用上下文中获取创建 feign 客户端的上下文对象 FeignContext
        // FeignContext 针对每个feign客户端定义会生成一个不同的 AnnotationConfigApplicationContext,
        // 这些应用上下文的parent都设置为当前应用的主应用上下文
        // 参考 : FeignAutoConfiguration
		FeignContext context = applicationContext.getBean(FeignContext.class);
        // 为目标feign客户端对象构建一个 builder,该builder最终生成的目标feign客户端是一个
        // 动态代理,使用 InvocationHandler : ReflectiveFeign$FeignInvocationHandler
		Feign.Builder builder = feign(context);

		if (!StringUtils.hasText(this.url)) {
           // @FeignClient 属性 url 属性没有指定的情况         
           // 根据属性 name , path 拼装一个 url,
           // 这种通常是需要在多个服务节点之间进行负载均衡的情况
			if (!this.name.startsWith("http")) {
				url = "http://" + this.name;
			}
			else {
				url = this.name;
			}
           // 方法cleanPath()加工属性path,使其以/开头,不以/结尾
			url += cleanPath();
           // 这里形成的url格式类似 :  http://test-service/test
           // 其中 test-service 是服务名,不是服务所在节点的IP,主机名或者域名
           
           // 函数 loadBalance 做如下动作 :
           // 1. 将builder和一个LoadBalancerFeignClient bean实例关联起来
           // 2. 使用一个HystrixTargeter将builder和一个 HardCodedTarget bean实例关联起来
           // 这里 HardCodedTarget 表示对应 url 为 http://test-service/test 的远程服务(可能
           // 包含多个服务方法)
           // 3. 生成最终的feign client 实例 : ReflectiveFeign$FeignInvocationHandler 的动态代理对象,
           // 使用 InvocationHandler : ReflectiveFeign$FeignInvocationHandler。
           // 每个远程服务方法会对应到一个@FeignClient注解的接口方法上(依据方法上的注解进行匹配)
			return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
					this.name, url));
		}
        
        // @FeignClient 属性 url 属性被指定的情况 
        // 这种通常是明确指出了服务节点的url的情况,实际上不需要负载均衡
		if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
			this.url = "http://" + this.url;
		}
		String url = this.url + cleanPath();
        // 将builder和一个LoadBalancerFeignClient bean实例关联起来
		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
				// 因为指定了明确的服务节点url,所以这里不需要负载均衡,
				// 所以这里尽管client是LoadBalancerFeignClient,所以
				// 实际上可以获取其所代理的对象作为最终的client,
				// 相当于去掉了LoadBalancerFeignClient这层的代理功能
				client = ((LoadBalancerFeignClient)client).getDelegate();
			}
			builder.client(client);
		}
        // 使用一个HystrixTargeter将builder和一个 HardCodedTarget bean实例关联起来
		Targeter targeter = get(context, Targeter.class);
        // 生成最终的feign client 实例 : ReflectiveFeign$FeignInvocationHandler 的动态代理对象,
        // 使用 InvocationHandler : ReflectiveFeign$FeignInvocationHandler。
        // 每个远程服务方法会对应到 一个@FeignClient注解的接口方法上(依据方法上的注解进行匹配)        
		return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
				this.type, this.name, url));
	}
 
方法FeignClientFactoryBean#feign – 创建feign客户端构建器

protected Feign.Builder feign(FeignContext context) {
		FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
		Logger logger = loggerFactory.create(this.type);

		// 从上下文获取一个 Feign.Builder 上,
		// 并从上下文获得 Encoder, Decoder, Contract 设置到该 builder 上
		Feign.Builder builder = get(context, Feign.Builder.class)
				// required values
				.logger(logger)
				.encoder(get(context, Encoder.class))
				.decoder(get(context, Decoder.class))
				.contract(get(context, Contract.class));
		
		// 对 builder 进行其他属性设置
		configureFeign(context, builder);

		return builder;
	}

 

方法FeignClientFactoryBean#loadBalance – 生成具备负载均衡能力的feign客户端

为feign客户端构建器绑定负载均衡客户端,绑定目标服务端点,并生成最终的feign客户端实例。

// 对builder设置负载均衡客户端,绑定到目标服务端点,构建最终的feign客户端对象
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget<T> target) {
		// 从上下文context获取一个Client,缺省是 LoadBalancerFeignClient 	
		Client client = getOptional(context, Client.class);
		if (client != null) {
			// 将client设置到builder上
			builder.client(client);
			// 从上下文中获取一个 targeter,缺省是一个 HystrixTargeter
			Targeter targeter = get(context, Targeter.class);
			// 上面获取得到的 targeter 会根据 builder 的类型决定如何将 target
			// 绑定到 builder 并设置有关的其他属性和功能,然后生成最终的feign客户端对象
			return targeter.target(this, builder, context, target);
		}

		throw new IllegalStateException(
			"No Feign Client for loadBalancing defined. Did you forget to include " +
			"spring-cloud-starter-netflix-ribbon?");
	}

 

从上面分析可以看出,缺省情况下
所使用的feign客户端构建器类为Feign.Builder
 且Targeter是一个HystrixTargeter
 HystrixTargeter#target方法的参数builder为Feign.Builder时
 会直接调用该builder的target方法
 

class HystrixTargeter implements Targeter {
@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.Builder#target是如何工作的:

  // 执行构建并且创建相应的feign客户端实例	
  public <T> T target(Target<T> target) {
      return build().newInstance(target);
    }
 
  // 构建过程,最终根据各种配置生成一个 ReflectiveFeign 对象	   
  public Feign build() {
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(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);
    }
  }    

 

然后再看ReflectiveFeign#newInstance方法:

  // 创建最终的feign客户端实例 : 一个 ReflectiveFeign$FeignInvocationHandler 的动态代理对象
  @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 
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
      	// 对于每个对应服务功能端点的方法,缺省使用nameToHandler获取的MethodHandler,缺省是
      	// SynchronousMethodHandler
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    // 创建feign客户端实例 ReflectiveFeign$FeignInvocationHandler,
    // 该对象包含了上面所创建的methodToHandler,用于对应各个开发者定义的@FeignClient接口方法
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 创建feign客户端实例的动态代理对象
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

	// 将缺省方法处理器绑定到feign客户端实例的动态代理对象上
    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

本文链接: https://www.Java265.com/JavaFramework/SpringCloud/202411/8188.html

最近发表

热门文章

好文推荐

Java265.com

https://www.java265.com

站长统计|粤ICP备14097017号-3

Powered By Java265.com信息维护小组

使用手机扫描二维码

关注我们看更多资讯

java爱好者