Spring Cloud 2.0 實戰——Eureka(四.Eureka架構整理以及原始碼分析 —— 自我理解)
簡述Eureka服務治理體系
在分析原始碼之前,我們再來簡單的梳理一下Eureka服務治理體系。在整個服務治理基礎架構中有三個核心要素:
- 服務註冊中心:Eureka提供的服務端,提供服務註冊與發現的功能,也就是在之前我們是實現的eurekaserver。
- 服務提供者: 提供服務的應用,可以是Springboot應用,也可以是其他技術平臺且遵循Eureka通訊機制的應用,他將自己提供的服務註冊到Eureka,以供其他應用發現。
- 服務消費者:消費者應用從服務註冊中心獲取服務列表,從而使消費者可以知道去何處呼叫其所需要的服務,在上一節中使用了Ribbon來實現消費,後面還會介紹使用Feign的消費方式
很多時候,客戶端即是服務提供者也是服務消費者。
原始碼分析
下面通過原始碼看一下Eureka是如何運作和配置的。
首先思考一下對於服務註冊中心、服務提供者、服務消費者這三個主要元素來說,後兩個也就是Eureka客戶端在整個執行機制中是大部分通訊行為的主動發起者,而註冊中心主要是處理請求的接受者,所以,我們可以從Eureka的客戶端作為入口看看它是如何完成這些主動通訊行為的。
我們將一個普通的SpringBoot應用註冊到EurekaServer或者從EurekaServer中獲取服務列表時候,主要做了兩件事:
- 在應用主類中配置了@EnableDiscoveryClient
- 在application.yaml配置檔案彙總,使用defaultZone來指定服務註冊中心的位置。
**注意:根據官方文件github顯示,在EnableEurekaServer.java
也就是@EnableEurekaServer註解中移除@EnableDiscoveryClient註解**
所以我們先看@EnableDiscoveryClient的原始碼,具體如下:
@EnableDiscoveryClient
/**
* Annotation to enable a DiscoveryClient implementation.
* @author Spencer Gibb
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {
/**
* If true, the ServiceRegistry will automatically register the local server.
*/
boolean autoRegister() default true;
}
我們通過註解可以清晰的知道,這個類的主要作用是開啟DiscoveryClient例項的。
同時會預設autoRegister為true;
那麼我們看一下是如是實現的:
實現DiscoveryClient例項
匯入EnableDiscoveryClientImportSelector類
我們都知道,@Import在Spring4.2之後,不僅可以匯入配置類,也可以匯入普通的java類,並將其宣告成一個bean。所以這裡將EnableDiscoveryClientImportSelector匯入進來。
EnableDiscoveryClientImportSelector類
這個類有一個重要方法,selectImports
@Override
public String[] selectImports(AnnotationMetadata metadata) {
String[] imports = super.selectImports(metadata);
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));
boolean autoRegister = attributes.getBoolean("autoRegister");
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;
}
首先可以看到這裡取到了之前定義的autoRegister為true。
其次這裡面呼叫的他的父類的selectImports方法,所以我們看下父類selectImports方法:
父類selectImports
@Override
public String[] selectImports(AnnotationMetadata metadata) {
if (!isEnabled()) {
return new String[0];
}
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(this.annotationClass.getName(), true));
Assert.notNull(attributes, "No " + getSimpleName() + " attributes found. Is "
+ metadata.getClassName() + " annotated with @" + getSimpleName() + "?");
// Find all possible auto configuration classes, filtering duplicates
// 查詢所有可能的自動配置類,過濾重複
List<String> factories = new ArrayList<>(new LinkedHashSet<>(SpringFactoriesLoader
.loadFactoryNames(this.annotationClass, this.beanClassLoader)));
if (factories.isEmpty() && !hasDefaultFactory()) {
throw new IllegalStateException("Annotation @" + getSimpleName()
+ " found, but there are no implementations. Did you forget to include a starter?");
}
if (factories.size() > 1) {
// there should only ever be one DiscoveryClient, but there might be more than
// one factory
log.warn("More than one implementation " + "of @" + getSimpleName()
+ " (now relying on @Conditionals to pick one): " + factories);
}
return factories.toArray(new String[factories.size()]);
}
這個方法裡面有兩行註釋,第一行簡單翻譯一下就是:查詢所有可能的自動配置類,過濾重複。所以我們合理猜測,下面的程式碼就是去進行查詢所有可能的自動配置類,所以我們點進loadFactoryNames方法。
loadFactoryNames方法:
/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
/**
* Load the fully qualified class names of factory implementations of the
* given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
* class loader.
* @param factoryClass the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading resources; can be
* {@code null} to use the default
* @see #loadFactories
* @throws IllegalArgumentException if an error occurs while loading factory names
*/
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
這裡有兩點要說明:
- FACTORIES_RESOURCE_LOCATIONL:定義的配置檔案路徑,此時該檔案指的是@EnableDiscoveryClient所在包的spring.factories檔案。
- 翻譯一下loadFactoryNames方法的註釋:使用給定的類載入器從給定的型別的工廠實現中載入完全限定的類名。
我們看下配置檔案:
#
# AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.client.CommonsClientAutoConfiguration,\
org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancerAutoConfiguration,\
org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration,\
org.springframework.cloud.commons.httpclient.HttpClientConfiguration,\
org.springframework.cloud.commons.util.UtilAutoConfiguration,\
org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.client.HostInfoEnvironmentPostProcessor
其實我一開始想的是,載入其中的某個類,進行實現,例項化DiscoveryClient。但是打過斷點後發現:EnableDiscoveryClientImportSelector中selectImports方法返回的imports是空。接下來的程式碼:
將AutoServiceRegistrationConfiguration新增到imports中,也就是這裡進行了自動註冊配置。
我找到AutoServiceRegistrationConfiguration類,看了原始碼:
AutoServiceRegistrationConfiguration
package org.springframework.cloud.client.serviceregistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author Spencer Gibb
*/
@Configuration
@EnableConfigurationProperties(AutoServiceRegistrationProperties.class)
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
public class AutoServiceRegistrationConfiguration {
}
說實話,重點是我認為@ConditionalOnProperty註解,然後再往下我就先不寫了,因為自己的思路斷了。
下一章看書怎麼寫的。