2023OpenFeign源码

news2024/11/17 15:53:19

原理-源码

让我们看着源~码~ 按顺序走~趟流~程~
分为两大部分:启动原理、调用流程
Feign 的Java 代码才 3w 多行,放眼现在热门的开源项目, Dubbo、Naocs、Skywalking 中 Java 代码都要 30w 行起步。

重要通知!看源码,就上新东方 一定要找对地方!!!
在一个SpringCloud项目中,我们引入了spring-cloud-openfeign依赖,而该依赖又引入了openfeign-core的依赖;
他们分别对应的github代码仓库分别是spring-cloud-openfeign和OpenFeign/feign

  • spring-cloud-openfeign是openFeign和SpringCloud的结合,我们的@EnableFeignClients注解、服务启动时的初始化工作就在此项目中

  • openfeign-core是OpenFeign/feign仓库的core模块,该仓库是feign的“娘家”,单独作为远程调用组件而存在的feign,没有“嫁入Spring Cloud豪门”(未被整合进Spring Cloud成套解决方案的单独组件,若单独使用,则启动流程略有不同)

    • 比如不会随启动自动注册Spring Bean,没有我们刚学的一些注解,
    • 仓库内还包含了不需要学习的测试模块、备选的http库实现等。

比如启动流程,在注册、代理Bean的过程中,会先从SpringCloud OpenFeign的源码看起,而其中引入的Feign包下的源码,则需要转到OpenFeign/feign仓库;
本文为了方便大家学习,会将他们整合到一起,需要跨仓库的地方会提一嘴,做到梳理流程无痛衔接。毕竟我们学习目的是流程原理,而不是为了学某一具体仓库。

依赖

总项目

			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-openfeign-dependencies</artifactId>
				<version>${project.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>

core模块

		<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-core</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>io.github.openfeign.form</groupId>
			<artifactId>feign-form-spring</artifactId>
			<exclusions>
<!--				Vulnerable in 3.8.0-->
				<exclusion>
					<groupId>commons-io</groupId>
					<artifactId>commons-io</artifactId>
				</exclusion>
				<exclusion>
					<groupId>commons-fileupload</groupId>
					<artifactId>commons-fileupload</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-slf4j</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-micrometer</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-hc5</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-okhttp</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-java11</artifactId>
			<optional>true</optional>
		</dependency>

辞典

contextId: FeignClient的容器id,通过该id从Spring容器中获取Bean;

代码风格

Builder\Factory都定义在自己内部。看到XXX.Builder,就知道是用来创建XXX的。
几乎所有获取对象的方法,都是用target()方法,内部build().newInstance()

OpenFeign,启动!(原理)

服务启动阶段,我们需要读取配置、注册Bean;
需要注册的Bean组件有:

启动时factories

org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration
org.springframework.cloud.openfeign.FeignAutoConfiguration
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration
  • 这个和注解扫描谁先谁后?
  • 注解是怎么扫描的?

入口类注解@EnableFeignClients

@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
    String[] value() default {};

    String[] basePackages() default {};

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

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

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

@Import 常用于动态注册Spring Bean
请添加图片描述
FeignClientsRegistrar.class实现ResourceLoaderAware、EnvironmentAware接口, 重写Set方法为 FeignClientsRegistrar 中两个属性 resourceLoader、environment 赋值,

  • can can spring

ImportBeanDefinitionRegistrar 负责动态注入 IOC Bean,分别注入 Feign 配置类、FeignClient Bean
FeignClientsRegistrar源码

// 资源加载器,可以加载 classpath 下的所有文件
private ResourceLoader resourceLoader;
// 上下文,可通过该环境获取当前应用配置属性等
private Environment environment;
//..
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
   // 注册 @EnableFeignClients 提供的自定义配置类中的相关 Bean 实例
    registerDefaultConfiguration(metadata,registry);
    // 扫描 packge,注册被 @FeignClient 修饰的接口类为 IOC Bean
    registerFeignClients(metadata, registry);
}
  • 为什么有了factory还要有factoryBean?

注册 FeignClient 接口

registerFeignClients方法处理带有 @FeignClient 的接口

1.扫描 @EnableFeignClients 注解,如果有在clients属性上配置client,则加载指定接口;若没有配置,则用 scanner在bashPackage下扫描出被@FeignClient 修饰的接口
2.获取 @FeignClient 上的属性,根据 configuration 属性去创建接口级的 FeignClientSpecification 配置类 IOC Bean
3.将 @FeignClient 注解上的属性设置到 FeignClientFactoryBean 对象上,并注册 IOC Bean

