SpringCloud源码之OpenFeign

news2024/11/16 2:15:20

OpenFeign

基于 OpenFeign 2.2.6.RELEASE版本进行源码阅读

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

一键拉取 spring-cloud-common、spring-cloud-alibaba、spring-cloud-netflix、spring-cloud-openfeign 源码

https://gitee.com/haijun1998/spring-cloud-git.git

1. 注解

  • EnableFeignClients:开启openFeign接口的处理(导入了 FeignClientsRegistrar 对注解进行处理)
  • FeignClient:标记openFeign的接口

2. FeignClientsRegistrar

@FeignClient注解的注册类

  • 第一步注册 @EnableFeignClients 中指定的默认配置类
    • bean定义的名称为 default. 开头
    • 通过 FeignClientSpecification 进行包装
  • 第二步处理 @EnableFeignClients 指定扫描的路径
    • 先注册 @FeignClient 指定的配置文件对象将其包装成 FeignClientSpecification
    • @FeignClient 的接口通过 FeignClientFactoryBean 代理类进行实现
class FeignClientsRegistrar
		implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    @Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		/**
		 * 注解注解上面标记的 defaultConfiguration 配置对象,配置类通过class进行表示
		 * 使用 FeignClientSpecification 对其进行包装
		 */
		registerDefaultConfiguration(metadata, registry);
		/**
		 * 处理 @FeignClient 注解并且会将注解中配置的 configuration 属性也注册到容器当中
		 * bean定义的名称 @FeignClient[name].FeignClientSpecification.class.name,然后将接口注入
		 * 然后将接口包装成 FeignClientFactoryBean 类型
		 */
		registerFeignClients(metadata, registry);
	}
}

3. FeignClientFactoryBean

@FeignClient 的动态代理类,在通过注入使用到时才会进行创建

class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
    /**
	 * 接口的类型
	 */
	private Class<?> type;

	/**
	 * 指定的名称
	 */
	private String name;

	/**
	 * 服务的请求接口地址
	 */
	private String url;

	/**
	 * 上下文id,如果没有直接指定会通过 serviceId或者name进行获取
	 */
	private String contextId;

	/**
	 * 调用路径
	 */
	private String path;

	/**
	 * 解码器找不到时是否返回404
	 */
	private boolean decode404;

	/**
	 * 是否需要继承父上下文
	 */
	private boolean inheritParentContext = true;

	/**
	 * spring容器
	 */
	private ApplicationContext applicationContext;

	/**
	 * 回调类
	 */
	private Class<?> fallback = void.class;

	/**
	 * 回调类的工厂类
	 */
	private Class<?> fallbackFactory = void.class;

	/**
	 * 读取超时的时间
	 */
	private int readTimeoutMillis = new Request.Options().readTimeoutMillis();

	/**
	 * 连接超时的时间
	 */
	private int connectTimeoutMillis = new Request.Options().connectTimeoutMillis();
}

getObject() 获取到动态代理对象,通过 Feign.Builder 进行构建,而 Feign.Builder 又根据是否导入了 hystrix 的依赖进行自动配置

  • DefaultTarget:当class路径下面没有 feign.hystrix.HystrixFeign 类时就创建当前
  • HystrixTargeter:当路径下面有 feign.hystrix.HystrixFeign 创建当前

Feign.Builder 会从容器中获取到对应需要的组件配置进行默认值的初始化

  • logger
  • Encoder:SpringEncoder
  • Decoder:默认类型 OptionalDecoder中内嵌的 SpringDecoder类型进行处理
  • Contract:默认类型 SpringMvcContract
  • Logger.Level:日志等级
  • Retryer:重试器
  • ErrorDecoder:错误编码器
  • Request.Options:配置对象来设置连接超时和读取超时的时间
  • RequestInterceptor:请求拦截器
  • QueryMapEncoder:查询映射编码器
  • ExceptionPropagationPolicy:异常传播的策略
