计算机系统应用教程网站

网站首页 > 技术文章 正文

阅读代码深入原理10——Spring Cloud Netflix之Feign

btikc 2024-09-10 12:03:01 技术文章 9 ℃ 0 评论

feign实现远程服务调用,根据接口及接口方法上的注解解析生成代理。

由于我们在启动类上加了EnableFeignClients注解,根据spring的Import注解机制,我们可以发现FeignClientsRegistrar.registerBeanDefinitions会注册FeignClientSpecification(当defaultConfiguration不为null时)和FeignClient。

FeignClient生成代码如下:

# spring-cloud-openfeign-core-2.2.7.RELEASE.jar!/org.springframework.cloud.openfeign.FeignClientsRegistrar
	public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) { // 1. 默认clients为空数组
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			scanner.setResourceLoader(this.resourceLoader);
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
			Set<String> basePackages = getBasePackages(metadata); // 2. EnableFeignClients的value/basePackages/basePackageClasses不为空则作为扫描的基础包,否则取被EnableFeignClients注解的类所在包
			for (String basePackage : basePackages) {
				candidateComponents.addAll(scanner.findCandidateComponents(basePackage)); // 3. 将有FeignClient注解的类生成BeanDefinition
			}
		}
		else {
			for (Class<?> clazz : clients) {
				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
			}
		}
		for (BeanDefinition candidateComponent : candidateComponents) {
			if (candidateComponent instanceof AnnotatedBeanDefinition) {
				// verify annotated class is an interface
				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); // 4. 按照contextId、value、name、serviceId从高到低优先级拿属性值取名(属性支持占位符)
				registerClientConfiguration(registry, name,
						attributes.get("configuration")); // 5. 根据@FeignClient的configuration属性(默认空数组),注册FeignClientSpecification
				// 6. 以FeignClientFactoryBean作为feignClientsRegistrarFactoryBean,接口类型作为factoryBeanObjectType注册BeanDefinition;
				// 如果qualifiers不存在,则以“http:”或“https:”,开头contextId/serviceId/name/value为中间,以“FeignClient”结尾,组合作为bean名称;按类型自动注入。
				registerFeignClient(registry, annotationMetadata, attributes); 
			}
		}
	}

这些FeignClientSpecification会被注入FeignContext作为配置(见FeignAutoConfiguration)。

spring上下文刷新时会触发生成接口对象,由于是FactoryBean形式,可以直接查看FeignClientFactoryBean.getObject代码:

# spring-cloud-openfeign-core-2.2.7.RELEASE.jar!/org.springframework.cloud.openfeign.FeignClientFactoryBean
	@Override
	public Object getObject() {
		return getTarget();
	}
	
	<T> T getTarget() {
		FeignContext context = beanFactory != null
				? beanFactory.getBean(FeignContext.class)
				: applicationContext.getBean(FeignContext.class); // 1. FeignClientFactoryBean实现了ApplicationContextAware、BeanFactoryAware接口, FeignContext在FeignAutoConfiguration定义
		// 2. FeignContext的默认配置为FeignClientsConfiguration,它定义了默认的FeignLoggerFactory、Feign.Builder、Encoder、Decoder、Contract、FeignBuilderCustomizer、FeignClientConfigurer;如果@EnableFeignClients/@EnableFeignClient有自定义配置类也会注册到FeignContext;
		// 从FeignContext获取FeignLoggerFactory、Feign.Builder、Encoder、Decoder、Contract、FeignBuilderCustomizer、FeignClientConfigurer,从ApplicationContext获取FeignClientProperties等,设置Feign.Builder;
		// 默认先根据配置类设置,然后用配置文件的默认属性进行覆盖,最后再用配置文件的特定client属性覆盖;
		// 如果存在FeignBuilderCustomizer,则最后使用FeignBuilderCustomizer.customize(默认不存在)
		Feign.Builder builder = feign(context); 
		if (!StringUtils.hasText(url)) { // 3. 测试直连时可能会设置url,使用客户端负载均衡不要设置
			if (!name.startsWith("http")) {
				url = "http://" + name;
			}
			else {
				url = name;
			}
			url += cleanPath();
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(type, name, url)); // 4. 创建ReflectiveFeign,调用其newInstance方法,返回代理
		}
		if (StringUtils.hasText(url) && !url.startsWith("http")) {
			url = "http://" + 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<>(type, name, url));
	}

target为DefaultTargeter,如果hystrix存在时则为HystrixTargeter。DefaultTargeter只是简单地调用Feign.Builder的target方法,此方法生成ReflectiveFeign,然后生成代理,代码如下:

# feign-core-10.10.1.jar!/feign.ReflectiveFeign
  public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); // 1. 在Feign中创建的ParseHandlersByName,此时调用其apply方法,contract(SpringMvcContract)解析类和方法上的注解信息,使用SynchronousMethodHandler.Factory创建SynchronousMethodHandler
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    for (Method method : target.type().getMethods()) { // 2. 遍历接口方法
      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))); // 3. 为接口的方法创建对应的处理方法
      }
    }
    InvocationHandler handler = factory.create(target, methodToHandler); // 4. handler为根据Method找到对应的MethodHandler(SynchronousMethodHandler),然后调用
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), // 5. 使用JDK动态代理生成代理对象
        new Class<?>[] {target.type()}, handler);
    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

当调用发生时,根据方法会分派到实际的MethodHandler,即SynchronousMethodHandler。

# feign-core-10.10.1.jar!/feign.SynchronousMethodHandler
  @Override
  public Object invoke(Object[] argv) throws Throwable {
	// 1. 见ReflectiveFeign$ParseHandlersByName如何创建BuildTemplateByResolvingArgs,BuildTemplateByResolvingArgs有Encoder,根据form、body、requestString等不同形式创建RequestTemplate
    RequestTemplate template = buildTemplateFromArgs.create(argv); 
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
		// 2. 发出HTTP请求,并对结果进行处理
        return executeAndDecode(template, options); 
      } catch (RetryableException e) {
        try {
          retryer.continueOrPropagate(e); // 3. 可重试异常时,当重试次数小于最大可重试次数时,线程休眠一段时间后重试,否则抛出异常
        } 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;
      }
    }
  }
  
  Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Request request = targetRequest(template); // 2.1 执行RequestInterceptor.apply;执行HardCodedTarget.apply
    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }
    Response response;
    long start = System.nanoTime();
    try {
      response = client.execute(request, options); // 2.2 使用apache http/okhttp等执行http请求
      // ensure the request is set. TODO: remove in Feign 12
      response = response.toBuilder()
          .request(request)
          .requestTemplate(template)
          .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);
    if (decoder != null)
      return decoder.decode(response, metadata.returnType()); //2.3 将请求结果根据方法返回类型用Decoder进行处理
    CompletableFuture<Object> resultFuture = new CompletableFuture<>();
    asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
        metadata.returnType(),
        elapsedTime);
    try {
      if (!resultFuture.isDone())
        throw new IllegalStateException("Response handling not done");
      return resultFuture.join();
    } catch (CompletionException e) {
      Throwable cause = e.getCause();
      if (cause != null)
        throw cause;
      throw e;
    }
  }

关于Client接口,feign有默认的实现是feign.Client.Default。它直接使用jdk的HttpURLConnection来发送请求和处理响应。另外feign也提供了apache http client适配类以及okHttp适配类。

ribbon提供了AbstractLoadBalancerAwareClient,而spring cloud netflix提供了LoadBalancerFeignClient。

Tags:

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

欢迎 发表评论:

最近发表
标签列表