Skip to content

Spring Cloud Commons 服务注册、发现与调用 ​

本文介绍Spring Cloud中的服务注册、发现与调用相关类与源码,涉及Load Balancer与Open Feign。

1. 服务注册 ​

1.1 ServiceRegistry ​

ServiceRegistry是由Spring Cloud提供的接口,用于注册与注销服务实例:

java
public interface ServiceRegistry<R extends Registration> {

  // 注册服务实例,registration可以理解为服务实例,下节介绍
	void register(@Nullable R registration);

	// 注销服务实例
	void deregister(@Nullable R registration);

	// 关闭ServiceRegistry本身及注册器相关资源
	void close();

	// 设置服务实例的状态,服务实例有哪些状态由ServiceRegistry实现定义
	void setStatus(R registration, String status);

	// 获取服务实例状态
	<T> T getStatus(R registration);

}

1.2 Registration ​

Registration是由Spring Cloud提供的接口,表示服务实例本身,Registration本身是一个标记接口,主要的方法在ServiceInstance中定义:

java
// Registration只是一个标记接口,本身没有定义方法
public interface Registration extends ServiceInstance {

}
java
// ServiceInstance定义了服务实例应该实现的方法
public interface ServiceInstance {

	// 返回服务实例唯一ID,实例维度
	default @Nullable String getInstanceId() {
		return null;
	}

	// 返回该实例属于的服务ID,服务维度
	String getServiceId();

	// 返回该服务实例的主机地址
	String getHost();

	// 返回该服务实例的端口
	int getPort();

	// 该服务实例是否使用HTTPS
	boolean isSecure();

	// 获取实例完整访问地址,例如:http://192.168.1.10:8080
	URI getUri();

	// 注册中心给这个实例附带的额外元数据
	@Nullable Map<String, String> getMetadata();

	// 获取 URI 的协议,HTTP或HTTPS
	default @Nullable String getScheme() {
		return null;
	}

	// 工具方法,用于获取完整的访问地址
	static URI createUri(ServiceInstance instance) {
		String scheme = (instance.isSecure()) ? "https" : "http";
		int port = instance.getPort();
		if (port <= 0) {
			port = (instance.isSecure()) ? 443 : 80;
		}
		String uri = String.format("%s://%s:%s", scheme, instance.getHost(), port);
		return URI.create(uri);
	}

}

假设order-service注册了3个服务:

java
order-service
    ├── 192.168.1.10:8080
    ├── 192.168.1.11:8080
    └── 192.168.1.12:8080

那getServiceId()返回的是服务ID,例如order-service,而getInstanceId()返回的是具体的服务实例ID,例如192.168.1.10#8080#DEFAULT#DEFAULT_GROUP。

getMetadata()用于获取实例的额外信息,例如:

txt
version = v2
zone = shenzhen
weight = 80
gray = true

这些信息可以用于:

txt
灰度发布
版本路由
机房路由
区域路由
权重
环境标识

1.3 AutoServiceRegistration ​

AutoServiceRegistration只是一个标记接口,用于声明一个Bean具备自动服务注册能力。

在Spring Cloud中,提供了基础实现:AbstractAutoServiceRegistration:

java
public abstract class AbstractAutoServiceRegistration<R extends Registration>
		implements AutoServiceRegistration, ApplicationListener<WebServerInitializedEvent> {
  
}

该抽象类实现了ApplicationListener,监听WebServerInitializedEvent事件:

