OpenFeign源码2-Bean注册过程和调用过程

news2024/11/18 9:34:52

0. 环境

  • nacos版本:1.4.1
  • Spring Cloud : Hoxton.SR9(没用2020.0.2版本后面说明
  • Spring Boot :2.4.4
  • Spring Cloud alibaba: 2.2.5.RELEASE
  • Spring Cloud openFeign 2.2.2.RELEASE

测试代码:github.com/hsfxuebao/s…

2020.0.X版本开始的OpenFeign底层不再使用Ribbon了

1. FeignClient的bean注册过程

1.1 @EnableFeignClients

  想要集成 Feign 客户端,需要我们通过注解 @EnableFeignClients 来开启。这个注解开启了FeignClient的解析过程。这个注解的声明如下,它用到了一个@Import注解,我们知道Import是用来导入一个配置类的,接下来去看一下FeignClientsRegistrar的定义:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}
复制代码

  FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,它是一个动态注入bean的接口,Spring Boot启动的时候,会去调用这个类中的registerBeanDefinitions来实现动态Bean的装载。它的作用类似于ImportSelector。

  然后就会进入 FeignClientsRegistrar# registerBeanDefinitionsregisterDefaultConfiguration 方法内部从 SpringBoot 启动类上检查是否有@EnableFeignClients, 有该注解的话, 则完成 Feign 框架相关的一些配置内容注册registerFeignClients 方法内部从 classpath 中, 扫描获得 @FeignClient修饰的类, 将类的内容解析为 BeanDefinition , 最终通过调用 Spring 框架中的BeanDefinitionReaderUtils.resgisterBeanDefinition 将解析处理过的 FeignClientBeanDeifinition 添加到 spring 容器中.

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   // todo 将@EnableFeignClients注解中的defaultConfiguration属性注册到一个缓存map
   registerDefaultConfiguration(metadata, registry);
   // todo 1) 扫描所有@FeignClient注解的接口,即扫描到所有Feign接口
   // 2) 将每个@FeignClient注解的configuration属性注册到一个缓存map
   // 3) 根据@FeignClient注解元数据生成 FeignClientFactoryBean 的BeanDefinition,
   //    并将这个BeanDefinition注册到一个缓存map
   registerFeignClients(metadata, registry);
}
复制代码
  • registerDefaultConfiguration:
    private void registerDefaultConfiguration(AnnotationMetadata metadata,
          BeanDefinitionRegistry registry) {
       // 获取到@EnableFeignClients注解的属性值
       // 第二个参数true,表示将Class类型的属性变为了String类型
       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();
          }
          // todo 注册这个defaultConfiguration属性
          registerClientConfiguration(registry, name,
                defaultAttrs.get("defaultConfiguration"));
       }
    }
    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
       // 生成一个Builder
       BeanDefinitionBuilder builder = BeanDefinitionBuilder
             .genericBeanDefinition(FeignClientSpecification.class);
       builder.addConstructorArgValue(name);
       builder.addConstructorArgValue(configuration);
       registry.registerBeanDefinition(
             name + "." + FeignClientSpecification.class.getSimpleName(),
             builder.getBeanDefinition()); // 获取到构建的BeanDefinition
    }
    复制代码
  • registerFeignClients
    public void registerFeignClients(AnnotationMetadata metadata,
          BeanDefinitionRegistry registry) {
       // 定义一个扫描器
       ClassPathScanningCandidateComponentProvider scanner = getScanner();
       // 初始化扫描器
       scanner.setResourceLoader(this.resourceLoader);
    
       Set<String> basePackages;
    
       // 获取@EnableFeignClients注解的属性元数据
       Map<String, Object> attrs = metadata
             .getAnnotationAttributes(EnableFeignClients.class.getName());
       AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
             FeignClient.class);
       // 获取clients属性值
       final Class<?>[] clients = attrs == null ? null
             : (Class<?>[]) attrs.get("clients");
       // 若clients属性为空,则启用类路径扫描
       if (clients == null || clients.length == 0) {
          // 为扫描器添加一个扫描@FeignClient注解的Filter
          scanner.addIncludeFilter(annotationTypeFilter);
          // todo 获取@EnableFeignClients注解中所有指定的基本包
          basePackages = getBasePackages(metadata);
       }
       else {
          // 指定了clients属性,将所有指定的Feign接口类添加到集合
          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)));
       }
    
       // 遍历这些基本包,将其它的@FeignClient接口找到并写入到集合
       for (String basePackage : basePackages) {
          Set<BeanDefinition> candidateComponents = scanner
                .findCandidateComponents(basePackage);
          // 遍历 候选组件 集合(所有Feign接口都在这个集合中)
          for (BeanDefinition candidateComponent : candidateComponents) {
             // 只处理Feign接口组件
             if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is an interface
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                // 从BeanDefinition中获取注解元数据
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                // 断言:若不是接口,直接抛出异常
                Assert.isTrue(annotationMetadata.isInterface(),
                      "@FeignClient can only be specified on an interface");
    
                // 获取@FeignClient注解属性
                Map<String, Object> attributes = annotationMetadata
                      .getAnnotationAttributes(
                            FeignClient.class.getCanonicalName());
                // todo 获取Feign名称
                String name = getClientName(attributes);
                // 将当前遍历的Feign接口的configuration注册到缓存map
                registerClientConfiguration(registry, name,
                      attributes.get("configuration"));
                // 将FeignClientFactoryBean的BeanDefinition注册到缓存map
                registerFeignClient(registry, annotationMetadata, attributes);
             }
          }
       }
    }
    复制代码
    registerFeignClient 在这个方法中,就是去组装BeanDefinition,也就是Bean的定义,然后注册到Spring IOC容器。
    private void registerFeignClient(BeanDefinitionRegistry registry,
          AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
       // 获取FeignClient接口的类全路径
       String className = annotationMetadata.getClassName();
       // 生成一个beanFactory,其会为FeignClientFactoryBean生成一些必要的组件
       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";
       // 获取到FeignClientFactoryBean的beanDefinition
       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;
       }
    
       // 生成beanDefinition的holder,通过holder可以获取到这个beanDefinition
       BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
             new String[] { alias });
       // 把BeanDefinition的这个bean定义注册到IOC容器
       BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }
    复制代码
    我们关注一下,BeanDefinitionBuilder是用来构建一个BeanDefinition的,它是通过 genericBeanDefinition 来构建的,并且传入了一个FeignClientFactoryBean的类,代码如下。
    public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {
       BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
       builder.beanDefinition.setBeanClass(beanClass);
       return builder;
    }
    复制代码