public Object getObject() throws Exception {
		return getTarget();
}
/**
	 * 当前方法主要是通过从容器中获取到 Feign.Builder 类型进行配置,而 Feign.Builder 有两个实现类目前
	 * DefaultTarget:当class路径下面没有 feign.hystrix.HystrixFeign 类时就创建当前
	 * HystrixTargeter:当路径下面有 feign.hystrix.HystrixFeign 创建当前
	 */
	<T> T getTarget() {
		//从spring中获取到 FeignContext 对象,在 FeignAutoConfiguration 进行的自动导入
		FeignContext context = applicationContext.getBean(FeignContext.class);
		/**
		 * 根据上下文创建 Builder 进行创建
		 * context 继承至 NamedContextFactory 会为每个接口都创建一个容器进行存储
		 * 从容器中获取了 Feign.Builder 类型进行构建,并且通过配置信息进行配置,以下属性都是通过子容器中的配置进行获取
		 * 
		 * Logger.Level:日志级别
		 * Retryer:重试器
		 * ErrorDecoder:错误解码
		 * FeignErrorDecoderFactory:错误解码工厂
		 * Request.Options:配置读取和连接超时的时间
		 * RequestInterceptor:请求的拦截器
		 * QueryMapEncoder:查询映射的编码
		 * ExceptionPropagationPolicy:异常传播的策略
		 */
		Feign.Builder builder = feign(context);
		/**
		 * 处理url地址,如果没有指定url地址,拼接url地址为 http://name属性的值
		 */
		if (!StringUtils.hasText(url)) {
			if (!name.startsWith("http")) {
				url = "http://" + name;
			}
			else {
				url = name;
			}
			url += cleanPath();
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(type, name, url));
		}
		//如果指定了url,并且不是http开头,默认拼接上http
		if (StringUtils.hasText(url) && !url.startsWith("http")) {
			url = "http://" + url;
		}
		//处理以下 /
		String url = this.url + cleanPath();
		/**
		 * 从容器中获取到客户端对象,这个 Client 接口就是后续执行请求的类
		 * 一般都是通过组件自定义实现,是否是负载的客户端等
		 */
		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();
			}
			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();
			}
			builder.client(client);
		}
		/**
		 * 获取到 Targeter 通过target类型进行创建接口的代理对象有两个实现类
		 * DefaultTarget:当class路径下面没有 feign.hystrix.HystrixFeign 类时就创建当前
		 * HystrixTargeter:当路径下面有 feign.hystrix.HystrixFeign 创建当前
		 */
		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(type, name, url));
	}

4. Targeter

目标构造器,会根据不同的方式进行构建,目前有两个构建器

  • DefaultTargeter:默认的目标构建器
  • HystrixTargeter:hystrix的构建器

4.1 DefaultTargeter

没有做什么操作直接调用了构建方法

class DefaultTargeter implements Targeter {

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

}

4.2 HystrixTargeter

需要进行回调函数的处理,最终调用到 feign.hystrix.HystrixFeign.Builder 方法中

class HystrixTargeter implements Targeter {

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
		//判断以下如果类型不是 Hystrix 提供的类型直接通过传递的target进行创建
		if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
			return feign.target(target);
		}
		//builder都是 HystrixFeign.Builder
		feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
		//计算出回调函数的名称
		String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
				: factory.getContextId();
		//从容器中获取到 SetterFactory
		SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
		if (setterFactory != null) {
			builder.setterFactory(setterFactory);
		}
		//获取到接口中的回调函数类
		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 feign.target(target);
	}

5. Feign.Builder

feign包里面提供的构建器是一个抽象类,其它的扩展都是继承 feign.Feign.BuilderHystrixFeign.Builder 也继承至 当前类 feign.Feign.Builder

public static class Builder {
        private final List<RequestInterceptor> requestInterceptors = new ArrayList();
        private Level logLevel;
        private Contract contract;
        private Client client;
        private Retryer retryer;
        private Logger logger;
        private Encoder encoder;
        private Decoder decoder;
        private QueryMapEncoder queryMapEncoder;
        private ErrorDecoder errorDecoder;
        private Options options;
        private InvocationHandlerFactory invocationHandlerFactory;
        private boolean decode404;
        private boolean closeAfterDecode;
        private ExceptionPropagationPolicy propagationPolicy;
        private boolean forceDecoding;
        private List<Capability> capabilities;

