Eureka 客户端源码分析:启动过程

前面章节介绍了 Eureka 的基础用法,本章将开始简单的分析 Eureka 源码,查看它的启动过程,它是怎样注册、续约、服务同步等。下面是 Spring Cloud 对应的 Spring Boot 版本信息:

Eureka 客户端源码分析:启动过程

本文分析的源码版本信息如下:

  • Spring cloud 版本为 2020.0.0

  • Spring Boot 版本为 2.4.2

其中,涉及的 jar 包名称和版本如下:

  • spring-cloud-netflix-eureka-client-3.0.0.jar 

  • eureka-client-1.10.10.jar

  • eureka-core-1.10.10.jar

Eureka 分为服务端(Eureka server)和客户端(Eureka client)。服务端就是注册中心,注册到服务端的服务实例称为客户端;客户端可以是服务提供者、也可是服务消费者。服务端也会向另一个服务端实例注册自己的信息,从而实现 Eureka server 集群。

Eureka Client 启动过程

启动服务需要添加 @EnableDiscoveryClient 或 @EnableEurekaClient 注解,从 Spring Cloud Edgware 开始,可省略这两个注解,只需在 classpath 上存在 Eureka 相关依赖,并进行相应配置,即可将微服务注册到服务发现组件上。

@EnableDiscoveryClient 和 @EnableEurekaClient 共同点是让注册中心能够发现,扫描到该服务。不同点是 @EnableEurekaClient 注解只适用于 Eureka 作为注册中心,而 @EnableDiscoveryClient 可以是其他注册中心。

下面开始分析源码:

(1)@EnableDiscoveryClient

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {
   // 控制 ServiceRegistry 是否自动注册本地服务,默认为 true
   boolean autoRegister() default true;
}

上面源码中,@Import 注解是将指定的 Bean 加入到 IOC 容器之中进行管理,ImportSelector 接口有一个 selectImports 方法,该方法将返回一个数组,也就是类实例名称,@Import 注解将会把 selectImports 方法返回的 Bean 加入到 IOC 容器中进行管理。

我们下面进一步分析 EnableDiscoveryClientImportSelector 类的源码。

(2)EnableDiscoveryClientImportSelector

@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector extends SpringFactoryImportSelector<EnableDiscoveryClient> {

   @Override
   public String[] selectImports(AnnotationMetadata metadata) {
       // 1.核心功能在这里,获取需要注册到 Spring 的类
       String[] imports = super.selectImports(metadata);
       AnnotationAttributes attributes = AnnotationAttributes
               .fromMap(metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));
       // autoRegister 为 @EnableDiscoveryClient 注解的属性
       boolean autoRegister = attributes.getBoolean("autoRegister");

       // 2.autoRegister默认为true,同时则注册AutoServiceRegistrationConfiguration类到Spring中
       if (autoRegister) {
           List<String> importsList = new ArrayList<>(Arrays.asList(imports));
           importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
           imports = importsList.toArray(new String[0]);
       }
       else {
           Environment env = getEnvironment();
           if (ConfigurableEnvironment.class.isInstance(env)) {
               ConfigurableEnvironment configEnv = (ConfigurableEnvironment) env;
               LinkedHashMap<String, Object> map = new LinkedHashMap<>();
               map.put("spring.cloud.service-registry.auto-registration.enabled", false);
               MapPropertySource propertySource = new MapPropertySource("springCloudDiscoveryClient", map);
               configEnv.getPropertySources().addLast(propertySource);
           }

       }

       return imports;
   }

   @Override
   protected boolean isEnabled() {
       // 默认为 true,在父类的 selectImports() 方法中会用到
       // if (!isEnabled()) { return new String[0]; }
       return getEnvironment().getProperty("spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE);
   }

   @Override
   protected boolean hasDefaultFactory() {
       return true;
   }
}

继续进一步分析 super.selectImports(metadata) 语句,这里的 selectImports() 方法实际在 SpringFactoryImportSelector 抽象类中,代码如下:

public abstract class SpringFactoryImportSelector<T>
       implements DeferredImportSelector, BeanClassLoaderAware, EnvironmentAware {
   //...
   @SuppressWarnings("unchecked")
   protected SpringFactoryImportSelector() {
       this.annotationClass = (Class<T>) GenericTypeResolver.resolveTypeArgument(this.getClass(),
               SpringFactoryImportSelector.class);
   }

   @Override
   public String[] selectImports(AnnotationMetadata metadata) {
       // enabled 默认值为 true,见子类 EnableDiscoveryClientImportSelector 的 isEnabled() 方法
       if (!isEnabled()) {
           return new String[0];
       }
       //...
       // Find all possible auto configuration classes, filtering duplicates
       // 查找所有可能的自动配置类,过滤重复项
       List<String> factories = new ArrayList<>(new LinkedHashSet<>(
               SpringFactoriesLoader.loadFactoryNames(this.annotationClass, this.beanClassLoader)));
       //...
       if (factories.size() > 1) {
           // 只能有一个DiscoveryClient,但可能有多个工厂
           this.log.warn("More than one implementation " + "of @" + getSimpleName()
                   + " (now relying on @Conditionals to pick one): " + factories);
       }
       return factories.toArray(new String[factories.size()]);
   }
   //...
}

