微服务feign组件学习

news2024/11/26 8:23:37

手写不易,对您有帮助。麻烦一键三连。也欢饮各位大料指正,交流。
在这里插入图片描述

微服务feign组件学习

      • 1.概念
        • 1.1 feign 概念
        • 1.2 Ribbon概念
      • 2.使用
        • 2.1 集成feign
          • 2.1.1 maven依赖
          • 2.1.2 项目结构
        • 2.2 使用
          • 2.2.1 定义feign接口
          • 2.2.2 消费端服务调用
          • 2.2.3 消费端扫描feign接口
        • 2.3 参数配置
          • 2.3.1 @FeignClient参数配置
          • 2.3.2 @EnableFeignClients参数配置
      • 3.Feign原理
        • 3.1 @EnableFeignClients
        • 3.2 注册默认配置
          • 3.2.1 解析@EnableFeignClients注解
          • 3.2.2 注册配置信息到spring容器
          • 3.2.3 总结
        • 3.3 注册FeignClientBeanFactory
          • 3.3.1 扫描@FeignClient的接口类
          • 3.3.2 注册@FeignClient的接口类
        • 3.4 feign调用原理
          • 3.4.1 自动装配类
          • 3.4.2 FeignAutoConfiguration作用
          • 3.4.3 FeignContext对象
          • 3.4.4 FeignClientFactoryBean创建Target对象
          • 3.4.5 Targeter targeter方法
        • 4 总结

1.概念

​ 当我们使用微服务架构后,服务之间的调用就是我们需要额外关注的事情,springCloud Alibaba为我们提供了服务调用的两个核心组件Feign、Ribbon

  • Feign: 微服务间的通信都是通过Feign这种HttpClient客户段进行调用
  • Ribbon:负责服务间调用之前的负载均衡
1.1 feign 概念

Feign是Neflix开发的声明式、模板化的HTTP客户端,可帮助我们更加便捷、优雅地调用HTTP API。Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。

Sping cloud openfeign 对Feign进行了增强,使其支持Spring MVC注解,另外还整合了RibbonNacos,从而使得Feign的使用更加方便。

1.2 Ribbon概念

Spring Cloud Ribbon 是基于Netflix Ribbon 实现的一套客户端负载均衡的工具。基于消费端这边先从注册中心拿到服务列表,直接在消费端就做了负载均衡,它是一种基于消费端的负载均衡。与之区别的是而基于服务端的负载均衡就像nginx(软件)和F5(硬件)这样的对服务器做反向代理的方式。

在springCloud Alibaba 中Feign默认已经集成了Ribbon。如果想要单独使用可以考虑 RestTemplate+@LoadBalanced注解的形式

@Configuration
public class ConfigBean {
    @Bean
    @LoadBalanced //使用Ribbon让RestTemplate负载均衡
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

后面所有使用Feign的操作则默认集成了Ribbon,此处不在赘述。

2.使用

项目基于spring-cloud版本Hoxton.SR8创建。

2.1 集成feign
2.1.1 maven依赖
<!--集成openFeign接口 其引入了feign组件和Ribbon组件-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.1.2 项目结构

在这里插入图片描述

如上项目结构,customer模块feign调用order,并提供了一个order-api的feign接口依赖层。

2.2 使用
2.2.1 定义feign接口
@FeignClient(name = "${com.xiu.order.service}", primary = false,decode404 = false)
public interface OrderClient {

    @GetMapping("/provider/order/{orderPath}/create")
    String create(@PathVariable(value = "orderPath") String orderPath);


    //get请求 多于1个参数,则必须写@RequestParam注解
    @GetMapping("findOrder")
    Order findOrder(@RequestParam(name = "pName") String name,
                     @RequestParam(name = "pAge") Integer age);

    //TODO 无法正常传参
    //@RequestParam后面是自定义参数类型将不会封装到接口的方法参数中
    @GetMapping("getOrder1") 
    Order getOrder(@RequestParam("Order") Order Order);

    //TODO 无法正常传参
    //feign将会把url拼接成url?name=xx&age=yy&address=zz(address字符串形式)
    //这个address将会导致接口那边在获取address时,不能正常封装成Address对象而导致报错
    @GetMapping("getOrder2") 
    Order getOrder1(@RequestParam(name = "name") String name,
                      @RequestParam(name = "age") Integer age
            ,@RequestParam(name = "address") Address address);