java
public void onApplicationEvent(WebServerInitializedEvent event) {
  ApplicationContext context = event.getApplicationContext();
  if (context instanceof ConfigurableWebServerApplicationContext) {
    if ("management".equals(((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {
      return;
    }
  }
  this.port.compareAndSet(0, event.getWebServer().getPort());
  this.start();
}

在配置了 Spring Boot Actuator 独立管理端口情况下:

yaml
server:
  port: 8080

management:
  server:
    port: 8081

会启动两个容器:

java
                    Spring Boot
                  /             \
                 /               \
        业务 Web Server       Management Server
             :8080                 :8081
                │                    │
                │                    ├── /actuator/health
                │                    ├── /actuator/info
                │                    └── /actuator/metrics
                │
                └── /api/...

所以,如果是Management ApplicationContext情况下,不需要注册服务。

如果是业务容器的情况下,会调用start():

java
public void start() {
  // isEnabled() 是接口方法,由具体实现决定要不要注册服务
  if (!isEnabled()) {
    if (logger.isDebugEnabled()) {
      logger.debug("Discovery Lifecycle disabled. Not starting");
    }
    return;
  }

  // 判断是否已经执行了自动服务注册
  if (!this.running.get()) {
    // 发布InstancePreRegisteredEvent事件
    this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));
    // 调用生命周期方法:postProcessBeforeStartRegister()
    registrationLifecycles.forEach(
        registrationLifecycle -> registrationLifecycle.postProcessBeforeStartRegister(getRegistration()));
    // 注册服务实例
    register();
    // 调用生命周期方法:postProcessAfterStartRegister()
    this.registrationLifecycles.forEach(
        registrationLifecycle -> registrationLifecycle.postProcessAfterStartRegister(getRegistration()));
    // 注册管理服务实例
    if (shouldRegisterManagement()) {
      this.registrationManagementLifecycles
        .forEach(registrationManagementLifecycle -> registrationManagementLifecycle
          .postProcessBeforeStartRegisterManagement(getManagementRegistration()));
      this.registerManagement();
      registrationManagementLifecycles
        .forEach(registrationManagementLifecycle -> registrationManagementLifecycle
          .postProcessAfterStartRegisterManagement(getManagementRegistration()));

    }
    // 发布InstanceRegisteredEvent事件
    this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration()));
    // 标记自动服务注册组件已经启动了
    this.running.compareAndSet(false, true);
  }

}

在register()方法中,实际就是调用ServiceRegistry的注册方法:

java
protected void register() {
  this.serviceRegistry.register(getRegistration());
}

1.4 小结 ​

Spring Cloud Commons关于服务注册,主要接口就是三个:

  • Registration:回答注册什么?
  • ServiceRegistry:回答怎么注册?
  • AutoServiceRegistration:回答什么时候注册?

不同的框架对应不同实现,例如NacosRegistration、NacosServiceRegistry和NacosAutoServiceRegistration,NacosServiceRegistryAutoConfiguration配置向容器中自动注册以上组件,自动完成服务注册。

2. 服务发现 ​

在Spring Cloud中,服务发现抽象接口为DiscoveryClient:

java
public interface DiscoveryClient extends Ordered {

  // 默认实现顺序
	int DEFAULT_ORDER = 0;

	// 返回描述
	String description();

	// 通过服务ID,获取所有的服务实例
	List<ServiceInstance> getInstances(String serviceId);

	// 获取所有的服务ID
	List<String> getServices();

	// 探测 DiscoveryClient 本身是否能正常工作
	default void probe() {
		getServices();
	}

	@Override
	default int getOrder() {
		return DEFAULT_ORDER;
	}

}

其中主要的方法就是getInstances(),用于获取所有注册的服务实例。

不同的实现通过实现该接口,实现服务发现功能,例如NacosDiscoveryClient,在其中与Nacos的组件相联系起来。

3. LoadBalancer ​

LoadBalancer用于实现服务调用的负载均衡。

TIP

在介绍LoadBalancer之前,先了解一个基本原理:**LoadBalancer会为每个服务创建一个子容器。**例如,假设现在有一个名为order-service的服务,那么LoadBalancer会创建一个名为order-service的子容器。

3.1 NamedContextFactory与NamedContextFactory.Specification ​

3.1.1 NamedContextFactory ​

NamedContextFactory是可以按照名称创建和管理多个子容器(ApplicationContext)的工厂类,这是一个抽象类。

它解决的问题是,假设一个应用需要为:

order-service
user-service
product-service

分别维护独立的 Spring 容器。主应用容器只有一个,而 NamedContextFactory 可以管理:

txt
NamedContextFactory
│
├── "order-service"  → ApplicationContext
├── "user-service"   → ApplicationContext
└── "product-service"→ ApplicationContext

所以NamedContextFactory 的核心数据结构如下:

java
private final Map<String, GenericApplicationContext> contexts = new ConcurrentHashMap<>();
  • 键(key):用于表示子容器名称;
  • 值(value):用于表示子容器;

3.1.2 NamedContextFactory.Specification ​

NamedContextFactory.Specification用于定义子容器的配置类,定义如下:

java
public interface Specification {

  String getName();

  Class<?>[] getConfiguration();

}
  • getName():返回子容器名称;
  • getConfiguration():返回对应子容器的配置类数组;

在NamedContextFactory中,也会维护配置类的Map:

java
private final Map<String, C> configurations = new ConcurrentHashMap<>();

3.1.3 创建容器 ​

