feign远程调用原理

目录

一、简介

二、调用流程分析

2.1 添加注解

 2.2 @Import(FeignClientsRegistrar.class),

2.3 代理创建流程

2.4 代理调用


一、简介

        feign是springCloud全家桶中的远程调用组件,其底层主要依赖于Java的动态代理机制,然后基于http client进行http请求,同时它还能配合其它组件实现Loadbalance(负载均衡)、Hystrix(熔断)、fallback(降级)等功能。

二、调用流程分析

 

2.1 添加注解

在application启动类上添加@EnableFeignClients注解,并在basePackages属性中添加FeignClient所在的包,使Spring容器能够扫描到所有的FeignClient对象,这样就可以开始使用feign的远程调用功能了,EnableFeignClients包含以下属性,常用的是basePackages。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

	/**
	 * basePackages() 属性的别名
	 */
	String[] value() default {};

	/**
	 * 注解扫描包路径集
	 */
	String[] basePackages() default {};

	/**
	 * basePackages的替代值,注解扫描类所在的每个包,
     * 若使用此属性,会在每个feign类所在包下建立一个特殊意义的空类
	 */
	Class[] basePackageClasses() default {};

	/**
	 * 针对所有client生效的注解类集
	 */
	Class[] defaultConfiguration() default {};

	/**
	 * 标注有@FeignClient注解的类集合,若不为空,则禁止路径扫描
	 * @return
	 */
	Class[] clients() default {};
}

 2.2 @Import(FeignClientsRegistrar.class),

        将feign相关对象注入到容器中,导入了 FeignClientsRegistrar注册器,该类实现了spring提供的ImportBeanDefinitionRegistrar接口,能够在registerBeanDefinitions方法中注册自定义的bean。同时也实现了ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware等钩子接口,能够持有spring的环境、资源等变量,方便进行包扫描。

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
		ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {

	private ResourceLoader resourceLoader;
	private ClassLoader classLoader;
	private Environment environment;

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        //从@EnableFeignClients注解中提取defaultConfiguration属性配置的key和value,并将配置类注册至spring容器
		registerDefaultConfiguration(metadata, registry);
        //扫描所有的feignclient类,注册进spring容器
		registerFeignClients(metadata, registry);
	}
}

        registerFeignClients方法:注册所有feign逻辑

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set basePackages;

		Map attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);        
		final Class[] clients = attrs == null ? null
				: (Class[]) attrs.get("clients");
        //若clients为空,则进行包扫描
		if (clients == null || clients.length == 0) {
            //添加注解过滤条件
			scanner.addIncludeFilter(annotationTypeFilter);
			basePackages = getBasePackages(metadata);
		}else {
        //不为空直接注入
			final Set 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)));
		}
        //遍历包路径集
		for (String basePackage : basePackages) {
            //scanner扫描所有符合要求的bean
			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();
                    //校验是否为接口,若不为则抛异常,@FeignClient只能在接口上使用
					Assert.isTrue(annotationMetadata.isInterface(),
							"@FeignClient can only be specified on an interface");

					Map attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());

					String name = getClientName(attributes);
                    //添加独有配置
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));
                    //注册FeignClient
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

        registerFeignClient方法:实际注册单个FeignClient的逻辑

private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map attributes) {
		String className = annotationMetadata.getClassName();
        //获取FeignClientFactoryBean的BeanDefinitionBuilder
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
        //属性校验
		validate(attributes);
        //bean属性添加
		definition.addPropertyValue("url", getUrl(attributes));
		definition.addPropertyValue("path", getPath(attributes));
		String name = getName(attributes);
		definition.addPropertyValue("name", name);
        //class类型
		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 });
        //注册BeanDefinition
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}

        到这里,我们所有的feignClient bean对象就已经被注入到spring容器中了,就可以正常使用feignClient接口去远程调用了。

