Feign详解3-工作原理

本文解析了Feign的设计原理,重点介绍了如何通过ReflectiveFeign构建代理对象,以及MethodHandler在执行过程中的作用。还详细讲述了如何利用Feign实现Ribbon负载均衡和Hystrix熔断策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

目录

1. Feign 是如何设计的

2. Feign 动态代理

2.1 ReflectiveFeign 构建

2.2 生成代理对象

2.3 MethodHandler 方法执行器

3. Feign 调用过程

3.1 FeignInvocationHandler#invoke

3.2 SynchronousMethodHandler#invoke

4. 思考:如何基于 Feign 实现负载均衡与熔断

4.1 基于 Feign 的负载均衡 - 整合 Ribbon

4.2 基于 Feign 的熔断与限流 - 整合 Hystrix


1. Feign 是如何设计的

首先回顾一下 Feign 的基本用法:

接口与调用类:

@Produces("application/json")
public interface GitHub2_javax {
    @GET
    @Path("/repos/{owner}/{repo}/contributors")
    List<Contributor> contributors(@PathParam("owner") String owner, @PathParam("repo") String repo);

}
// 1. Feign 动态代理
 GitHub2_javax github = Feign.builder()
                .client(new OkHttpClient())
                .decoder(new GsonDecoder())
                .contract(new JAXRSContract())
                .options(  new Request.Options(1000,3500))  //options方法指定连接超时时长及响应超时时长
                .retryer(  new Retryer.Default(5000,5000,3))   // retryer方法主要是指定重试策略
                .target(GitHub2_javax.class, "https://api.github.com");
// 2. Feign 执行
List<Contributor> contributors = github.contributors("OpenFeign", "feign");

小结: Feign 使用时分成两步:一是根据接口生成 Feign 的动态代理类;二是 Feign 执行。 

 

总结:

  1. 前两步是生成动态对象:将 Method 方法的注解解析成 MethodMetadata,并最终生成 Feign 动态代理对象。
  2. 后几步是调用过程:根据解析的 MethodMetadata 对象,将 Method 方法的参数转换成 Request,最后调用 Client 发送请求。

2. Feign 动态代理

Feign 的默认实现是 ReflectiveFeign,通过 Feign.Builder 构建。它的创建顺序如下:

Feign.builder()-> 创建一个Builder对象,它可以通过一系列的方法如: contract(),client(),retryer()等配置所需的依赖对象. 最后它会调用 
public <T> T target(Class<T> apiType, String url) {
  return target(new HardCodedTarget<T>(apiType, url));
}

生成一个HardCodedTarget对象,这个对象实现了Target接口, 看代码前,先了解一下 Target 这个接口。

public interface Target<T> {
  // 接口的类型
  Class<T> type();

  // 代理对象的名称,默认为url,负载均衡时有用
  String name();
  // 请求的url地址,eg: https://api/v2
  String url();
}

其中 Target.type 是用来生成代理对象的,url 是 Client 对象发送请求的地址。 所以Target里面包装了用于生成请求的地址和请求业务接口. 

2.1 ReflectiveFeign 构建

接下来,它调用了 target( Target<T> target) 中的build() 方法. 如下图

 在build()方法中它最终生成了 ReflectiveFeign对象

public Feign build() {
    // 构建一个SynchronousMethodHandler工厂
    SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
        new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
                                             logLevel, decode404, closeAfterDecode, propagationPolicy);
    //  ParseHandlersByName中包括了前面在代码中设置的contract,options,encoder等对象
    ParseHandlersByName handlersByName =
        new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
                                errorDecoder, synchronousMethodHandlerFactory);
    //最后创建RelectiveFeign
    return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

总结: 介绍一下几个主要的参数:

  • Client 这个没什么可说的,有三种实现 JdkHttp/ApacheHttp/okHttp
  • RequestInterceptor 请求拦截器
  • Contract REST 注解解析器,默认为 Contract.Default(),即支持 Feign 的原生注解。
  • InvocationHandlerFactory 生成 JDK 动态代理,实际执行是委托给了 MethodHandler。

