源码探究-openfeign

本文分两个部分:

1、项目启动,openfeign如何加载远程接口实例化并且注入到容器中

2、远程调用实现原理

1、openfeign加载

1.1、在启动类加入开启feign注解
@EnableFeignClients
1.2、远程调用接口

1.3、在使用的地方注入

1.4、openfeign工作原理

我们知道openfeign是一个rpc远程调用框架,其本质上还是http请求访问。

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

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

1.5、@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 {};

}

发现有个@Import(FeignClientsRegistrar.class) ,这其实就是将FeignClientsRegistrar类注入,启动类运行会先引入此类。

1.6 FeignClientsRegistrar
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar 并重写了里面的方法registerBeanDefinitions会自动调用这个方法进行初始化。所以我们在看看重写的registerBeanDefinitions做了什么。

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    //注册默认配置
    this.registerDefaultConfiguration(metadata, registry);
    //注册feign客户端
    this.registerFeignClients(metadata, registry);
}
1.7 registerFeignClients

此方法扫描使用了@FeignClient注解的接口并进行注入

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

@EnableFeignClients(clients = {SchedualServiceHi.class, SchedualServiceHi2.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);
                }
            }
        }
    }
1.8 registerFeignClient
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
			Map<String, Object> attributes) {
    //...
	BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
    //...
}

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

1.9 FeignClientFactoryBean
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {}

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

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

<T> 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 是一个接口,它有两个实现类,我们来看默认的实现类

这个FactoryBean接口,可以理解成是Spring的一个钩子,它的getObject方法,是在初始化bean的时候会去调用。

target 最终调用的是 Feign.class 方法

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

public Feign build() {
            Client client = (Client)Capability.enrich(this.client, this.capabilities);
            Retryer retryer = (Retryer)Capability.enrich(this.retryer, this.capabilities);
            List<RequestInterceptor> requestInterceptors = (List)this.requestInterceptors.stream().map((ri) -> {
                return (RequestInterceptor)Capability.enrich(ri, this.capabilities);
            }).collect(Collectors.toList());
            Logger logger = (Logger)Capability.enrich(this.logger, this.capabilities);
            Contract contract = (Contract)Capability.enrich(this.contract, this.capabilities);
            Request.Options options = (Request.Options)Capability.enrich(this.options, this.capabilities);
            Encoder encoder = (Encoder)Capability.enrich(this.encoder, this.capabilities);
            Decoder decoder = (Decoder)Capability.enrich(this.decoder, this.capabilities);
            InvocationHandlerFactory invocationHandlerFactory = (InvocationHandlerFactory)Capability.enrich(this.invocationHandlerFactory, this.capabilities);
            QueryMapEncoder queryMapEncoder = (QueryMapEncoder)Capability.enrich(this.queryMapEncoder, this.capabilities);
            SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);
            ReflectiveFeign.ParseHandlersByName handlersByName = new ReflectiveFeign.ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
            return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

build()返回一个ReflectiveFeign最后创建一个新实例

public <T> T newInstance(Target<T> target) {
        Map<String, InvocationHandlerFactory.MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
        Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler = new LinkedHashMap();
        List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList();
        Method[] var5 = target.type().getMethods();
        int var6 = var5.length;

        for(int var7 = 0; var7 < var6; ++var7) {
            Method method = var5[var7];
            if (method.getDeclaringClass() != Object.class) {
                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)));
                }
            }
        }

        InvocationHandler handler = this.factory.create(target, methodToHandler);
        T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
        Iterator var12 = defaultMethodHandlers.iterator();

        while(var12.hasNext()) {
            DefaultMethodHandler defaultMethodHandler = (DefaultMethodHandler)var12.next();
            defaultMethodHandler.bindTo(proxy);
        }

        return proxy;
    }

newInstance方法里面有一个JDK代理,所以openfeign其实是基于JDK代理来实现的。JDK代理是基于接口代理的,所以主要还是我们的InvocationHandler,它代表了我们代理对象最终指向的内容,所以下面我们就要来看看这个handler是如何产生的。

 1.10 this.targetToHandlersByName.apply(target);
