OpenFeign

相关注解

  • @EnableFeignClients: basePackages和clients属性都是配置@FeignClient类扫描位置,只能为接口
  • @FeignClient:name、value、contextId、serviceId都是服务名称,可以包含占位符

Feign的创建

@EnableFeignClients
EnableFeignClients导入了一个ImportBeanDefinitionRegistrar,FeignClientsRegistrar,它会读取EnableFeignClients#basePackages或clients,扫描对应包下@FeignClient类,得到Bean定义。

@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients

这里的Bean定义的目的不是为了注册,而是获取类上@FeignClient注解的属性信息。
@FeignClient
@FeignClient注解的属性:

  • value或name:被Feign访问服务名称,会根据服务名称从注册中心获取服务实例ip和端口号
  • contextId:非必填,如果指定了,就是Feign代理对象的beanName;如果没指定,用value或name作为beanName
  • path:方法中公共的访问路径前缀
  • url:具体某个服务实例的url
  • fallback:必须实现Feign接口,
  • FallbackFactory:必须实现FallbackFactory接口

根据@FeignClients的属性创建Feign代理工厂对象(FeignClientFactoryBean),注册一个bean定义,它的实例工厂(instanceSupplier)执行FeignClientFactoryBean.getObject()创建了Feign代理对象

FeignClientFactoryBean
FeignClientFactoryBean.getObject()最终通过Feign这个抽象类的newInstance(Target target)方法创建代理对象

public abstract <T> T newInstance(Target<T> target);

而Feign通过建造者Feign.Builder创建

public Feign build() {
。。。
}

默认是ReflectiveFeign,最终得到的是jdk代理对象,默认的InvocationHandler实现是FeignInvocationHandler,它为方法到methodHandler的映射,将每个方法调用转发到对应的MethodHandler

class FeignInvocationHandler implements InvocationHandler{
	private final Map<Method, MethodHandler> dispatch;
	@Override
	public Object invoke(Object proxy, Method method, Object[] args){
	...
		return dispatch.get(method).invoke(args);
	}
}

MethodHandler

MethodHandler由SynchronousMethodHandler.Factory创建

public MethodHandler create(Target<?> target,
                                MethodMetadata md,
                                RequestTemplate.Factory buildTemplateFromArgs,
                                Options options,
                                Decoder decoder,
                                ErrorDecoder errorDecoder) {
                                ...
}

创建它需要MethodMetadata ,也就是方法元数据,由契约(Contract)创建

public interface Contract {
  List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType);
}

契约定义了Feign接口方法的解析方式,或者说编写标准,比如SpringMvcContract定义了按照SpringMvc的编写标准来声明Http请求头、请求体参数

SpringMvcContract

总体分为3个步骤:

  • 处理类上的注解:注意Feign接口上不能定义@RequestMapping,org.springframework.cloud.openfeign.support.SpringMvcContract#processAnnotationOnClass
  • 处理方法上的注解:处理方法上的@RequestMapping,url路径、方法、produce、consume、header,produce相当于accept请求头,consume相当于Content-Type请求头,每次请求都会默认加上这些请求头。
  • 处理方法参数上的注解:由AnnotatedParameterProcessor处理,实现类有RequestParamParameterProcessor,处理@RequestParam;CookieValueParameterProcessor,处理@CookieValue等。注意没有@RequestBody的处理器,请求体实际上是根据类型判断的(用户Pojo、MultipartFile、Map)
public interface AnnotatedParameterProcessor {
//支持的方法参数的注解类型
	Class<? extends Annotation> getAnnotationType();
//处理注解,根据注解属性设置MethodMetadata的属性
	boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method);
}

对于没有AnnotatedParameterProcessor 能处理的注解标注的方法参数,根据类型保存它们的参数下标

public final class MethodMetadata{
//URI类型
	private Integer urlIndex;
	//其他类型
	private Integer bodyIndex;
}

RequestTemplate
保存Contract解析出来的HTTP请求信息模板,包括请求头、请求方法、uri、查询串、请求体等

public class RequestParamParameterProcessor implements AnnotatedParameterProcessor {
	@Override
	public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
	...
	//将@RequestParam的值写入RequestTemplate
		String name = requestParam.value();
	//params.add(String.format("{%s}", paramName));
		Collection<String> query = context.setTemplateParameter(name, data.template().queries().get(name));
		data.template().query(name, query);
	}
}
public final class RequestTemplate{
	private final Map<String, QueryTemplate> queries;
	private final Map<String, HeaderTemplate> headers;
	private UriTemplate uriTemplate;
	private HttpMethod method;
	private Request.Body body;
}

QueryTemplate、HeaderTemplate和UriTemplate 组合了Template,Template用来表示包含几对大括号的字符串,根据大括号分割,大括号外部的是纯文本,内部的是变量名称,执行时会根据参数动态替换。