@FeignClient 修饰的接口使用了 Spring 的代理工厂生成代理类,所以这里会把修饰了 @FeignClient 接口的 BeanDefinition 设置为 FeignClientFactoryBean 类型,而 FeignClientFactoryBean 继承自 FactoryBean

根据Spring加载Bean的过程,当我们用@FeignClient 修饰interface时,注册到 IOC 容器中 Bean 类型其实是其工厂 FeignClientFactoryBean;当我们要获取这个bean时,再调用工厂的getObjtct()生产出bean;

之前我们手写工厂模式的案例的时候,在创建对象时经常是亲自调用Factory的创建方法;
但在Spring中,(作为中间层的容器层?)Spring统一包揽了Bean对象的创建和管理。我们可以只要无脑getbean,而具体细节,如是先用工厂创建还是直接返回之前的Bean ,直接由中间的Spring管理了,我们只要无脑get。甚至在注解方式下,Get都省了,直接@Autowired。

  • 这里既有代理(?)又有工厂的思想。代理下,我们只管找中间层要bean这一“产品”,别管这东西哪儿来的,甚至别管有没有生产出来,这让代理想办法;
    • 而代理如果缓存里有Bean就直接返回,没有的话他自己再找工厂要。这种具体的小事不要烦调用它的程序猿。
    • 突然有种当领导的感觉,哈哈。大决战的林总。
  • 如果说工厂模式解决了创建对象不需要知道具体对象细节的问题,那么工厂+代理就一条龙解决了不用知道工厂细节、管理细节的问题,哈哈。

在 Spring 中,FactoryBean 是一个工厂 Bean,用来创建代理 Bean。/对于需要获取 Bean 的消费者而言,它是不知道 Bean 是普通 Bean 或是工厂 Bean 的。工厂 Bean 返回的实例不是工厂 Bean 本身,而是会返回执行了工厂 Bean 中 FactoryBean#getObject 逻辑的实例

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet();
// if @EnableFeignClients参数中有name属性的情况,此处暂且省略
 else {
            ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
            scanner.setResourceLoader(this.resourceLoader);
            scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            Set<String> basePackages = this.getBasePackages(metadata);
            Iterator var8 = basePackages.iterator();

            while(var8.hasNext()) {
                String basePackage = (String)var8.next();
                candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
            }
        }

        Iterator var13 = candidateComponents.iterator();

        while(var13.hasNext()) {
            BeanDefinition candidateComponent = (BeanDefinition)var13.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<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
                String name = this.getClientName(attributes);
                this.registerClientConfiguration(registry, name, attributes.get("configuration"));
                this.registerFeignClient(registry, annotationMetadata, attributes);
            }
        }

    }

    private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        Class clazz = ClassUtils.resolveClassName(className, (ClassLoader)null);
        ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory)registry : null;
        String contextId = this.getContextId(beanFactory, attributes);
        String name = this.getName(attributes);
        FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
        factoryBean.setBeanFactory(beanFactory);
        factoryBean.setName(name);
        factoryBean.setContextId(contextId);
        factoryBean.setType(clazz);
        factoryBean.setRefreshableClient(this.isClientRefreshEnabled());
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
            factoryBean.setUrl(this.getUrl(beanFactory, attributes));
            factoryBean.setPath(this.getPath(beanFactory, attributes));
            factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
            Object fallback = attributes.get("fallback");
            if (fallback != null) {
                factoryBean.setFallback(fallback instanceof Class ? (Class)fallback : ClassUtils.resolveClassName(fallback.toString(), (ClassLoader)null));
            }

            Object fallbackFactory = attributes.get("fallbackFactory");
            if (fallbackFactory != null) {
                factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class)fallbackFactory : ClassUtils.resolveClassName(fallbackFactory.toString(), (ClassLoader)null));
            }

            return factoryBean.getObject();
        });
        definition.setAutowireMode(2);
        definition.setLazyInit(true);
        this.validate(attributes);
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        beanDefinition.setAttribute("factoryBeanObjectType", className);
        beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
        boolean primary = (Boolean)attributes.get("primary");
        beanDefinition.setPrimary(primary);
        String[] qualifiers = this.getQualifiers(attributes);
        if (ObjectUtils.isEmpty(qualifiers)) {
            qualifiers = new String[]{contextId + "FeignClient"};
        }

        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
        this.registerOptionsBeanDefinition(registry, contextId);
    }

    }

