Appearance
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?”javaBeanDefinition bd = ...; bd.setBeanClassName( "com.example.OrderService" );需要将全限定名
"com.example.OrderService"变为类对象Class<OrderService>,这是就需要类加载器,在Spring BeanFactory中,就通过beanClassLoader来完成。在创建子容器,还需要设置类加载器:
javaClassLoader classLoader = getClass().getClassLoader(); context.setClassLoader(classLoader);这是用于加载子容器自身需要的资源、类等。
设置容器环境:
javacontext.getEnvironment().getPropertySources().addFirst( new MapPropertySource(this.propertySourceName, Collections.singletonMap(this.propertyName, name)));其实就是在子容器的环境最前面,插入一个名为
propertySourceName配置源:txtEnvironment │ └── propertySource(name=propertySourceName) │ └── propertyName = name其中,
propertySourceName和propertyName都是由子类定义的,name就是容器名称。例如,在
LoadBalancerClientFactory(NamedContextFactory子类)中:propertySourceName值为loadbalancer;propertyName值为loadbalancer.client.name;
javapublic 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; } }设置父容器和显示名称:
javaif (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选择服务实例的步骤。