Interview preparation-- Feign源码分析

Feign 源码分析

  • 当我们用FeignClient来坐微服务之间的服务调用时候,我们需要实现如下的一个FeignClient的接口
@FeignClient(name = "zaip-pms-decisioncalc-service",configuration = DecisioncalcConfiguration.class)
public interface StockFeignClient {
    @GetMapping("/stock/reduce/{produceId}")
    String reduce(@PathVariable("produceId") String produceId);
}

  • 如上代码中:
    • 我们通过注解@FeignClient可以得到服务名称 zip-pms-decisioncalc-service
    • 通过SpringMVC注解@GetMapping可用得到他是发送一个Get请求,路径是/stock/reduce/{produceId}
    • 通过解析他的参数,我们就能得到最终要请求的一个地址信息如下:http://zaip-pms-decisioncalc-service/stock/reduce/{produceId}
    • 那FeignClient的作用就是将以上这个interface的定义转化成如上的请求连接,并且替换成IP+port的请求方式,并且完成http请求,那么我们从Feign的源码入手来分析他是如何做到这一点的
找入口
  • 在使用FeignClient的时候,必不可少的注解是@EnableFeignClients,这个注解作用是启动我们的Feign,如下代码
@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 {};
}
  • 如上源码中,他利用了@Import(FeignClientsRegistrar.class) 来注入FeignClientsRegistrar
  • @Import的作用参考如下链接:https://blog.csdn.net/liaojiamin0102/article/details/129443392
  • @Import的作用是导入三种类似,分别如下:
    • {@link Configuration @Configuration}
    • {@link ImportSelector} 他必须是SelectImpot 的实现类,返回的数组,批量加载类,SpringBoot自动装配的原理
    • {@link ImportBeanDefinitionRegistrar},他必须是ImportBeanDefinitionRegistrar 的实现类,所以他需要实现ImportBeanDefinitionRegistrar 中的方法。registerBeanDefinitions(注册bean)
Feign 注册代理类Bean流程
  • 通过registerBeanDefinitions 方法为入口,我们找到如下代码:
@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		registerDefaultConfiguration(metadata, registry);
		registerFeignClients(metadata, registry);
	}
  • 关键入口是。registerFeignClients,他是注册Feign代理类Bean的入口,FeignClient修饰的都是接口,目的是将接口转换成相应的BeanDefinition并且注册到Spring容器中
  • 流程如下:
    • 首先通过getScanner()来获取一个扫描器,他会遍历你配置的类路径(没有配置默认用当前类路径)
    • 通过scanner的过滤器来实现对应目录下每一个类的扫描,代码如下:
 scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
  • 如上代码:
    • scanner扫描器目的是将带上了@FeignClient注解的所有类筛选出来得到一个数组LinkedHashSet candidateComponents
    • 接着利用得到的元数据信息进行注册:registerFeignClient(registry, annotationMetadata, attributes);
    • 得到元数据后,进行Bean注册但是我们FeignClient修饰的都是Interface,接口无法直接实例化因此无法直接注入到Spring容器中,因此我们需要创建一个代理对象来替代当前的Interface,并且将代理对象注入到Spring容器中
代理对象创建
第一步获取创建代理类需要的上下文信息(可以认为是Bean属性)
  • 要创建一个Bean对象注入到Spring容器中,一般有两种方式:
    • 第一种通过BeanFactory,通过他创建SpringBean对象需要严格遵循Bean创建的生命周期流程,太复杂
    • 第二种通过FactoryBean,他是一个接口,可以通过实现接口中的getObject方法来完成自定义对象创建
private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		.......
		FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
		.......
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(clazz, () -> {
					.......
					return factoryBean.getObject();
				});
		.......
		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				qualifiers);
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}
  • FeignClien他使用的是第二种,在registerFeignClient方法中,初始化了一个FeignClientFactoryBean,在自定义getObject方法中,他给每一个FeignClient接口生成个了一个Spring上下文信息AnnotationConfigApplicationContext,
  • 创建AnnotationConfigApplicationContext所需要的组建是Feign组建的Spring上下文中获取此处是FeignClientsConfiguration他定义了Feign对象创建必要的信息
  • 创建之后的对象放入Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap()中,key就是服务名:zaip-pms-decisioncalc-service value就是创建的AnnotationConfigApplicationContext Spring上下文信息。
第二部通过上下文信息创建代理类
  • getObject方法中,通过loadBanance方法入口创建代理类
......
return (T) loadBalance(builder, context,
					new HardCodedTarget<>(type, name, url));
  • 创建代理类:
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;
  }
  • 通过newInstance 方法中factory.create创建了一个FeignInvocationHandler,他是JDK动态代理中InvocationHandler 的实现类
  • 利用JDK的动态代理技术生成一个代理类proxxy,这个代理类就是我们需要注入到Spring容器中
  • 之后返回到最初定义FactorBean的地方,通过BeanDefinitionReaderUtils.registerBeanDefinition,将得到的代理类元数据信息注入到Spring容器中
  • 至此,FeignClient 修饰的接口,对象创建注入的过程就结束了
FeignClient代理类调用
  • 在调用Feign请求的时候,其实就是调用JDK动态代理生成的代理类proxy,那么他一定会调用代理类中的invoke方法,也就是FeignInvocationHandler.invoke方法
  • 最终会调用SynchronousMethodHandler 类的invoke方法,在方法中执行最终的HTTP调用
    • 第一步,executeAndDecode(template, options);
    • 第二步,client.execute(request, options);
    • 第三步,lbClient(clientName) 通过Ribbon选择一个服务
    • 第四步,AbstractLoadBalancerAwareClient.this.reconstructURIWithServer(server, request.getUri());通过Ribbon重构URI,将之前服务名修改为IP+port的形式,如下图
    • 第五步,发起Http请求:Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
    • 第六步:FeignLoadBalancer.execute 利用HttpUrlConnection发起http请求

在这里插入图片描述

public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        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;
      }
    }
  }
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值