相关类

 protected ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                boolean isCandidate = false;
                if (beanDefinition.getMetadata().isIndependent() && !beanDefinition.getMetadata().isAnnotation()) {
                    isCandidate = true;
                }

                return isCandidate;
            }
        };
    }

    protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());
        Set<String> basePackages = new HashSet();
        String[] var4 = (String[])((String[])attributes.get("value"));
        int var5 = var4.length;

        int var6;
        String pkg;
        for(var6 = 0; var6 < var5; ++var6) {
            pkg = var4[var6];
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }

        var4 = (String[])((String[])attributes.get("basePackages"));
        var5 = var4.length;

        for(var6 = 0; var6 < var5; ++var6) {
            pkg = var4[var6];
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }

        Class[] var8 = (Class[])((Class[])attributes.get("basePackageClasses"));
        var5 = var8.length;

        for(var6 = 0; var6 < var5; ++var6) {
            Class<?> clazz = var8[var6];
            basePackages.add(ClassUtils.getPackageName(clazz));
        }

        if (basePackages.isEmpty()) {
            basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
        }

        return basePackages;
    }

请添加图片描述
依据 Spring InitializingBean 接口:FeignClientFactoryBean在类初始化时执行一段逻辑;
依据 Spring FactoryBean 接口:如果它被别的类 @Autowired 进行注入,返回的不是它本身,而是 FactoryBean#getObject 返回的类,
依据 Spring ApplicationContextAware 接口:它能够获取 Spring 上下文对象,赋值到对象的局部变量里。

  • Resource怎么样?

初始化逻辑没有特别的操作,只是使用断言工具类判断两个字段不为空。

@Override
public void afterPropertiesSet() {
    Assert.hasText(contextId, "Context id must be set");
    Assert.hasText(name, "Name must be set");
}

关键看 FactoryBean#getObject 方法

@Override
public Object getObject() throws Exception {
    return getTarget();
}
  • Target是什么?

getTarget() 源码挺长的,这里分段展示:

这里提出一个疑问:FeignContext 什么时候、在哪里被注入到 Spring 容器里的?![请添加图片描述](https://img-blog.csdnimg.cn/37ff2926b178428c8e0af5873ed66bbe.bmp)
使用了 SpringBoot 自动装配的功能,FeignContext 就是在 FeignAutoConfiguration 中被成功创建
初始化父子容器
这里涉及到 Spring 父子容器的概念,默认子容器 Map 为空,获取不到服务名对应 Context 则新建

从下图中看到,注册了一个 FeignClientsConfiguration 类型的 Bean,我们上述方法 feign 中的获取的编码、解码器等组件都是从此类中获取默认

默认注册如下,FeignClientsConfiguration 是由创建 FeignContext 调用父类 Super 构造方法传入的

关于父子类容器对应关系,以及提供 @FeignClient 服务对应子容器的关系(每一个服务对应一个子容器实例)

回到 getInstance 方法,子容器此时已加载对应 Bean,直接通过 getBean 获取 FeignLoggerFactory

如法炮制,Feign.Builder、Encoder、Decoder、Contract 都可以通过子容器获取对应 Bean

configureFeign 方法主要进行一些配置赋值,比如超时、重试、404 配置等,就不再细说赋值代码了

到这里有必要总结一下创建 Spring 代理工厂的前半场代码

  1. 注入@FeignClient 服务时,调用 FactoryBean#getObject 返回的代理对象
  2. 通过 IOC 容器获取 FeignContext 上下文
  3. 创建 Feign.Builder 对象时会创建 Feign 服务对应的子容器
  4. 从子容器中获取日志工厂、编码器、解码器等 Bean
  5. 为 Feign.Builder 设置配置,比如超时时间、日志级别等属性,每一个服务都可以个性化设置

用applicationContext.getBean()获取FeignContext,然后用feign(context)获取Builder;
然后先以FeignContext为参数创建Client对象,再用Builder构建Client。

又一个Builder案例

动态代理生成Bean

继续看FeignClientFactory#getTarget方法:
此时的Targeter是HystrixTargeter的对象,Client则是LoadBalanceFeignClient

新版不是Hystrix了,而是自己的FeignCircuitBreakerTargeter.java

	<T> T getTarget() {
		FeignClientFactory feignClientFactory = beanFactory != null ? beanFactory.getBean(FeignClientFactory.class)
				: applicationContext.getBean(FeignClientFactory.class);
		Feign.Builder builder = feign(feignClientFactory);
		//if分支:@注解上没有url的话,则使用负载均衡的Client
		if (!StringUtils.hasText(url) && !isUrlAvailableInConfig(contextId)) {

			//...log
			if (!name.startsWith("http")) {
				url = "http://" + name;
			}
			else {
				url = name;
			}
			url += cleanPath();
			return (T) loadBalance(builder, feignClientFactory, new HardCodedTarget<>(type, name, url));
		}
		if (StringUtils.hasText(url) && !url.startsWith("http")) {
			url = "http://" + url;
		}
		String url = this.url + cleanPath();
		Client client = getOptional(feignClientFactory, Client.class);
		if (client != null) {
			if (client instanceof FeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
			}
			if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
			}
			builder.client(client);
		}

		applyBuildCustomizers(feignClientFactory, builder);

		Targeter targeter = get(feignClientFactory, Targeter.class);
		return targeter.target(this, builder, feignClientFactory, resolveTarget(feignClientFactory, contextId, url));
	}

