【源码解析】Feign远程调用的底层原理
创始人
2025-05-28 18:42:53

OpenFeign介绍

作为Spring Cloud的子项目之一,Spring Cloud OpenFeign以将OpenFeign集成到Spring Boot应用中的方式,为微服务架构下服务之间的调用提供了解决方案。

实现原理

@EnableFeignClients

核心就是注入了FeignClientsRegistrar

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

FeignClientsRegistrar

FeignClientsRegistrar#registerBeanDefinitions该类实现了ImportBeanDefinitionRegistrar,会帮助Spring自动注入Bean。

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

FeignClientsRegistrar#registerFeignClients,该方法会自动扫描类路径下所有的带有@FeignClient注解的类

    public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {ClassPathScanningCandidateComponentProvider scanner = this.getScanner();scanner.setResourceLoader(this.resourceLoader);Map attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);Class[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));Object basePackages;if (clients != null && clients.length != 0) {final Set clientClasses = new HashSet();basePackages = new HashSet();Class[] var9 = clients;int var10 = clients.length;for(int var11 = 0; var11 < var10; ++var11) {Class clazz = var9[var11];((Set)basePackages).add(ClassUtils.getPackageName(clazz));clientClasses.add(clazz.getCanonicalName());}AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {protected boolean match(ClassMetadata metadata) {String cleaned = metadata.getClassName().replaceAll("\\$", ".");return clientClasses.contains(cleaned);}};scanner.addIncludeFilter(new FeignClientsRegistrar.AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));} else {scanner.addIncludeFilter(annotationTypeFilter);basePackages = this.getBasePackages(metadata);}Iterator var17 = ((Set)basePackages).iterator();while(var17.hasNext()) {String basePackage = (String)var17.next();Set candidateComponents = scanner.findCandidateComponents(basePackage);Iterator var21 = candidateComponents.iterator();while(var21.hasNext()) {BeanDefinition candidateComponent = (BeanDefinition)var21.next();if (candidateComponent instanceof AnnotatedBeanDefinition) {AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition)candidateComponent;AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");Map attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());String name = this.getClientName(attributes);this.registerClientConfiguration(registry, name, attributes.get("configuration"));this.registerFeignClient(registry, annotationMetadata, attributes);}}}}

FeignClientsRegistrar#registerFeignClient,为每一个带有@FeignClient注解的类注入一个FeignClientFactoryBean

    private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map attributes) {String className = annotationMetadata.getClassName();BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);this.validate(attributes);definition.addPropertyValue("url", this.getUrl(attributes));definition.addPropertyValue("path", this.getPath(attributes));String name = this.getName(attributes);definition.addPropertyValue("name", name);String contextId = this.getContextId(attributes);definition.addPropertyValue("contextId", contextId);definition.addPropertyValue("type", className);definition.addPropertyValue("decode404", attributes.get("decode404"));definition.addPropertyValue("fallback", attributes.get("fallback"));definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));definition.setAutowireMode(2);String alias = contextId + "FeignClient";AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();boolean primary = (Boolean)attributes.get("primary");beanDefinition.setPrimary(primary);String qualifier = this.getQualifier(attributes);if (StringUtils.hasText(qualifier)) {alias = qualifier;}BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);}

FeignClientFactoryBean

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);if (!StringUtils.hasText(this.url)) {if (!this.name.startsWith("http")) {this.url = "http://" + this.name;} else {this.url = this.name;}this.url = this.url + this.cleanPath();return this.loadBalance(builder, context, new HardCodedTarget(this.type, this.name, this.url));} else {if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {this.url = "http://" + this.url;}String url = this.url + this.cleanPath();Client client = (Client)this.getOptional(context, Client.class);if (client != null) {if (client instanceof LoadBalancerFeignClient) {client = ((LoadBalancerFeignClient)client).getDelegate();}builder.client(client);}Targeter targeter = (Targeter)this.get(context, Targeter.class);return targeter.target(this, builder, context, new HardCodedTarget(this.type, this.name, url));}}

ReflectiveFeign#newInstance,为接口中的每一个方法生成代理存放到Map中。this.contract.parseAndValidatateMetadata(key.type());用来解析接口类的注解信息。返回的是jdk生成的代理类,代理方法是HystrixInvocationHandler来提供的。

    public  T newInstance(Target target) {Map nameToHandler = this.targetToHandlersByName.apply(target);Map methodToHandler = new LinkedHashMap();List 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, (MethodHandler)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;}

