第四章 Feign接口服务及源码解析笔记

前面已经学习了Ribbon,从Eureka获取服务的实例在通过RestTemplate调用,并转换成需要的对象

如:List list = restTemplate.exchange(PRODUCT_LIST_URL,HttpMethod.GET,new HttpEntity(httpHeaders), List.class).

getBody();

可以发现所有的数据调用和转换都是由用户直接来完成的,我们可能不想直接访问Rest接口,如果转换回来的直接是对象而不需要直接使用RestTemplate进行转换就好了,这个时候就需要使用Feign了。

代码Git地址:https://gitee.com/hankin_chj/springcloud-micro-service.git

注意:Feign不建议大家使用,流程简单并发不高的方法可以用一用。

一、Feign基本使用

1、创建新模块Feign

复制springcloud-micro-consumer成一个新的模块springcloud-micro-consumer-feign

Feign服务修改pom文件,增加对feign的支持:

<dependencies>
    <dependency>
        <groupId>com.chjgroupId>
        <artifactId>springcloud-micro-apiartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
        <scope>testscope>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-devtoolsartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-openfeignartifactId>
    dependency>
dependencies>

注意:这里又有版本问题,如果是Edgware或之前的版本,使用下面的版本

<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-feignartifactId>
dependency>

feign的操作其实需要ribbon的支持。

2、新建springcloud-micro-service-feign模块

2.1、这个模块专门定义客户端的调用接口,修改pom文件如下:

<dependencies>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-openfeignartifactId>
    dependency>
    <dependency>
        <groupId>com.chjgroupId>
        <artifactId>springcloud-micro-apiartifactId>
    dependency>
dependencies>

2.2、安全服务提供方的认证问题

service-feign模块如果要通过Feign进行远程调用,依然需要安全服务提供方的认证问题,不过在feign里面已经集成了这块功能,代码如下:

@Configuration
public class FeignClientConfig {
    @Bean
    public BasicAuthRequestInterceptor getBasicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("admin", "admin");
    }
}

2.3、service-feign模块新建一个IProductClientService接口

@FeignClient(name = "SPRINGCLOUD-MICRO-PRODUCT", configuration = FeignClientConfig.class)
public interface IProductClientService {
    @RequestMapping("/prodcut/get/{id}")
    public Product getProduct(@PathVariable("id")long id);
    @RequestMapping("/prodcut/list")
    public List listProduct() ;
    @RequestMapping("/prodcut/add")
    public boolean addPorduct(Product product) ;
}

3、模块springcloud-micro-consumer-feign配置

3.1、consumer-feign引入service-feign包

springcloud-micro-consumer-feign模块修改pom文件,引入springcloud-micro-service-feign包:


<dependency>
    <groupId>com.chjgroupId>
    <artifactId>springcloud-micro-service-feignartifactId>
dependency>

3.2、consumer-feign模块删除RestConfig.java类

springcloud-micro-consumer-feign模块由于microcloud-service里面已经做了安全验证,并且后面并不直接使用RestTemplate ,所以删除RestConfig.java类。

3.3、consumer-feign模块修改ConsumerProductController

springcloud-micro-consumer-feign模块修改ConsumerProductController,这个时候直接使用microcloud-service定义的服务就可以了,具体实现如下:

@RestController
@RequestMapping("/consumer")
public class ConsumerProductController {
    @Resource
    private IProductClientService iProductClientService;
    @RequestMapping("/product/get")
    public Object getProduct(long id) {
        return  iProductClientService.getProduct(id);
    }
    @RequestMapping("/product/list")
    public  Object listProduct() {
        return iProductClientService.listProduct();
    }
    @RequestMapping("/product/add")
    public Object addPorduct(Product product) {
        return  iProductClientService.addPorduct(product);
    }
}

由此可见,这个时候ConsumerProductController的代码已经简洁了不少。

3.4、consumer-feign模块修改程序主类

由于使用了feign接口功能,需要添加@EnableFeignClients()注解:

@SpringBootApplication
@EnableEurekaClient
//@RibbonClient(name ="SPRINGCLOUD-MICRO-PRODUCT" ,configuration = RibbonConfig.class)
@EnableFeignClients("com.chj.service") // 扫描feign接口服务的service包
public class ConsumerFeignApp {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerFeignApp.class,args);
    }
}

4、启动测试:

访问地址:http://localhost/consumer/product/list

可以发现Feign在调用接口的时候自带负载均衡,并且使用了默认的轮询方式,这也不奇怪,因为Fegin里面内置就使用了Ribbon,可以做个测试,看下是否真的如此consumer-feign修改程序主类ConsumerFeignApp。