targeter: 一个接口,用target方法返回一个对象(此处为FeignClient的bean);
负载均衡的targeter直接从FeignClientFactory context根据HardCodedTarget类获取。需要引入spring-cloud-starter-loadbalancer模块,可能是在那里放入上下文的吧。
最终的targeter.target()返回的是Feign.Builder的target方法返回的对象;
而Feign.Builder又是feign(feignClientFactory)返回的,是用get(FeignClientFactory context, Feign.Builder.class)从上下文获取的

  • import feign.Target.HardCodedTarget;哪里的代码?没见feign,是依赖里的么
  • Feign.Builder这个包没找到,倒是FeignClientBuilder.java里有个内部类Builder,但应该不是这个
    • 是feign仓库里的。但是FeignClientBuilder已经是个builder了还要内部Builder?

这下不得不看get方法了:

	protected <T> T get(FeignClientFactory context, Class<T> type) {
		T instance = context.getInstance(contextId, type);
		if (instance == null) {
			throw new IllegalStateException("也妹(没)找着这个bean呐!");
		}
		return instance;
	}

看源码怕太枯燥了,报错信息我就整活翻译了下hhh

  • contextid那儿来的?
    factory为啥是context?

FeignClientFactory的getInstance方法:

	public <T> T getInstance(String contextName, String beanName, Class<T> type) {
		return getContext(contextName).getBean(beanName, type);
	}

getContext可能是父类NamedContextFactory<FeignClientSpecification> 的方法,这里can can spring。
终于是返回了Builder了。

Client: Feign 发送请求以及接收响应等都是由 Client 完成,该类默认 Client.Default,另外支持 HttpClient、OkHttp 等客户端

代码中的 Client、Targeter 在自动装配时注册,配合上文中的父子容器理论,这两个 Bean 在父容器中存在

为了使用负载均衡,我们常在 @FeignClient 注解是使用 服务name 而不是 url。因为如源码所示,不传入url才会执行负载均衡策略的分支, return一个loadBalance()返回的targeter.target()。

  • 注入这个?那Client哪里去了?

Feign依赖的spring-cloud-loadbalancer,

获取到Builder和Targeter之后,我们将Builder传给Targeter的getTarget(),获取我们想要的FeignClient的代理类

  • 怎么知道是FeignCircuitBreakerTargeter? 自动装备时注册的?
  • 那怎么样又让他不是呢?

查看FeignCircuitBreakerTargeter的 target()方法

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignClientFactory context,
			Target.HardCodedTarget<T> target) {
		if (!(feign instanceof FeignCircuitBreaker.Builder builder)) {
			return feign.target(target);
		}
		String name = !StringUtils.hasText(factory.getContextId()) ? factory.getName() : factory.getContextId();
		Class<?> fallback = factory.getFallback();
		if (fallback != void.class) {
			return targetWithFallback(name, context, target, builder, fallback);
		}
		Class<?> fallbackFactory = factory.getFallbackFactory();
		if (fallbackFactory != void.class) {
			return targetWithFallbackFactory(name, context, target, builder, fallbackFactory);
		}
		return builder(name, builder).target(target);
	}

判断传入的Builder是FeignCircuitBreaker.Builder对象,调用其target(target)方法,该方法返回build(null).newInstance(target)。

		@Override
		public <T> T target(Target<T> target) {
			return build(null).newInstance(target);
		}

		public Feign build(final FallbackFactory<?> nullableFallbackFactory) {
			super.invocationHandlerFactory((target, dispatch) -> new FeignCircuitBreakerInvocationHandler(
					circuitBreakerFactory, feignClientName, target, dispatch, nullableFallbackFactory,
					circuitBreakerGroupEnabled, circuitBreakerNameResolver));
			return super.build();
		}

