前面章节介绍了 Eureka 的基础用法,本章将开始简单的分析 Eureka 源码,查看它的启动过程,它是怎样注册、续约、服务同步等。下面是 Spring Cloud 对应的 Spring Boot 版本信息:
本文分析的源码版本信息如下:
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 集群。
启动服务需要添加 @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 接口。它们之间的关系如图所示:
以上是 @EnableDiscoveryClient 的启动逻辑,从 EnableDiscoveryClient 的注释中我们可以看到,它最终主要是用来开启 com.netflix.discovery.DiscoveryClient 的实例。从该类的注释可以看出,该类包含服务注册、服务续约、服务下线、获取服务等功能。