分布式纽带-feign

本文基于openfeign2.1.x版本
分布式系统中,不同服务的沟通尤其重要,本文就基于springcloud的feign进行分析。

一.启动流程

1.先从@EnableFeignClients开始
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {}

@EnableFeignClients 引入了FeignClientsRegistrar,其他都是配置信息,说明具体逻辑在FeignClientsRegistrar中。

ImportBeanDefinitionRegistrar接口是用于来动态注册bean的,结合@import来一起使用。spring启动时,会执行ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法,从而实现动态加载bean。

class FeignClientsRegistrar
   	implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
   	
   	public void registerBeanDefinitions(AnnotationMetadata metadata,
   			BeanDefinitionRegistry registry) {
   		// 如何自定义了配置类,使用自定义的配置类
   		registerDefaultConfiguration(metadata, registry);
   		// 注册feignclient
   		registerFeignClients(metadata, registry);
   	}
}

对于自定义配置不是重点,主要看下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());
   	// 自定义过滤标注了@FeignClient的类		
   	AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
   			FeignClient.class);
   	// clients属性是用于指定只扫描哪些client对象,默认为[]		
   	final Class<?>[] clients = attrs == null ? null
   			: (Class<?>[]) attrs.get("clients");
   	if (clients == null || clients.length == 0) {
   		// 设置要扫描的注解
   		scanner.addIncludeFilter(annotationTypeFilter);
   		// 获取扫描路径,如果没有设置,则以标注@EnableFeignClients的类所在包作为起始路径
   		basePackages = getBasePackages(metadata);
   	}
   	else {
   		...
   	}
   	// 遍历扫描每个包路径
   	for (String basePackage : basePackages) {
   		// 扫描到标注了@FeignClient的BeanDefinition集合
   		Set<BeanDefinition> candidateComponents = scanner
   				.findCandidateComponents(basePackage);
   		for (BeanDefinition candidateComponent : candidateComponents) {
   			if (candidateComponent instanceof AnnotatedBeanDefinition) {
   				AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
   				AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
   				// 获取FeignClient的配置信息
   				Map<String, Object> attributes = annotationMetadata
   						.getAnnotationAttributes(
   								FeignClient.class.getCanonicalName());
   				String name = getClientName(attributes);
   				// 这里也是处理@FeignClient自定义的配置
   				registerClientConfiguration(registry, name,
   						attributes.get("configuration"));
   				// 实际执行注册FeignClient
   				registerFeignClient(registry, annotationMetadata, attributes);
   			}
   		}
   	}
   }

registerFeignClient主要定义BeanDefinition,并将@FeignClient对应的配置写入。这里要注意的是将类设置为FeignClientFactoryBean。

private void registerFeignClient(BeanDefinitionRegistry registry,
   		AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
   	String className = annotationMetadata.getClassName();
   	// 设置FeignClientFactoryBean为FactoryBean
   	BeanDefinitionBuilder definition = BeanDefinitionBuilder
   			.genericBeanDefinition(FeignClientFactoryBean.class);
   	...
   	BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
   			new String[] { alias });
   	BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
   }
2.FeignClientFactoryBean