在NamedContextFactory中,创建容器的核心方法getContext()如下:

java
// name:容器名称
protected GenericApplicationContext getContext(String name) {
  if (!this.contexts.containsKey(name)) {
    // 如果contexts中没有,也就是还没有创建过,需要创建一个新的容器
    synchronized (this.contexts) {
      // 加锁后再次判断,防止并发重复创建
      if (!this.contexts.containsKey(name)) {
        // createContext创建容器,之后放进contexts(相当于缓存)中
        this.contexts.put(name, createContext(name));
      }
    }
  }
  // contexts中有指定名称的容器,直接返回
  return this.contexts.get(name);
}

createContext()方法如下:

java
public GenericApplicationContext createContext(String name) {
  // 首先创建容器
  GenericApplicationContext context = buildContext(name);
  // 在 AOT(Ahead-of-Time)模式下,提前生成的一段“子 ApplicationContext 初始化代码”,用它来替代运行时动态执行 registerBeans()。
  if (applicationContextInitializers.get(name) != null) {
    applicationContextInitializers.get(name).initialize(context);
    context.refresh();
    return context;
  }
  // 在非AOT模式下,动态注册Bean
  registerBeans(name, context);
  // refresh容器
  context.refresh();
  return context;
}

在buildContext()中,主要逻辑如下:

  • 确定容器类型 :在AOT模式下,使用生成的ApplicationContextInitializer初始化容器,所以不需要动态解析配置,直接使用基本的GenericApplicationContext容器即可;在普通模式下,需要解析@Configuration配置类等,所以需要使用AnnotationConfigApplicationContext容器,具有注解解析功能;

  • 确定类加载器:如果有父容器,那么使用父容器的beanClassLoader,并且获取当前类的类加载器作为子容器的类加载器;

    • BeanClassLoader 不是一种特殊的 ClassLoader。它通常就是一个 ClassLoader 对象,只是被 Spring 的 BeanFactory 专门保存下来,用于加载 Bean 相关的类。

      Spring BeanFactory 需要:“如果要根据 BeanDefinition 找到这个 Bean 的 Class,应该用哪个 ClassLoader?”

      java
      BeanDefinition bd = ...;
      
      bd.setBeanClassName(
          "com.example.OrderService"
      );

      需要将全限定名"com.example.OrderService"变为类对象Class<OrderService>,这是就需要类加载器,在Spring BeanFactory中,就通过beanClassLoader来完成。

    • 在创建子容器,还需要设置类加载器:

      java
      ClassLoader classLoader = getClass().getClassLoader();
      context.setClassLoader(classLoader);

      这是用于加载子容器自身需要的资源、类等。

  • 设置容器环境:

    java
    context.getEnvironment().getPropertySources().addFirst(
    				new MapPropertySource(this.propertySourceName, Collections.singletonMap(this.propertyName, name)));

    其实就是在子容器的环境最前面,插入一个名为propertySourceName配置源:

    txt
    Environment
        │
        └── propertySource(name=propertySourceName)
              │
              └── propertyName = name

    其中,propertySourceName和propertyName都是由子类定义的,name就是容器名称。

    例如,在LoadBalancerClientFactory(NamedContextFactory子类)中:

    • propertySourceName值为loadbalancer;
    • propertyName值为loadbalancer.client.name;
    java
    public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>
    		implements ReactiveLoadBalancer.Factory<ServiceInstance> {
    
    	private static final Log log = LogFactory.getLog(LoadBalancerClientFactory.class);
    
    	/**
    	 * Property source name for load balancer.
    	 */
    	public static final String NAMESPACE = "loadbalancer";
    
    	/**
    	 * Property for client name within the load balancer namespace.
    	 */
    	public static final String PROPERTY_NAME = NAMESPACE + ".client.name";
    
    	private final LoadBalancerClientsProperties properties;
    
    	public LoadBalancerClientFactory(LoadBalancerClientsProperties properties) {
    		super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME, new HashMap<>());
    		this.properties = properties;
    	}
    }
  • 设置父容器和显示名称:

    java
    if (this.parent != null) {
      // Uses Environment from parent as well as beans
      context.setParent(this.parent);
    }
    context.setDisplayName(generateDisplayName(name));

以上创建的容器只是一个空架子,下一步就是向容器中注册配置类(普通非AOT模式下):