添加注解:@RibbonClient(name ="MICROCLOUD-PROVIDER-PRODUCT" ,configuration = RibbonConfig.class)

启动测试:http://localhost/consumer/product/list

可以发现,现在的路由规则以及变成了随机访问

二、其他配置

1、数据压缩

前面我们已经知道Feign之中最核心的作用就是将Rest服务的信息转化为接口,这其中还有其他的一些地方应该要考虑,比如,数据的压缩。Rest协议更多的传输的是文本,JSON或者XML,如果用户发送的请求很大,这个时候有必要对数据进行压缩处理,好在feign本身就提供了压缩的支持。

FeignContentGzipEncodingAutoConfiguration可以先看下这个类:

@Configuration
@EnableConfigurationProperties({FeignClientEncodingProperties.class})
@ConditionalOnClass({Feign.class})
@ConditionalOnBean({Client.class})
@ConditionalOnMissingBean(
    type = { "okhttp3.OkHttpClient"}
)
@ConditionalOnProperty(
    value = { "feign.compression.request.enabled"},
    matchIfMissing = false
)
@AutoConfigureAfter({FeignAutoConfiguration.class})
public class FeignContentGzipEncodingAutoConfiguration {

虽然Feign支持压缩,但默认是不开启的,再看下FeignClientEncodingProperties,可以根据这里面的属性进行相关压缩的配置。

@ConfigurationProperties("feign.compression.request")
public class FeignClientEncodingProperties {
    private String[] mimeTypes = new String[]{ "text/xml", "application/xml", "application/json"};
    private int minRequestSize = 2048;

consumer-feign模块修改application.yml配置文件:

feign:
  compression:
    request:
      enabled: true
      mime-types: # 可以被压缩的类型
        - text/xml
        - application/xml
        - application/json
      min-request-size: 2048 # 超过2048的字节进行压缩

2、日志配置

在构建@FeignClient注解修饰的服务客户端时,会为一个客户端都创建一个feign.Logger实例,可以利用日志来分析Feign的请求细节,不过默认Feign的日志是不开启的。

2.1、consumer-feign模块修改

