计算机系统应用教程网站

网站首页 > 技术文章 正文

Spring Cloud OpenFeign源码FeignClientFactoryBean原理

btikc 2024-09-10 12:02:00 技术文章 18 ℃ 0 评论


前言:

在上一篇文章Spring Cloud OpenFeign源码加载原理

我谈到registerFeignClients方法注册的bean是FeignClientFactoryBean类型,此时的Bean只是注册到容器中尚未实例化,这篇文章我们来看看FeignClientFactoryBean实例化都发生了什么事情。

源码分析:

我们先看看FeignClientFactoryBean的类图结构:

可以看出FeignClientFactoryBean实现了三个接口,分别是FactoryBean、InitializingBean、ApplicationContextAware,实现了FactoryBean接口意味着在实例化时候会调用getObject方法一个定义的对象而不是注册到容器中的Bean类型;实现了InitializingBean接口意味着Bean属性设置完成以后会调用afterPropertiesSet方法进行一些业务逻辑;实现了ApplicationContextAware表示Spring容器会给该对象注入ApplicationContext对象,即容器对象。

那么我们重点关注FeignClientFactoryBean的getObject方法,该方法的代码如下:


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

可以看出该getObject方法又调用了getTarget方法,该方法的代码如下:

<T> T getTarget() {
   FeignContext context = this.applicationContext.getBean(FeignContext.class);
   Feign.Builder builder = feign(context);

   if (!StringUtils.hasText(this.url)) {
      if (!this.name.startsWith("http")) {
         this.url = "http://" + this.name;
      }
      else {
         this.url = this.name;
      }
      this.url += cleanPath();
      return (T) loadBalance(builder, context,
            new HardCodedTarget<>(this.type, this.name, this.url));
   }
   if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
      this.url = "http://" + this.url;
   }
   String url = this.url + cleanPath();
   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);
   }
   Targeter targeter = get(context, Targeter.class);
   return (T) targeter.target(this, builder, context,
         new HardCodedTarget<>(this.type, this.name, url));
}

首先从容器中获取FeignContext对象,这个对象又是什么时候加载到Spring容器中的那?

它是在引入了OpenFeign的Pom依赖以后自动引入了openfegin-core包的jar包,在该jar包的spring.factories文件中引入了一个自动配置类是FeignAutoConfiguration,在该自动配置类中又@Autowired List<FeignClientSpecification>和导入一个FeignContext的Bean对象,截图如下:

可以看出FeignContext继承了NamedContextFactory类,FeignClientSpecification实现了NamedContextFactory.Specification接口,这两个类的设计和Ribbon源码的设计理念是一样的,关于这两个类是做什么的,可以观看我的文章SpringBoot整合Ribbon源码分析之核心组件加载原理

我们继续回到getTarget方法中调用了feign方法,该方法的代码如下:

protected Feign.Builder feign(FeignContext context) {
   FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
   Logger logger = loggerFactory.create(this.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));
   // @formatter:on

   configureFeign(context, builder);

   return builder;
}

这里面又调用了一个get方法,get方法主要是从FeignContext子容器中取出是FeignLoggerFactory类型的类,这个是在org.springframework.cloud.openfeign.FeignClientsConfiguration中配置的,默认实现是DefaultFeignLoggerFactory,接着调用该对象的create方法创建一个Logger,这个Logger是Feign自己定义的而不是Slf4j的。

接着又从FeignContext子容器中取出是Feign.Builder类型的类,这些Bean同样是org.springframework.cloud.openfeign.FeignClientsConfiguration中配置的,在这个Feign内部类的Build类里面有几个重要的成员属性,它们分别是:

1、List<RequestInterceptor>

这个RequestInterceptor类看名字可以猜测它是一个拦截器,主要是远程请求Feign时候添加一些请求头参数用的,它没有默认实现,需要我们自己往容器中添加。

2、Encoder encoder

这个类负责编码一个Object对象到一个Http请求识别的类型,例如把Object转成Byte,json等等,默认实现是PageableSpringEncoder。

3、Decoder decoder

这个类负责解码Feign远程调用返回的Response对象,把它解码成指定的Object对象,默认实现是OptionalDecoder。

4、boolean decode404

是否处理404的标识,默认是false。

接着调用configureFeign方法,该方法主要是看用户是否配置了一下自定义的组件,如果设置了就把自定义的组件加载到Feign.Builder类上,这个方法就不分析了,感兴趣的可以自己看看这个方法。