    //可以使用Map封装(远程接口使用@RequestBody Map来接收(address属性能正常接收到))
    @PostMapping("getOrder")  
    Map<String,Object> getOrder2(Map<String,Object> map);

    //不能使用超过1个@RequestBody
    @PostMapping("addOrder") 
    Order addOrder(@RequestBody Order order, @RequestParam("pAge") Integer age);

    //TODO 无法正常传参    
    @RequestParam后面是自定义参数Address将不会封装到接口的方法参数中
    @PostMapping("addOrder2")
    Order addOrder2(@RequestBody Order Order,@RequestParam("addr") Address addr);

    // 多于1个参数,则必须写@RequestParam注解(并且必须写value)
    // feign拼接url?ids=1%2C2%2C3 (%2C,即逗号)
    @PostMapping("checkOrder") 
    Order checkOrder(@RequestBody Order order, @RequestParam("ids") Integer[] ids);

    // 多于1个参数,则必须写@RequestParam注解(并且必须写value)
    // feign拼接url??ids=1&ids=2&ids=3
    @PostMapping("checkOrder2") 
    Order checkOrder2(@RequestBody Order order, @RequestParam("ids") List<Integer> ids);
}
#配置文件中声明服务名 用于@FeignClient 的name属性
com:
  xiu:
    order:
      service: order-server

图上定义了一个feign接口并给出了feign的常用方式。需要关注的一点是接口需要使用**@FeignClient**注解修饰

name属性设置服务名(负载均衡组件会根据服务名获取对应的注册中心的所有服务对应的服务列表)。

2.2.2 消费端服务调用
public class FeignTestServiceImpl implements FeignTestService {
    @Resource
    private OrderClient orderClient;
    @Override
    public String sayHello() {
        String orderPath = orderClient.create("aaaa");
        log.info(orderPath);

        return "hello nacos!!!";
    }
}

在customer模块中可以像使用其自己的服务一样使用feign服务OrderClient。

2.2.3 消费端扫描feign接口
@Slf4j
@SpringBootApplication(scanBasePackages = {"com.xiu"})
//扫描指定包路径下的feignClient接口
//不指定 默认回扫描当前类CustomerApp所在的路径为basePackages
@EnableFeignClients(basePackages = {"com.xiu"})
@EnableDiscoveryClient
public class CustomerApp {

    public static void main(String[] args) {
        SpringApplication.run(CustomerApp.class, args);
    }
}

注意事项

消费者模块启动类上使用@EnableFeignClients注解后一定要指明Feign接口所在的包路径。 所以这里推荐你们在开发中所有feign模块最好能统一包名前缀。

2.3 参数配置
2.3.1 @FeignClient参数配置
属性说明
name指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
urlurl一般用于调试,可以手动指定@FeignClient调用的地址
decode404当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException
configurationFeign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract
path定义当前FeignClient的统一前缀
fallback定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
fallbackFactory工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
2.3.2 @EnableFeignClients参数配置
属性说明
value扫描@FeignClient包地址
basePackages用户扫描Feign客户端的包,也就是@FeignClient标注的类,与value同义,并且互斥
basePackageClassesbasePackages属性的安全替代方案,每个指定类别的包将被扫描。 考虑在每个包中创建一个特殊的无操作标记类或接口,除了被该属性引用之外没有其他用途。
defaultConfiguration为所有客户端定制@Configuration,默认配置都在FeignClientsConfiguration中,可以自己定制
clients可以指定@FeignClient标注的类,如果不为空,就会禁用类路径(value、basePackages、basePackageClasses)扫描

3.Feign原理

3.1 @EnableFeignClients

feign起作用最核心的是在启动类上声明@EnableFeignClients,那么我们从该注解入手,来分析一下Feign组件的原理。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
//导入FeignClientsRegistrar类来
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
}

该注解使用了@Import导入FeignClientsRegistrar,我们知道@Import注解的作用

  • 第一个是单纯的注册 beanDefinition,用于后续 bean 的加载。
  • 第二个是注册配置类,即该类会跟启动类一样,作为配置类加载。