        public Builder() {
            //默认日志等级
            this.logLevel = Level.NONE;
            //默认的处理 @Headers、@RequestLine、@Body、@Headers、@Param等注解
            this.contract = new Default();
            //默认客户端
            this.client = new feign.Client.Default((SSLSocketFactory)null, (HostnameVerifier)null);
            //默认的重试器
            this.retryer = new feign.Retryer.Default();
            this.logger = new NoOpLogger();
            //编码
            this.encoder = new feign.codec.Encoder.Default();
            //解码
            this.decoder = new feign.codec.Decoder.Default();
            //查询映射编码
            this.queryMapEncoder = new FieldQueryMapEncoder();
            //错误解码
            this.errorDecoder = new feign.codec.ErrorDecoder.Default();
            //读取超时连接超时的配置
            this.options = new Options();
            // InvocationHandlerFactory 动态代理的实现接口工厂
            this.invocationHandlerFactory = new feign.InvocationHandlerFactory.Default();
            //关闭后进行编码
            this.closeAfterDecode = true;
            //异常传播策略
            this.propagationPolicy = ExceptionPropagationPolicy.NONE;
            //是否强制解码
            this.forceDecoding = false;
            //其它的能力支持
            this.capabilities = new ArrayList();
        }
    
    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);
        //默认的处理 @Headers、@RequestLine、@Body、@Headers、@Param等注解
            Contract contract = (Contract)Capability.enrich(this.contract, this.capabilities);
        //配置
            Options options = (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);
        //接口中的方法解析出来之后需要通过当前工厂类进行创建包装后的接口 MethodHandler
            Factory synchronousMethodHandlerFactory = new Factory(client, retryer, requestInterceptors, logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);
        //用于解析接口中的方法
            ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
        //最终创建反射类型的Feign,通过调用内部的 newInstance() 进行动态代理的创建
            return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
        }
}

5.1 HystrixFeign.Builder

可以发现它继承了 feign.Feign.Builder 类型,最终调用都会调用到父类的 build() 方法;调用 HystrixFeign.Builder.build() 方法修改了动态代理的实现方式以及封装了一下 Contract 类型用于接口的解析

public static final class Builder extends feign.Feign.Builder {
    public Feign build() {
            return this.build((FallbackFactory)null);
        }
        Feign build(final FallbackFactory<?> nullableFallbackFactory) {
            //指定以下当前HystrixFeign需要创建代理类的类型
            super.invocationHandlerFactory(new InvocationHandlerFactory() {
                public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
                    return new HystrixInvocationHandler(target, dispatch, Builder.this.setterFactory, nullableFallbackFactory);
                }
            });
            //对解析接口的类型包装一层
            super.contract(new HystrixDelegatingContract(this.contract));
            //调用父类进行构建
            return super.build();
        }
}

6. Feign

feign再对接口方法进行解析时用到的比较重要的几个类

  • InvocationHandlerFactory:用于创建动态代理的处理实现类,默认是创建 FeignInvocationHandler,Hystrix进行重新实现了创建的是 HystrixInvocationHandler 类型
  • Contract:用于解析接口中的方法,默认是 Contract.Default 类型,hystrix进行了复写创建的是 HystrixDelegatingContract类, 当前类将接口的方法解析为 List<MethodMetadata> 方法的元数据对象
  • ReflectiveFeign.ParseHandlersByName:根据 Conract 解析出来的元数据 List<MethodMetadata> 去调用 SynchronousMethodHandler.Factory 给每一个方法都创建一个 MethodHandler 类型,默认是 SynchronousMethodHandler
  • SynchronousMethodHandler.Factory:工厂方法,用于在解析接口方法之后为每个 MethodMetadata 创建一个用于包装方法的代理实现类

将接口解析后的 Feign 接口类型,定义一个抽象方法,传入接口的类型然后创建动态代理类

public abstract class Feign {
    public Feign() {
    }
    public abstract <T> T newInstance(Target<T> var1);
}

6.1 ReflectiveFeign

public class ReflectiveFeign extends Feign {
    //用于解析接口中方法的类
    private final ReflectiveFeign.ParseHandlersByName targetToHandlersByName;
    //动态代理的创建工厂
    private final InvocationHandlerFactory factory;
    //查询映射编码器
    private final QueryMapEncoder queryMapEncoder;
    