1.2 小节

综上代码分析,其实实现逻辑很简单。

  1. 创建一个BeanDefinitionBuilder
  2. 创建一个工厂Bean,并把从@FeignClient注解中解析的属性设置到这个FactoryBean
  3. 调用registerBeanDefinition注册到IOC容器中

2. 动态代理过程详解

我们可以发现,FeignClient被动态注册成了一个FactoryBean.

  Spring Cloud FengnClient实际上是利用Spring的代理工厂来生成代理类,所以在这里地方才会把所有的FeignClient的BeanDefinition设置为FeignClientFactoryBean类型,而FeignClientFactoryBean继承自FactoryBean,它是一个工厂Bean。在Spring中,FactoryBean是一个工厂Bean,用来创建代理Bean。工厂 Bean 是一种特殊的 Bean, 对于 Bean 的消费者来说, 他逻辑上是感知不到这个 Bean 是普通的 Bean 还是工厂 Bean, 只是按照正常的获取 Bean 方式去调用, 但工厂bean 最后返回的实例不是工厂Bean 本身, 而是执行工厂 Bean 的 getObject 逻辑返回的示例。

  简单来说,FeignClient标注的这个接口,会通过FeignClientFactoryBean.getObject()这个方法获得一个代理对象。

2.1 FeignClientFactoryBean.getObject

  getObject调用的是getTarget方法,它从applicationContext取出FeignContext,FeignContext继承了NamedContextFactory,它是用来统一维护feign中各个feign客户端相互隔离的上下文

springboot启动时会加载类路径下/META-INF/spring.factories中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类,FeignContext注册到容器是在FeignAutoConfiguration上完成的。FeignContext是全局唯一的上下文,它继承了NamedContextFactory,它是用来统一维护feign中各个feign客户端相互隔离的上下文