其父类是Feign.Builder。这里是先设置invocationHandlerFactory,再调用父类的build方法:
feign/core/src/main/java/feign/Feign.java

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

    public Feign build() {
      super.enrich();

      final ResponseHandler responseHandler =
          new ResponseHandler(logLevel, logger, decoder, errorDecoder,
              dismiss404, closeAfterDecode, responseInterceptor);
      MethodHandler.Factory<Object> methodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors,
              responseHandler, logger, logLevel, propagationPolicy,
              new RequestTemplateFactoryResolver(encoder, queryMapEncoder),
              options);
      return new ReflectiveFeign<>(contract, methodHandlerFactory, invocationHandlerFactory,
          () -> null);
    }
  }

好家伙,不但自己内部定义factory接口,build也是定义到自己内部,自己建造自己了属于是

Feign.bulild()创建反射类 ReflectiveFeign的对象,该类设置编码、解码、重试等属性;

  • Feign对象到底是干什么的?返回他干嘛
    • 怎么设置的? 和Dubbo对比如何?

ReflectiveFeign的 newInstance(target) 方法对 @FeignClient 修饰的接口中 SpringMvc 等配置进行解析转换,对接口类中的方法进行归类,生成动态代理类
newInstance()按照行为大致划分,共做了四件事

  1. 处理 @FeignClient 注解(SpringMvc 注解等)封装为 MethodHandler 包装类
  2. 遍历接口中所有方法,过滤 Object 方法,并将默认方法以及 FeignClient 方法分类 为啥要过滤?
  3. 创建动态代理对应的 InvocationHandler 并创建 Proxy 实例
  4. 接口内 default 方法 绑定动态代理类 为啥是他们

feign/core/src/main/java/feign/ReflectiveFeign.java

  /**
   * creates an api binding to the {@code target}. As this invokes reflection, care should be taken
   * to cache the result.
   */
  public <T> T newInstance(Target<T> target) {
    return newInstance(target, defaultContextSupplier.newContext());
  }

  @SuppressWarnings("unchecked")
  public <T> T newInstance(Target<T> target, C requestContext) {
    TargetSpecificationVerifier.verify(target);

    Map<Method, MethodHandler> methodToHandler =
        targetToHandlersByName.apply(target, requestContext);
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (MethodHandler methodHandler : methodToHandler.values()) {
      if (methodHandler instanceof DefaultMethodHandler) {
        ((DefaultMethodHandler) methodHandler).bindTo(proxy);
      }
    }

使用参数传来的invocationFactory创建InvocationHandler。在该案例中,传来的Factory是cloud中的new FeignCircuitBreakerInvocationHandler。

看来cloud OpenFeign和feign的不同,这里是一处

  • InvocationHandler和methodhandler什么区别?说起来我连这个类干什么的都不知道。proxy怎么创建的,这种handler反射怎么实现的,也有必要严查!!
  • 从上文代码来看DefaultMethodHandler才进行最后的绑定,那要是不是Default的呢?方法怎么代理?

MethodHandler 将方法参数、方法返回值、参数集合、请求类型、请求路径进行解析存储

  • 似乎是因为Proxy.newInstance()方法需要handler
  • 返回的是泛型T类型
  • mvc的什么配置?
  • 大概知道为什么自己看不懂了,,一堆target,给target()传入target参数来返回target,,这Tm谁写的代码

可以看出 Feign 创建动态代理类的方式和 Mybatis Mapper 处理方式是一致的,因为两者都没有实现类

  • mapper咋创建的来着,细节也一样吗?

创建MethodHandler

feign/core/src/main/java/feign/ReflectiveFeign.java自己内部一通瞎jb调用后:

    private MethodHandler createMethodHandler(final Target<?> target,
                                              final MethodMetadata md,
                                              final C requestContext) {
      if (md.isIgnored()) {
        return args -> {
          throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
        };
      }

      return factory.create(target, md, requestContext);
    }
  }

没错这个MethodHandler的Factory还是定义在他自己内部的。至于实现,是创建反射Feign代理对象的时候new出来的SynchronousMethodHandler,刚才已经看到源码了
我以为这个叫createXX的方法要自己亲自创建methon了,没想到还是调用内部工厂类
接口自己里面定义自己的工厂接口,实现的时候也是实现类+内部实现接口,哈哈。内部接口语法上要求内部实现吗?

原理总结:
@EnableFeignClient扫描带有@FeignClient注解的接口,生成动态代理类,并为接口内的方法绑定methodHandler;(注册成Bean?)我们用@Autowired注入的bean其实就是自动生成的代理类,按Spring的正常流程,调用FactoryBean的getObject()方法生成。

  • 为什么不直接代理或者重写,而是用Handler?类也用Handler,方法也handler
  • 什么时候扫描的?怎么扫描?
  • 什么时候具体生成这个类?

也就是说在我们调用 @FeignClient接口时,会被 FeignInvocationHandler#invoke 拦截,并在动态代理方法中进行远程调用

  • 拦截?应当说注入的是代理类吧。还是说被Handler拦截了?

