源码学习之【OpenFeign工作原理】


一、SpringBoot中使用 openFeign

SpringBoot项目中使用openFeign是很简单的,只需要四步

1-1、导入pom文件

<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-openfeign</artifactId>
     <version>2.1.3.RELEASE</version>
 </dependency>


1-2、在启动类加入开启feign注解

@EnableFeignClients


1-3、在具体的接口上加feign注解标签

@FeignClient(value = "backend", url = "${marketCloudBackend.url}", configuration = BackendFeignConfiguration.class)
public interface MarketCloudBackendFeign 
{

    @PostMapping(value = "/visitor/aiCallDisVisitor", consumes = "application/json", produces = "application/json")
    Object distributePublicCustomer(@RequestBody T params);
}


1-4、在使用的地方注入

@Resource
private MarketCloudBackendFeign marketCloudBackendFeign;

二、openFeign的工作原理


我们知道openFeign是一个rpc远程调用框架,目的是使得我们调用接口和调用本地方法一样简单,本质上还是需要http请求。

openFeign是通过jdk代理来实现这个操作的,下面我们将通过源码一步步探寻它的运作原理。

我们的目的很简单,因为我们在使用的时候是直接进行注入的,我们只需要知道每一个接口是如何被注入到Spring容器的,找到代理所做的事情即可。


2-1、@EnableFeignClients

一切的都是从@EnableFeignClients注解开始:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients 
{
    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<?>[] defaultConfiguration() default {};

    Class<?>[] clients() default {};
}

除去@Retention、@Target、@Documented这几个通用注解外,我们看到还有一个@Import 并且引入了 FeignClientsRegistrar.class


2-2、FeignClientsRegistrar

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrarResourceLoaderAwareEnvironmentAware {}

FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar 并重写了里面的方法registerBeanDefinitions会自动调用这个方法进行初始化。

FeignClientsRegistrar里面重写registerBeanDefinitions的逻辑如下

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    this.registerDefaultConfiguration(metadata, registry);
    this.registerFeignClients(metadata, registry);
}


2-3、registerFeignClients

这个方法主要是去获取使用了@FeignClient的接口,找到之后进行注入。

这里有两种方式,如果你使用了@EnableFeignClients 的 clients那它就直接去获取对应的class,不通过类路径自动扫描

@EnableFeignClients(clients = {SchedualServiceHi.classSchedualServiceHi2.class})
    public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 获取@EnableFeignClients注解里面的参数
        Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
        Class<?>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));
        Object basePackages;
        // 如果使用了clients 就根据class加载
        if (clients != null && clients.length != 0) {
            ((Set)basePackages).add(ClassUtils.getPackageName(clazz));
        } else {
            // 否则使用类路径加载
            basePackages = this.getBasePackages(metadata);
        }
        // 迭代全部的接口
        Iterator var17 = ((Set)basePackages).iterator();
        while(var17.hasNext()) {
            String basePackage = (String)var17.next();
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
            Iterator var21 = candidateComponents.iterator();
            while(var21.hasNext()) {
                BeanDefinition candidateComponent = (BeanDefinition)var21.next();
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
                    // 进行下一步的注册
                    this.registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
    }

2-4、registerFeignClient

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
   // ....
    BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
   // ...

可以看到registerFeignClient方法里面生成了一个BeanDefinitionBuilder,而入参是FeignClientFactoryBean


2-5、FeignClientFactoryBean

class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBeanApplicationContextAware {}

可以看到它是实现了FactoryBean 里面有一个getObject是获取bean实例的,FeignClientFactoryBean 重写了这个 getObject

public Object getObject() throws Exception {
    return this.getTarget();
}

<T> getTarget() {
    FeignContext context = (FeignContext)this.applicationContext.getBean(FeignContext.class);
    Builder builder = this.feign(context);
    // url 为 null 、""、" "  StringUtils.hasText 就返回false
    if (!StringUtils.hasText(this.url)) {
        // ......
        
    } else {
        // ......
        Targeter targeter = (Targeter)this.get(context, Targeter.class);
        return targeter.target(this, builder, context, new HardCodedTarget(this.type, this.name, url));
    }
}

Targeter 是一个接口,它有两个实现类,我们来看默认的实现类

源码学习之【OpenFeign工作原理】

注:这个FactoryBean接口,可以理解成是Spring的一个钩子,它的getObject方法,是在初始化bean的时候会去调用。(暂时大家先这么理解后面再写一篇文章解释,里面也很复杂)


2-6、DefaultTargeter

class DefaultTargeter implements Targeter {
    DefaultTargeter() {
    }

    public <T> target(FeignClientFactoryBean factory, Builder feign, FeignContext context, HardCodedTarget<T> target) {
        return feign.target(target);
    }
}

target 最终调用的是 Feign.class 方法,如下,可以看到最终是调用ReflectiveFeign.newInstance

public <T> target(Target<T> target) {
    return this.build().newInstance(target);
}

public Feign build() {
    Factory synchronousMethodHandlerFactory = new Factory(this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy);
    ParseHandlersByName handlersByName = new ParseHandlersByName(this.contract, this.options, this.encoder, this.decoder, this.queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
    return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory, this.queryMapEncoder);
}


2-7、ReflectiveFeign.newInstance

newInstance方法里面有一个JDK代理,所以openfeign其实是基于JDK代理来实现的

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

JDK代理是基于接口代理的,当然最主要的还是我们的InvocationHandler,它代表了我们代理对象最终指向的内容,所以下面我们就要来看看这个handler是如何产生的。


2-8、this.targetToHandlersByName.apply(target);

第一行代码,就是获取当前对象的方法

this.contract.parseAndValidatateMetadata(key.type());

最后返回 类名#方法名 为key,MethodHandler 为value的map

存入map的时候调用的方法是

 this.factory.create(key, md, (Factory)buildTemplate, this.options, this.decoder, this.errorDecoder))

点进去这个 create 方法,我们看到是new了一个对象,这个类是实现了MethodHandler

return new SynchronousMethodHandler(target, this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, md, buildTemplateFromArgs, options, decoder, errorDecoder, this.decode404, this.closeAfterDecode, this.propagationPolicy);

记住这个 SynchronousMethodHandler 后面还会看到


2-9、this.factory.create(target, methodToHandler);

我们再返回newInstance 方法,可以看到最终是调用了一个 this.factory.create 来创建 InvocationHandler

InvocationHandlerFactorycreate 里面自己就对这个接口进行实现 return new FeignInvocationHandler(target, dispatch);


2-10、FeignInvocationHandler

FeignInvocationHandler 实现了 InvocationHandler 接口,重写了里面的 invoke 方法

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

可以看到它最终是调用MethodHandler的 invoke方法。


2-11、SynchronousMethodHandler

上面我们说到最终的MethodHandler其实是SynchronousMethodHandler,那么我们只需要看这个里面的invoke方法就好了

这个里面其实就是显现了http的调用

public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = this.buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();

    while(true) {
        try {
            return this.executeAndDecode(template);
        } catch (RetryableException var8) {
           // 为了方面看,我去除了异常处理部分
        }
    }
}





原文始发于微信公众号(小道仙97):源码学习之【OpenFeign工作原理】

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/41313.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!