 修改application.yml配置文件,增加日志信息:

logging:
  level:
    com.chj.service: DEBUG

只添加上面配置还无法实现对DEBUG日志的输出,以因为Feign客户端默认的logger.level对象定义为none级别,所以不会记录feign调用过程中的信息。

public static enum Level {
    NONE, // 默认
    BASIC,
    HEADERS,
    FULL;
    private Level() {
    }
}

2.2、service-feign模块修改FeignClientConfig,开启日志输出

@Configuration
public class FeignClientConfig {
    @Bean
    public BasicAuthRequestInterceptor getBasicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("admin", "admin");
    }
    @Bean
    public Logger.Level getFeignLoggerLevel() {
        return feign.Logger.Level.FULL ;
    }
}

2.3、启动访问,查看日志输出

访问诶之:localhost/consumer/product/list

日志打印结果:

2019-11-21 01:41:46.696 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService    : [IProductClientService#listProduct] ---> GET http://SPRINGCLOUD-MICRO-PRODUCT/prodcut/list HTTP/1.1

2019-11-21 01:41:46.697 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService    : [IProductClientService#listProduct] Authorization: Basic YWRtaW46YWRtaW4=

2019-11-21 01:41:46.697 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService    : [IProductClientService#listProduct] ---> END HTTP (0-byte body)

2019-11-21 01:41:46.782 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService    : [IProductClientService#listProduct] <--- HTTP/1.1 200 (85ms)

2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService    : [IProductClientService#listProduct] cache-control: no-cache, no-store, max-age=0, must-revalidate

2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService    : [IProductClientService#listProduct] content-type: application/json;charset=UTF-8

2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService    : [IProductClientService#listProduct] date: Wed, 20 Nov 2019 17:41:46 GMT

2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService    : [IProductClientService#listProduct] expires: 0

2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService    : [IProductClientService#listProduct] pragma: no-cache

2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService    : [IProductClientService#listProduct] transfer-encoding: chunked

2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService    : [IProductClientService#listProduct] x-content-type-options: nosniff

2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService    : [IProductClientService#listProduct] x-frame-options: DENY

2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService    : [IProductClientService#listProduct] x-xss-protection: 1; mode=block

2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService    : [IProductClientService#listProduct]

2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService    : [IProductClientService#listProduct] [{"productId":null,"productName":"java编程

","productDesc":"springcloud"},{"productId":null,"productName":"Springboot","productDesc":"springcloud"},{"productId":null,"productName":"西游记","productDesc":"springcloud"},{"productId":null,"productName":"水浒传

","productDesc":"springcloud"},{"productId":null,"productName":"西厢记","productDesc":"springcloud"}]

2019-11-21 01:41:46.783 DEBUG 313788 --- [p-nio-80-exec-4] com.chj.service.IProductClientService    : [IProductClientService#listProduct] <--- END HTTP (368-byte body)

3、总结

·当使用Feign要通过接口的方法访问Rest服务的时候会根据设置的服务类型发出请求,这个请求是发送给Eureka。

·随后由于配置了授权处理,所以继续发送授权信息(Authorization),其实在外面使用RestTemplate的时候也是这么做的,可以对应日志的加密内容和直接访问其实是一样的。

·在进行服务调用的时候Feign融合了Ribbon技术,所以也支持有负载均衡的处理。

Feign = RestTempate + HttpHeader + Ribbon + Eureka 综合体,使用feign大大增加了代码的灵活程度。

三、Feign源码解析

1、代码入口分析

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name ="SPRINGCLOUD-MICRO-PRODUCT" ,configuration = RibbonConfig.class)
@EnableFeignClients("com.chj.service") // 扫描feign接口服务的service包
public class ConsumerFeignApp {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerFeignApp.class,args);
    }
}

在@EnableFeignClients标签中,import了一个FeignClientsRegistrar类:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    String[] value() default {};
    String[] basePackages() default {};
    Class[] basePackageClasses() default {};
    Class[] defaultConfiguration() default {};
    Class[] clients() default {};
}

org.springframework.cloud.openfeign.FeignClientsRegistrar#registerBeanDefinitions方法里面有两个核心方法,它会在AbstractApplicationContext#refresh()方法中被调用,这部分可以参考spring的源代码。

核心方法registerDefaultConfiguration()从EnableFeignClients的属性值来构建Feign的Configuration。

核心方法registerFeignClients():扫描package,注册被@FeignClient修饰的接口类Bean的信息。

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    //TODO 从EnableFeignClients的属性值来构建Feign的Configuration
    registerDefaultConfiguration(metadata, registry);
    //TODO 扫描package,注册被@FeignClient修饰的接口类Bean的信息
    registerFeignClients(metadata, registry);
}

2、核心方法registerDefaultConfiguration分析

org.springframework.cloud.openfeign.FeignClientsRegistrar#registerDefaultConfiguration

private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    //TODO 获取到metadata中关于EnableFeignClients的属性值键值对。
    Map defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
    //TODO 如果配置了defaultConfiguration 进行配置,如果没有使用默认的configuration
    if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
        String name;
        if (metadata.hasEnclosingClass()) {
            name = "default." + metadata.getEnclosingClassName();
        }else {
            name = "default." + metadata.getClassName();
        }
        //TODO 进行注册
        registerClientConfiguration(registry, name,defaultAttrs.get("defaultConfiguration"));
    }
}

2.1、registerClientConfiguration进行注册方法分析

org.springframework.cloud.openfeign.FeignClientsRegistrar#registerClientConfiguration接下来进行注册:

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {

//使用BeanDefinitionBuilder来生成BeanDefinition,并把它进行注册
   BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(FeignClientSpecification.class);
   builder.addConstructorArgValue(name);
   builder.addConstructorArgValue(configuration);
   registry.registerBeanDefinition( name + "." + FeignClientSpecification.class.getSimpleName(),
         builder.getBeanDefinition());
}

方法的入参BeanDefinitionRegistry是spring框架用于动态注册BeanDefinition信息的接口调用registerBeanDefinition方法可以将BeanDefinition注册到Spring容器中,其中name属性就是注册的BeanDefinition的名称,在这里它注册了一个FeignClientSpecification的对象。

FeignClientSpecification实现了NamedContextFactory.Specification接口,它是Feign实例化的重要一环,在上面的方法中,它持有自定义配置的组件实例,SpringCloud使用NamedContextFactory创建一些列的运行上下文ApplicationContext来让对应的Specification在这些上下文中创建实例对象。

2.2、NamedContextFactory有3个功能:

·创建AnnotationConfigApplicationContext上下文。

·在上下文中创建并获取bean实例。

·当上下文销毁时清除其中的feign实例。

NamedContextFactory有个非常重要的子类FeignContext,用于存储各种OpenFeign的组件实例。

public class FeignContext extends NamedContextFactory {
   public FeignContext() {
      super(FeignClientsConfiguration.class, "feign", "feign.client.name");
   }
}

2.3、FeignContext是哪里构建的呢?

配置见:pring-cloud-openfeign-core-2.0.0.RELEASE.jar!\META-INF\spring.factories

 

第四章 Feign接口服务及源码解析笔记_第1张图片

2.4、启动配置类FeignAutoConfiguration代码分析:

org.springframework.cloud.openfeign.FeignAutoConfiguration#feignContext方法代码如下:

@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
   @Autowired(required = false)
   private List configurations = new ArrayList<>();
   @Bean
   public HasFeatures feignFeature() {
      return HasFeatures.namedFeature("Feign", Feign.class);
   }
   @Bean
   public FeignContext feignContext() {
      FeignContext context = new FeignContext();
      context.setConfigurations(this.configurations);
      return context;
   }

public class FeignContext extends NamedContextFactory {
   public FeignContext() {

//将默认的FeignClientsConfiguration作为参数传递给构造函数
      super(FeignClientsConfiguration.class, "feign", "feign.client.name");
   }
}

FeignContext创建的时候会将之前FeignClientSpecification通过setConfigurations设置给context上下文。

2.5、NamedContextFactory的createContext方法解析:

代码详见:org.springframework.cloud.context.named.NamedContextFactory#createContext方法。

FeignContext的父类的createContext方法会将创建AnnotationConfigApplicationContext实例,这实例将作为当前上下文的子上下文,用于关联feign组件的不同实例。

protected AnnotationConfigApplicationContext createContext(String name) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    //TODO 获取name所对应的configuration,如果有就注册到子context中
    if (this.configurations.containsKey(name)) {
        for (Class configuration : this.configurations.get(name).getConfiguration()) {
            context.register(configuration);
        }
    }
    //TODO 注册default的Configuration,也就是 FeignClientsRegistrar类中registerDefaultConfiguration方法中注册的Configuration
    for (Map.Entry entry : this.configurations.entrySet()) {
        if (entry.getKey().startsWith("default.")) {
            for (Class configuration : entry.getValue().getConfiguration()) {
                context.register(configuration);
            }
        }
    }
    //TODO 注册PropertyPlaceholderAutoConfiguration
    context.register(PropertyPlaceholderAutoConfiguration.class,this.defaultConfigType);
    //TODO 设置Environment的propertySources属性源
    context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,
            Collections. singletonMap(this.propertyName, name)));
    if (this.parent != null) { // Uses Environment from parent as well as beans
        context.setParent(this.parent);
    }
    context.setDisplayName(generateDisplayName(name));
    context.refresh();
    return context;
}

由于NamedContextFactory实现了DisposableBean,所以当实例消亡的时候会调用

@Override
public void destroy() {
   Collection values = this.contexts.values();
   for (AnnotationConfigApplicationContext context : values) {
      context.close();
   }
   this.contexts.clear();
}

2.6、总结一下:

NamedContextFactory会创建出AnnotationConfigApplicationContext实例,并以name作为唯一标识,然后每个AnnotationConfigApplicationContext实例都会注册部分配置类,从而可以给出一系列的基于配置类生成的组件实例,这样就可以基于name来管理一系列的组件实例,为不同的FeignClient准备不同配置组件实例。

3、核心方法registerFeignClients()分析

3.1、registerFeignClients源码分析

FeignClientsRegistrar做的第二件事就是扫描指定包下的类文件,注册@FeignClient修饰的接口类信息。

org.springframework.cloud.openfeign.FeignClientsRegistrar.registerFeignClients(metadata, registry)源码:

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    //TODO 生成自定义的ClassPathScanningCandidateComponentProvider
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);
    Set basePackages;
    //TODO 获取EnableFeignClients所有属性的键值对
    Map attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
    //TODO 依照AnnotationTypeFilter 来进行过滤,只会扫描出EnableFeignClients修饰的类
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
    final Class[] clients = attrs == null ? null: (Class[]) attrs.get("clients");
    //TODO 如果没有配置clients属性,那么就要扫描basePackages
    if (clients == null || clients.length == 0) {
        scanner.addIncludeFilter(annotationTypeFilter);
        basePackages = getBasePackages(metadata);
    } else {
        final Set clientClasses = new HashSet<>();
        basePackages = new HashSet<>();
        //TODO 遍历上诉过程中获取basePackages 的列表
        for (String basePackage : basePackages) {
            //TODO 获取basePackage中所有的BeanDefinition
            Set 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");
                    //TODO 从这些BeanDefinition中获取FeignClient的属性值
                    Map attributes = annotationMetadata.getAnnotationAttributes(

FeignClient.class.getCanonicalName());
                    String name = getClientName(attributes);
                    //TODO 对单独的某个FeignClient的configuration进行配置
                    registerClientConfiguration(registry, name,attributes.get("configuration"));
                    //TODO 注册
                    registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
    }

接下来分析注册方法registerFeignClient()如下:

private void registerFeignClient(BeanDefinitionRegistry registry,
      AnnotationMetadata annotationMetadata, Map 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方法会依据@EnableFeignClients的属性获取要扫描的包路径信息,然后获取这些包下被@FeignClient注解修饰的接口的BeanDefinition。例如springcloud-micro-service-feign模块的IProductClientService接口实现:

@FeignClient(name = "SPRINGCLOUD-MICRO-PRODUCT", configuration = FeignClientConfig.class)
public interface IProductClientService {

3.2、实例化@FeignClient修饰的接口

FeignClientFactoryBean是工厂类,Spring容器通过调用它的getObject方法来获取对应的bean实例,被@FeignClient修饰的接口都是通过FeignClientFactoryBean的getObject方法来进行实例化的。

源码见org.springframework.cloud.openfeign.FeignClientFactoryBean#getObject方法:

@Override
public Object getObject() throws Exception {
   FeignContext context = applicationContext.getBean(FeignContext.class);
   Feign.Builder builder = feign(context);
   if (!StringUtils.hasText(this.url)) {
      String url;
      if (!this.name.startsWith("http")) {
         url = "http://" + this.name;
      } else {
         url = this.name;
      }
      url += cleanPath();
      return loadBalance(builder, context, new HardCodedTarget<>(this.type,this.name, url));
   }
   if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
      this.url = "http://" + this.url;
   }
   String url = this.url + cleanPath();

   //调用FeignContext的getInstance方法获取Client对象
   Client client = getOptional(context, Client.class);
   if (client != null) {
      if (client instanceof LoadBalancerFeignClient) {
         client = ((LoadBalancerFeignClient)client).getDelegate();
      }
      builder.client(client);
   }
   Targeter targeter = get(context, Targeter.class);
   return targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url));
}

3.3、调用FeignContext的getInstance方法获取Client对象

protected <T> T getOptional(FeignContext context, Class<T> type) {
   return context.getInstance(this.name, type);
}

public <T> T getInstance(String name, Class<T> type) {
   AnnotationConfigApplicationContext context = getContext(name);
   if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,type).length > 0) {
      return context.getBean(type);
   }
   return null;
}

3.4、DefaultTargeter的实现

 

第四章 Feign接口服务及源码解析笔记_第2张图片

Targeter是一个接口,它的target方法会生成对应实例对象,它有两个实现类,分表为DefaultTargeter和HystrixTargeter,fegign使用HystrixTargeter这一层抽象来封装关于Hystrix的实现,DefaultTargeter的实现如下所示,只是调用了Feign.Builder的target方法

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

Feign.Builder负责生成被@FeignClient修饰的FeignClient接口类实例,它通过JAVA反射机制构建:

代码见:feign-core-9.5.1.jar!\feign\Feign.target方法:

public T target(Target target) {
    return this.build().newInstance(target);
}

public Feign build() {
    Factory synchronousMethodHandlerFactory = new Factory(this.client, this.retryer,

this.requestInterceptors, this.logger, this.logLevel, this.decode404);
    ParseHandlersByName handlersByName = new ParseHandlersByName(this.contract, this.options, this.encoder,

 this.decoder, this.errorDecoder, synchronousMethodHandlerFactory);
    return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory);
}

3.5、实例化方法ReflectiveFeign

feign-core-9.5.1.jar!\feign\ReflectiveFeign.newInstance()方法分析:

这方法主要做两件事:

1)扫描FeignClient接口类的所有函数,生成对应的Handler

2)使用Proxy生成FeignClient的实例对象

public <T> T newInstance(Target<T> target) {
    //TODO arseHandlersByName.appy方法填充信息
    Map nameToHandler = targetToHandlersByName.apply(target);
    Map methodToHandler = new LinkedHashMap();
    List defaultMethodHandlers = new LinkedList();
    for (Method method : target.type().getMethods()) {
        if (method.getDeclaringClass() == Object.class) {
            continue;
        } else if(Util.isDefault(method)) {
            //TODO 为每一个默认方法生成一个DefaultMethodHandler
            DefaultMethodHandler handler = new DefaultMethodHandler(method);
            defaultMethodHandlers.add(handler);
            methodToHandler.put(method, handler);
        } else {
            methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
        }
    }
    //TODO 生成java  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;
}

3.6、扫描FeignClient接口类的所有函数,生成对应的Handler