2.2 生成代理对象

调用  build().newInstance(target); ,  build()生成的对象是  ReflectiveFeign,所以这个newInstance(target)也是ReflectiveFeign的方法. 

ReflectiveFiegn对象中注入了三个参数对象: 

  • handlersByName:   
  • invocationHandlerFactory:  处理工厂   
  • queryMapEncoder:  编码器

追踪 newInstance().

public <T> T newInstance(Target<T> target) {
    // 1. Contract 将 target.type 接口类上的方法和注解解析成 MethodMetadata,
    //    并转换成内部的MethodHandler处理方式
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    // type()获取接口,从中获取  方法, 再以<Method,MethodHandler>形式存到methodToHAndler中
    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 {
            methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
        }
    }

    // 2. 生成 target.type 的 jdk 动态代理对象     返回类似于:ReflectiveFeign$FeignInvocationHandler@7642
    // methodToHandler中包含Feign.builder()、Feign.client()等信息
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
                                         new Class<?>[]{target.type()}, handler);

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

总结: newInstance() 生成了 JDK 的动态代理,从 factory.create(target, methodToHandler) 也可以看出 InvocationHandler 实际委托给了 methodToHandler。methodToHandler 默认是 SynchronousMethodHandler.Factory 工厂类创建的。   

注意: 如果对JDK动态代理还不熟悉的,请先学习JDK动态代理.

2.3 MethodHandler 方法执行器

 接下来查看一下 newInstance( Target<T> target)中的第一句代码,

Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);

targetToHandlersByName是一个ParseHandlersByName对象,调用它的apply 生成了每个方法的执行器 MethodHandler,其中最重要的一步就是通过 Contract 解析 MethodMetadata。

public Map<String, MethodHandler> apply(Target key) {
    // 1. contract 将接口类中的方法和注解解析 成 MethodMetadata, 
    // 注意  contract 是注解规范的实现,前面已经介绍过了,有 JAVAX-RS, spring mvc,及feign等.
    List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
    Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
    for (MethodMetadata md : metadata) {
        // 2. buildTemplate 实际上将 Method 方法的参数转换成 Request
        BuildTemplateByResolvingArgs buildTemplate;
        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
            // 2.1 表单
            buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
        } else if (md.bodyIndex() != null) {
            // 2.2 @Body 注解
            buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
        } else {
            // 2.3 其余
            buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
        }
        // 3. 将 metadata 和 buildTemplate 封装成 MethodHandler
        result.put(md.configKey(),
                   factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
    }
    return result;
}

总结: 这个方法由以下几步:

  1. Contract 统一将方法解析 MethodMetadata(*),这样就可以通过实现不同的 Contract 适配各种 REST 声明式规范。
  2. buildTemplate 实际上将 Method 方法的参数转换成 Request。
  3. 将 metadata 和 buildTemplate 封装成 MethodHandler。

2.4 InvocationHandler的创建

再观察newInstance(Target<T> target)中的

InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);
factory.create(target, methodToHandler),如果当前客户端是feign的话,则: factory是一个 InvocationHandlerFactory 对象,它的create 方法返回一个FeignInvocationHandler 对象
public interface InvocationHandlerFactory {

  InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);

  interface MethodHandler {

    Object invoke(Object[] argv) throws Throwable;
  }

  static final class Default implements InvocationHandlerFactory {

    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }
  }
}

  如果当前客户端是Hystrix的话,则factory是 HystrixInvocationHandler 下的 Factory, 创建的是一个HystrixInvocationHandler

final class HystrixInvocationHandler implements InvocationHandler {
    private final Target<?> target;
    private final Map<Method, MethodHandler> dispatch;
    private final Object fallback;

    HystrixInvocationHandler(Target<?> target, Map<Method, MethodHandler> dispatch, Object fallback) {
        this.target = (Target)Util.checkNotNull(target, "target", new Object[0]);
        this.dispatch = (Map)Util.checkNotNull(dispatch, "dispatch", new Object[0]);
        this.fallback = fallback;
    }