这里的FeignClientsRegistrar就是一个配置类,其实现了ImportBeanDefinitionRegistrar接口,其中registerBeanDefinitions方法会将的bean注册成Spring的BeanDefinition并最终被实例化为Bean实例,下面来看看ImportBeanDefinitionRegistrar注册哪些bean

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
 	  //解析@EnableFeignClients注解 注册默认的Configuration到spring容器
   	registerDefaultConfiguration(metadata, registry);
   	//注册所有扫描到的使用@FeignClient注解修饰的实例FeignClientBeanFactory(创建feignClient的工厂)到spring容器
   	registerFeignClients(metadata, registry);
}
  • 解析@EnableFeignClients注解 注册默认的Configuration到spring容器
  • 注册所有扫描到的使用@FeignClient注解修饰的实例到spring容器
3.2 注册默认配置
3.2.1 解析@EnableFeignClients注解
 private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
   //获取EnableFeignClients的相关属性信息 参考:2.3.2 @EnableFeignClients参数配置   
   Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
   //设置名字(内部类和普通类) 
   if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
     String name;
     if (metadata.hasEnclosingClass()) {
       //内部类命名方式
       name = "default." + metadata.getEnclosingClassName();
     } else {
       //普通类
       name = "default." + metadata.getClassName();
     }
     //上面获取到了配置信息
     //将配置信息defaultConfiguration 注册到spring容器
     this.registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
   }
}
  • 解析@EnableFeignClients获取其中配置属性defaultConfiguration

  • 将配置信息注册到spring容器。

3.2.2 注册配置信息到spring容器
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
    //创建FeignClientSpecification的BeanDefinition
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
    //设置属性信息 name
    builder.addConstructorArgValue(name);
     //设置属性信息 配置信息
    builder.addConstructorArgValue(configuration);
    //注册到spring容器中
    registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition());
}

FeignClientSpecification 是一个 Feign Client 的生成规范,可以简单理解为是@EnableFeignClients注解和@FeignClient注解解析后数据存放的一个类,通过bean扫描,将扫描到的@EnableFeignClients注解的defaultConfiguration属性、@FeignClient注解的configuration属性维护在这个类中,将来初始化FeignClient子容器时会将这些类实例化到子容器,这些配置类实例都是Feign Cient构建所依赖的组件。

3.2.3 总结

注册配置主要分成三部分

  1. 解析@EnableFeignClients获取其中配置属性defaultConfiguration、name属性
  2. 构建FeignClientSpecification的BeanDefinition将defaultConfiguration、name属性放入
  3. 注册FeignClientSpecification到spring容器。
3.3 注册FeignClientBeanFactory
3.3.1 扫描@FeignClient的接口类
public void registerFeignClients(AnnotationMetadata metadata,
       BeanDefinitionRegistry registry) {
    //获取扫描类包的组件
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);
    //设置扫描类型 只扫描@FeignClient修饰的类(默认)
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
          FeignClient.class);
  
    Set<String> basePackages;
    //获取@EnableFeignClients的client属性
    Map<String, Object> attrs = metadata
          .getAnnotationAttributes(EnableFeignClients.class.getName());
    final Class<?>[] clients = attrs == null ? null
          : (Class<?>[]) attrs.get("clients");
    //clients属性为空则 使用类路径扫描 
    //获取其value、basePackages、basePackageClasses)作为包扫描
    if (clients == null || clients.length == 0) {
       scanner.addIncludeFilter(annotationTypeFilter);
       basePackages = getBasePackages(metadata);
    }
    else {
       //clients不为空 则禁用类路径扫描,只使用clients扫描
       final Set<String> clientClasses = new HashSet<>();
       basePackages = new HashSet<>();
       for (Class<?> clazz : clients) {
          basePackages.add(ClassUtils.getPackageName(clazz));
          clientClasses.add(clazz.getCanonicalName());
       }
       //clients扫描需要新增一个过滤器 即:类使用@@FeignClient修饰,且还需要是Clients中的类
       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) {
       //获取到了类包路径中符合条件的@FeignClient修饰的接口
       //转换为BeanDefinition(方便注册到spring容器)
       Set<BeanDefinition> candidateComponents = scanner
             .findCandidateComponents(basePackage);
       for (BeanDefinition candidateComponent : candidateComponents) {
          if (candidateComponent instanceof AnnotatedBeanDefinition) {
             //校验@EnableClient修饰的必须为接口类
             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);
             //注册某个@EnableClient的局部配置,只针对该@FeignClient接口类
             //注册配置方式同全局配置注册 参考:3.2注册默认配置
             registerClientConfiguration(registry, name,
                   attributes.get("configuration"));
             //注册@FeignClient接口类
             registerFeignClient(registry, annotationMetadata, attributes);
          }
       }
    }
}