 在扫描FeignClient接口类所有函数生成对应的Handle过程中,feign会生成调用该函数时发送网络请求的模板,也就是RequestTemplate实例,RequestTemplate中包含了发送网络请求的URL和产生填充信息,@RequestMapping、@RequestVariable等注解信息也会包含到RequestTemplate中,这一具体过程就是ParseHandlersByName.apply()方法来实现的。

1)feign.ReflectiveFeign.ParseHandlersByName#apply方法分析:

public Map apply(Target key) {

//获取type中所有的方法信息,会根据注解生成每个方法的RequestTemplate
        List metadata = this.contract.parseAndValidatateMetadata(key.type());
        Map result = new LinkedHashMap();
        MethodMetadata md;
        Object buildTemplate;
        for(Iterator var4 = metadata.iterator(); var4.hasNext(); result.put(md.configKey(),

this.factory.create(key, md, (Factory)buildTemplate,

this.options, this.decoder, this.errorDecoder))) {
            md = (MethodMetadata)var4.next();
            if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
                buildTemplate = new ReflectiveFeign.BuildFormEncodedTemplateFromArgs(md, this.encoder);
            } else if (md.bodyIndex() != null) {
                buildTemplate = new ReflectiveFeign.BuildEncodedTemplateFromArgs(md, this.encoder);
            } else {
                buildTemplate = new ReflectiveFeign.BuildTemplateByResolvingArgs(md);
            }
        }
        return result;
    }
}