public final class QueryTemplate {
//查询串的值,在url中一般是逗号分割
	private List<Template> values;
	//查询串的名称
	private final Template name;
}
public class Template {
//TemplateChunk的实现类有Literal和Expression,表示纯文本和带大括号的表达式,解析方法feign.template.Template#parseFragment
	private final List<TemplateChunk> templateChunks;
//解析得到templateChunks
	private void parseFragment(String fragment) {
	...
		Expression expression = Expressions.create(chunk);
        if (expression == null) {          	
          	this.templateChunks.add(Literal.create(this.encodeLiteral(chunk)));
        } else {
          	this.templateChunks.add(expression);
        }
	}
	protected String resolveExpression(
                                     Expression expression,
                                     Map<String, ?> variables) {
	    String resolved = null;
	    //根据变量动态替换
	    Object value = variables.get(expression.getName());
	    ...
    }
}

RequestTemplate.Factory

负责填充RequestTemplate的参数,实现类有BuildTemplateByResolvingArgs、BuildFormEncodedTemplateFromArgs、BuildEncodedTemplateFromArgs

interface Factory { 
    RequestTemplate create(Object[] argv);
  }

实现类:

  • BuildTemplateByResolvingArgs:执行时根据feign.MethodMetadata#indexToName来获取名称到实参的映射
    indexToName在解析方法参数时添加的
//比如RequestParamAnnoationProcessor
//data.indexToName().put(i, names);
//	解析时添加indexToName
	context.setParameterName(name);
class BuildTemplateByResolvingArgs implements RequestTemplate.Factory {
	@Override
    public RequestTemplate create(Object[] argv) {
		//执行时根据实参argv获取值,添加到varBuilder,传给RequestTemplate进行参数替换
		Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
		for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
	        int i = entry.getKey();
	        Object value = argv[entry.getKey()];
	        for (String name : entry.getValue()) {
	            varBuilder.put(name, value);
	        }
	    }
	    //Template.resolveExpression
	    RequestTemplate template = resolve(argv, mutable, varBuilder);
	}
}
  • BuildFormEncodedTemplateFromArgs:负责解析表单参数,比如@RequestPart,具体则交给Encoder处理
  • BuildTemplateByResolvingArgs:负责解析用户自定义的Model参数,也就是feign.MethodMetadata#bodyIndex()对应的参数

Encoder
负责编码请求体,将用户定义的Model编码后放入请求体

public interface Encoder {
  void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;
 }

默认实现是SpringEncoder,在FeignClientsConfiguration中定义了

@Bean
@ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
public Encoder feignEncoder(ObjectProvider<AbstractFormWriter> formWriterProvider,
		ObjectProvider<HttpMessageConverterCustomizer> customizers) {
		//return new SpringEncoder(new SpringFormEncoder(), messageConverters, encoderProperties, customizers);
	return springEncoder(formWriterProvider, encoderProperties, customizers);
}
  • SpringEncoder: 默认从容器中拿到的Encoder,编码时会判断,如果Content-Type是Multipart/FormData或者application/formurlencoded,调用SpringFormEncoder编码;否则使用HttpMessageConverter将Model转换成字节流写入FeignOutputMessage,FeignOutputMessage将字节流保存到ByteArrayOutputStream,以便再赋给RequestTemplate.body
request.body(outputMessage.getOutputStream().toByteArray(), charset);
  • SpringFormEncoder: 使用ContentProcessor处理,如果是formurlencoded,则将pojo的键值对用"&"分隔(k1=v1&k2=v2),如果是FormData,pojo的键值对用一个随机分割串分隔

Feign的执行

SynchronousMethodHandler

Feign接口实现类是JDK动态代理对象,InvocationHandler的默认实现是FeignInvocationHandler,服务调用时转发给方法对应的MethodHandler处理,默认实现是SynchronousMethodHandler,处理流程:

  • 使用RequestTemplate.Factory替换RequestTemplate中的变量。RequestTemplate
    template = buildTemplateFromArgs.create(argv);
  • 执行RequestInterceptor.apply进一步处理RequestTemplate ,比如添加公共请求头、查询串,实现类有:FeignContentGzipEncodingInterceptor(请求体gzip压缩,Content-Encoding=gzip,deflate),FeignAcceptGzipEncodingInterceptor(响应gzip压缩,Accept-Encoding=gzip,deflate)等
public interface RequestInterceptor {
  void apply(RequestTemplate template);
}
  • 根据RequestTemplate的url、请求方法、请求头、请求体创建Request
  • 打印请求日志
  • 使用Client发起请求,得到响应。feign.Client#execute,如果执行过程中抛IO异常,打印日志
public interface Client {
  Response execute(Request request, Options options) throws IOException;
}
  • 解码响应:解码是把Response转成方法返回值类型对象或异常对象,使用AsyncResponseHandler处理响应,如果响应码>=200且<300,使用Decoder处理;否则用ErrorDecoder处理,抛出ErrorDecoder处理后的异常
class AsyncResponseHandler {
	void handleResponse(CompletableFuture<Object> resultFuture,
	                      String configKey,
	                      Response response,
	                      Type returnType,
	                      long elapsedTime) {
	                      ...
	}
}

Decoder

响应码>=200且<300,使用Decoder处理,负责将Response转成用户Pojo