上述代码比较长,但是逻辑相对简单,主要做如下事情

  1. 创建扫描器scanner扫描接口
  2. 针对clients属性来判断是否走类路径扫描,还是只扫描clients中对应的类包路径
  3. 循环扫描类包获取@FeignClient修饰的接口并转换为BeanDefinition
  4. 获取@FeignClient类中的属性信息、metadata信息
  5. 注册扫描到的BeanDefinition。
3.3.2 注册@FeignClient的接口类
private void registerFeignClient(BeanDefinitionRegistry registry,
       AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    //从元数据中获取类信息(@FientClient注解修饰的类)
    String className = annotationMetadata.getClassName();
    //构造FeignClientFactoryBean的Bean
    BeanDefinitionBuilder definition = BeanDefinitionBuilder
          .genericBeanDefinition(FeignClientFactoryBean.class);
    //校验属性
    validate(attributes);
    //设置@FientClient相关属性 参考 2.3.1 @FeignClient参数配置
    definition.addPropertyValue("url", getUrl(attributes));
    definition.addPropertyValue("path", getPath(attributes));
    String name = getName(attributes);
    definition.addPropertyValue("name", name);
    //contextId 上下文id FeignClient的唯一标识,如果没有设置,则系统会自动生成
    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);

    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);

    //bean的primary设置为true,容器中有多个与该bean相同类型, 优先使用primary为true的bean
    boolean primary = (Boolean) attributes.get("primary");
    beanDefinition.setPrimary(primary);
  
    //设置别名 
    String alias = contextId + "FeignClient";
    String qualifier = getQualifier(attributes);
    if (StringUtils.hasText(qualifier)) {
       alias = qualifier;
    }
    //注册到spring容器
    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
          new String[] { alias });
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

构建@FeignClient注解修饰的接口类对应的BeanDefinition,并注册到spring容器中,其实注册的BeanDefintion是FeignClientFactoryBean。例如:该接口被扫描后会生成一个OrderClient的BeanDefinition,注册到spring容器中,最终被spring实例化(透个底:生成的实例对象是一个FeignClientFactoryBean的getObject 创建的HttpClient的代理对象)

@FeignClient(name = "${com.xiu.order.service}", primary = false,decode404 = false)
public interface OrderClient {
}
3.4 feign调用原理

3.1、3.2从@EnableFeignClients注解为切入点分析类其做了哪些操作,下面从open-feign的自动装配类为切入点,来看看feign做了哪些。

3.4.1 自动装配类
自动装配类作用
FeignAutoConfiguration核心配置类
FeignRibbonClientAutoConfiguration负责配置Feign与Ribbon的集成(负载均衡相关)
FeignLoadBalancerAutoConfiguration支持feign调用时候的负载均衡
FeignAcceptGzipEncodingAutoConfiguration支持Feign调用时候可以使用GZIP编码进行响应。
可以节省网络资源,加快响应时间
FeignContentGzipEncodingAutoConfiguration支持Feign调用时候可以使用GZIP编码发送请求。
  • FeignRibbonClientAutoConfiguration和FeignLoadBalancerAutoConfiguration显然是做负载均衡的
  • FeignHalAutoConfiguration是用来处理application/hal+json这种的,
  • FeignAcceptGzipEncodingAutoConfiguration和FeignContentGzipEncodingAutoConfiguration都是定义了feign的拦截器,用来设置压缩相关的请求头,
  • 核心的配置类就一个FeignAutoConfiguration。
3.4.2 FeignAutoConfiguration作用