Contract的默认实现是org.springframework.cloud.openfeign.support.SpringMvcContract其基类为Contract.BaseContract。

 

第四章 Feign接口服务及源码解析笔记_第3张图片

public abstract static class BaseContract implements Contract {

2)parseAndValidateMetadata()方法代码解析

org.springframework.cloud.openfeign.support.SpringMvcContract.parseAndValidateMetadata()方法会与HTTP请求相关的所有函数的基本信息和注解信息

@Override
public MethodMetadata parseAndValidateMetadata(Class targetType, Method method) {
   this.processedMethods.put(Feign.configKey(targetType, method), method);

   //调用父类BaseContract的函数
   MethodMetadata md = super.parseAndValidateMetadata(targetType, method);
   RequestMapping classAnnotation = findMergedAnnotation(targetType, RequestMapping.class);

//处理RequestMapping注解
   if (classAnnotation != null) {
      if (!md.template().headers().containsKey(ACCEPT)) {
         parseProduces(md, method, classAnnotation);
      }
      if (!md.template().headers().containsKey(CONTENT_TYPE)) {
         parseConsumes(md, method, classAnnotation);
      }
      parseHeaders(md, method, classAnnotation);
   }
   return md;
}

3)feign.Contract.BaseContract#parseAndValidateMetadata 父类的parseAndValidateMetadata方法会依次解析接口类中的注解,方法中的注解,和各种参数注解,并将这些注解包含的信息封装到MethodMetadata 对象中,然后返回。

