@EnableFeignClients为什么会生效呢?
下文笔者讲述@EnableFeignClients生效的原理简介说明,如下所示
在日常开发中,我们经常涉及多个服务之间的调用,那么为什么EnableFeignClients生效了呢?
如:
一些SpringBoot应用程序,都采用这种方式使用
在日常开发中,我们经常涉及多个服务之间的调用,那么为什么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; }
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。