java
public void registerBeans(String name, GenericApplicationContext context) {
  Assert.isInstanceOf(AnnotationConfigRegistry.class, context);
  AnnotationConfigRegistry registry = (AnnotationConfigRegistry) context;
  // 第一步:从configurations中获取特定名称的配置(专有配置)
  if (this.configurations.containsKey(name)) {
    for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
      registry.register(configuration);
    }
  }
  // 第二步:获取默认配置(以default.开头的配置)
  for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
    if (entry.getKey().startsWith("default.")) {
      for (Class<?> configuration : entry.getValue().getConfiguration()) {
        registry.register(configuration);
      }
    }
  }
  // 第三步:注册PropertyPlaceholderAutoConfiguration配置类和最终的默认配置类(defaultConfigType),defaultConfigType是在创建子容器的时候传入的
  registry.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
}

例如,在LoadBalancerClientFactory中,传入的默认配置类就是LoadBalancerClientConfiguration。

之后,调用refresh()方法刷新容器,至此,子容器创建完成。

3.2 LoadBalancerClientFactory与LoadBalancerClientConfiguration ​

LoadBalancerClientFactory实现了LoadBalancerClientFactory,是LoadBalancer用于管理子容器的工厂类。

LoadBalancerClientFactory还实现了ReactiveLoadBalancer.Factory<ServiceInstance>接口,用于获取某个服务(对应着一个子容器)的配置以及服务实例选择器。

在创建LoadBalancerClientFactory时,传入的子容器默认配置类是LoadBalancerClientConfiguration:

java
public LoadBalancerClientFactory(LoadBalancerClientsProperties properties,
    Map<String, ApplicationContextInitializer<GenericApplicationContext>> applicationContextInitializers) {
  super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME, applicationContextInitializers);
  this.properties = properties;
}

默认情况下,LoadBalancerClientConfiguration会往子容器中注入以下Bean:

  • ReactorLoadBalancer<ServiceInstance>:从多个服务实例中,选取一个;
  • ServiceInstanceListSupplier:根据服务名称,获取所有的服务实例;

以下是最低项目,用于查看子容器中的默认Bean。

pom.xml内容如下:

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.1.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.lee</groupId>
    <artifactId>loadbalancer-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>loadbalancer-demo</name>
    <description>loadbalancer-demo</description>

    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2025.1.3</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
    </dependencies>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

主启动类:

java
@SpringBootApplication
public class LoadbalancerDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(LoadbalancerDemoApplication.class, args);
    }

}

测试类:

java
import org.springframework.boot.CommandLineRunner;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
public class TestRunner implements CommandLineRunner {

    private final LoadBalancerClientFactory factory;

    public TestRunner(LoadBalancerClientFactory factory) {
        this.factory = factory;
    }

    @Override
    public void run(String... args) {

      	// 创建子容器
        ApplicationContext context = factory.createContext("order-service");

        System.out.println("===== Child Context =====");
        System.out.println(context);

        System.out.println("\n===== Beans =====");

        Arrays.stream(context.getBeanDefinitionNames())
                .sorted()
                .forEach(name -> {
                    Object bean = context.getBean(name);
                    System.out.println(
                            name + " -> " + bean.getClass().getName()
                    );
                });
    }
}

3.3 @LoadBalancerClient与@LoadBalancerClients ​

@LoadBalancerClient 和 @LoadBalancerClients 的核心作用定义某个服务的 LoadBalancer 子容器应该使用哪些自定义配置。

这两个注解主要解决的是 “不同服务使用不同 LoadBalancer 配置” 的问题。

  • @LoadBalancerClient:用于配置单个服务容器,例如:

    java
    @Configuration
    @LoadBalancerClient(
            name = "order-service",
            configuration = OrderLoadBalancerConfig.class
    )
    public class LoadBalancerConfig {
    }

    表示order-service服务对应的子容器,增加OrderLoadBalancerConfig配置类,可以自定义容器中的Bean;

  • @LoadBalancerClients:用于批量定义服务容器,例如:

    java
    @Configuration
    @LoadBalancerClients(
            value = {
                    @LoadBalancerClient(
                            name = "order-service",
                            configuration = OrderLoadBalancerConfig.class
                    ),
                    @LoadBalancerClient(
                            name = "user-service",
                            configuration = UserLoadBalancerConfig.class
                    )
            }
    )
    public class LoadBalancerConfig {
    }

    在@LoadBalancerClients中还可以定义默认配置:

    java
    @Configuration
    @LoadBalancerClients(
            defaultConfiguration = DefaultLoadBalancerConfig.class
    )
    public class LoadBalancerConfig {
    }

    表示所有通过 LoadBalancerClientFactory 创建的 LoadBalancer 子容器,都增加 DefaultLoadBalancerConfig 作为默认配置。