@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
    FeignContext context = new FeignContext();
    context.setConfigurations(this.configurations);
    return context;
}
复制代码

  在初始化FeignContext时,会把configurations在容器中放入FeignContext中。configurations 的来源就是在前面registerFeignClients方法中将@FeignClient的配置 configuration。

  接着,构建feign.builder,在构建时会向FeignContext获取配置的Encoder,Decoder等各种信息。FeignContext在上文中已经提到会为每个Feign客户端分配了一个容器,它们的父容器就是spring容器。

  配置完Feign.Builder之后,再判断是否需要LoadBalance,如果需要,则通过LoadBalance的方法来设置。实际上他们最终调用的是Target.target()方法。

@Override
public Object getObject() throws Exception {
   return getTarget();
}

/**
 * @param <T> the target type of the Feign client
 * @return a {@link Feign} client created with the specified data and the context
 * information
 */
<T> T getTarget() {
   // 实例化Feign上下文对象FeignContext
   FeignContext context = this.applicationContext.getBean(FeignContext.class);
   // todo 从Spring子容器中获取相应的实例
   Feign.Builder builder = feign(context);
   // 若url属性为空,则说明其要采用负载均衡方式调用提供者,生成有负载均衡功能的代理类
   if (!StringUtils.hasText(this.url)) {
      // 若name属性不以http开头
      if (!this.name.startsWith("http")) {
         this.url = "http://" + this.name;
      }
      else {
         this.url = this.name;
      }
      // 将规范化的path属性连接到url后
      // 规范化path:若path属性不以斜杠(/)开头,则为其添加斜杠,以斜杠结尾,则去掉尾部斜杠
      this.url += cleanPath();
      // 负载均衡调用
      return (T) loadBalance(builder, context,
            new HardCodedTarget<>(this.type, this.name, this.url));
   }
   // 这是url不为空的情况,即采用直连方式访问提供者,则生成默认的代理类
   if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
      this.url = "http://" + this.url;
   }
   String url = this.url + cleanPath();
   // 从Spring子容器中获取Client
   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
         client = ((LoadBalancerFeignClient) client).getDelegate();
      }
      if (client instanceof FeignBlockingLoadBalancerClient) {
         // not load balancing because we have a url,
         // but Spring Cloud LoadBalancer is on the classpath, so unwrap
         client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
      }
      builder.client(client);
   }
   // todo 生成默认的代理类
   Targeter targeter = get(context, Targeter.class);
   return (T) targeter.target(this, builder, context,
         new HardCodedTarget<>(this.type, this.name, url));
}
复制代码

Feign.Builder用来构建Feign对象,基于builder实现上下文信息的构建,代码如下。

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

    // @formatter:off
    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)); //contract协议,用来实现模版解析(后面再详细分析)
    // @formatter:on

    configureFeign(context, builder);
    applyBuildCustomizers(context, builder);

    return builder;
}
复制代码

从代码中可以看到,feign方法,主要是针对不同的服务提供者生成Feign的上下文信息,比如loggerencoderdecoder等。因此,从这个分析过程中,我们不难猜测到它的原理结构,如下图所示:

父子容器隔离的实现方式如下,当调用get方法时,会从context中去获取指定type的实例对象。

//FeignContext.java
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;
}
复制代码

接着,调用NamedContextFactory中的getInstance方法:

//NamedContextFactory.java
public <T> T getInstance(String name, Class<T> type) {
    //根据`name`获取容器上下文
    AnnotationConfigApplicationContext context = this.getContext(name);

    try {
        //再从容器上下文中获取指定类型的bean。
        return context.getBean(type);
    } catch (NoSuchBeanDefinitionException var5) {
        return null;
    }
}
复制代码

getContext方法根据namecontexts容器中获得上下文对象,如果没有,则调用createContext创建。

protected AnnotationConfigApplicationContext getContext(String name) {
    if (!this.contexts.containsKey(name)) {
        synchronized(this.contexts) {
            if (!this.contexts.containsKey(name)) {
                this.contexts.put(name, this.createContext(name));
            }
        }
    }

    return (AnnotationConfigApplicationContext)this.contexts.get(name);
}
复制代码

2.2 生成动态代理