通过FeignAutoConfiguration类的代码研究,该类做了如下事情

  • 引入两个配置FeignClientProperties、FeignHttpClientProperties(都是http请求相关配置类,前者是为HttpURLConnection(默认的client)配置类,后者是ApacheHttpClient和OkHttpClient的配置类。
  • 构造FeignContext对象实例。
  • 引入的类有feign.hystrix.HystrixFeign则创建HystrixTargeter(openfeign会添加熔断机制,所以该对象被创建)否则创建DefaultTargeter,Target:Feign客户端代理对象的由该对象target方法生成。(前者是带有熔断机制的Target,后者是默认普通的Target
3.4.3 FeignContext对象

FeignContext主要用于管理Feign客户端的上下文信息。它是一个spring的子容器。承担了以下几个关键职责:

  1. Bean存储与检索:spring子容器,它保存了所有与Feign客户端相关的bean,包括自定义的编解码器、连接(Contract)、拦截器(Request Interceptors)等。这些bean可以根据名称或类型从FeignContext 中获取,为Feign客户端的构建提供必要的组件。
  2. 作用域隔离:在多客户端场景下,FeignContext 支持不同客户端之间的配置隔离,每个客户端都可以拥有自己独立的配置。
  3. 动态配置:它允许在运行时动态地向Feign客户端添加或替换组件。
//1、自动注入配置类
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();

//构造FeignContext
@Bean
public FeignContext feignContext() {
    FeignContext context = new FeignContext();
    //设置配置信息
    context.setConfigurations(this.configurations);
    return context;
}

public void setConfigurations(List<C> configurations) {
  //把所有的feign客户端配置类实例保存到configurations成员map中,key是client的名字,value是client本身。 
  for (C client : configurations) {
    this.configurations.put(client.getName(), client);
  }
}

上述方式中构建了FeignContext并将其通过3.2.2小节配置信息构造的FeignClientSpecification 是一个 Feign Client 的生成规范放入FeignContext的map容器中。

createContext方法

FeignContext有一个核心方法createContext 其会基于某个figenClient的name创建对应的AnnotationConfigApplicationContext对象

//针对每个feign客户端创建一个AnnotationConfigApplicationContext。
protected AnnotationConfigApplicationContext createContext(String name) {
    //构造AnnotationConfigApplicationContext (spring容器)
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    //注册每个feign客户端自己的配置类
    if (this.configurations.containsKey(name)) {
       for (Class<?> configuration : this.configurations.get(name)
             .getConfiguration()) {
          //注册configuration对应的beanDeinftion对象
          context.register(configuration);
       }
    }
    //注册全局的配置类
    for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
       if (entry.getKey().startsWith("default.")) {
          for (Class<?> configuration : entry.getValue().getConfiguration()) {
             context.register(configuration);
          }
       }
    }
    //注册用于占位符解析的配置类
    context.register(PropertyPlaceholderAutoConfiguration.class,
          this.defaultConfigType);
    //添加了环境变量feign.client.name = 客户端的名字
    context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
          this.propertySourceName,
          Collections.<String, Object>singletonMap(this.propertyName, name)));
    //设置了父类Context和本Context的名字。
    if (this.parent != null) {
       // Uses Environment from parent as well as beans
       context.setParent(this.parent);
       // jdk11 issue
       // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
       context.setClassLoader(this.parent.getClassLoader());
    }
    context.setDisplayName(generateDisplayName(name));
    context.refresh();
    return context;
}

也就是说每一个feign客户端都有一个AnnotationConfigApplicationContext,这个context里面注册了这个feign客户端的自己的配置类、全局默认的配置类、注册用于占位符解析的配置类,添加了环境变量feign.client.name = feign客户端的名字,设置了父类Context和本Context的名字。

3.4.4 FeignClientFactoryBean创建Target对象

Target是我们服务端实际请求的fegin真正对象。负责创建和配置Feign客户端的目标对象。这个接口定义了如何将一个接口绑定到一个具体的HTTP服务地址上,是连接Feign客户端定义与实际HTTP请求执行逻辑的纽带。其中FeignAutoConfiguration自动装配类会创建一个默认普通的DefaultTargeter或者带有熔断机制的HystrixTargeter。

同时3.3.3小节注册的FeignClientFactoryBean在spring容器启动进行实例化该类的时候会调用其中的getObject方法也会创建对应的Target对象。下面我们来看看代码实现。

