Feign源码深度刨析-(6)动态代理核心处理器:FeignInvocationHandler

本文深入剖析了Feign客户端的工作原理,从FeignInvocationHandler的invoke方法开始,详细解释了请求的构建过程,包括RequestTemplate的创建、SynchronousMethodHandler的invoke方法调用,以及如何通过LoadBalancerFeignClient执行请求。文章还揭示了LoadBalancerFeignClient如何利用Ribbon进行负载均衡,并预告了下一步将探讨如何将服务名称替换为实际的IP和Port。

“不积跬步,无以至千里。”

前面说了,FeignClientFactoryBeangetObject()方法返回的就是一个类型为FeignInvocationHandler的代理对象

所有,一旦Controller 中调用feign接口的方法,实际调用的就是这个动态代理,有点尝试的都知道,对于动态代理对象的方法调用,都会经过其绑定的InvocationHandler 的invoke()方法来处理

private final Map<Method, MethodHandler> dispatch;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if ("equals".equals(method.getName())) {
        try {
            Object
                otherHandler =
                args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
            return equals(otherHandler);
        } catch (IllegalArgumentException e) {
            return false;
        }
    } else if ("hashCode".equals(method.getName())) {
        return hashCode();
    } else if ("toString".equals(method.getName())) {
        return toString();
    }
    return dispatch.get(method).invoke(args);
}

首先经过一系列的方法判断,看看是不是“equals”,“hashCode”,“toString”方法,我们的feign接口方法当然不是这些啦

dispatch.get(method).invoke(args)

这个dispatch 是一个Map,key是方法Method对象,value就是SynchronousMethodHandler ,方法处理组件,里面包含处理方法的所有零散组件

所以这行代码最终会调用到SynchronousMethodHandler 的invoke() 方法

@Override
public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
        try {
            return executeAndDecode(template);
        } catch (RetryableException e) {
            retryer.continueOrPropagate(e);
            if (logLevel != Logger.Level.NONE) {
                logger.logRetry(metadata.configKey(), logLevel);
            }
            continue;
        }
    }
}

RequestTemplate template = buildTemplateFromArgs.create(argv);

argv,就是方法的参数

之前通过SpringMvcContract解析出来的是

GET /test_1/{param} HTTP/1.1 这种格式

GET /test_1/zhangsan HTTP/1.1 现在得到的是实际的restTemplate了,用实参替换了形参,这个create解析的过程,就不看了,体力活,一大坨代码,抓大放小

然后就去调用了executeAndDecode()方法,把这个template 作为参数传入

Object executeAndDecode(RequestTemplate template) throws Throwable {
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
        logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
        response = client.execute(request, options);
        // ensure the request is set. TODO: remove in Feign 10
        response.toBuilder().request(request).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);

    boolean shouldClose = true;
    try {
        if (logLevel != Logger.Level.NONE) {
            response =
                logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
            // ensure the request is set. TODO: remove in Feign 10
            response.toBuilder().request(request).build();
        }
        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;
            }
            // Ensure the response body is disconnected
            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;
            }
            ... ...
        }
    }
}

首先调用了一个targetRequest()方法,把之前解析好的RestTemplate 传进去

Request targetRequest(RequestTemplate template) {
    for (RequestInterceptor interceptor : requestInterceptors) {
        interceptor.apply(template);
    }
    return target.apply(new RequestTemplate(template));
}

遍历请求拦截器,将每个请求拦截器都应用到RequestTemplate 请求模板上面去,也就是让每个请求拦截器对请求进行处理

target.apply(new RequestTemplate(template))

然后基于target 构建一个请求的Request 并把它返回

这个target 是啥,不就是HardCodedTarget 吗,里面包含了目标服务的信息(服务名),这块忘记了可以翻翻前几篇文章

response = client.execute(request, options);

这个client是一个LoadBalancerFeignClient类型的实例,在这里调用execute()方法并将请求的参数传入了进去

options,我们没有自定义这个options,使用的是默认的,连接超时时间是10000ms,读超时时间是60000ms

optins

接着看,由LoadBalancerFeignClient 的execute 来处理代理请求,这个一看名字就是跟ribbon有一腿,还LoadBalancerXXX…

@Override
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);
        FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
            this.delegate, request, uriWithoutHost);

        IClientConfig requestConfig = getClientConfig(options, clientName);
        return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
                                                            requestConfig).toResponse();
    }
    catch (ClientException e) {
        IOException io = findIOException(e);
        if (io != null) {
            throw io;
        }
        throw new RuntimeException(e);
    }
}

这个asUri 就是拿到请求的url,目前来看是这样子

http://springboot-project-2/test_1/zhangsan

也就是说还是服务名称,没有替换上ip和port

String clientName = asUri.getHost();

这里拿到服务名

URI uriWithoutHost = cleanUrl(request.url(), clientName);

这里是把上面的asUri的serviceName给扣掉了,最终得到的是这样子

http:///test_1/zhangsan

一通操作之后,IClientConfig requestConfig = getClientConfig(options, clientName);

这又是干嘛呢?实际上就是搞了一个所谓的IClientConfig ,马上要用ribbon的负载均衡器来选取目标server了,所以这里猜想,只能是跟ribbon相关的配置

lbClient(clientName)

接着就是根据服务名称,去获取对应的FeignLoadBalancer

ok,这篇文章就先到这里,下一讲来看下这个FeignLoadBalancer 是怎么获取的,然后怎么跟ribbon整合将请求url中的服务名称替换成实际的ip和port,拭目以待!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值