loadBalance :生成具备负载均衡能力的feign客户端,为feign客户端构建起绑定负载均衡客户端。

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
      HardCodedTarget<T> target) {

    //Feign发送请求以及接受响应的http client,默认是Client.Default的实现,可以修改成OkHttp、HttpClient等。
   Client client = getOptional(context, Client.class);
   if (client != null) {
      builder.client(client);//针对当前Feign客户端,设置网络通信的client

      //targeter表示HystrixTarger实例,因为Feign可以集成Hystrix实现熔断,所以这里会一层包装。
      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?");
}
复制代码

Client client = (Client)this.getOptional(context, Client.class); 从上下文中获取一个 Client,默认是LoadBalancerFeignClient。它是在FeignRibbonClientAutoConfiguration这个自动装配类中,通过Import实现的

@Import({ HttpClientFeignLoadBalancedConfiguration.class,
        OkHttpFeignLoadBalancedConfiguration.class,
        DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
  .....
}
复制代码

  这里的通过 DefaultFeignLoadBalancedConfiguration 注入客户端 Client 的实现

@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
            SpringClientFactory clientFactory) {
        return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
                clientFactory);
    }
}
复制代码

  接下去进入 targeter.target(this, builder, context, target) ,携带着构建好的这些对象去创建代理实例 ,这里有两个实现 HystrixTargeter 、DefaultTargeter 很显然,我们没有配置 Hystrix ,这里会走 DefaultTargeter

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.Feign.Builder#target(feign.Target)

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

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

  这个方法是用来创建一个动态代理的方法,在生成动态代理之前,会根据Contract协议(协议解析规则,解析接口类的注解信息,解析成内部的MethodHandler的处理方式。

  从实现的代码中可以看到熟悉的Proxy.newProxyInstance方法产生代理类。而这里需要对每个定义的接口方法进行特定的处理实现,所以这里会出现一个MethodHandler的概念,就是对应方法级别的InvocationHandler

public <T> T newInstance(Target<T> target) {
    // 解析接口注解信息
    //根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式
    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 handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    
    InvocationHandler handler = factory.create(target, methodToHandler);
  // 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给InvocationHandler 处理。
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
}
复制代码

  targetToHandlersByName.apply(target) :根据Contract协议规则,解析接口类的注解信息,解析成内部表现:targetToHandlersByName.apply(target);会解析接口方法上的注解,从而解析出方法粒度的特定的配置信息,然后生产一个SynchronousMethodHandler 然后需要维护一个<method,MethodHandler>的map,放入InvocationHandler的实现FeignInvocationHandler中。

public Map<String, MethodHandler> apply(Target target) {
      List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
      Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
      for (MethodMetadata md : metadata) {
        BuildTemplateByResolvingArgs buildTemplate;
        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
          buildTemplate =
              new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
        } else if (md.bodyIndex() != null) {
          buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
        } else {
          buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
        }
        if (md.isIgnored()) {
          result.put(md.configKey(), args -> {
            throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
          });
        } else {
          result.put(md.configKey(),
              factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
        }
      }
      return result;
}
复制代码

2.3 小节

上述代码,其实也不难理解。

  1. 解析@FeignClient接口声明的方法,根据不同方法绑定不同的处理器。

    1. 默认方法,绑定DefaultMethodHandler
    2. 远程方法,绑定SynchronousMethodHandler
  2. 使用JDK提供的Proxy创建动态代理

MethodHandler,会把方法参数、方法返回值、参数集合、请求类型、请求路径进行解析存储,如下图所示。

  1. 被声明为@FeignClient注解的类,在被注入时,最终会生成一个动态代理对象FeignInvocationHandler

    当触发方法调用时,会被FeignInvocationHandler#invoke拦截,FeignClientFactoryBean在实例化过程中所做的事情如下图所示。

总结来说就几个点:

  1. 解析Feign的上下文配置,针对当前的服务实例构建容器上下文并返回Feign对象
  2. Feign根据上下围配置把 log、encode、decoder、等配置项设置到Feign对象中
  3. 对目标服务,使用LoadBalance以及Hystrix进行包装
  4. 通过Contract协议,把FeignClient接口的声明,解析成MethodHandler
  5. 遍历MethodHandler列表,针对需要远程通信的方法,设置SynchronousMethodHandler处理器,用来实现同步远程调用。
  6. 使用JDK中的动态代理机制构建动态代理对象。

3. OpenFeign调用过程

OpenFeign调用过程图示:


在前面的分析中,我们知道OpenFeign最终返回的是一个 ReflectiveFeign.FeignInvocationHandler 的对象。那么当客户端发起请求时,会进入到 FeignInvocationHandler.invoke 方法中,这个大家都知道,它是一个动态代理的实现。

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if ("equals".equals(method.getName())) {
        try {
          Object otherHandler =
              args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
          return equals(otherHandler);
        } catch (IllegalArgumentException e) {
          return false;
        }
      } else if ("hashCode".equals(method.getName())) {
        return hashCode();
      } else if ("toString".equals(method.getName())) {
        return toString();
      }
      // todo 利用分发器筛选方法,找到对应的handler 进行处理
      return dispatch.get(method).invoke(args);
}
复制代码