    public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
       ...
     }



    static final class Factory implements InvocationHandlerFactory {
        Factory() {
        }

        public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
            return new HystrixInvocationHandler(target, dispatch, (Object)null);
        }
    }
}

   但不管是哪一个对象,它们都实现了 InvocationHandler接口,并重写了回调方法 invoke(Object proxy, final Method method, final Object[] args), 这不正好就是jdk动态代理的核心回调方法吗,当调代理对象的被代理的方法时,jvm会回调这个invoke方法完成操作. 

  最后一句: 

T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),new Class<?>[] {target.type()}, handler);

完成了代理对象的创建. 

这样通过以上几步就创建了一个 Target.type 的代理对象 proxy,这个代理对象就可以像访问普通方法一样发送 Http 请求,其实和 RPC 的 Stub 模型是一样的。了解 proxy 后,其执行过程其实也就一目了然。

3. Feign 调用过程

  通过上面的步骤我们完成一个代理对象的创建,即 GitHub2_javax是一个代理对象 $Proxy8@1525 ,例如下面的源码:

//生成了代理对象
GitHub2_javax github = Feign.builder()
                .client(new OkHttpClient())
                .decoder(new GsonDecoder())
                .contract(new JAXRSContract())
                .options(  new Request.Options(1000,3500))  //options方法指定连接超时时长及响应超时时长
                .retryer(  new Retryer.Default(5000,5000,3))   // retryer方法主要是指定重试策略
                .target(GitHub2_javax.class, "https://api.github.com");

List<Contributor> contributors = github.contributors("OpenFeign", "feign");

当我们通过  github.contributors()调用方法时,jvm会自动调用InvocationHandler对象( 前面已经讲了,有两种可能 FeignInvocationHandler 和 HystrixInvocationHandler  )的的invoke方法. 

3.1 FeignInvocationHandler#invoke

private final Map<Method, MethodHandler> dispatch;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    ... //前面的 equals(), hashCode(), toString() 不做处理
    // 每个Method方法对应一个MethodHandler, 取出来调用 invoke () . 
    return dispatch.get(method).invoke(args);
}
dispatch是一个Map<Method, MethodHandler> , 通过 method获取到MethodHandler对象. 

总结: 和上面的结论一样,实际的执行逻辑实际上是委托给了 MethodHandler,它的实现是 SynchronousMethodHandler类,所以查看它的 invoke()

3.2 SynchronousMethodHandler#invoke

// 发起 http 请求,并根据 retryer 进行重试
public Object invoke(Object[] argv) throws Throwable {
    // template 将 argv 参数构建成 Request
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();

    // 调用client.execute(request, options)
    while (true) {
        try {
            //执行完后 返回数据进行解码. 
            return executeAndDecode(template, options);
        } catch (RetryableException e) {
            try {
                // 重试机制
                retryer.continueOrPropagate(e);
            } catch (RetryableException th) {
                ...
            }
            continue;
        }
    }
}

总结: invoke 主要进行请求失败的重试机制,至于具体执行过程委托给了 executeAndDecode 方法。

// 一是编码生成Request;二是http请求;三是解码生成Response
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    // 1. 调用拦截器 RequestInterceptor,并根据 template 生成 Request
    Request request = targetRequest(template);
    // 2.  client (各种类型) 执行  http 请求
    Response response = client.execute(request, options);
	// 3. response 解码
    if (Response.class == metadata.returnType()) {
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        return response.toBuilder().body(bodyData).build();
    }
    ...
}

Request targetRequest(RequestTemplate template) {
    // 执行拦截器
    for (RequestInterceptor interceptor : requestInterceptors) {
        interceptor.apply(template);
    }
    // 生成 Request
    return target.apply(template);
}

总结: executeAndDecode 最终调用 client.execute(request, options) 进行 http 请求。

4. 思考:如何基于 Feign 实现负载均衡与熔断