//FactoryBean 获取其工厂创建的bean
@Override
public Object getObject() throws Exception {
    return getTarget();
}


<T> T getTarget() {
    //获取feignContext 自动装配后会实例化该对象并放入spring容器中
    FeignContext context = applicationContext.getBean(FeignContext.class);
    //构造feign对象(基础信息)
    Feign.Builder builder = feign(context);
    //基于负载均衡创建httpClient,用于后面feign调用
    if (!StringUtils.hasText(url)) {
       if (!name.startsWith("http")) {
          url = "http://" + name;
       }
       else {
          url = name;
       }
       url += cleanPath();
       //基于负载均衡创建httpclient代理对象
       return (T) loadBalance(builder, context,
             new HardCodedTarget<>(type, name, url));
    }
  
    //如果有 @FeignClient的url属性 则基于url创建httpClient,用于后面feign调用
    if (StringUtils.hasText(url) && !url.startsWith("http")) {
       url = "http://" + url;
    }
    String url = this.url + cleanPath();
    //获取可用的client
    Client client = getOptional(context, Client.class);
    if (client != null) {
       if (client instanceof LoadBalancerFeignClient) {
          //url直连不使用负载,但是引入了ribbon相关的负载,则基于ribbon进行url的负载
          client = ((LoadBalancerFeignClient) client).getDelegate();
       }
       if (client instanceof FeignBlockingLoadBalancerClient) {
          //url直连不使用负载,但是引入了Spring Cloud LoadBalancer相关的负载,
          //则基于Spring Cloud LoadBalancer进行url的负载
          client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
       }
       builder.client(client);
    }
    //url直连的httpclient代理对象
    Targeter targeter = get(context, Targeter.class);
    return (T) targeter.target(this, builder, context,
          new HardCodedTarget<>(type, name, url));
}

上述代码做了如下事情:

  1. 获取FeignContext对象,便于从其中获取对应的FeignClient客户端的配置信息构建feign对象
  2. 基于基础信息构建feign(基础信息:日志类、编解码、连接信息等)
  3. 创建feign对象的client客户端,有两种方式 url直连的普通client和具有负载功能的client
  4. Targeter.target创建HttpClient代理对象。

feignContext获取相关配置对象

在上述构建feign对象的代码中大量用到了context.getInstance方法获取对应的配置信息,所以还是比较FeignContext还是比较重要的。

其底层调用FeignContext有一个核心方法createContext获取的。

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

protected <T> T get(FeignContext context, Class<T> type) {
		T instance = context.getInstance(contextId, type);
		if (instance == null) {
			throw new IllegalStateException(
					"No bean found of type " + type + " for " + contextId);
		}
		return instance;
	}
//上述两个方法都使用 context.getInstance(contextId, type);

  • get(context, Feign.Builder.class):Feign.Builder
  • get(context, FeignLoggerFactory.class):获取feign日志工厂
  • encoder(get(context, Encoder.class)) 编码器对象
  • decoder(get(context, Decoder.class)) 解码器对象
  • contract(get(context, Contract.class)); 连接对象
  • getOptional(context, Client.class); FeignClient客户端
  • get(context, Targeter.class); Targeter对象
3.4.5 Targeter targeter方法

不管是直连还是负载的代理对象创建都需要先获取targeter对象在调用其target方法。