而接着,在invoke方法中,会调用 this.dispatch.get(method)).invoke(args) this.dispatch.get(method) 会返回一个SynchronousMethodHandler,进行拦截处理。这个方法会根据参数生成完成的RequestTemplate对象,这个对象是Http请求的模版,代码如下。

@Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        // todo
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        try {
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
}
复制代码

  经过上述的代码,我们已经将restTemplate拼装完成,上面的代码中有一个 executeAndDecode() 方法,该方法通过RequestTemplate生成Request请求对象,然后利用Http Client获取response,来获取响应信息。

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    //转化为Http请求报文
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }
     
    Response response;
    long start = System.nanoTime();
    try {
      //发起远程通信
      response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 12
      //获取返回结果
     
      response = response.toBuilder()
          .request(request)
          .requestTemplate(template)
          .build();
    } catch (IOException e) {
    // .......

}
复制代码

  经过上面的分析,这里的 client.execute client 的类型是LoadBalancerFeignClient,这里就很自然的进入 LoadBalancerFeignClient#execute

public Response execute(Request request, Request.Options options) throws IOException {
        try {
            URI asUri = URI.create(request.url());
            String clientName = asUri.getHost();
            URI uriWithoutHost = cleanUrl(request.url(), clientName);
            FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
                    this.delegate, request, uriWithoutHost);

            IClientConfig requestConfig = getClientConfig(options, clientName);
            return lbClient(clientName)
                    .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
        }
        catch (ClientException e) {
            IOException io = findIOException(e);
            if (io != null) {
                throw io;
            }
            throw new RuntimeException(e);
        }

}
复制代码

  其实这个execute里面得流程就是 Ribbon 的那一套。我们可以简单的看一下。首先是构造URI,构造RibbonRequest,选择 LoadBalance,发起调用。

  来看一下lbClient 选择负载均衡器的时候做了什么

public FeignLoadBalancer create(String clientName) {
        FeignLoadBalancer client = this.cache.get(clientName);
        if (client != null) {
            return client;
        }
        IClientConfig config = this.factory.getClientConfig(clientName);
        ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
        ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
                ServerIntrospector.class);
        client = this.loadBalancedRetryFactory != null
                ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
                        this.loadBalancedRetryFactory)
                : new FeignLoadBalancer(lb, config, serverIntrospector);
        this.cache.put(clientName, client);
        return client;
}
复制代码

  可以得出的结论就是 this.factory.getLoadBalancer(clientName)Ribbon 源码里的获取方式一样,无疑这里获取的就是默认的 ZoneAwareLoadBalancer。然后包装成一个 FeignLoadBalancer 进行返回

  既然负载均衡器选择完了,那么一定还有个地方通过该负载去选择一个服务,接着往下看:

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
        LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

        try {
            return command.submit(
                new ServerOperation<T>() {
                    @Override
                    public Observable<T> call(Server server) {
                        URI finalUri = reconstructURIWithServer(server, request.getUri());
                        S requestForServer = (S) request.replaceUri(finalUri);
                        try {
                            return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                        } 
                        catch (Exception e) {
                            return Observable.error(e);
                        }
                    }
                })
                .toBlocking()
                .single();
        } catch (Exception e) {
            Throwable t = e.getCause();
            if (t instanceof ClientException) {
                throw (ClientException) t;
            } else {
                throw new ClientException(e);
            }
        }

}
复制代码

  上面这段代码就是通过获取到的负载进行执行请求,但是这个时候 服务还没有选择,我们跟进去 submit 请求看一看究竟:

public Observable<T> submit(final ServerOperation<T> operation) {
        final ExecutionInfoContext context = new ExecutionInfoContext();
        // .........
        Observable<T> o = 
                (server == null ? selectServer() : Observable.just(server))
                .concatMap(new Func1<Server, Observable<T>>() {
                    //........
                });
       // .......

}
复制代码

  可以看到这里有个 selectServer的方法 ,跟进去:

public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
        String host = null;
        int port = -1;
        if (original != null) {
            host = original.getHost();
        }
        if (original != null) {
            Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original);        
            port = schemeAndPort.second();
        }

    // Various Supported Cases
    // The loadbalancer to use and the instances it has is based on how it was registered
    // In each of these cases, the client might come in using Full Url or Partial URL
    ILoadBalancer lb = getLoadBalancer();
    if (host == null) {
        // ............
    } else {
        // ...........if (shouldInterpretAsVip) {
            Server svc = lb.chooseServer(loadBalancerKey);
            if (svc != null){
                host = svc.getHost();
                if (host == null){
                    throw new ClientException(ClientException.ErrorType.GENERAL,
                            "Invalid Server for :" + svc);
                }
                logger.debug("using LB returned Server: {} for request: {}", svc, original);
                return svc;
            } else {
                // just fall back as real DNS
                logger.debug("{}:{} assumed to be a valid VIP address or exists in the DNS", host, port);
            }
        } else {
            // consult LB to obtain vipAddress backed instance given full URL
            //Full URL execute request - where url!=vipAddress
            logger.debug("Using full URL passed in by caller (not using load balancer): {}", original);
        }
    }
    // ..........
    return new Server(host, port);
}
复制代码

  可以看到的是这里获取到了之前构造好的 ZoneAwareLoadBalancer 然后调用 chooseServer 方法获取server ,这个是跟Ribbon 中是一样的流程,这里就不赘述了。

  获取到了server 后,会回调先前 executeWithLoadBalancer 方法里构造的 ServerOperation 的 call 方法:

return command.submit(
                new ServerOperation<T>() {
                    @Override
                    public Observable<T> call(Server server) {
                        URI finalUri = reconstructURIWithServer(server, request.getUri());
                        S requestForServer = (S) request.replaceUri(finalUri);
                        try {
                            return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                        } 
                        catch (Exception e) {
                            return Observable.error(e);
                        }
                    }
                })
                .toBlocking()
                .single();
复制代码

  然后会执行 AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig) 进行最后的调用,实际上这里走的是 FeignLoadBalancer#execute

@Override
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
            throws IOException {
        Request.Options options;
        if (configOverride != null) {
            RibbonProperties override = RibbonProperties.from(configOverride);
            options = new Request.Options(override.connectTimeout(this.connectTimeout),
                    override.readTimeout(this.readTimeout));
        }
        else {
            options = new Request.Options(this.connectTimeout, this.readTimeout);
        }
        Response response = request.client().execute(request.toRequest(), options);
        return new RibbonResponse(request.getUri(), response);
}
复制代码

  而这里调用的 request.client().execute(request.toRequest(), options) 则是 DefaultFeignLoadBalancedConfiguration 注入的 LoadBalancerFeignClient ,在构造 LoadBalancerFeignClient 的时候 ,传递了个 feign.Client.Default ,然后利用 feign.Client.Default 构造了一个 RibbonRequest

  所以这里走 feign.Client.Default#execute

@Override
public Response execute(Request request, Options options) throws IOException {
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection, request);
}
复制代码

  利用 JDK 提供的 HttpURLConnection 发起远程的 HTTP通讯。至此发起请求的流程就完成了。下面附上一张这个过程的流程图,对于Ribbon的调用过程请参考 :Ribbon 源码分析。

4. 总结

Spring Cloud OpenFeign 的核心工作原理经上文探究可以非常简单的总结为:

  1. 通过 @EnableFeignCleints 触发 Spring 应用程序对 classpath@FeignClient 修饰类的扫描
  2. 解析到 @FeignClient 修饰类后, Feign 框架通过扩展 Spring Bean Deifinition 的注册逻辑, 最终注册一个 FeignClientFacotoryBean 进入 Spring 容器
  3. Spring 容器在初始化其他用到 @FeignClient 接口的类时, 获得的是 FeignClientFacotryBean 产生的一个代理对象 Proxy.
  4. 基于 java 原生的动态代理机制, 针对 Proxy 的调用, 都会被统一转发给 Feign 框架所定义的一个 InvocationHandler , 由该 Handler 完成后续的 HTTP 转换, 发送, 接收, 翻译HTTP响应的工作。

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

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