    public <T> T newInstance(Target<T> target) {
        //先通过 ParseHandlersByName 将接口中的每个方法解析为对应的 MethodHandler 类型
        Map<String, MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
        //缓存方法对应每个方法的处理类
        Map<Method, 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) {
                //如果是默认类型的方法就存储 defaultMethodHandlers
                if (Util.isDefault(method)) {
                    DefaultMethodHandler handler = new DefaultMethodHandler(method);
                    defaultMethodHandlers.add(handler);
                    methodToHandler.put(method, handler);
                } else {
                    //将方法存入 methodToHandler,这里会构建key,格式是 接口名称#(参数名称,参数名称2)
                    methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
                }
            }
        }
		/**
		 * 创建代理类有两种类型:
		 * 默认:FeignInvocationHandler
		 * hystrix:HystrixInvocationHandler
		*/
        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;
    }
}

7. InvocationHandler

接口的动态代理类有两个实现方式

7.1 FeignInvocationHandler

默认的代理类没有做什么特别的操作,区分了一下 equals、hashcode、toString 方法后就直接通过 this.dispatch 去获取到需要执行的方法执行了

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

7.2 HystrixInvocationHandler

熔断器的动态代理处理类,其中所有的操作都放在了 HystrixCommand ,采用大量的 RxJava 响应式编程进行的编程,目前还没有学习过看不了。

8. MethodHandler

方法处理器,接口中的每一个方法都会对应一个当前类型

8.1 SynchronousMethodHandler

final class SynchronousMethodHandler implements MethodHandler {
    //方法对应的元数据信息
    private final MethodMetadata metadata;
    // HardCodedTarget 传入的类型,里面只包含了接口类型、名称、url地址
    private final Target<?> target;
    //客户端
    private final Client client;
    //重试器
    private final Retryer retryer;
    //请求拦截器
    private final List<RequestInterceptor> requestInterceptors;
    private final Logger logger;
    private final Level logLevel;
    //构建参数模板工厂类,会根据参数构建一个 RequestTemplate
    private final feign.RequestTemplate.Factory buildTemplateFromArgs;
    private final Options options;
    //异常传播策略
    private final ExceptionPropagationPolicy propagationPolicy;
    //编码
    private final Decoder decoder;
    //异步响应handler,根据 forceDecoding 是否强制解码进行创建
    private final AsyncResponseHandler asyncResponseHandler;
    
    public Object invoke(Object[] argv) throws Throwable {
        //根据传入的参数构建一个 RequestTemplate 请求对象的模板对象,后续会根据它进行创建Request
        RequestTemplate template = this.buildTemplateFromArgs.create(argv);
        //通过传入的参数来获取到 Options 里面的读超时连接超时等问题
        Options options = this.findOptions(argv);
        //复制一份重试器
        Retryer retryer = this.retryer.clone();
		//默认为true死循环,根据 retryer 定义下一次进行重试的时间
        while(true) {
            try {
                //执行请求并且进行解码
                return this.executeAndDecode(template, options);
            } catch (RetryableException var9) {
                RetryableException e = var9;

                try {
                    //继续重试或者传播异常
                    retryer.continueOrPropagate(e);
                } catch (RetryableException var8) {
                    Throwable cause = var8.getCause();
                    //根据配置的异常传播策略判断是否需要向外抛出异常
                    if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
                        throw cause;
                    }

                    throw var8;
                }

                if (this.logLevel != Level.NONE) {
                    this.logger.logRetry(this.metadata.configKey(), this.logLevel);
                }
            }
        }
    }
    
    /**
     * 执行请求并且进行数据解码
     */
    Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
        //执行请求的拦截器,然后通过 RequestTemplate 创建Request对象
        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 {
            /**
             * 通过客户端进行执行 client,这个客户端可以通过三方组件自行实现
             * 默认使用的是 Client.Default实现 直接开启url连接
             * 例如ribbon实现的client:LoadBalancerFeignClient
             * 例如seata实现的:SeataFeignClient、SeataLoadBalancerFeignClient
             * 具体ribbon是如何实现负载的后续通过ribbon的源码进行分析
             */
            response = this.client.execute(request, options);
            //复制一次response对象设置 request 和 requestTemplate
            response = response.toBuilder().request(request).requestTemplate(template).build();
        } catch (IOException var13) {
            if (this.logLevel != Level.NONE) {
                this.logger.logIOException(this.metadata.configKey(), this.logLevel, var13, this.elapsedTime(start));
            }

            throw FeignException.errorExecuting(request, var13);
        }

        long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
        //如果编码器不为空的话直接执行编码,结果直接返回
        if (this.decoder != null) {
            return this.decoder.decode(response, this.metadata.returnType());
        } else {
            //下面是直接通过异步处理的方式来执行请求的结果
            CompletableFuture<Object> resultFuture = new CompletableFuture();
            this.asyncResponseHandler.handleResponse(resultFuture, this.metadata.configKey(), response, this.metadata.returnType(), elapsedTime);

            try {
                if (!resultFuture.isDone()) {
                    throw new IllegalStateException("Response handling not done");
                } else {
                    return resultFuture.join();
                }
            } catch (CompletionException var12) {
                Throwable cause = var12.getCause();
                if (cause != null) {
                    throw cause;
                } else {
                    throw var12;
                }
            }
        }
    }
}