public Map<String, InvocationHandlerFactory.MethodHandler> apply(Target target) {
            List<MethodMetadata> metadata = this.contract.parseAndValidateMetadata(target.type());
            Map<String, InvocationHandlerFactory.MethodHandler> result = new LinkedHashMap();
            Iterator var4 = metadata.iterator();

            while(var4.hasNext()) {
                MethodMetadata md = (MethodMetadata)var4.next();
                Object buildTemplate;
                if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
                    buildTemplate = new BuildFormEncodedTemplateFromArgs(md, this.encoder, this.queryMapEncoder, target);
                } else if (md.bodyIndex() == null && !md.alwaysEncodeBody()) {
                    buildTemplate = new BuildTemplateByResolvingArgs(md, this.queryMapEncoder, target);
                } else {
                    buildTemplate = new BuildEncodedTemplateFromArgs(md, this.encoder, this.queryMapEncoder, target);
                }

                if (md.isIgnored()) {
                    result.put(md.configKey(), (args) -> {
                        throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
                    });
                } else {
                    result.put(md.configKey(), this.factory.create(target, md, (RequestTemplate.Factory)buildTemplate, this.options, this.decoder, this.errorDecoder));
                }
            }

            return result;
        }

this.contract.parseAndValidateMetadata(target.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);

而这一个 apply我们会走很多遍,一个远程接口每个方法都会走一遍

1.11 this.factory.create(target, methodToHandler)

我们再返回newInstance 方法,可以看到最终是调用了一个 this.factory.create 来创建 InvocationHandler,InvocationHandlerFactory 的 create 里面自己就对这个接口进行实现 return new FeignInvocationHandler(target, dispatch);

1.12 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() : ((InvocationHandlerFactory.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、远程调用实现原理

2.1 SynchronousMethodHandler

由于上面FeignInvocationHandler.invoke()最后返回的是((InvocationHandlerFactory.MethodHandler)this.dispatch.get(method)).invoke(args),而这个MethodHandler实际上就是SynchronousMethodHandler

发现最后方法参数会在this.buildTemplateFromArgs.create(argv);返回请求模板,最后去访问executeAndDecode方法,也就是真正执行http请求的方法执行并且解码。

 执行真正的http请求,至此feign调用原理走完了

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 安卓码合集-csdn是一个非常有用的工具和资网站,提供了大量的开安卓项目码供开发者学习和使用。 首先,这个合集汇集了来自开社区的各种优秀的安卓项目码。这些码不仅涵盖了安卓开发的各个方面,如UI界面、网络通信、数据存储等,还有一些特定领域的应用,如音视频处理、图像处理、机器学习等。从这些码中,开发者可以学习到各种技术和知识,拓宽自己的视野和开发能力。 其次,通过这个合集,开发者可以找到适合自己需求的开项目码。不同的项目可能有不同的功能和特点,可以根据自己的需求进行选择和使用。有些码可能是完整的应用程序,可以直接使用或基于此进行二次开发;有些码可能是某个功能模块的实现,可以作为学习参考或直接集成到自己的项目中。 此外,这个合集还提供了码的下载和查看方式。开发者可以通过下载码来进行学习和使用,也可以在线查看码进行参考。对于一些比较复杂的项目,还提供了详细的项目文档和使用说明,方便开发者的使用和理解。 总的来说,安卓码合集-csdn为开发者提供了一个集成了优秀开项目码的平台,为开发者学习和使用安卓开发技术提供了便利。无论是初学者还是有一定经验的开发者,都可以在这里找到适合自己的项目码,提升自己的开发水平和能力。 ### 回答2: 安卓码合集 -csdn 是一个在 CSDN 上收集和分享安卓开项目的一个资合集。CSDN(博客专区)是一个面向IT技术人员的专业社区平台,它提供了丰富的技术资和社交交流的平台。 安卓码合集 -csdn 这个资合集汇集了众多优秀的安卓开项目,包括应用程序码、工具类库、框架和示例代码等。通过该合集,开发者们可以快速地找到他们感兴趣的项目,并获取码用于学习和参考。 这个合集的优点在于它的更新频率高,可以及时收录最新的开项目。同时,合集中的项目都经过精心筛选,保证了其质量和可靠性。此外,该合集还提供了搜索功能,开发者们可以根据关键词进行搜索,找到适合自己需求的项目。 使用安卓码合集 -csdn,开发者们可以快速地找到自己需要的安卓开项目,可以通过学习他人的代码,了解他们的实现思路和技术。对于新手开发者来说,这个合集也可以作为一个学习的平台,他们可以通过阅读和理解他人的代码,提高自己的编码能力。 总之,安卓码合集 -csdn 是一个非常有用的资合集,可以帮助开发者们快速找到优秀的安卓开项目,提高自己的技术水平。无论是初学者还是资深开发者,都可以从中受益。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值