protected MethodMetadata parseAndValidateMetadata(Class targetType, Method method) {
    MethodMetadata data = new MethodMetadata();
    //TODO 函数的返回值
    data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));
    data.configKey(Feign.configKey(targetType, method));
    //TODO 获取并处理修饰class的注解信息
    if(targetType.getInterfaces().length == 1) {
        processAnnotationOnClass(data, targetType.getInterfaces()[0]);
    }
    //TODO 调用子类processAnnotationOnClass的实现
    processAnnotationOnClass(data, targetType);
    //TODO 处理修饰method的注解信息
    for (Annotation methodAnnotation : method.getAnnotations()) {
        processAnnotationOnMethod(data, methodAnnotation, method);
    }
    checkState(data.template().method() != null,
            "Method %s not annotated with HTTP method type (ex. GET, POST)",method.getName());
    //TODO 方法参数的类型
    Class[] parameterTypes = method.getParameterTypes();
    Type[] genericParameterTypes = method.getGenericParameterTypes();
    //TODO 方法参数的注解
    Annotation[][] parameterAnnotations = method.getParameterAnnotations();
    int count = parameterAnnotations.length;
    //TODO 依次处理各个函数参数的注解
    for (int i = 0; i < count; i++) {
        boolean isHttpAnnotation = false;
        if (parameterAnnotations[i] != null) {
            isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
        }
        if (parameterTypes[i] == URI.class) {
            data.urlIndex(i);
        } else if (!isHttpAnnotation) {
            checkState(data.formParams().isEmpty(), "Body parameters cannot be used with form parameters.");
            checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method);
            data.bodyIndex(i);
            data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));
        }
    }
    if (data.headerMapIndex() != null) {
        checkMapString("HeaderMap", parameterTypes[data.headerMapIndex()], genericParameterTypes[data.headerMapIndex()]);
    }
    if (data.queryMapIndex() != null) {
        checkMapString("QueryMap", parameterTypes[data.queryMapIndex()], genericParameterTypes[data.queryMapIndex()]);
    }
    return data;
}