2.3 代理创建流程

        FeignClientFactoryBean工厂类,用于提供FeignClient实例,其关键方法如下:

    public Object getObject() throws Exception {
        //获取feign的上下文
		FeignContext context = applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);
        //若未指定url,则根据名称去构建feignClient,且具备负载均衡能力
		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));
		}
        //若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 lod balancing because we have a url,
				// but ribbon is on the classpath, so unwrap
                // 解除负载均衡能力
				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));
	}

        真正的获取方法其实是targeter.target()方法,targeter包含两个实现:

  • DefaultTargeter:默认实现,直接调用Feign.Builder的target方法;
  • HystrixTargeter:具备限流熔断机制,若builder不属于HystrixFeign类型,则直接调用Feign.Builder的target方法。若属于,则将builder强转为feign.hystrix.HystrixFeign.Builder类型,然后按顺序调用factory.getFallback()或factory.getFallbackFactory(),根据fallback或fallbackFactory是否为void.class,则调用targetWithFallback或targetWithFallbackFactory构建对象。若都为void.class,则直接调用Feign.Builder的target方法。
	@Override
	public  T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
						Target.HardCodedTarget target) {
		if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
			return feign.target(target);
		}
		feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
		SetterFactory setterFactory = getOptional(factory.getName(), context,
			SetterFactory.class);
		if (setterFactory != null) {
			builder.setterFactory(setterFactory);
		}
		Class fallback = factory.getFallback();
		if (fallback != void.class) {
			return targetWithFallback(factory.getName(), context, target, builder, fallback);
		}
		Class fallbackFactory = factory.getFallbackFactory();
		if (fallbackFactory != void.class) {
			return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
		}

		return feign.target(target);
	}

	private  T targetWithFallbackFactory(String feignClientName, FeignContext context,
											Target.HardCodedTarget target,
											HystrixFeign.Builder builder,
											Class fallbackFactoryClass) {
		FallbackFactory fallbackFactory = (FallbackFactory)
			getFromContext("fallbackFactory", feignClientName, context, fallbackFactoryClass, FallbackFactory.class);
		/* We take a sample fallback from the fallback factory to check if it returns a fallback
		that is compatible with the annotated feign interface. */
		Object exampleFallback = fallbackFactory.create(new RuntimeException());
		Assert.notNull(exampleFallback,
			String.format(
			"Incompatible fallbackFactory instance for feign client %s. Factory may not produce null!",
				feignClientName));
		if (!target.type().isAssignableFrom(exampleFallback.getClass())) {
			throw new IllegalStateException(
				String.format(
					"Incompatible fallbackFactory instance for feign client %s. Factory produces instances of '%s', but should produce instances of '%s'",
					feignClientName, exampleFallback.getClass(), target.type()));
		}
		return builder.target(target, fallbackFactory);
	}


	private  T targetWithFallback(String feignClientName, FeignContext context,
									 Target.HardCodedTarget target,
									 HystrixFeign.Builder builder, Class fallback) {
		T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());
		return builder.target(target, fallbackInstance);
	}

        翻看源码调用链,会发现不管是HystrixFeign.Builder的target方法还是Feign.Builder的target方法,最终都会调用Feign接口的newInstance方法,该方法默认实现在ReflectiveFeign类中,这个里面就能看到熟悉的Proxy,也就是jdk动态代理

/**
 * 创建绑定到目标的 API。由于这会调用反射,因此应注意缓存结果。
 */
@Override
  public  T newInstance(Target target) {
    //创建configKey→SynchronousMethodHandler的映射,基本上是SynchronousMethodHandler,主要用于处理用户自定义的方法
    Map nameToHandler = targetToHandlersByName.apply(target);
    //存储处理用户定义的FeignClient接口中的default方法的handler
    Map methodToHandler = new LinkedHashMap();
    List defaultMethodHandlers = new LinkedList();
    //遍历代理对象中的所有方法
    for (Method method : target.type().getMethods()) {
      //如果是Object中的方法,跳过不处理
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if(Util.isDefault(method)) {
        //如果是default方法,则创建DefaultMethodHandler处理
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        //用户自定义的方法了,此时从nameToHandler中拿出SynchronousMethodHandler进行映射
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    //创建InvocationHandler 核心代理对象,代理逻辑都封装在该对象中
    InvocationHandler handler = factory.create(target, methodToHandler);
    //jdk动态代理
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);

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

2.4 代理调用

        远程代理调用核心逻辑在FeignInvocationHandler类的invoke方法中,首先判断是否为equals、hashcode或toString方法,是则直接调用target对象的这些方法。若不是,则根据创建过程中的configKey→MethodHandler的映射获取MethodHandler进行处理。

    @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();
      }
      return dispatch.get(method).invoke(args);
    }

default方法:

  @Override
  public Object invoke(Object[] argv) throws Throwable {
    if(handle == null) {
      throw new IllegalStateException("Default method handler invoked before proxy has been bound.");
    }
    return handle.invokeWithArguments(argv);
  }

 自定义方法:核心逻辑,包含远程调用及重试机制。其中executeAndDecode会执行远程的http调用,同时也会进行http报文的解析,而catch块中会利用retryer进行重试。

  @Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template);
      } catch (RetryableException e) {
        retryer.continueOrPropagate(e);
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }
  Object executeAndDecode(RequestTemplate template) throws Throwable {
    //构造请求对象
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
      //client执行http远程调用,client可以是Apache HttpClient或者Feign封装的具有负载均衡能力的FeignBlockingLoadBalancerClient
    //或者RetryableFeignBlockingLoadBalancerClient,
    //但这两个client的execute()方法底层最终会调用其中的delegate(即Apache HttpClient)执行http远程调用
      response = client.execute(request, options);
      // 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();
      }
      if (Response.class == metadata.returnType()) {
        if (response.body() == null) {
          return response;
        }
        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());
      }
    }
  }

重试机制:retryer.continueOrPropagate(e)

   public void continueOrPropagate(RetryableException e) {
      //重试次数
      if (attempt++ >= maxAttempts) {
        throw e;
      }

      //计算等待时间间隔
      long interval;
      if (e.retryAfter() != null) {
        interval = e.retryAfter().getTime() - currentTimeMillis();
        if (interval > maxPeriod) {
          interval = maxPeriod;
        }
        if (interval < 0) {
          return;
        }
      } else {
        interval = nextMaxInterval();
      }
      try {
        Thread.sleep(interval);
      } catch (InterruptedException ignored) {
        Thread.currentThread().interrupt();
      }
      //总等待时长
      sleptForMillis += interval;
    }

你可能感兴趣的:(Java,spring,java,开发语言,spring,cloud)