进一步分析上面源码中的 SpringFactoriesLoader.loadFactoryNames() 方法,源码如下:

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
   ClassLoader classLoaderToUse = classLoader;
   if (classLoaderToUse == null) {
       classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
   }
   String factoryTypeName = factoryType.getName();
   // 使用给定的类加载器从 FACTORIES_RESOURCE_LOCATION 加载给定类型的工厂实现的标准类名称。
   // 从Spring Framework 5.3开始,如果多次发现给定工厂类型的特定实现类名称,则将忽略重复项。
   return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
   // classLoader 类型为 Launcher$AppClassLoader@1713
   Map<String, List<String>> result = cache.get(classLoader);
   if (result != null) {
       return result;
   }

   result = new HashMap<>();
   try {
       // FACTORIES_RESOURCE_LOCATION 是一个常亮,定义如下:
       // public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
       // urls 类型为 CompoundEnumeration@1737
       // 查找拥有 FACTORIES_RESOURCE_LOCATION 文件的 jar 文件列表
       Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
       while (urls.hasMoreElements()) {
           URL url = urls.nextElement();
           // spring-boot-2.4.2.jar 文件,它包含了 META-INF/spring.factories
           // jar:file:/D:/repository/maven/org/springframework/boot/spring-boot/2.4.2/spring-boot-2.4.2.jar!/META-INF/spring.factories
           UrlResource resource = new UrlResource(url);
           // 解析 META-INF/spring.factories 属性文件
           Properties properties = PropertiesLoaderUtils.loadProperties(resource);
           for (Map.Entry<?, ?> entry : properties.entrySet()) {
               // 属性key,如:org.springframework.boot.autoconfigure.EnableAutoConfiguration
               String factoryTypeName = ((String) entry.getKey()).trim();
               // 属性值,一个列表
               String[] factoryImplementationNames =
                       StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
               for (String factoryImplementationName : factoryImplementationNames) {
                   result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                           .add(factoryImplementationName.trim());
               }
           }
       }

       // Replace all lists with unmodifiable lists containing unique elements
       // 将所有列表替换为包含唯一元素的不可修改列表
       result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
               .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
       cache.put(classLoader, result);
   }
   catch (IOException ex) {
       throw new IllegalArgumentException("Unable to load factories from location [" +
               FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
   return result;
}

spring-cloud-netflix-eureka-client-3.0.0.jar 中的 spring.factories,内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.reactive.EurekaReactiveDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.loadbalancer.LoadBalancerEurekaAutoConfiguration

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaConfigServerBootstrapConfiguration

org.springframework.boot.Bootstrapper=\
org.springframework.cloud.netflix.eureka.config.EurekaConfigServerBootstrapper

将以上类注册到 Spring 容器中,EurekaClient 的关键功能就在 EurekaClientConfigServerAutoConfiguration 中,下面我们一起来看下这个类:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass({ EurekaInstanceConfigBean.class, EurekaClient.class, ConfigServerProperties.class })
public class EurekaClientConfigServerAutoConfiguration {

   @Autowired(required = false)
   private EurekaInstanceConfig instance;

   @Autowired(required = false)
   private ConfigServerProperties server;

   @PostConstruct
   public void init() {
       if (this.instance == null || this.server == null) {
           return;
       }
       String prefix = this.server.getPrefix();
       if (StringUtils.hasText(prefix) && !StringUtils.hasText(this.instance.getMetadataMap().get("configPath"))) {
           this.instance.getMetadataMap().put("configPath", prefix);
       }
   }

}

上面的 init() 方法中依赖 EurekaClient.class,EurekaClient 主要是一个接口,该接口定义了 Eureka 客户端的主要功能,并定义了默认实现类 DiscoveryClient,代码如下:

@ImplementedBy(DiscoveryClient.class)
public interface EurekaClient extends LookupService {
   //...
}
@Singleton
public class DiscoveryClient implements EurekaClient {
   //...
}

该类包含了 Eureka Client 向 Eureka Server 的相关方法。并且它是一个单例模式,而 EurekaClient 又继承了 LookupService 接口。它们之间的关系如图所示:

Eureka 客户端源码分析:启动过程

以上是 @EnableDiscoveryClient 的启动逻辑,从 EnableDiscoveryClient 的注释中我们可以看到,它最终主要是用来开启 com.netflix.discovery.DiscoveryClient 的实例。从该类的注释可以看出,该类包含服务注册、服务续约、服务下线、获取服务等功能。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号