3.7、使用Proxy生成FeignClient的实例对象

1)实例化方法分析feign.ReflectiveFeign#newInstance

InvocationHandler handler = factory.create(target, methodToHandler);中另外一件事情是OpenFeign使用Proxy.newProxyInstance方法来创建FeignClient接口类的实例,然后将InvocationHandle绑定到接口实例上,用于处理接口类函数调用。

feign.InvocationHandlerFactory.Default()方法:

public interface InvocationHandlerFactory {
    InvocationHandler create(Target var1, Map var2);
    public static final class Default implements InvocationHandlerFactory {
        public Default() {
        }
        public InvocationHandler create(Target target, Map dispatch) {
            return new FeignInvocationHandler(target, dispatch);
        }
    }
    public interface MethodHandler {
        Object invoke(Object[] var1) throws Throwable;
    }
}

Default 实现了InvocationHandlerFactory 接口,create方法其实返回FeignInvocationHandler的实例。

2)ReflectiveFeign.FeignInvocationHandler.invoke()方法源码如下:

static class FeignInvocationHandler implements InvocationHandler {
    private final Target target;
    private final Map dispatch;
    FeignInvocationHandler(Target target, Map dispatch) {
        this.target = (Target)Util.checkNotNull(target, "target", new Object[0]);
        this.dispatch = (Map)Util.checkNotNull(dispatch, "dispatch for %s", new Object[]{target});
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (!"equals".equals(method.getName())) {
            if ("hashCode".equals(method.getName())) {
                return this.hashCode();
            } else {
                return "toString".equals(method.getName()) ? this.toString() :

((MethodHandler)this.dispatch.get(method)).invoke(args);
            }
        } else {
            try {
            Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
                return this.equals(otherHandler);
            } catch (IllegalArgumentException var5) {
                return false;
            }
        }
    }

注意:((MethodHandler)this.dispatch.get(method)).invoke(args),Invoke方法会根据函数名称来调用不同的MethodHandle实例invoke方法。

3.8、网络请求

接下来就直接使用FeignClient接口类的实例,调用它的函数来发送网络请求,发送网络请求可以分为3个阶段:

1)是将函数实际参数添加到RequestTemplate中

2)调用Target生成具体的Request对象

3)调用Client来发送网络请求,将Response转化为对象返回

首先分析feign.SynchronousMethodHandler#invoke()方法:

public Object invoke(Object[] argv) throws Throwable {

//根据函数参数创建RequestTemplate
    RequestTemplate template = this.buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while(true) {
        try {
            return this.executeAndDecode(template);
        } catch (RetryableException var5) {
            retryer.continueOrPropagate(var5);
            if (this.logLevel != Level.NONE) {
                this.logger.logRetry(this.metadata.configKey(), this.logLevel);
            }
        }
    }
}

3.9、根据函数参数创建RequestTemplate.create()

@Override
public RequestTemplate create(Object[] argv) {
    RequestTemplate mutable = new RequestTemplate(metadata.template());
    //TODO 设置url
    if (metadata.urlIndex() != null) {
        int urlIndex = metadata.urlIndex();
        checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
        mutable.insert(0, String.valueOf(argv[urlIndex]));
    }
    Map varBuilder = new LinkedHashMap();
    //TODO 遍历MethodMetadata中关于参数的所以以及对应名称的配置信息
    for (Entry> entry : metadata.indexToName().entrySet()) {
        int i = entry.getKey();
        //TODO entry.getKey就是参数的索引
        Object value = argv[entry.getKey()];
        if (value != null) { // Null values are skipped.
            //TODO indexToExpander保存着将各种类型参数的值转化为String类型的Expander转换器
            if (indexToExpander.containsKey(i)) {
                //TODO 将Value值为String
                value = expandElements(indexToExpander.get(i), value);
            }
            for (String name : entry.getValue()) {
                varBuilder.put(name, value);
            }
        }
    }
    RequestTemplate template = resolve(argv, mutable, varBuilder);
    //TODO 设置queryMap参数
    if (metadata.queryMapIndex() != null) {
        template = addQueryMapQueryParameters((Map) argv[metadata.queryMapIndex()], template);
    }
    //TODO 设置headersMap参数
    if (metadata.headerMapIndex() != null) {
        template = addHeaderMapHeaders((Map) argv[metadata.headerMapIndex()], template);
    }
    return template;
}

3.10、feign.RequestTemplate#resolve()方法代码:

