Spring Cloud OpenFeign源码加载原理

简介:

Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。
Feign被广泛应用在Spring Cloud 的解决方案中,是学习基于Spring Cloud 微服务架构不可或缺的重要组件。

源码分析:

当我们需要在项目中使用Feign的功能,需要在我们的pom.xml文件中引入Maven的依赖,依赖的代码如下:

<!--引入openfeign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>Spring-cloud-starter-openfeign</artifactId>
    <version>2.2.1.RELEASE</version>
</dependency>

当我们引入这个依赖以后会间接依赖如下的jar包:

要使用Feign的功能,需要在我们的启动类上添加@EnableFeignClients注解,这个注解里面的内容是什么那?

package org.springframework.cloud.openfeign;

import java.lang.Annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
Import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Import;


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

    //basePackages 属性的别名,允许使用更简洁的注释声明
   String[] value() default {};

   //扫描包下带注释的组件
   String[] basePackages() default {};

   //basePackages() 的类型安全的替代方法,用于指定要扫描带注释的组件的软件包,指定类别的包装将被扫描。
   Class<?>[] basePackageClasses() default {};

   //适用于所有自定义@Configuration,可以包含组成客户端的部分的@Bean 
   Class<?>[] defaultConfiguration() default {};

   
   Class<?>[] clients() default {};

}

可以看出这个注解类是
spring-cloud-openfeign-core包下的,正是我们前面间接分析的依赖的jar包,这个注解类里面@Import一个FeignClientsRegistrar类,它实现了ImportBeanDefinitionRegistrar接口,SpringBoot在启动时候会调用registerBeanDefinitions方法,该方法的代码如下:

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   registerDefaultConfiguration(metadata, registry);
   registerFeignClients(metadata, registry);
}

首先调用
registerDefaultConfiguration方法,该方法主要判断注解EnableFeignClients是否有defaultConfiguration属性,往容器注入一个类型是FeignClientSpecification,它的构造函数名称分别是String类型的名称、defaultConfiguration类型是Class类型的数组,这个方法主要也是我们自定义的一些加载Bean,如果没有就会使用默认的Bean配置。

在我这个例子中没有自定义defaultConfiguration属性,所以会跳过这个方法的执行,我们重点分析registerFeignClients方法,它是Feign的核心方法,

方法负责读取@EnableFeignClients的属性,获取需要扫描的包名,然后扫描指定的所有包名下的被@FeignClient注解注释的接口,将扫描出来的接口调用registerFeignClient方法注册到spring容器,我们先看registerFeignClients方法的内容:

public void registerFeignClients(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   ClassPathScanningCandidateComponentProvider scanner = getScanner();
   scanner.setResourceLoader(this.resourceLoader);

   Set<String> basePackages;

   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) {
      scanner.addincludeFilter(annotationTypeFilter);
      basePackages = getBasePackages(metadata);
   }
   else {
      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)));
   }

   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");

            Map<String, Object> attributes = annotationMetadata
                  .getAnnotationAttributes(
                        FeignClient.class.getCanonicalName());

            String name = getClientName(attributes);
            registerClientConfiguration(registry, name,
                  attributes.get("configuration"));

            registerFeignClient(registry, annotationMetadata, attributes);
         }
      }
   }
}

首先创建了一个
ClassPathScanningCandidateComponentProvider对象,它主要用于在类路径上扫描指定包(或包含特定注解)的类,并将它们注册为 Spring 容器中的 Bean 候选者。获取启动类下的EnableFeignClients注解信息,创建一个AnnotationTypeFilter的用于匹配FeignClient注解的类,这个FeignClient它的代码如下:

/*
 * Copyright 2013-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.openfeign;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {

   //指定要调用的目标服务的名称。该属性通常用于指定服务的名称。
   String value() default "";


   String serviceId() default "";

   //在使用多个 @FeignClient 注解时,用于标识不同的 Feign 客户端,防止冲突。
   String contextId() default "";


   @AliasFor("value")
   String name() default "";


   String qualifier() default "";

  //指定目标服务的 URL 地址。当不使用服务注册中心时,可以使用该属性直接指定服务的地址。
   String url() default "";

  //默认情况下,Feign 不会解码 HTTP 404 错误。设置为 true 后,Feign 将解码 404 错误。
   boolean decode404() default false;


  //指定 Feign 客户端的配置类,该配置类可以包含一些自定义的配置,如超时时间、日志级别等。
   Class<?>[] configuration() default {};

  //定 Feign 客户端的熔断器(Hystrix)回退类。当调用远程服务失败时,会执行回退类中的逻辑。
   Class<?> fallback() default void.class;

   //与 fallback 类似,不同之处在于 fallbackFactory 允许提供一个工厂类,用于创建回退类的实例。
   Class<?> fallbackFactory() default void.class;

   //指定 Feign 客户端的统一前缀路径,该路径会添加到每个请求的 URL 前面。
   String path() default "";


   boolean primary() default true;

}

把它添加到
ClassPathScanningCandidateComponentProvider对象的成员属性includeFilters上,获取当前要扫描FeignClient注解的包名,如果指定了包名则用指定的,如果没有指定就用启动类所在包路径下,循环变量这个包下的所有子包看是否 又@FeignClient的注解,如果有获取标有该注解的所有属性封装为Map。

根据Map获取@Feign注解的value值,又调用了
registerClientConfiguration方法判断是否有自己定义的Bean,接着调用registerFeignClient方法,这是一个重载方法,该方法的定义如下:

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);
   String contextId = getContextId(attributes);
   definition.addPropertyValue("contextId", contextId);
   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 = contextId + "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);
}

该方法的逻辑是获取拥有@FeignClient注解的类名,创建一个Bean定义的BeanDefinitionBuilder对象,该对象主要用于创建FeignClientFactoryBean的Bean定义对象,这个类是关键的类,后面会分析它,从Map数据结构中获取url、path、name、contextId、type、decode404、fallback、fallbackFactory属性设置到BeanDefinitionBuilder对象的值上,以后这些属性都会是FeignClientFactoryBean的成员属性。

最后调用BeanDefinitionBuilder对象的getBeanDefinition方法获取FeignClientFactoryBean的Bean定义对象注入到容器中。

到此为止被@Feign修饰的类已经加载到了Spring容器中,此时它的Bean类型已经变为FeignClientFactoryBean类型,但是此时的Bean尚未进行实例化,下一篇文章我主要分析它的实例化过程。

总结:
 

本节主要分析了Feign是怎么被加载到Spring容器中的,其实主要就是在我们的启动类中添加@EnableFeignClients注解,然后注解又引入了一个FeignClientsRegistrar,它是Spring容器会回调的一个类,它是去指定的包路径下寻找所有子包标有@FeginClient注解的类并把他们封装为FeignClientFactoryBean类型的Bean加载到spring容器中。好了,我是程序员老徐,如果喜欢我的文章,请点赞关注。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值