我们继续看getTarget方法,往下执行获取到url,是拼接上http:,我这个例子完整的url是http://exerice,接着创建一个HardCodedTarget对象,该对象的参数主要有Class<T> type、String name、String url,type是标有@FeignClient接口类,name是@FeignClient注解的value属性值,url是完整的http://exerice。

接着调用loadBalance方法,该方法的代码如下:

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
      HardCodedTarget<T> target) {
   Client client = getOptional(context, Client.class);
   if (client != null) {
      builder.client(client);
      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实现了,实现类是LoadBalancerFeignClient,把LoadBalancerFeignClient赋值到Feign.Builder对象属性中,在从容器中获取类型为Targeter的实现类是HystrixTargeter,然后调用其target方法,该方法的代码如下:

@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
      FeignContext context, Target.HardCodedTarget<T> target) {
   if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
      return feign.target(target);
   }
   feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
   String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
         : factory.getContextId();
   SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
   if (setterFactory != null) {
      builder.setterFactory(setterFactory);
   }
   Class<?> fallback = factory.getFallback();
   if (fallback != void.class) {
      return targetWithFallback(name, context, target, builder, fallback);
   }
   Class<?> fallbackFactory = factory.getFallbackFactory();
   if (fallbackFactory != void.class) {
      return targetWithFallbackFactory(name, context, target, builder,
            fallbackFactory);
   }

   return feign.target(target);
}

判断Feign.Builder不是feign.hystrix.HystrixFeign.Builder类型,调用Feign.Builder的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);
  }
}

可以看到build方法里面创建一个SynchronousMethodHandler.Factory对象,然后把Feign.Builder一些client、requestInterceptors属性赋值给该对象的属性,又创建一个ParseHandlersByName对象,把Feign.Builder一些encoder、decoder、SynchronousMethodHandler.Factory等一些属性赋值给ParseHandlersByName对象的属性,然后在创建一个ReflectiveFeign对象,把ParseHandlersByName对象传入到ReflectiveFeign对象的属性。

我们继续看newInstance方法,该方法的代码如下:

public <T> T newInstance(Target<T> target) {
  Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
  Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
  List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

  for (Method method : target.type().getMethods()) {
    if (method.getDeclaringClass() == Object.class) {
      continue;
    } else if (Util.isDefault(method)) {
      DefaultMethodHandler 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);
  T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
      new Class<?>[] {target.type()}, handler);

  for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
    defaultMethodHandler.bindTo(proxy);
  }
  return proxy;
}

首先调用了ParseHandlersByName对象的apply方法,该方法主要是处理Feign客户端接口上的注解,如 @RequestMapping@RequestHeader@RequestParam 等,将它们解析为 Feign 中可用的配置信息,以便在发起 HTTP 请求时使用。由于这个方法代码量很大就不在此做分析了。

接着创建一个数据结构是Map<Method, MethodHandler>的对象,遍历标有@FeginClient注解的接口类的方法,这个方法是通过反射获取的,是java.lang.reflect.Method对象,放入到Map中,key是java.lang.reflect.Method对象对象,value是MethodHandler对象,该对象是通过ParseHandlersByName的apply方法封装而来的。

接着调用feign.InvocationHandlerFactory.Default的create方法,该方法创建了一个FeignInvocationHandler并传入属性Target target、Map<Method, MethodHandler> dispatch,该类的定义如下图所示:

可以看到这个对象实现了InvocationHandler接口,由此可知在生成完动态代理后调用接口方法时候会调用该对象的invoke方法。

最后调用T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),

new Class<?>[] {target.type()}, handler)这段代码生成一个接口的类的代理类返回,至此整个FeignClientFactoryBean的getObject方法就返回了,返回的是接口的代理类。

总结:

FeignClientFactoryBean的getObject方法最终会返回一个接口的代码类,是用的JDK的动态代理方式生成的代理,在生成接口代理类时候最重要的参数是feign.ReflectiveFeign.FeignInvocationHandler,它是实现了InvocationHandler,会调用其invoke方法,这个对象的成员属性Map<Method, MethodHandler> dispatch已经封装了所有的Feign方法,然后当我们请求的时候就可以进行远程调用,下一节我会分析具体的调用源码,好了,我是爱编程的老徐,如果喜欢我的文章请点赞关注,我们下期见。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表