9. Client

feign中定义的客户端顶级接口,在 FeignClientFactoryBean 中进行创建代理对象时就会从容器中去找是否有 Client 的实现类,目前有多个实现类,引入的组件不同可能 Client 的实现不同

  • LoadBalancerFeignClient:feign提供的默认负载客户端
  • FeignBlockingLoadBalancerClient:spring cloud提供的自定义负载策略器,目前用不到

image-20230414165555786

9.1 LoadBalancerFeignClient

LoadBalancerFeignClient 实现了 Feign中的 Client 接口其中 CachingSpringLoadBalancerFactoryribbon 中的 SpringClientFactory 进行了包装;

SpringClientFactory 作为 ribbon 中的配置名称配置工厂对 ribbon 中的容器进行管理实现了 NamedContextFactory<RibbonClientSpecification>SpringClientFactory 可以根据对应的客户端名称去获取到 ILoadBalancer 进行服务的选择;

SpringClientFactory 的自动装配是在 RibbonAutoConfiguration 中进行的装配

//创建的默认客户端
	private final Client delegate;

	//对ribbon中的 SpringClientFactory 对象进行包装
	private CachingSpringLoadBalancerFactory lbClientFactory;

public Response execute(Request request, Request.Options options) throws IOException {
		try {
			//创建uri对象
			URI asUri = URI.create(request.url());
			//获取到服务的名称
			String clientName = asUri.getHost();
			URI uriWithoutHost = cleanUrl(request.url(), clientName);
			//创建请求对象
			FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
					this.delegate, request, uriWithoutHost);
			//根据客户端名称去获取到属于自己的配置文件对象
			IClientConfig requestConfig = getClientConfig(options, clientName);
			/**
			 * 通过 CachingSpringLoadBalancerFactory 创建客户端,其中获取到的是 FeignLoadBalancer类
			 * 实际调用的是父类:AbstractLoadBalancerAwareClient#executeWithLoadBalancer() 方法
			 * 方法中用到了 LoadBalancerCommand对象通过RxJava响应式编程进行编写的,重点方法是 submit() 方法中通过
			 * selectServer()进行对应的服务选择,通过调用 LoadBalancerContext 中调用 ILoadBalancer.choose() 进行服务的选择
			 */
			return lbClient(clientName)
					.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
		}
		catch (ClientException e) {
			IOException io = findIOException(e);
			if (io != null) {
				throw io;
			}
			throw new RuntimeException(e);
		}
	}

LoadBalancerFeignClientDefaultFeignLoadBalancedConfiguration 中进行自动装配其中最重要的属性是 CachingSpringLoadBalancerFactory 这个类里面包装了 ribbon 中的客户端工厂

public class CachingSpringLoadBalancerFactory {

	/**
	 * ribbon中进行自动配置的负载客户端工厂类
	 */
	protected final SpringClientFactory factory;

	/**
	 * 负载的重试工厂
	 */
	protected LoadBalancedRetryFactory loadBalancedRetryFactory = null;
	/**
	 * Feign的负载缓存
	 */
	private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>();
}