而loadBalance方法除了和直连调用一样的逻辑,只是其创建的实LoadBalancerFeignClient对象。所以此处分析该方法即可了解其中逻辑。

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
       HardCodedTarget<T> target) {
    //从feignContext中获取Client,其获取的是LoadBalancerFeignClient
    Client client = getOptional(context, Client.class);
    if (client != null) {
       builder.client(client);
       //创建的是HystrixTargeter(openFeign会自动引入熔断机制)
       Targeter targeter = get(context, Targeter.class);
       //生成代理对象
       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?");
}

在这里插入图片描述
通过上图可以看到Targeter. targeter方法其实基于jdk动态代理创建了FeignClient对象。

4 总结

feign服务调用的完整流程如下:

  • 容器启动的时候,首先是走@EnableFeignClients去注册默认的配置类、注册FeignClient和FeignClient的配置类

  • 基于自动装配类@FeignAutoConfiguration,创建FeignContext,把配置类都放进去。创建Targeter用于创建代理对象

  • FactoryBean的getObject() 创建对应的代理对象,基于Targeter.targeter的jdk动态代理生成FeignClient对象

  • 当@Autowired注入feign客户端的时候,发起调用通过代理对象发起http请求完成整个调用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1826164.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java面试题汇总(持续更新.....)

Java面试题 1. JVM & JDK & JRE Java虚拟机&#xff08;JVM&#xff09;是运行Java字节码的虚拟机&#xff0c;JVM有针对不同系统的特定实现&#xff0c;目的是使用相同的字节码&#xff0c;他们都会给出相同的结果。字节码和不同系统的JVM实现是Java语言“一次编译、…

个人网站制作 Part 25 添加实时聊天功能 | Web开发项目添加页面缓存

文章目录 &#x1f469;‍&#x1f4bb; 基础Web开发练手项目系列&#xff1a;个人网站制作&#x1f680; 添加实时聊天功能&#x1f528;使用聊天服务&#x1f527;步骤 1: 选择聊天服务&#x1f527;步骤 2: 安装Socket.io&#x1f527;步骤 3: 创建Socket.io服务器 &#x1…

抽奖系统源码_微信抽奖系统PHP源码开源

介绍&#xff1a; 微信抽奖系统源码是一个以php MySQL进行开发的手机抽奖系统源码。用途&#xff1a;适合做推广营销、直播、粉丝抽奖。 功能介绍&#xff1a; 1、后台可以设置每个抽奖用户的抽奖次数,后台添加设置奖品,适和企业和商场搞活动,后台添加用户&#xff0c;才能抽…

如何应对缺失值带来的分布变化?探索填充缺失值的最佳插补算法

本文将探讨了缺失值插补的不同方法&#xff0c;并比较了它们在复原数据真实分布方面的效果&#xff0c;处理插补是一个不确定性的问题&#xff0c;尤其是在样本量较小或数据复杂性高时的挑战&#xff0c;应选择能够适应数据分布变化并准确插补缺失值的方法。 我们假设存在一个…

【多线程】Thread类及其基本用法

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录 1. Java中多线程编程1.1 操作系统线程与Java线程1.2 简单使用多线程1.2.1 初步创建新线程代码1.2.2 理解每个…

小功率无变压器电源设计

采用无变压器电源解决方案为低功率电路提供所需电源通常是有利的。 事实上&#xff0c;如果负载电流只有几十毫安&#xff0c;则可以将输入交流电压转换为直流电压&#xff0c;而无需使用大型、昂贵且笨重的变压器。不带变压器的替代方案也更便宜、更轻并且占地面积更小。无变…

深入剖析人才管理的关键要素:“选、用、育、留”四大核心要素

在当今这个日新月异的商业时代&#xff0c;企业的成功不再仅仅取决于资金、技术或市场策略&#xff0c;而更多地依赖于企业所拥有的人才资源。有效的人才管理策略&#xff0c;尤其是“选、用、育、留”四大核心要素&#xff0c;已成为推动企业持续发展的关键。 一、选&#xff…

28.启动与暂停程序

上一个内容&#xff1a;27.设计注入功能界面 以它 27.设计注入功能界面 的代码为基础进行修改 点击添加游戏按钮之后就把游戏启动了 CWndINJ.cpp文件中修改&#xff1a; void CWndINJ::OnBnClickedButton1() {// TODO: 在此添加控件通知处理程序代码/*ExeLst.InsertItem(0, L…

虚函数机制-动态绑定的应用

虚函数使得程序在运行的时候根据指针指向对象的类型来确定调用哪个函数。 下图中&#xff1a;都为静态绑定。因为在编译器就确定了可以调用的函数 此时当基类指针指向派生类对象时&#xff0c;因为没有virtual关键字&#xff0c;所以在编译阶段就根据指针类型确定了要指向的函…

博客论坛系统java博客管理系统基于springboot+vue的前后端分离博客论坛系统

文章目录 博客论坛系统一、项目演示二、项目介绍三、部分功能截图四、部分代码展示五、底部获取项目源码&#xff08;9.9&#xffe5;带走&#xff09; 博客论坛系统 一、项目演示 博客论坛系统 二、项目介绍 基于springbootvue的前后端分离博客论坛系统 系统角色&#xff1a…

创业者的孤独之旅:马云视角下的战略定位与自我激励

一、引言 在创业的道路上&#xff0c;每一位创业者都如同孤独的旅人&#xff0c;背负着梦想与希望&#xff0c;踏上了充满未知与挑战的征途。马云&#xff0c;这位中国电子商务的巨擘&#xff0c;以其独特的视角和坚韧不拔的精神&#xff0c;为我们揭示了创业者所面临的孤独与…

js注册popstate事件并阻止浏览器返回

提示&#xff1a;记录工作中遇到的需求及解决办法 文章目录 前言一、第一步二、第二步三、第三步四、最后 前言 在做一些重要资料填写的时候, 我们基本都会阻止一下浏览器的回退, 刷新等等, 今天我们主要针对回退, 做一次讲解。 提示&#xff1a;以下是本篇文章正文内容&#…

HCIP认证笔记(填空)

1、为防止攻击者伪造BGP报文对设备进行攻击,可以通过配置GTSM功能检测IP报文中的TTL值的范围来对设备进行保护。如果某台设备配置了“peer x.x.x.x valid-ttl-hops 100",则被检测的报文的TTL值的有效范围为【(156),255】; 解析: peer {group-name | ipv4-address…

2024年7款硬盘恢复软件:即刻恢复硬盘删除的文件!

当文件被删除后&#xff0c;它并不是立即从硬盘中消失&#xff0c;而是被标记为“已删除”&#xff0c;等待垃圾回收处理。因此&#xff0c;在文件被删除后&#xff0c;有几种方法可以尝试恢复删除的数据。 以下是7款常用的数据恢复软件&#xff0c;以及它们的详细介绍&#xf…

IDEA创建SpringBoot项目教程,讲解超详细!!!(2024)

前言 在创建Spring Boot项目时&#xff0c;为了确保项目的顺利构建和运行&#xff0c;我们依赖于JDK&#xff08;Java开发工具包&#xff09;和Maven仓库。 JDK作为Java编程的基础&#xff0c;提供了编译和运行Java应用程序所需的核心类库和工具。 JDK安装配置教程&#xff1…

相对与绝对布局:悬浮的提示框页面

目录 任务描述 相关知识 HTML和CSS基础: 盒子模型: 定位: 伪类: 编程要求 任务描述 在本关中&#xff0c;你需要创建一个简单的HTML页面&#xff0c;其中包括一个按钮。当鼠标悬停在按钮上时&#xff0c;会显示一个浮动的提示框&#xff08;tooltip&#xff09;&#xf…

【SpringBoot】SpringBoot:构建实时聊天应用

文章目录 引言项目初始化添加依赖 配置WebSocket创建WebSocket配置类创建WebSocket处理器 创建前端页面创建聊天页面 测试与部署示例&#xff1a;编写单元测试 部署扩展功能用户身份验证消息持久化群组聊天 结论 引言 随着实时通信技术的快速发展&#xff0c;聊天应用在现代We…

python pandas处理股票量化数据:笔记2

有一个同学用我的推荐链接注册了tushare社区帐号https://tushare.pro/register?reg671815&#xff0c;现在有了170分积分。目前使用数据的频率受限制。不过可以在调试期间通过python控制台获取数据&#xff0c;将数据保存在本地以后使用不用高频率访问tushare数据接口&#xf…

2024年6个恢复删除数据的方法,看这篇就够了~

在数字化飞速发展的今天&#xff0c;数据已成为我们生活中不可或缺的组成部分&#xff0c;它记录着我们的记忆、创意和辛勤付出。然而&#xff0c;生活总是充满意外&#xff0c;我们可能会遭遇数据意外删除或丢失的困境。在这种关键时刻&#xff0c;如何高效、准确地恢复数据就…

Linxu开机出现 Generating “/run/initramfs/rdsosreport.txt“解决方案

Linxu开机出现 Generating "/run/initramfs/rdsosreport.txt"解决方案 解决&#xff1a; 一、找这个-root结尾的文件也不一样。 大家可以用ls /dev/mapper查看到自己装的镜像对应的以-root结尾的文件是哪个。 二、所以我们运行的是&#xff1a;xfs_repair /dev/map…