protected RequestTemplate resolve(Object[] argv, RequestTemplate mutable, Map variables) {
        Map variableToEncoded = new LinkedHashMap();
        Iterator var5 = this.metadata.indexToEncoded().entrySet().iterator();

        while(var5.hasNext()) {
            Entry entry = (Entry)var5.next();
            Collection names = (Collection)this.metadata.indexToName().get(entry.getKey());
            Iterator var8 = names.iterator();

            while(var8.hasNext()) {
                String name = (String)var8.next();
                variableToEncoded.put(name, entry.getValue());
            }
        }

        return mutable.resolve(variables, variableToEncoded);
    }
}

回调到feign-core-9.5.1.jar!\feign\RequestTemplate.resolve()方法:

RequestTemplate resolve(Map unencoded, Map alreadyEncoded) {

//替换query数值,将{queryVariable} 替换成实际的值
    this.replaceQueryValues(unencoded, alreadyEncoded);
    Map encoded = new LinkedHashMap();
    Iterator var4 = unencoded.entrySet().iterator();
    while(var4.hasNext()) {

//把所有参数进行编码
        Entry entry = (Entry)var4.next();
        String key = (String)entry.getKey();
        Object objectValue = entry.getValue();
        String encodedValue = this.encodeValueIfNotEncoded(key, objectValue, alreadyEncoded);
        encoded.put(key, encodedValue);
    }
    String resolvedUrl = expand(this.url.toString(), encoded).replace("+", "%20");
    if (this.decodeSlash) {
        resolvedUrl = resolvedUrl.replace("%2F", "/");
    }
    this.url = new StringBuilder(resolvedUrl);

//把头部信息进行传串行化
    Map> resolvedHeaders = new LinkedHashMap();
    Iterator var14 = this.headers.keySet().iterator();
    while(var14.hasNext()) {
        String field = (String)var14.next();
        Collection resolvedValues = new ArrayList();
        Iterator var9 = Util.valuesOrEmpty(this.headers, field).iterator();
        while(var9.hasNext()) {
            String value = (String)var9.next();
            String resolved = expand(value, unencoded);
            resolvedValues.add(resolved);
        }
        resolvedHeaders.put(field, resolvedValues);
    }
    this.headers.clear();
    this.headers.putAll(resolvedHeaders);
    if (this.bodyTemplate != null) {

//处理body信息
        this.body(urlDecode(expand(this.bodyTemplate, encoded)));
    }
    return this;
}

feign.SynchronousMethodHandler#invoke方法中的executeAndDecode()方法会根据RequestTemplate生成Request对象,然后交给Client实例发送网络请求,最后返回对应的函数返回类型的实例。

这里的client为org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient

3.11、executeAndDecode()源码分析

源码位置:feign.SynchronousMethodHandler.executeAndDecode()

Object executeAndDecode(RequestTemplate template) throws Throwable {
    //TODO 根据RequestTemplate生成Request
    Request request = targetRequest(template);
    if (logLevel != Logger.Level.NONE) {
        logger.logRequest(metadata.configKey(), logLevel, request);
    }
    Response response;
    long start = System.nanoTime();
    try {
        //TODO client发送网络请求
        response = client.execute(request, options);
        //TODO  ensure the request is set. TODO: remove in Feign 10
        response.toBuilder().request(request).build();
    } catch (IOException e) {
        if (logLevel != Logger.Level.NONE) {
            logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
        }
        throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
    boolean shouldClose = true;
    try {
        if (logLevel != Logger.Level.NONE) {
            response =logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
            // ensure the request is set. TODO: remove in Feign 10
            response.toBuilder().request(request).build();
        }
        //TODO 如果是response返回类型就可以直接返回
        if (Response.class == metadata.returnType()) {
            if (response.body() == null) {
                return response;
            }
            //TODO 设置body
            if (response.body().length() == null || response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
                shouldClose = false;
                return response;
            }
            // Ensure the response body is disconnected
            byte[] bodyData = Util.toByteArray(response.body().asInputStream());
            return response.toBuilder().body(bodyData).build();
        }
        if (response.status() >= 200 && response.status() < 300) {
            if (void.class == metadata.returnType()) {
                return null;
            } else {
                return decode(response);
            }
        } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
            return decode(response);
        } else {
            throw errorDecoder.decode(metadata.configKey(), response);
        }
    } catch (IOException e) {
        if (logLevel != Logger.Level.NONE) {
            logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
        }
        throw errorReading(request, response, e);
    } finally {
        if (shouldClose) {
            ensureClosed(response.body());
        }
    }
}

你可能感兴趣的:(SpringCloud框架解读,spring)