create() 方法通过 LoadBalancerFeignClient.execute() 中通过 ribbon 的负载工厂进行创建一个客户端进行执行返回的类型是 FeignLoadBalancer

public FeignLoadBalancer create(String clientName) {
		//获取缓存中是否保存了
		FeignLoadBalancer client = this.cache.get(clientName);
		if (client != null) {
			return client;
		}
		//通过springClientFactory工厂中获取到配置,默认是 RibbonClientConfiguration 中进行自动装配的
		IClientConfig config = this.factory.getClientConfig(clientName);
		//获取到ribbon中定义的负载器,默认是 RibbonClientConfiguration 中进行自动装配的 ZoneAwareLoadBalancer
		ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
		//继续获取服务内省器
		ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
				ServerIntrospector.class);
		//再根据是否存在重试工厂创建两个feign的负载器
		client = this.loadBalancedRetryFactory != null
				? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
						this.loadBalancedRetryFactory)
				: new FeignLoadBalancer(lb, config, serverIntrospector);
		//进行缓存
		this.cache.put(clientName, client);
		return client;
	}

10. FeignLoadBalancer

核心的实现都在父类 AbstractLoadBalancerAwareClient

image-20230427094215184

最终 Feign 会为每一个接口都创建一个 FeignLoadBalancer 进行负载的客户端,通过 FeignLoadBalancer 进行服务的选择然后替换掉服务的请求地址;通过 LoadBalancerFeignClient 可以看到实际调用的是 executeWithLoadBalancer() 方法也是其父类 AbstractLoadBalancerAwareClient 中实现的

lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
    //创建一个 LoadBalancerCommand
        LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

        try {
            //通过函数接口提交一个 ServerOperation的函数接口需要接收的参数是 Server 接口基本上nacos、eureka、zk做服务的负载都实现了这个接口
            return command.submit(
                new ServerOperation<T>() {
                    @Override
                    public Observable<T> call(Server server) {
                        //通过服务的地址进行请求的替换
                        URI finalUri = reconstructURIWithServer(server, request.getUri());
                        //替换服务地址
                        S requestForServer = (S) request.replaceUri(finalUri);
                        try {
                            //最后通过内部的默认 feign.Client.Default 客户端来发起请求
                            return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                        } 
                        catch (Exception e) {
                            return Observable.error(e);
                        }
                    }
                })
                .toBlocking()
                .single();
        } catch (Exception e) {
            Throwable t = e.getCause();
            if (t instanceof ClientException) {
                throw (ClientException) t;
            } else {
                throw new ClientException(e);
            }
        }
        
    }

10.1 LoadBalancerCommand

LoadBalancerCommand 中最重要的方法就是 submit() 来提交一个函数接口 ServerOperation<T>,方法中的核心又是 selectServer() 方法来选择一个实例服务

省略代码
Observable<T> o = 
                (server == null ? selectServer() : Observable.just(server))

selectServer() 方法又是通过 ILoadBalancer 类型通过服务名称选择一个服务

ILoadBalancer lb = getLoadBalancer();
if (lb != null){
    Server svc = lb.chooseServer(loadBalancerKey);
    if (svc == null){
        throw new ClientException(ClientException.ErrorType.GENERAL,
                                  "Load balancer does not have available server for client: "
                                  + clientName);
    }
    return svc;
}
            

10.2 ILoadBalancer

ILoadBalancer 接口主要的实现类是 ZoneAwareLoadBalancer 类型,这个类型是在 RibbonClientConfiguration 中进行自动装配这是 ribbon 中提供的能力后续在 ribbon 中的源码继续分析

public interface ILoadBalancer {

	/**
	 * 添加服务
	 */
	public void addServers(List<Server> newServers);
	
	/**
	 * 根据名称选择一个服务
	 */
	public Server chooseServer(Object key);
	
	/**
	 * 标记服务下线
	 */
	public void markServerDown(Server server);
	
	/**
	 * 参数如果为true,应该返回活跃的服务列表
	 */
	@Deprecated
	public List<Server> getServerList(boolean availableOnly);

	/**
	 * 只返回可获得的服务
     */
    public List<Server> getReachableServers();

    /**
     * 返回所有服务包括了可获得和不可获得的
     */
	public List<Server> getAllServers();
}