这两个注解都是由LoadBalancerClientConfigurationRegistrar进行解析的,主要逻辑如下:

java
// registry: 是主应用容器的Bean定义注册器
// name: 配置名称,如果不是以default.开头的配置名称,也表示容器名称
// configuration: 配置类数组
private static void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
    Object configuration) {
  // BeanDefinition定义的类是LoadBalancerClientSpecification
  BeanDefinitionBuilder builder = BeanDefinitionBuilder
      .genericBeanDefinition(LoadBalancerClientSpecification.class);
  // 添加构造方法参数
  builder.addConstructorArgValue(name);
  builder.addConstructorArgValue(configuration);
  // 在主应用容器中注册Bean
  registry.registerBeanDefinition(name + ".LoadBalancerClientSpecification", builder.getBeanDefinition());
}

// 解析注解的主要逻辑
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
  // 解析@LoadBalancerClients注解
  Map<String, Object> attrs = metadata.getAnnotationAttributes(LoadBalancerClients.class.getName());
  // 获取并解析 每一个@LoadBalancerClient
  if (attrs != null && attrs.containsKey("value")) {
    AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
    for (AnnotationAttributes client : clients) {
      registerClientConfiguration(registry, getClientName(client), client.get("configuration"));
    }
  }
  // 解析 @LoadBalancerClients中的defaultConfiguration
  if (attrs != null && attrs.containsKey("defaultConfiguration")) {
    String name;
    if (metadata.hasEnclosingClass()) {
      name = "default." + metadata.getEnclosingClassName();
    }
    else {
      name = "default." + metadata.getClassName();
    }
    registerClientConfiguration(registry, name, attrs.get("defaultConfiguration"));
  }
  // 解析@LoadBalancerClient注解
  Map<String, Object> client = metadata.getAnnotationAttributes(LoadBalancerClient.class.getName());
  String name = getClientName(client);
  if (name != null) {
    registerClientConfiguration(registry, name, client.get("configuration"));
  }
}

// 获取配置名称
private static String getClientName(Map<String, Object> client) {
  if (client == null) {
    return null;
  }
  String value = (String) client.get("value");
  if (!StringUtils.hasText(value)) {
    value = (String) client.get("name");
  }
  if (StringUtils.hasText(value)) {
    return value;
  }
  throw new IllegalStateException("Either 'name' or 'value' must be provided in @LoadBalancerClient");
}

所以,以上解析完成后,在主应用容器中会添加类型为LoadBalancerClientSpecification的Bean。

3.4 LoadBalancerAutoConfiguration ​

LoadBalancerAutoConfiguration主要作用是向主应用容器中添加LoadBalancerClientFactory 组件的:

java
@Configuration(proxyBeanMethods = false)
@LoadBalancerClients
@EnableConfigurationProperties({ LoadBalancerClientsProperties.class, LoadBalancerEagerLoadProperties.class })
@AutoConfigureBefore({ ReactorLoadBalancerClientAutoConfiguration.class,
		LoadBalancerBeanPostProcessorAutoConfiguration.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.enabled", havingValue = "true", matchIfMissing = true)
public class LoadBalancerAutoConfiguration {

	@ConditionalOnMissingBean
	@Bean
	public LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties,
			ObjectProvider<List<LoadBalancerClientSpecification>> configurations) {
		LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory(properties);
		clientFactory.setConfigurations(configurations.getIfAvailable(Collections::emptyList));
		return clientFactory;
	}
}

3.5 ServiceInstanceListSupplier ​

ServiceInstanceListSupplier接口用于获取某个服务的所有实例,接口定义如下:

java
public interface ServiceInstanceListSupplier extends Supplier<Flux<List<ServiceInstance>>> {

	String getServiceId();

	default Flux<List<ServiceInstance>> get(Request request) {
		return get();
	}

	static ServiceInstanceListSupplierBuilder builder() {
		return new ServiceInstanceListSupplierBuilder();
	}

}

最主要的实现是DiscoveryClientServiceInstanceListSupplier,表示从DiscoveryClient实现获取某个服务的所有实例,定义如下:

java
public class DiscoveryClientServiceInstanceListSupplier implements ServiceInstanceListSupplier {
    public static final String SERVICE_DISCOVERY_TIMEOUT = "spring.cloud.loadbalancer.service-discovery.timeout";
    private static final Log LOG = LogFactory.getLog(DiscoveryClientServiceInstanceListSupplier.class);
    private Duration timeout = Duration.ofSeconds(30L);
  	// 服务ID
    private final String serviceId;
  	// Flux可以理解为异步获取服务实例
    private final Flux<List<ServiceInstance>> serviceInstances;

  	// delegate:DiscoveryClient具体实现
  	// environment:注意,这里是LoadBalancer子容器对应的环境
    public DiscoveryClientServiceInstanceListSupplier(DiscoveryClient delegate, Environment environment) {
      	// 获取服务ID
        String property = environment.getProperty("loadbalancer.client.name");
        Assert.hasText(property, "'serviceId' must not be empty");
        this.serviceId = property;
        this.resolveTimeout(environment);
      	// 通过DiscoveryClient获取某个服务的的所有实现
        this.serviceInstances = Flux.defer(() -> {
            return Mono.fromCallable(() -> {
                return delegate.getInstances(this.serviceId);
            });
        }).timeout(this.timeout, Flux.defer(() -> {
            this.logTimeout();
            return Flux.just(new ArrayList());
        }), Schedulers.boundedElastic()).onErrorResume((error) -> {
            this.logException(error);
            return Flux.just(new ArrayList());
        });
    }
  
  	public Flux<List<ServiceInstance>> get() {
        return this.serviceInstances;
    }
}

在实际使用中,可能还会加上缓存等功能,具体配置如下:

java
@Bean
@ConditionalOnBean(DiscoveryClient.class)
@ConditionalOnMissingBean
@Conditional(DefaultConfigurationCondition.class)
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
    ConfigurableApplicationContext context) {
  return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withCaching().build(context);
}

3.6 ReactiveLoadBalancer ​

ReactiveLoadBalancer的主要功能是从多个服务实例中,根据一定算法选取某一个实例进行调用。接口定义如下:

java
public interface ReactiveLoadBalancer<T> {

	Request<DefaultRequestContext> REQUEST = new DefaultRequest<>();

	@SuppressWarnings("rawtypes")
	Publisher<Response<T>> choose(Request request);

	default Publisher<Response<T>> choose() { 
		return choose(REQUEST);
	}

}

默认实现是RoundRobinLoadBalancer,即轮询选择服务实例:

java
public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
  // 上一次服务实例位置
  final AtomicInteger position;
	// 服务ID
	final String serviceId;
	// 提供服务实例列表
	ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
  
  public RoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
			String serviceId, int seedPosition) {
		this.serviceId = serviceId;
		this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
    // 随机设置起始位置
		this.position = new AtomicInteger(seedPosition);
	}
  
  // 从多个服务实例中,选择一个服务实例
  private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
		if (instances.isEmpty()) {
			if (log.isWarnEnabled()) {
				log.warn("No servers available for service: " + serviceId);
			}
			return new EmptyResponse();
		}

		// Do not move position when there is only 1 instance, especially some suppliers
		// have already filtered instances
		if (instances.size() == 1) {
			return new DefaultResponse(instances.get(0));
		}

		// Ignore the sign bit, this allows pos to loop sequentially from 0 to
		// Integer.MAX_VALUE
		int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;

		ServiceInstance instance = instances.get(pos % instances.size());

		return new DefaultResponse(instance);
	}

}

4. OpenFeign ​

TIP

在了解OpenFeign之前,需要明白OpenFeign也会像LoadBalancer一样,创建子容器。

4.1 基本使用 ​

Spring Cloud OpenFeign是通过声明式的方式调用远程服务的依赖,基本使用需要引入依赖:

xml
<properties>
    <java.version>17</java.version>
    <spring-cloud.version>2025.1.3</spring-cloud.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
  
  	<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
</dependencies>

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

之后,需要定义Feign接口,例如:

java
@FeignClient(
    name = "user-service",
    contextId = "userClient"
)
public interface UserClient {
}

TIP

@FeignClient中name/value和contextId的用法与区别。

  • name/value:需要调用的服务名称,也就是LoadBalancer会根据name/value的值获取服务实例;
  • contextId:是OpenFeign创建的子容器名称;

为什么需要contextId?

假设有两个 Feign Client,都调用user-service服务,但是它们需要不同的配置。