调用流程

  • 具体的还是要先看看Ribbon

  • methodHandler到底是feign中的还是cloud中的那个?

    • 应该是feign中的。cloud的invocationHandler不是处理方法的。
    • 也许要看看spring AOP动态代理原理是否相同。因为handler反向代理的方式没自己的反射中见过。
  • import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;

FeignCircuitBreakerInvocationHandler.java

 @Override
	public Object invoke(final Object proxy, final Method method, final Object[] args) {
		// early exit if the invoked method is from java.lang.Object
		// code is the same as ReflectiveFeign.FeignInvocationHandler
		if ("equals".equals(method.getName())) {
			//...对equals\hashcode等Object类的方法,不用代理
		}

		String circuitName = circuitBreakerNameResolver.resolveCircuitBreakerName(feignClientName, target, method);
		CircuitBreaker circuitBreaker = circuitBreakerGroupEnabled ? factory.create(circuitName, feignClientName)
				: factory.create(circuitName);
		Supplier<Object> supplier = asSupplier(method, args);
		if (this.nullableFallbackFactory != null) {
			Function<Throwable, Object> fallbackFunction = throwable -> {
				Object fallback = this.nullableFallbackFactory.create(throwable);
				try {
					return this.fallbackMethodMap.get(method).invoke(fallback, args);
				}
				catch (Exception exception) {
					unwrapAndRethrow(exception);
				}
				return null;
			};
			return circuitBreaker.run(supplier, fallbackFunction);
		}
		return circuitBreaker.run(supplier);
	}
  1. 接口注解信息封装为 HTTP Request
  2. 通过 Ribbon 获取服务列表,并对服务列表进行负载均衡调用(服务名转换为 ip+port)
  3. 请求调用后,将返回的数据封装为 HTTP Response,继而转换为接口中的返回类型

feign/

 @Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    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;
      }
    }
  }
  Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Request request = targetRequest(template);
	// ...log

    Response response;
    long start = System.nanoTime();
    try {
      response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 12
      response = response.toBuilder()
          .request(request)
          .requestTemplate(template)
          .build();
    } catch (IOException e) {
		// ... log and throw 
    }

    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
    return responseHandler.handleResponse(
        metadata.configKey(), response, metadata.returnType(), elapsedTime);
  }
  • methodHandler是私有构造方法,是单例模式吗?

封装Request

如上文MethodHandler的invoke()所示:
先创建requestTemplate:调用传入的RequestTemplate.Factory这一内部接口的create(Object[] args)方法创建。

还是第一次见到把工厂类接口定义为自己内部接口的工厂模式实现,可能是写着方便吧。
create()方法调用resolve()方法解析参数、返回template实例
RequestTemplate.Factory接口在RequestTemplateFactoryResolver.java文件中有静态实现类BuildTemplateByResolvingArgs,该类又有两个静态子类,根据参数情况分别调用这三个类的resolve方法。

  • 回头研究下springboot的restTemplate怎么做的

将RequestTemplate对象转换成Request对象:在template上aplly拦截器,然后target.applly()即可。

Client.execute()执行请求

  • client实例从哪里来的?
    • 没看到SynchronousMethodHandler什么时候初始化的,所以不知道Client,,甚至构造方法也是私有的
      • 看到了,,是Feign.java创建反射Feign的时候new出的Factory,在反射Feign里,
      • Feign. builder定义的时候有个private Client client = new Client.Default(null, null);不知道有其他初始化没有
      • 好吧早在FeignClientFactory#getTarget里设置了client,,
        直接从上下文getContext(name).getBeansOfType(type);了

DefaultLoadBalancerClientConfiguration中用了FeignBlockingLoadBalancerClient;

  • 你怎么知道是这个配置类的算话?
  • 怎么阻塞的?

FeignBlockingLoadBalancerClient:

@Override
	public Response execute(Request request, Request.Options options) throws IOException {
		final URI originalUri = URI.create(request.url());
		String serviceId = originalUri.getHost();
		Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
		String hint = getHint(serviceId);
		DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
				new RequestDataContext(buildRequestData(request), hint));
		Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
				.getSupportedLifecycleProcessors(
						loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
						RequestDataContext.class, ResponseData.class, ServiceInstance.class);
		supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
		ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
		org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
				instance);
		if (instance == null) {
			String message = "Load balancer does not contain an instance for the service " + serviceId;
			//...服务不可用,自行封装response返回				
		}
		String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
		Request newRequest = buildRequest(request, reconstructedUrl, instance);
		return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,
				supportedLifecycleProcessors);
	}

选择服务