image-20230427095702036

总结

请添加图片描述

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

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

相关文章

Linux_红帽8学习笔记分享_6(yum软件仓库技术)

Linux_红帽8学习笔记分享_6(yum软件仓库技术) 文章目录 Linux_红帽8学习笔记分享_6(yum软件仓库技术)1. RPM软件包的使用技巧1.1如何查询指定软件包是否安装1.2如何删除指定软件包1.3如何安装指定软件包1.5依赖关系 2. YUM软件仓库的配置及使用2.1修改YUM软件仓库的配置文件 3.…

张量、标量、向量和矩阵

张量、标量、向量和矩阵 https://github.com/bovem/publications/tree/master/Linear%20Algebra 张量是一个数据数组(数字、函数等)&#xff0c;它以任意数量(0 或更大)的维度展开。维数称为张量秩。 秩 0 张量 没有维度(0)的张量。 A 是 0 维张量 秩 1 张量 仅在一维中展开的张…

MySQL 字段为 NULL 的5大坑,99%人踩过

数据库字段允许空值(null)的问题&#xff0c;你遇到过吗&#xff1f; 在验证问题之前&#xff0c;我们先建一张测试表及测试数据。 数据库字段允许空值(null)的问题&#xff0c;你遇到过吗&#xff1f; 在验证问题之前&#xff0c;我们先建一张测试表及测试数据。 构建的测试…

如何实现U盘低格?这样操作快速搞定!

案例&#xff1a;怎么对U盘进行低级格式化&#xff1f; 【我的U盘出现了异常&#xff0c;我想对它进行低级格式化处理&#xff0c;有没有小伙伴知道怎么操作&#xff1f;】 随着电脑和移动设备的普及&#xff0c;U盘已经成为我们生活中必不可少的存储工具。当我们使用U盘的时…

xml注入漏洞

一、先认识XML XML有两个先驱——SGML&#xff08;标准通用标记语言&#xff09;和HTML&#xff08;超文本标记语言&#xff09;&#xff0c;这两个语言都是非常成功的标记语言。SGML多用于科技文献和政府办公文件中&#xff0c;SGML非常复杂&#xff0c;其复杂程度对于网络上…

windows中vscode配置C/C++环境

首先要把MinGW的环境安装完&#xff0c;我一般是下载带有MinGW的codeblocks&#xff0c;这样省去自己安装MinGW。因为安装MinGW还挺麻烦的。 安装完codeblocks&#xff0c;找到其安装目录&#xff0c;把bin文件配置到环境变量去&#xff1a; 将bin添加到环境变量 然后打开vsco…

【MySQL高级】——索引数据结构

一、全表遍历 每个数据页一次加载搜索 二、Hash结构 1. Hash简介 2. 两种常见加快查找速度的数据结构 3. 为什么不采用Hash结构 <1> Hash索引仅能满足&#xff0c;!和IN查询。如果进行范围查询&#xff0c;哈希型的索引&#xff0c;时间复杂度会退化为o(n);而树型的“…

PLUS模型和InVEST模型生态系统服务多情景模拟预测、ArcGIS空间数据处理、空间分析与制图、土地利用时空变化

查看原文>>>基于”PLUS模型“生态系统服务多情景模拟预测实践技术应用 目录 第一章、理论基础与软件讲解 第二章、数据获取与制备 第三章、土地利用格局模拟 第四章、生态系统服务评估 第五章、时空变化及驱动机制分析 第六章、论文撰写技巧及案例分析 基于Ar…

B端产品如何搭建用户帮助体系

用户帮助体系可以提升用户的使用体验&#xff0c;引导用户正确的使用产品&#xff0c;并且体验产品的各个功能&#xff0c;B端产品因为其特殊的业务属性和复杂度&#xff0c;通常其学习成本不低。这些成本不仅仅体现在对于复杂业务概念及流程的认知方面&#xff0c;同时体现在整…

零信任网络安全:为什么越来越多的企业选择采用这一方法?

第一章&#xff1a;引言 随着信息化技术的发展和普及&#xff0c;网络安全问题日益严峻&#xff0c;黑客攻击、病毒感染、数据泄露等问题层出不穷&#xff0c;给企业带来了极大的损失。为了保障企业信息安全&#xff0c;企业采取了一系列安全措施&#xff0c;比如加强防火墙、…