相关文章

深入理解java虚拟机:虚拟机类加载机制(1)

文章目录1. 类加载的时机2. 类加载的过程2.1 加载2.2 验证2.3 准备2.4 解析2.5 初始化1. 类加载的时机 类从被加载到虚拟机内存中开始&#xff0c;到卸载出内存为止&#xff0c;它的整个生命周期包括了&#xff1a; 加载&#xff08;Loading&#xff09;验证&#xff08;Verif…

计算机网络-应用层(文件传输协议(FTP协议),电子邮件系统(SMTP协议,MIME,POP3,IMAP协议))

文章目录1. 文件传输协议2. 电子邮件系统1. 文件传输协议 文件传输协议&#xff1a; 文件传送协议FTP&#xff1a;提供不同种类主机系统&#xff08;硬、软件体系等都可以不同&#xff09;之间的文件传输能力。简单文件传送协议TFTP&#xff1a;使用于UDP环境&#xff0c;代码…

MyBatis-Plus入门案例

MybatisPlus&#xff08;简称MP&#xff09;是基于MyBatis框架基础上开发的增强型工具&#xff0c;旨在简化开发、提供效率。 开发方式 基于MyBatis使用MyBatisPlus基于Spring使用MyBatisPlus基于SpringBoot使用MyBatisPlus SpringBoot它能快速构建Spring开发环境用以整合其…

设计模式之美——看似面向对象,实则面向过程

常见的编程范式或者说编程风格有三种&#xff0c;面向过程编程、面向对象编程、函数式编程&#xff0c;而面向对象编程又是这其中最主流的编程范式。现如今&#xff0c;大部分编程语言都是面向对象编程语言&#xff0c;大部分软件都是基于面向对象编程这种编程范式来开发的。 …

《Redis设计与实现》笔记

第二章&#xff1a;简单动态字符串 1.Redis没有直接使用C语言传统的字符串表示(以空字符结尾的字符数组,以下简称C字符串),而是自己构建了一种名为简单动态字符串( simple dynamic string,SDS)的抽象类型,并将SDS用作 Redis的默认字符串表示。 Redis里面,C字符串只会作为字符串…

操作系统第三章习题及答案(汤子瀛第四版)

第三章 1&#xff0e;高级调度与低级调度的主要任务是什么&#xff1f;为什么要引入中级调度&#xff1f; 答&#xff1a;高级调度的主要任务是根据某种算法&#xff0c;把外存上处于后备队列中的那些作业调入内存。低级调度是保存处理机的现场信息&#xff0c;按某种算法先取…

【UE4】打包失败 Failed to build UATTempProj.proj

我抄我自己之 https://zhuanlan.zhihu.com/p/586117443 前两天编 UE4.27 源码内存和CPU直接全是100%爆满&#xff0c;甚至还报错&#xff1a;c1060, the compiler is out of heap&#xff0c;设置虚拟内存也不不好使&#xff0c;发现16GB内存不配编 UE&#xff0c;火速换了64G之…

Kotlin或将超越Go?

知名软件行业分析公司 RedMonk 发布了 2022 年 6 月&#xff08;第三季度&#xff09;编程语言排行榜。 RedMonk 编程语言排行榜通过追踪编程语言在 GitHub 和 Stack Overflow 上的代码使用情况与讨论数量&#xff0c;统计分析后进行排序&#xff0c;其旨在深入了解潜在的语言…

你真的知道Spring Security安全框架吗?

1、什么是安全管理框架&#xff1f; 解决系统安全问题的框架。如果没有安全框架&#xff0c;我们需要手动处理每个资源的访问控制&#xff0c;非常麻烦。使 用安全框架&#xff0c;我们可以通过配置的方式实现对资源的访问限制。 安全框架&#xff0c;简单说是对访问权限进行控…

Android Material Design之MaterialButton(一)