public interface Decoder {
  Object decode(Response response, Type type) throws IOException, DecodeException, FeignException;
}

默认实现在FeignClientsConfiguration中定义了

@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder(ObjectProvider<HttpMessageConverterCustomizer> customizers) {
	return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(messageConverters, customizers)));
}

从AsyncResponseHandler.handleResponse和Decoder 实现类可以看出,Feign接口方法返回值支持的类型:

  1. feign.Response:不做处理,直接将响应对象返回
  2. void
  3. Optional:由OptionalDecoder处理
  4. ResponseEntity:由ResponseEntityDecoder处理,可以获取响应码和响应头
  5. 用户Pojo:使用SpringDecoder处理,具体是HttpMessageConverter来转换

ErrorDecoder

响应码<200或>=300时调用,抛出得到的异常

public interface ErrorDecoder {
  public Exception decode(String methodKey, Response response);
}

默认实现是抛出FeignException,并且检查如果有Retry-after响应头,在Retry-after的时间点进行重试

RetryableFeignBlockingLoadBalancerClient

支持重试的负载均衡客户端,通过spring retry实现,默认相同实例不重试,不同实例重试一次,http访问抛异常时重试,返回非2xx响应码不重试,可以通过LoadBalancerClientsProperties配置。生效条件是引入spring-retry, 配置类是DefaultFeignLoadBalancerConfiguration(默认Client)或HttpClientFeignLoadBalancerConfiguration(ApacheHttpClient)

InterceptorRetryPolicy 是spring retry的重试策略,主要负责创建LoadBalancedRetryContext,判断重试和处理异常时则是通过LoadBalancedRetryPolicy 来完成

public class InterceptorRetryPolicy implements RetryPolicy {
	private final LoadBalancedRetryPolicy policy;
	@Override
	public RetryContext open(RetryContext parent) {
		return new LoadBalancedRetryContext(parent, request);
	}
	@Override
	public boolean canRetry(RetryContext context) {
		//执行LoadBalancedRetryPolicy.canRetryNextServer
	}
	@Override
	public void registerThrowable(RetryContext context, Throwable throwable) {
		//执行LoadBalancedRetryPolicy.registerThrowable
	}
}

LoadBalancedRetryPolicy 用来判断是否应该在同一实例或不同实例间重试,出现异常时registerThrowable记录同一实例和不同实例的重试次数,得到响应时根据响应码判断是否应该重试

public interface LoadBalancedRetryPolicy {
	boolean canRetrySameServer(LoadBalancedRetryContext context);
	boolean canRetryNextServer(LoadBalancedRetryContext context);
	void registerThrowable(LoadBalancedRetryContext context, Throwable throwable);
	boolean retryableStatusCode(int statusCode);
}

RetryAwareServiceInstanceListSupplier

是ServiceInstanceListSupplier的实现类,并且是一个包装器,实现了过滤服务实例中上一次访问过的实例,如果过滤后没有实例了,则还是使用过滤前的

public class RetryAwareServiceInstanceListSupplier{
	@Override
	public Flux<List<ServiceInstance>> get(Request request) {
		...
		RetryableRequestContext context = (RetryableRequestContext) request.getContext();
		filteredByPreviousInstance(instances, previousServiceInstance);
		...
	}
	private List<ServiceInstance> filteredByPreviousInstance(List<ServiceInstance> instances,
			ServiceInstance previousServiceInstance) {
		...
	}
}

前一次请求的实例保存在LoadBalancedRetryContext中,然后写入到RetryableRequestContext,它保存在DefaultRequest中

public class LoadBalancedRetryContext{
	private ServiceInstance previousServiceInstance;
	public void setServiceInstance(ServiceInstance serviceInstance) {
	//注意,这里是this.serviceInstance,保存前一次请求的实例
		setPreviousServiceInstance(this.serviceInstance);
		this.serviceInstance = serviceInstance;
	}
}

总结

Client执行请求时,首先会执行LoadBalancerClient.choose(),而它委派给ReactiveLoadBalancer.choose(),ReactiveLoadBalancer实现了根据多个服务实例获取其中一个的逻辑,服务实例通过执行ServiceInstanceListSupplier.get()获取,ServiceInstanceListSupplier最终执行服务发现客户端DiscoveryClient获取服务实例。

得到服务实例后,执行LoadBalancerClient#reconstructURI将请求中的服务名称替换成真正的服务实例的ip和端口号,最后执行真正的Client发出请求

  • Client:实现类有可重试的RetryableFeignBlockingLoadBalancerClient,不支持重试的FeignBlockingLoadBalancerClient,真正执行请求的ApacheHttpClient
  • LoadBalancerClient:默认只有BlockingLoadBalancerClient
  • ReactiveLoadBalancer: RoundRobinLoadBalancer
  • ServiceInstanceListSupplier:重试对应的RetryAwareServiceInstanceListSupplier,CachingServiceInstanceListSupplier(通过CacheManager缓存服务实例),DiscoveryClientServiceInstanceListSupplier(负责调用DiscoveryClient,支持配置超时)
  • DiscoveryClient:NacosDisceryClient
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值