• 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
    
    • 1
    • 2

    这里的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);
    
    • 1

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

    public Feign build() {
    。。。
    }
    
    • 1
    • 2
    • 3

    默认是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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    MethodHandler

    MethodHandler由SynchronousMethodHandler.Factory创建

    public MethodHandler create(Target<?> target,
                                    MethodMetadata md,
                                    RequestTemplate.Factory buildTemplateFromArgs,
                                    Options options,
                                    Decoder decoder,
                                    ErrorDecoder errorDecoder) {
                                    ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

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

    契约定义了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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    public final class MethodMetadata{
    //URI类型
    	private Integer urlIndex;
    	//其他类型
    	private Integer bodyIndex;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    public final class QueryTemplate {
    //查询串的值,在url中一般是逗号分割
    	private List<Template> values;
    	//查询串的名称
    	private final Template name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    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());
    	    ...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    RequestTemplate.Factory

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

    interface Factory { 
        RequestTemplate create(Object[] argv);
      }
    
    • 1
    • 2
    • 3

    实现类:

    • BuildTemplateByResolvingArgs:执行时根据feign.MethodMetadata#indexToName来获取名称到实参的映射
      indexToName在解析方法参数时添加的
    //比如RequestParamAnnoationProcessor
    //data.indexToName().put(i, names);
    //	解析时添加indexToName
    	context.setParameterName(name);
    
    • 1
    • 2
    • 3
    • 4
    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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • BuildFormEncodedTemplateFromArgs:负责解析表单参数,比如@RequestPart,具体则交给Encoder处理
    • BuildTemplateByResolvingArgs:负责解析用户自定义的Model参数,也就是feign.MethodMetadata#bodyIndex()对应的参数

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

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

    默认实现是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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • SpringEncoder: 默认从容器中拿到的Encoder,编码时会判断,如果Content-Type是Multipart/FormData或者application/formurlencoded,调用SpringFormEncoder编码;否则使用HttpMessageConverter将Model转换成字节流写入FeignOutputMessage,FeignOutputMessage将字节流保存到ByteArrayOutputStream,以便再赋给RequestTemplate.body
    request.body(outputMessage.getOutputStream().toByteArray(), charset);
    
    • 1
    • 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);
    }
    
    • 1
    • 2
    • 3
    • 根据RequestTemplate的url、请求方法、请求头、请求体创建Request
    • 打印请求日志
    • 使用Client发起请求,得到响应。feign.Client#execute,如果执行过程中抛IO异常,打印日志
    public interface Client {
      Response execute(Request request, Options options) throws IOException;
    }
    
    • 1
    • 2
    • 3
    • 解码响应:解码是把Response转成方法返回值类型对象或异常对象,使用AsyncResponseHandler处理响应,如果响应码>=200且<300,使用Decoder处理;否则用ErrorDecoder处理,抛出ErrorDecoder处理后的异常
    class AsyncResponseHandler {
    	void handleResponse(CompletableFuture<Object> resultFuture,
    	                      String configKey,
    	                      Response response,
    	                      Type returnType,
    	                      long elapsedTime) {
    	                      ...
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Decoder

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

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

    默认实现在FeignClientsConfiguration中定义了

    @Bean
    @ConditionalOnMissingBean
    public Decoder feignDecoder(ObjectProvider<HttpMessageConverterCustomizer> customizers) {
    	return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(messageConverters, customizers)));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    从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);
    }
    
    • 1
    • 2
    • 3

    默认实现是抛出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
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    public interface LoadBalancedRetryPolicy {
    	boolean canRetrySameServer(LoadBalancedRetryContext context);
    	boolean canRetryNextServer(LoadBalancedRetryContext context);
    	void registerThrowable(LoadBalancedRetryContext context, Throwable throwable);
    	boolean retryableStatusCode(int statusCode);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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) {
    		...
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

    public class LoadBalancedRetryContext{
    	private ServiceInstance previousServiceInstance;
    	public void setServiceInstance(ServiceInstance serviceInstance) {
    	//注意,这里是this.serviceInstance,保存前一次请求的实例
    		setPreviousServiceInstance(this.serviceInstance);
    		this.serviceInstance = serviceInstance;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    总结

    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
  • 相关阅读:
    mysql5.7.35安装配置教程【超级详细安装教程】
    Java“牵手”义乌购商品详情数据,义乌购商品详情接口,义乌购API接口申请指南
    Java Web 33道面试题汇总
    团队管理|如何提高技术 Leader 的思考技巧?
    ssm大学校园慈善拍卖网站的设计与实现毕业设计源码250910
    linux常见命令
    Google开源offload友好协议PSP,目前已正式部署到生产中
    半导体晶片切割
    【UI测试】内容及流程
    windows监听扬声器、麦克风静音、音量事件
  • 原文地址:https://blog.csdn.net/baidu_32223873/article/details/126768226