按规矩先上效果图 资源引入 implementation com.google.android.material:material:1.4.0关键属性 属性描述app:backgroundTint背景着色app:backgroundTintMode着色模式app:strokeColor描边颜色app:strokeWidth描边宽度app:cornerRadius圆角大小app:rippleColor按压水波纹颜色…

【Java八股文总结】之计算机网络

文章目录计算机网络一、基础1、网络体系结构2、HTTP协议、TCP协议、UDP协议比较3、网络协议4、WebSocket和Socket的区别&#xff1f;5、常见的端口及其对应的服务&#xff1f;6、从浏览器输入URL到页面展示发生了什么&#xff1f;&#xff08;★★★★★&#xff09;1、DNS域名…

MySQL纯代码复习(下)

前言 本文章的语言描述会比上篇多一些 数据库的创建修改与删除 标识符命名规则 数据库名、表名不得超过30个字符&#xff0c;变量限制为29个必须只能包含A-Z&#xff0c;a-z&#xff0c;0-9&#xff0c;_等63个字符数据库名、表名、字段名等对象名中间不要包含空格同一个My…

Twitter引流如何开发客户

要想在twitter平台上取得效果&#xff0c;你需要先了解twitter的算法规则&#xff0c;去迎合平台&#xff0c;推特群推王给出以下5条建议&#xff0c;让你发布的帖子更容易被推荐。 这里Twitter群推王可以给大家讲一下&#xff0c;关于推特平台的算法排名&#xff1a; Twitter…

nginx降权+安装php

nginx降权 使用普通用户启动Nginx 为什么要让nginx服务使用普通用户 默认情况下&#xff0c;nginx的master进程使用的是root用户&#xff0c;worker进程使用的是nginx指定的普通用户&#xff0c;使用root用户跑nginx的master进程有两个大问题&#xff1a; &#xff08;1&#x…

课程设计-天天象棋作弊软件判别

目录 1.作弊开挂可能迹象 2.设计作弊检测系统灵感 3.设计作弊检测系统思路 3.1反作弊系统应对策略框架 4.感想体悟 1.作弊开挂可能迹象 1.非实名认证&#xff1b; 2.头像&#xff1a;美女&#xff1b; 3.名称&#xff1a; (1)一串英文字母&#xff1b; (2)非正常中文名…

【深度分解网络:显著性分析:IVIF】

Infrared and Visible Image Fusion Based on Deep Decomposition Network and Saliency Analysis &#xff08;基于深度分解网络和显著性分析的红外与可见光图像融合&#xff09; 传统的图像融合侧重于选择一种有效的分解方法从源图像中提取代表性特征&#xff0c;并试图找到…

2022年海运行业研究报告

第一章 行业概况 海洋运输又称“国际海洋运输”&#xff0c;提供海上客运或者货运服务的行业。是国际物流中最主要的运输方式。它是指使用船舶通过海上航道在不同国家和地区的港口之间运送货物的一种方式&#xff0c;在国际货物运输中使用最广泛。国际贸易总运量中的2/3以上&a…

功率放大器和电压放大器的区别是什么意思

很多人经常会在后台咨询小编功率放大器和电压放大器的区别有哪些&#xff1f;今天就来为大家科普一下功率放大器和电压放大器的知识内容&#xff0c;希望大家下次能够区分&#xff0c;并且可以正常地选择和使用功率放大器。 图&#xff1a;功率放大电路与电压放大电路对比 功率…

Opengl ES之YUV数据渲染

YUV回顾 记得在音视频基础知识介绍中&#xff0c;笔者专门介绍过YUV的相关知识&#xff0c;可以参考&#xff1a; 《音视频基础知识-YUV图像》 YUV数据量相比RGB较小&#xff0c;因此YUV适用于传输&#xff0c;但是YUV图不能直接用于显示&#xff0c;需要转换为RGB格式才能显…

简单的股票行情演示(一) - 实时标的数据

一、概述二、效果展示三、实现代码 1、行情数据中心2、数据拉取模块3、基础服务模块4、UI展示四、相关文章原文链接&#xff1a;简单的股票行情演示&#xff08;一&#xff09; - 实时标的数据 一、概述 很长一段时间都有一个想法&#xff0c;使用QCP去做一个行情展示小事例&…