@FeignClient的代理创建和调用过程

1、动态代理的创建过程

前面分析过@EnablefeignClient会引入FeignClientsRegistrar,将@FeignClient注解的类以FeignClientFactoryBean的形式往BeanFactory注入BeanDefinition。Spring refresh生命周期的最后一步会将所有单例非懒加载的BeanDefinition执行getBean操作。所以我们需要关注FeignClientFactoryBean#getObejct方法,返回的对象。

为了方便分析,以下面的Feign接口为例:


@FeignClient(value = "log-service", contextId = "remoteOperateLogService",path = "/provider/v1/operateLog", fallback = RemoteOperateLogServiceFallbackImpl.class)
public interface RemoteOperateLogService {
    @PostMapping("create")
    ResponseEntity<Void> create(@RequestBody OperateLogDTO operateLogDTO);

先直接看一下FactoryBean返回的bean信息:

可以看见返回的是HystrixInvocationHandler代理对象:

target属性为当前代理的对象信息,name为服务名称(对应注册中心的名称),url为访问地址,type为原始接口全限定名

dispatch属性为LinkedHashMap类型,key为调用的方法(当前只有一个方法),value为SynchronousMethodHandler类型的代理对象

继续追踪FeignClientFactoryBean#getObject方法:

@Override
public Object getObject() throws Exception {
    return getTarget();
}

<T> T getTarget() {
    FeignContext context = this.applicationContext.getBean(FeignContext.class);
    Feign.Builder builder = feign(context);
 
    if (!StringUtils.hasText(this.url)) {
        if (!this.name.startsWith("http")) {
            this.url = "http://" + this.name;
        }
        else {
            this.url = this.name;
        }
        this.url += cleanPath();
        return (T) loadBalance(builder, context,
                new Target.HardCodedTarget<>(this.type, this.name, this.url));
    }
    if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
        this.url = "http://" + this.url;
    }
    String url = this.url + cleanPath();
    Client client = getOptional(context, Client.class);
    if (client != null) {
        if (client instanceof LoadBalancerFeignClient) {
            // not load balancing because we have a url,
            // but ribbon is on the classpath, so unwrap
            client = ((LoadBalancerFeignClient) client).getDelegate();
        }
        builder.client(client);
    }
    Targeter targeter = get(context, Targeter.class);
    return (T) targeter.target(this, builder, context,
            new Target.HardCodedTarget<>(this.type, this.name, url));
}

上面主要完成了

1获取在FeignAutoConfiguration中注入的FeignContext;

2、调用feign方法,获取构建器

protected Feign.Builder feign(FeignContext context) {
    FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
    Logger logger = loggerFactory.create(this.type);
 
    // @formatter:off
    Feign.Builder builder = get(context, Feign.Builder.class)
            // required values
            .logger(logger)
            .encoder(get(context, Encoder.class))
            .decoder(get(context, Decoder.class))
            .contract(get(context, Contract.class));
    // @formatter:on
 
    configureFeign(context, builder);
 
    return builder;
}

根据FeignContext处理了我们自定义的@FeignClient以及全局@EnableFeignClients中配置的日志,加、解密器,验证器等,最后configureFeign如下,处理了链接超时、读取超时等配置项:

protected void configureFeign(FeignContext context, Feign.Builder builder) {
    FeignClientProperties properties = this.applicationContext.getBean(FeignClientProperties.class);
    if (properties != null) {
        if (properties.isDefaultToProperties()) {
            configureUsingConfiguration(context, builder);
            configureUsingProperties(
                    properties.getConfig().get(properties.getDefaultConfig()),
                    builder);
            configureUsingProperties(properties.getConfig().get(this.contextId),
                    builder);
        } else {
            configureUsingProperties(
                    properties.getConfig().get(properties.getDefaultConfig()),
                    builder);
            configureUsingProperties(properties.getConfig().get(this.contextId),
                    builder);
            configureUsingConfiguration(context, builder);
        }
    } else {
        configureUsingConfiguration(context, builder);
    }
}

3、处理链接的Http或Https请求

4、根据Targeter(获取子类DefaultTargeter或HystrixTargeter),使用InvocationHandlerFactory工厂创建代理对象DefaultInvocationHandler或SynchronousMethodHandler【当前创建了该类型】。上面的截图已经非常清晰了

5、再调用Targeter内部类HardCodedTarget#terget方法,返回了创建的代理对象,结构如上图

2、方法调用【动态代理的执行invoke过程】

如上创建的代理对象结构,当真正访问@FeignClient接口时,就是层层拆代理的过程,主要代理对象有HystrixInvocationHandler:处理@FeignClient对应的接口类,其Map结构的属性dispatch的存储了接口方法与调用的代理SynchronousMethodHandler

1、HystrixInvocationHandler#invoke方法                      
   

   1)、创建HystrixCommand对象,内置了两个回调函数。Http请求的真正调用HystrixInvocationHandler.this.dispatch.get(method).invoke(args);

    2)、getFallback方法,预制了我们配置的降级方法或降级方法工厂

    3)、再调用该对象的execute或toObservable().toCompletable()方法,执行请求

2、SynchronousMethodHandler#invoke 

 真正每个方法的调用,根据Method获取到SynchronousMethodHandler查看其invoke方法:

@Override
public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Request.Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
        try {
            return executeAndDecode(template, options);
        } catch (RetryableException e) {
            try {
                retryer.continueOrPropagate(e);
            } catch (RetryableException th) {
                Throwable cause = th.getCause();
                if (propagationPolicy == UNWRAP && cause != null) {
                    throw cause;
                } else {
                    throw th;
                }
            }
            if (logLevel != Logger.Level.NONE) {
                logger.logRetry(metadata.configKey(), logLevel);
            }
            continue;
        }
    }
}

1、获取RequestTemplate类型的对象,为Ribbon的封装

2、如果我们引入了spring-retry重试机制,那么根据Retryer对象在try finally创建重试代码

3、executeAndDecode执行请求和编解码

    1)、遍历所有的RequestInterceptor拦截器进行处理

    2)、获取装饰器LoadBalancerFeignClient进行请求处理,真正的处理过程会交给我们根据优先级配置的 ApacheHttpClient、OkHttpClient或Client.Default。

4、最后除了对编解码进行处理,还有http 404等请求状态作处理
整个代理的创建,以及方法调用【执行代理的invoke方法】,就完成了Feign的整个过程

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值