4.1 基于 Feign 的负载均衡 - 整合 Ribbon

 前面所研究的 executeAndDecode() 中通过 response = client.execute(request, options); 发出请求并得到响应. 那么如果整合了Ribbon的话,则会通过RibbonClient 来调用execute()操作. 那么它是如何加载起来的呢?

FeignRibbonClientAutoConfiguration自动装配:

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
		OkHttpFeignLoadBalancedConfiguration.class,
		DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
   ..
}
DefaultFeignLoadBalancedConfiguration  -> 
@Configuration
class DefaultFeignLoadBalancedConfiguration {

	@Bean
	@ConditionalOnMissingBean
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
			SpringClientFactory clientFactory) {
		return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
				clientFactory);
	}

}

最终创建的是  LoadBalancerFeignClient,  

public class LoadBalancerFeignClient implements Client {
    static final Request.Options DEFAULT_OPTIONS = new Request.Options();
	private final Client delegate;
	private CachingSpringLoadBalancerFactory lbClientFactory;
	private SpringClientFactory clientFactory;

    @Override
	public Response execute(Request request, Request.Options options) throws IOException {
		// RibbonClient 主要逻辑
        private final Client delegate;
        private final LBClientFactory lbClientFactory;
        public Response execute(Request request, Request.Options options) throws IOException {
        try {
            URI asUri = URI.create(request.url());
            String clientName = asUri.getHost();
            URI uriWithoutHost = cleanUrl(request.url(), clientName);
            // 封装 RibbonRequest,包含 Client、Request、uri
            LBClient.RibbonRequest ribbonRequest =
                new LBClient.RibbonRequest(delegate, request, uriWithoutHost);
            // executeWithLoadBalancer 实现负载均衡
            return lbClient(clientName).executeWithLoadBalancer(
                ribbonRequest,
                new FeignOptionsClientConfig(options)).toResponse();
        } catch (ClientException e) {
            propagateFirstIOException(e);
            throw new RuntimeException(e);
        }
    }
    ....

}

总结: 实际上是把 Client 对象包装了一下,通过 executeWithLoadBalancer 进行负载均衡,这是 Ribbon 提供了 API。更多关于 Ribbon 的负载均衡就不在这进一步说明了。

public final class LBClient extends AbstractLoadBalancerAwareClient
	<LBClient.RibbonRequest, LBClient.RibbonResponse> {
	
	// executeWithLoadBalancer 方法通过负载均衡算法后,最终调用 execute
	@Override
    public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
            throws IOException, ClientException {
        Request.Options options;
        if (configOverride != null) {
            options = new Request.Options(
                    configOverride.get(CommonClientConfigKey.ConnectTimeout, connectTimeout),
                    configOverride.get(CommonClientConfigKey.ReadTimeout, readTimeout),
                    configOverride.get(CommonClientConfigKey.FollowRedirects, followRedirects));
        } else {
            options = new Request.Options(connectTimeout, readTimeout);
        }
        // http 请求
        Response response = request.client().execute(request.toRequest(), options);
        if (retryableStatusCodes.contains(response.status())) {
            response.close();
            throw new ClientException(ClientException.ErrorType.SERVER_THROTTLED);
        }
        return new RibbonResponse(request.getUri(), response);
    }
}

4.2 基于 Feign 的熔断与限流 - 整合 Hystrix

想要进行限流,那就要在方法执行前进行拦截,也就是重写 InvocationHandlerFactory,在方法执行前进行熔断与限流。相关代码见 HystrixFeign,其实也就是实现了 HystrixInvocationHandler。

// HystrixInvocationHandler 主要逻辑
public Object invoke(final Object proxy, final Method method, final Object[] args)
      throws Throwable {
    HystrixCommand<Object> hystrixCommand =
        new HystrixCommand<Object>(setterMethodMap.get(method)) {
            @Override
            protected Object run() throws Exception {
                return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
            }
            @Override
            protected Object getFallback() {
            };
        }
    ...
	return hystrixCommand.execute();
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangyingchengqi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值