FeignClientFactoryBean实现了FactoryBean,是一个FactoryBean。
FactoryBean类,spring启动时,会调用getObject方法调用真正要实例的对象。

  class FeignClientFactoryBean
  	implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
  	public Object getObject() throws Exception {
  		return getTarget();
  	}
}
 <T> T getTarget() {
 		// 这里FeignContext是在FeignAutoConfiguration自动装配时加载的
   	FeignContext context = this.applicationContext.getBean(FeignContext.class);
   	// 建造者模式,构建对象
   	Feign.Builder builder = feign(context);
   	// 没有自定义url参数
   	if (!StringUtils.hasText(this.url)) {
   		// 补个http前缀
   		if (!this.name.startsWith("http")) {
   			this.url = "http://" + this.name;
   		}else {
   			this.url = this.name;
   		}
   		// cleanPath添加统一前缀,如果FeignClient设置了path参数
   		this.url += cleanPath();
   		// 这里还是调用了 targeter.target
   		return (T) loadBalance(builder, context,
   				new HardCodedTarget<>(this.type, this.name, this.url));
   	}
   	// 自定义url,这里补一个http开头
   	if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
   		this.url = "http://" + this.url;
   	}
   	// cleanPath添加统一前缀,如果FeignClient设置了path参数
   	String url = this.url + cleanPath();
   	Client client = getOptional(context, Client.class);
   	if (client != null) {
   		if (client instanceof LoadBalancerFeignClient) {
   			client = ((LoadBalancerFeignClient) client).getDelegate();
   		}
   		builder.client(client);
   	}
   	// 这里Targeter来源于FeignAutoConfiguration
   	Targeter targeter = get(context, Targeter.class);
   	return (T) targeter.target(this, builder, context,
   			new HardCodedTarget<>(this.type, this.name, url));
   }

Targeter对象来自于FeignAutoConfiguration配置类,当启用了Hystrix时,会创建HystrixTargeter,否则为DefaultTargeter。

	@Configuration
	@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
	protected static class HystrixFeignTargeterConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public Targeter feignTargeter() {
			return new HystrixTargeter();
		}
	}

	@Configuration
	@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
	protected static class DefaultFeignTargeterConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public Targeter feignTargeter() {
			return new DefaultTargeter();
		}
	}
2.DefaultTargeter
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
		return feign.target(target);
	}

省略中间跳转之后,ReflectiveFeign调用newInstance。

public <T> T newInstance(Target<T> target) {
		// 解析每个方法,并构建方法与MethodHandler的映射
        Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
        // 每个Method与MethodHandler的映射
	    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
		...
	    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 {
	      	// Feign.configKey(target.type(), method)会构建"接口名#方法名(参数名,参数名)"的String
	        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
	      }
	    }
		// this.factory是指上文的SynchronousMethodHandler.Factory
        InvocationHandler handler = this.factory.create(target, methodToHandler);
        // jdk代理
        T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
       	...
        return proxy;
    }

create方法实际是构造了SynchronousMethodHandler。

public MethodHandler create(Target<?> target, MethodMetadata md, feign.RequestTemplate.Factory buildTemplateFromArgs, Options options, Decoder decoder, ErrorDecoder errorDecoder) {
   return new SynchronousMethodHandler(target, this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, md, buildTemplateFromArgs, options, decoder, errorDecoder, this.decode404, this.closeAfterDecode, this.propagationPolicy);
}

二.执行流程

feig调用时流程:

  1. 先执行FeignInvocationHandler的invoke,根据method获取对应映射的MethodHandler
  2. 然后再执行SynchronousMethodHandler里面的invoke方法
  3. 组装请求数据,发起请求
  4. 请求返回类型为Response,直接返回。否则,解码数据。
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     ...
     return dispatch.get(method).invoke(args);
   }
public Object invoke(Object[] argv) throws Throwable {
    // 填充请求参数 
    RequestTemplate template = buildTemplateFromArgs.create(argv);
       ...
       while(true) {
           try {
               return this.executeAndDecode(template, options);
           }catch (RetryableException var9) {
               try {
               	// 调用异常,会执行重试机制
                   retryer.continueOrPropagate(e);
               } 
               ...
           }
       }
   }
    Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    	// 这里会调用拦截器,然后生成实际的请求对象
       Request request = this.targetRequest(template);
    	...
       Response response;
       try {
       	   // 客户端执行http调用,client由专门的http工具实现
       	   // 默认为HttpURLConnection,一般配置为okHttp
           response = this.client.execute(request, options);
       } 
       ...
       // 返回类型是Response,直接返回
       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;
           }
           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 {
         Object result = decode(response);
         shouldClose = closeAfterDecode;
         return result;
       }
     } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
       Object result = decode(response);
       shouldClose = closeAfterDecode;
       return result;
     } else {
       throw errorDecoder.decode(metadata.configKey(), response);
     }
   }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值