从loadBalancerClient.choose(serviceId, lbRequest);开始,属于spring-cloud-loadbalancer模块
choose()方法是LoadBalancer接口的父接口ServiceInstanceChooser的方法(好家伙接口之间的extends,,不过也是,你总不能implments)

好吧,下一阶段目标:看看自动装配。
话说为什么build模式都不喜欢用setXXX方式命名,,有个http库,,还XXXBuilder.create(),,,谁知道你create()的是目标类还是Builder,,,

执行远端调用逻辑中使用到了 Rxjava (响应式编程),可以看到通过底层获取 server 后将服务名称转变为 ip+port 的方式

这种响应式编程的方式在 SpringCloud 中很常见,Hystrix 源码底层也有使用
网络调用默认使用 HttpURLConnection
调用远端服务后,再将返回值解析正常返回,到这里一个完整的 Feign 调用链就结束了。

补充

启动时,使用了建造者模式

    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition());
    }

feign的InvacationHandler源码真是让人难绷

public interface InvocationHandlerFactory {

  InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);

  /**
   * Like {@link InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])}, except for a
   * single method.
   */
  interface MethodHandler {

    Object invoke(Object[] argv) throws Throwable;

    interface Factory<C> {
      MethodHandler create(Target<?> target,
                           MethodMetadata md,
                           C requestContext);
    }
  }

  static final class Default implements InvocationHandlerFactory {

    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }
  }
}

一个接口内部定义一个接口,再在内部接口内定义自己的工厂
底下有个内部类直接命名为Default,跟关键字default只差一个大小写

参考资料:
https://zhuanlan.zhihu.com/p/346273428

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/809839.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Winform制作的用户界面在高DPI下缩放问题

引言 熟悉Winform的小伙伴应该都遇到过 在100%缩放下制作的用户界面在其他缩放百分比下会出现字体超出边框的情况&#xff0c;导致用户体验大打折扣。用户程序DPI感知是默认打开的&#xff0c;此时可以通过关闭这种感知来禁用字体的缩放&#xff0c;在这种情况下&#xff0c;用…

C语言指针详解

目录 指针是什么? 指针和指针类型 指针-整数 指针的解引用 野指针 野指针成因 如何规避野指针 指针运算 指针- 整数 指针-指针 指针的关系运算 指针和数组 二级指针 指针数组 指针数组 模拟二维数组 指针是什么? 指针理解的2个要点: 1. 指针是内存中一个…

51单片机——串行口通信

目录 1、51单片机串口通信介绍 2、串行口相关寄存器 2.1 、串行口控制寄存器SCON和PCON 2.1.1 SCON&#xff1a;串行控制寄存器 (可位寻址) 2.1.2 PCON&#xff1a;电源控制寄存器&#xff08;不可位寻址&#xff09; 2.2、串行口数据缓冲寄存器SBUF 2.3、从机地址控制…

iOS - 解压ipa包中的Assert.car文件

项目在 Archive 打包后&#xff0c;生成ipa包 将 xxx.ipa文件修改为zip后缀即 xxx.zip &#xff0c;然后再双击解压&#xff0c;会生成一个 Payload 文件夹&#xff0c;里面一个文件 如下图&#xff1a; 然后显示改文件的包内容&#xff1a; 解压 Assets.car 文件的方式&…

基于x-scan的渲染算法

基于x-scan算法实现的z-buffer染色&#xff0c;.net core framework 3.1运行。 x-scan算法实现&#xff1a; public List<Vertex3> xscan() {List<Vertex3> results new List<Vertex3>();SurfaceFormula formula getFormula();Box rect getBound();for …

力扣 968. 监控二叉树

题目来源&#xff1a;https://leetcode.cn/problems/binary-tree-cameras/description/ C题解&#xff08;来源代码随想录&#xff09;&#xff1a;节点可以分为3个状态&#xff1a;0无覆盖&#xff1b;1有摄像头&#xff1b;2有覆盖。 要想放的摄像头最少&#xff0c;应当叶子…

无涯教程-jQuery - stop( clearQueue, gotoEnd)方法函数