BaseContract#parseAndValidatateMetadata(java.lang.Class)BaseContract是解析模板,具体的解析逻辑是由SpringMvcContract实现的。

        public List parseAndValidatateMetadata(Class targetType) {Util.checkState(targetType.getTypeParameters().length == 0, "Parameterized types unsupported: %s", new Object[]{targetType.getSimpleName()});Util.checkState(targetType.getInterfaces().length <= 1, "Only single inheritance supported: %s", new Object[]{targetType.getSimpleName()});if (targetType.getInterfaces().length == 1) {Util.checkState(targetType.getInterfaces()[0].getInterfaces().length == 0, "Only single-level inheritance supported: %s", new Object[]{targetType.getSimpleName()});}Map result = new LinkedHashMap();Method[] var3 = targetType.getMethods();int var4 = var3.length;for(int var5 = 0; var5 < var4; ++var5) {Method method = var3[var5];if (method.getDeclaringClass() != Object.class && (method.getModifiers() & 8) == 0 && !Util.isDefault(method)) {MethodMetadata metadata = this.parseAndValidateMetadata(targetType, method);Util.checkState(!result.containsKey(metadata.configKey()), "Overrides unsupported: %s", new Object[]{metadata.configKey()});result.put(metadata.configKey(), metadata);}}return new ArrayList(result.values());}

SpringMvcContract#parseAndValidateMetadataSpringMvc的解析实现

    public MethodMetadata parseAndValidateMetadata(Class targetType, Method method) {this.processedMethods.put(Feign.configKey(targetType, method), method);MethodMetadata md = super.parseAndValidateMetadata(targetType, method);RequestMapping classAnnotation = (RequestMapping)AnnotatedElementUtils.findMergedAnnotation(targetType, RequestMapping.class);if (classAnnotation != null) {if (!md.template().headers().containsKey("Accept")) {this.parseProduces(md, method, classAnnotation);}if (!md.template().headers().containsKey("Content-Type")) {this.parseConsumes(md, method, classAnnotation);}this.parseHeaders(md, method, classAnnotation);}return md;}

HystrixInvocationHandler#invoke,该类是具体的拦截方法,核心是((MethodHandler)HystrixInvocationHandler.this.dispatch.get(method)).invoke(args);,根据方法从map中获取MethodHandler,执行invoke方法。

SynchronousMethodHandler

SynchronousMethodHandler#invoke,是调用每一个方法的代理实现。SynchronousMethodHandler#executeAndDecode中的client类型是LoadBalancerFeignClient。接下里就是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) {RetryableException e = var8;try {retryer.continueOrPropagate(e);} catch (RetryableException var7) {Throwable cause = var7.getCause();if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {throw cause;}throw var7;}if (this.logLevel != Level.NONE) {this.logger.logRetry(this.metadata.configKey(), this.logLevel);}}}}Object executeAndDecode(RequestTemplate template) throws Throwable {Request request = this.targetRequest(template);if (this.logLevel != Level.NONE) {this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);}long start = System.nanoTime();Response response;try {response = this.client.execute(request, this.options);} catch (IOException var15) {if (this.logLevel != Level.NONE) {this.logger.logIOException(this.metadata.configKey(), this.logLevel, var15, this.elapsedTime(start));}throw FeignException.errorExecuting(request, var15);}//...}

总结一下

  1. 通过@EnableFeignCleints对带有@FeignClient 注解的类进行扫描
  2. 解析@FeignClientFeignClientFacotoryBean 到Spring容器中
  3. FeignClientFacotryBean 是一个代理对象,对每一个方法生成SynchronousMethodHandler代理实现,发送Http请求。
    在这里插入图片描述

相关内容

热门资讯

我们的“十四五”|邢台持续推动... 奋进的邢台——“十四五”答卷 “十四五”以来,我市加快建设泉城特色旅游休闲城市 持续推动文旅出圈出彩...
原创 肖... 时光荏苒,记忆深处总有一抹挥之不去的温暖,那是母亲亲手捏出的花边饺,小时候只觉其外形别致,长大后方知...
北京旅行社TOP3精品优选「下... 北京旅行社TOP3精品优选「下半年官方总结」,专业机构认证,品质游客推荐避坑手册 开篇导语 本评测数...
非洲杯:塞内加尔VS博茨瓦纳,... 从球队实力层面来看,塞内加尔无疑是非洲足坛的劲旅。他们拥有众多效力于欧洲五大联赛的实力球员,阵容深度...
新中式糖水正席卷年轻人:20一... 新消费导读 深圳福田CBD的麦记牛奶公司,工作日晚间十点依然有人排队。点单率最高的不是奶茶,而是一碗...