java
@FeignClient(
    name = "user-service",
    contextId = "userClient",
    configuration = UserFeignConfiguration.class
)
public interface UserClient {
}
java
@FeignClient(
    name = "user-service",
    contextId = "adminUserClient",
    configuration = AdminUserFeignConfiguration.class
)
public interface AdminUserClient {
}

于是 Spring Cloud 可以创建两个独立的 Feign 配置上下文:

txt
ApplicationContext
│
├── userClient
│    ├── Encoder
│    ├── Decoder
│    ├── Contract
│    ├── Client
│    └── ...
│
└── adminUserClient
     ├── Encoder
     ├── Decoder
     ├── Contract
     ├── Client
     └── ...

启用OpenFeign,在配置类上加上@EnableFeignClients注解:

java
@SpringBootApplication
@EnableFeignClients
public class LoadBalancerDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(LoadBalancerDemoApplication.class, args);
    }
}

4.2 @EnableFeignClients ​

@EnableFeignClients的作用扫描并注册项目中的 @FeignClient 接口,主要是通过FeignClientsRegistrar发挥作用的。

java
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
  
  	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // 向主应用容器注册默认配置
		registerDefaultConfiguration(metadata, registry);
    // 向主应用容器注册FeignClient
		registerFeignClients(metadata, registry);
	}
}

4.2.1 注册默认配置 ​

首先第一个逻辑是向主应用容器中注册默认配置,假设在@EnableFeignClients中设置了默认配置:

java
@Configuration
public class FeignDefaultConfig {

    @Bean
    public RequestInterceptor requestInterceptor() {
        return template -> {
            template.header("token", "xxx");
        };
    }
}

@EnableFeignClients(
    defaultConfiguration = FeignDefaultConfig.class
)
@SpringBootApplication
public class Application {
}

那么就会通过registerDefaultConfiguration()注册该默认配置:

java
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
  // 获取@EnableFeignClients 注解里的属性
  Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

  // 判断有没有配置默认配置
  if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
    // 获取默认配置唯一的配置名称
    String name;
    if (metadata.hasEnclosingClass()) {
      // 如果@EnableFeignClients配置在内部类中,那么获取外部类名称
      name = "default." + metadata.getEnclosingClassName();
    }
    else {
      // 如果@EnableFeignClients不是配置在内部类,正常获取类名称
      name = "default." + metadata.getClassName();
    }
    // 向主容器中注册OpenFeign默认配置
    registerClientConfiguration(registry, name, "default", defaultAttrs.get("defaultConfiguration"));
  }
}
java
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object className,
    Object configuration) {
  BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
  builder.addConstructorArgValue(name);
  builder.addConstructorArgValue(className);
  builder.addConstructorArgValue(configuration);
  registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
      builder.getBeanDefinition());
}

这里的配置类,就是FeignClientSpecification,其实就是NamedContextFactory.Specification的子类:

java
public class FeignClientSpecification implements NamedContextFactory.Specification {

	private String name;

	private String className;

	private Class<?>[] configuration;
  
  public FeignClientSpecification(String name, String className, Class<?>[] configuration) {
		this.name = name;
		this.className = className;
		this.configuration = configuration;
	}
}

如上例子,完成后在主应用容器中会注册如下默认配置类:

txt
Bean
	- name: default.xxx.Application.FeignClientSpecification
	- beanClass: org.springframework.cloud.openfeign.FeignClientSpecification

创建FeignClientSpecification默认配置类的构造方法参数如下:

txt
name: default.xxx.Application
className: default
configuration: ["xxx.FeignDefaultConfig"]

4.2.2 注册FeignClient ​

FeignClientsRegistrar的主要作用就是注册Feign Client,主要逻辑如下:

java
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
  // candidateComponents为待注册的客户端
  LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
  // 获取 @EnableFeignClients属性
  Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
  // 获取clients属性值, clients表示@FeignClient注解的接口
  final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
  if (clients == null || clients.length == 0) {
    // 如果clients为空,那么就需要扫描@FeignClient注解的接口
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);
    scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
    // 获取value/basePackages/basePackageClasses等值,获取要扫描的包
    Set<String> basePackages = getBasePackages(metadata);
    for (String basePackage : basePackages) {
      candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
    }
  }
  else {
    // clients不为空,直接使用clients指定的Feign Client
    for (Class<?> clazz : clients) {
      candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
    }
  }

  // 对获取到的FeignClient进行注册,也就是放入主应用容器中
  for (BeanDefinition candidateComponent : candidateComponents) {
    if (candidateComponent instanceof AnnotatedBeanDefinition beanDefinition) {
      // verify annotated class is an interface
      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 = getClientName(attributes);
      String className = annotationMetadata.getClassName();
      // 注册特定的FeignClient对应的配置
      registerClientConfiguration(registry, name, className, attributes.get("configuration"));
      // 注册FeignClient
      registerFeignClient(registry, annotationMetadata, attributes);
    }
  }
}