stop([clearQueue&#xff0c;gotoEnd])方法停止所有指定元素上的所有当前正在运行的动画。 stop( [clearQueue, gotoEnd ]) - 语法 selector.stop( [clearQueue], [gotoEnd] ) ; 这是此方法使用的所有参数的描述- clearQueue - 这是可选的布尔参数。设置为true会清除动画…

绕过TLS/akamai指纹护盾

文章目录 前言TLS指纹什么是TLS指纹测试TLS指纹绕过TLS指纹使用原生urllib使用其他成熟库&#xff01;&#xff01;修改requests底层代码 Akamai指纹相关&#xff08;HTTP/2指纹&#xff09;什么是Akamai指纹测试Akamai指纹绕过Akamai指纹使用其他成熟库 实操参考 前言 有道是…

Eureka 学习笔记3:EurekaHttpClient

版本 awsVersion ‘1.11.277’ EurekaTransport 用于客户端和服务端之间进行通信&#xff0c;封装了以下接口的实现&#xff1a; ClosableResolver 接口实现TransportClientFactory 接口实现EurekaHttpClient 接口实现及其对应的 EurekaHttpClientFactory 接口实现 private …

【雕爷学编程】MicroPython动手做(16)——掌控板之图片图像显示2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

第120天:免杀对抗-防朔源防流量防特征CDN节点SSL证书OSS存储上线

知识点 #知识点&#xff1a; 1、CS-CDN节点-防拉黑 2、CS-SSL证书-防特征 3、CS-OSS存储-防流量#章节点&#xff1a; 编译代码面-ShellCode-混淆 编译代码面-编辑执行器-编写 编译代码面-分离加载器-编写 程序文件面-特征码定位-修改 程序文件面-加壳花指令-资源 代码加载面-D…

springCloud Eureka注册中心配置详解

1、创建一个springBoot项目 2、在springBoot项目中添加SpringCloud依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2021.0.3</version><type>…

IDEA 使用 maven 搭建 spring mvc

1. 创建项目 1.1 创建成功之后配置 Spring MVC 1.2 勾选 Spring MVC 2.更改配置文件 2.1 更改web.xml配置 更改为 <servlet-mapping><servlet-name>dispatcher</servlet-name><url-pattern>/</url-pattern></servlet-mapping>2.2 dispat…

CF1833 A-E

A题 题目链接&#xff1a;https://codeforces.com/problemset/problem/1833/A 基本思路&#xff1a;for循环遍历字符串s&#xff0c;依次截取字符串s的子串str&#xff0c;并保存到集合中&#xff0c;最后输出集合内元素的数目即可 AC代码&#xff1a; #include <iostrea…

Michael.W基于Foundry精读Openzeppelin第15期——SignedMath.sol

Michael.W基于Foundry精读Openzeppelin第15期——SignedMath.sol 0. 版本0.1 SignedMath.sol 1. 目标合约2. 代码精读2.1 max(int256 a, int256 b) && min(int256 a, int256 b)2.2 average(int256 a, int256 b)2.3 abs(int256 n) 0. 版本 [openzeppelin]&#xff1a;v…

final的使用以及权限修饰符

final表示最终的、不可改变的 final跟abstract不可以同时使用&#xff0c;因为二者是冲突的。final表示不可变&#xff0c;abstrac表示必须要重写、必须要变。 常见的四种用法 修饰一个类 格式&#xff1a; public final class 类名称{ }final修饰之后&#xff0c;这个类不能…

杂谈项——关于我在bw上的见闻,以及个人对二次元游戏行业方面的前瞻

君兮_的个人主页 勤时当勉励 岁月不待人 C/C 游戏开发 Hello,米娜桑们&#xff0c;这里是君兮_&#xff0c;今天为大家带来一点不一样的&#xff0c;首先先光速叠一下甲&#xff1a; 在此说明博主并不是一个什么都知道的大佬&#xff0c;只是一个普通的老二次元以及期望以后能…

大数据-Spark批处理实用广播Broadcast构建一个全局缓存Cache

1、broadcast广播 在Spark中&#xff0c;broadcast是一种优化技术&#xff0c;它可以将一个只读变量缓存到每个节点上&#xff0c;以便在执行任务时使用。这样可以避免在每个任务中重复传输数据。 2、构建缓存 import org.apache.spark.sql.SparkSession import org.apache.s…

Xshell配置ssh免密码登录-公钥与私钥登录linux服务器

目录 简介 提示 方法步骤 步骤1&#xff1a;生成密钥公钥&#xff08;Public key&#xff09;与私钥(Private Key) 方法1&#xff1a;使用xshell工具 方法2&#xff1a;使用命令行 步骤2&#xff1a;放置公钥(Public Key)到服务器 方法1&#xff1a;&#xff08;我使用的是…

LeetCode551.Student-Attendance-Record-i<学生出勤记录 I>

题目&#xff1a; 思路&#xff1a; 遍历就完事了.连续三天不来return false; 超过两次缺勤 fasle; 代码是&#xff1a; //codeclass Solution { public:bool checkRecord(string s) {int n s.length();int abtimes0,latimes0;for(int i0;i<n;i){switch(s[i]){case(A):l…