【JavaScript】8.DOM的节点操作

DOM的节点操作 获取元素通常使用两种方式&#xff1a; 利用 DOM 提供的方法获取元素利用节点层级关系获取元素 利用 DOM 提供的方法获取元素 document.getElementById()document.getElementsByTagName()document.querySelector 等逻辑性不强、繁琐 利用节点层级关系获取元素 利…

vite中使用html2canvas 将img、svg和div转canvas

目录 div转canvas svg转canvas img转canvas div转canvas 使用 html2canvas 插件&#xff0c;其官网&#xff1a;html2canvas - Screenshots with JavaScripthttp://html2canvas.hertzen.com/ 安装html2canvas&#xff1a; npm i -S html2canvas 引入&#xff1a; import h…

zotero文献管理软件应用

文章目录 基础教学快速安装与入门第二部分&#xff0c;比较详细的教学与补充文献管理工具比较浏览器插件获取文献与文献pdfZotero 界面添加文件的其他方式管理题录在论文中插入引文文献同步插件与常见问题茉莉花PDFTranslator常见问题1.CNKI页面批量抓取题录失败2.文献类型标识…

Python入门教程+项目实战-11.4节: 元组与列表的区别

目录 11.4.1 不可变数据类型 11.4.2 可变数据类型 11.4.3 元组与列表的区别 11.4.4 知识要点 11.4.5 系统学习python 11.4.1 不可变数据类型 不可变数据类型是指不可以对该数据类型进行修改&#xff0c;即只读的数据类型。迄今为止学过的不可变数据类型有字符串&#xff…

leetcode 494. 目标和 (01 背包

。。最近背包问题做得好崩溃 这题的解法和 分割子集、石头 差不多 分成两个集合 &#xff08;注意这里计算的时候是不带上符号的&#xff0c;只是单纯的数字 a. 正号 的数字集合 P b. 负号 的数字集合 N 所以就有以下公式 sum&#xff08;P&#xff09; sum&#xff08;N&…

XGBoost学习总结

XGBoost有两种实现&#xff0c;原生版本和Sklearn版本 &#xff0c;我选的是sklearn的主要是都用sklearn方便 参数 参数表 class xgboost.XGBRegressor ( max_depth3, learning_rate0.1, n_estimators100, silentTrue, objectivereg:linear, boostergbtree, n_jobs1, nthre…

超简单有趣的模拟算法:元胞自动机(CA)原理简介与 matlab 代码实现

很久之前就就听说了元胞自动机&#xff08;cellular automata&#xff0c;CA&#xff09;&#xff0c;但是一直没有尝试。得知2023年美赛春季赛只有两道赛题的时候&#xff0c;怕考到这个&#xff0c;所以出一篇元胞自动机的博客&#xff0c;权且当一篇学习笔记。 尝试过后才发…

亚科转债,鹿山转债上市价格预测

亚科转债 基本信息 转债名称&#xff1a;亚科转债&#xff0c;评级&#xff1a;AA&#xff0c;发行规模&#xff1a;11.59亿元。 正股名称&#xff1a;亚太科技&#xff0c;今日收盘价&#xff1a;5.58元&#xff0c;转股价格&#xff1a;6.46元。 当前转股价值 转债面值 / 转…

SpringBoot搭建的方便易用、多租户、高颜值的教学管理平台

一、开源项目简介 硕果云&#xff0c;基于 Spring Boot 搭建的方便易用、高颜值的教学管理平台&#xff0c;提供多租户、权限管理、考试、练习、在线学习等功能。 主要功能为在线考试、练习、刷题&#xff0c;在线学习 课程内容支持图文、视频&#xff0c;考试类型支持考试、练…

vue可视化大屏

为什么要用 mapbox ? 各位可以自行搜索上面提到的或者其他的webGIS框架或者集成服务商&#xff0c;可以很轻松的比较出mapbox的优势&#xff1a; 支持3D地形、3D模型支持多种坐标系投影mapbox studio 在线编辑地图样式&#xff0c;使用时只需一个链接&#xff0c;更新时链接…