在注册FeignClient时,根据配置,分为懒注册和积极注册:

java
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
    Map<String, Object> attributes) {
  String className = annotationMetadata.getClassName();
  if (String.valueOf(false)
    .equals(environment.getProperty("spring.cloud.openfeign.lazy-attributes-resolution",
        String.valueOf(false)))) {
    eagerlyRegisterFeignClientBeanDefinition(className, attributes, registry);
  }
  else {
    lazilyRegisterFeignClientBeanDefinition(className, attributes, registry);
  }
}

懒注册和积极注册两种模式最终都会向 Spring 主应用容器注册@FeignClient注解接口 对应的 FeignClientFactoryBean BeanDefinition。区别在于 @FeignClient 注解属性是在注册阶段解析,还是延迟到 Bean 创建阶段解析。

  • 积极注册:@FeignClient注解的属性在注册阶段解析;

  • 懒注册:@FeignClient的url、path、dismiss404、fallback、fallbackFactory属性在Bean创建阶段解析;

    注意,@FeignClient的name和contextId属性并没有延迟解析;

    懒注册是为了解决以下场景:

    java
    @FeignClient(
        url = "${service.url}"
    )

    如果在应用启动初期,${service.url}属性还没有值,那么就需要推迟解析时机;

懒注册和积极注册,都是向主应用容器中注册FeignClientFactoryBean;

4.3 FeignClientFactoryBean ​

FeignClientFactoryBean是FactoryBean的实现类,当我们从主应用容器中获取Bean时,会调用FactoryBean的getObject()方法。

@FeignClient只能标注在接口上,因此,从主应用容器中获取的Bean,实际是代理对象。

实际上,是通过JDK的动态代理生成的代理对象,即通过实现接口来生成的。

feign.ReflectiveFeign#newInstance(feign.Target<T>, C):

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

  return proxy;
}

4.4 MethodHandler ​

当实际调用接口中的方法时,也就是调用代理对象的方法,实际会调用到MethodHandler中的invoke()方法,同步调用实现是SynchronousMethodHandler,最终是通过Open Feign的Client执行请求的:

java
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
  Request request = targetRequest(template);

  if (methodHandlerConfiguration.getLogLevel() != Logger.Level.NONE) {
    methodHandlerConfiguration
        .getLogger()
        .logRequest(
            methodHandlerConfiguration.getMetadata().configKey(),
            methodHandlerConfiguration.getLogLevel(),
            request);
  }

  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) {
    if (methodHandlerConfiguration.getLogLevel() != Logger.Level.NONE) {
      methodHandlerConfiguration
          .getLogger()
          .logIOException(
              methodHandlerConfiguration.getMetadata().configKey(),
              methodHandlerConfiguration.getLogLevel(),
              e,
              elapsedTime(start));
    }
    throw errorExecuting(request, e);
  }

  long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
  return responseHandler.handleResponse(
      methodHandlerConfiguration.getMetadata().configKey(), response,
      methodHandlerConfiguration.getMetadata().returnType(), elapsedTime);
}

4.5 Client ​

Open Feign的Client用来执行请求,如果配置了Load Balancer,那么实现是FeignBlockingLoadBalancerClient,执行请求方法如下:

java
@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;
    if (LOG.isWarnEnabled()) {
      LOG.warn(message);
    }
    supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
      .onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(
          CompletionContext.Status.DISCARD, lbRequest, lbResponse)));
    return Response.builder()
      .request(request)
      .status(HttpStatus.SERVICE_UNAVAILABLE.value())
      .body(message, StandardCharsets.UTF_8)
      .build();
  }
  String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
  Request newRequest = buildRequest(request, reconstructedUrl, instance);
  return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,
      supportedLifecycleProcessors);
}

在以上第14行,就是选择服务实例。之后,重新构造请求URL,并执行远程调用。

注意,FeignBlockingLoadBalancerClient中有Client delegate,在第33行时,其实是委托Open Client进行远程调用。FeignBlockingLoadBalancerClient实际上只是一个包装器,多了通过Load Balancer选择服务实例的步骤。