Spring cloud 中@EnableEurekaClient原始碼分析
Spring cloud 中@EnableEurekaClient原始碼分析
上一篇文章中講述了@EnableEurekaClient和@EnableDiscoveryClient區別,原想可能底層會有較多不同,但是檢視原始碼的時候發現@EnableEurekaClient本身就是用@EnableDiscoveryClient來實現的,因此沒有多大的研究價值,但是如果繼續講@EnableEurekaClient原始碼的話,篇幅過長,因此另外單開一篇文章講述@EnableDiscoveryClient的原始碼。
首先點進@EnableEurekaClient註解原始碼,如下:
/**
* Convenience annotation for clients to enable Eureka discovery configuration
* (specifically). Use this (optionally) in case you want discovery and know for sure that
* it is Eureka you want. All it does is turn on discovery and let the autoconfiguration
* find the eureka classes if they are available (i.e. you need Eureka on the classpath as
* well).
*
* @author Dave Syer
* @author Spencer Gibb
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableDiscoveryClient
public @interface EnableEurekaClient {
}
這裡使用了@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 {
}
註解@EnableDiscoveryClient上有註解@Import(EnableDiscoveryClientImportSelector.class)修飾,
@Import不僅可以單獨匯入一個配置,另外也可以匯入普通的java類,並將其宣告為一個bean。此處匯入EnableDiscoveryClientImportSelector.class後,載入我們用到的bean,EnableDiscoveryClientImportSelector.class原始碼如下:
/**
* @author Spencer Gibb
*/
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector<EnableDiscoveryClient> {
@Override
protected boolean isEnabled() {
return new RelaxedPropertyResolver(getEnvironment()).getProperty(
"spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE);
}
@Override
protected boolean hasDefaultFactory() {
return true;
}
}
這個類中有一個覆蓋父類的方法isEnabled(),返回預設為true,那麼說明只要是引入了EnableDiscoveryClientImportSelector類,spring.cloud.discovery.enabled就處於enable狀態。
EnableDiscoveryClientImportSelector繼承了類SpringFactoryImportSelector,我們再來看這個類的原始碼:
在關鍵的方法selectImports中:
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()]);
}
關鍵程式碼:
// Find all possible auto configuration classes, filtering duplicates
List<String> factories = new ArrayList<>(new LinkedHashSet<>(SpringFactoriesLoader
.loadFactoryNames(this.annotationClass, this.beanClassLoader)));
根據這一步來找到configuration class,這裡的SpringFactoriesLoader.loadFactoryNames就是根據配置檔案來load class,轉到SpringFactoriesLoader.loadFactoryNames,原始碼如下:
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration ex = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
ArrayList result = new ArrayList();
while(ex.hasMoreElements()) {
URL url = (URL)ex.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
} catch (IOException var8) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
}
}
這裡loadMETA-INF下的spring.factories檔案,這個檔案中就是load那幾個配置,這裡要提一點的就是這個spring.factories指的是@EnableEurekaClient對應原始碼中META-INF下的spring.factories檔案,圖如下:
spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration
org.springframework.cloud.client.discovery.EnableDiscoveryClient=\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
開啟EurekaClientConfigServerAutoConfiguration,如下:
package org.springframework.cloud.netflix.eureka.config;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.config.server.config.ConfigServerProperties;
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.discovery.EurekaClient;
/**
* Extra configuration for config server if it happens to be a Eureka instance.
*
* @author Dave Syer
*/
@Configuration
@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)) {
this.instance.getMetadataMap().put("configPath", prefix);
}
}
}
這個類上的註解為:
@ConditionalOnClass({ EurekaInstanceConfigBean.class, EurekaClient.class,
ConfigServerProperties.class })
意義為當前程式中存在EurekaInstanceConfigBean或者EurekaClient,或者ConfigServerProperties的時候,當前這個配置類會被進行載入,否則不會載入。
這個在載入EurekaClient.class,例項化EurekaClient,但是EurekaClient本身是由DiscoveryClient來實現的,程式碼如下:
/**
* Define a simple interface over the current DiscoveryClient implementation.
*
* This interface does NOT try to clean up the current client interface for eureka 1.x. Rather it tries
* to provide an easier transition path from eureka 1.x to eureka 2.x.
*
* EurekaClient API contracts are:
* - provide the ability to get InstanceInfo(s) (in various different ways)
* - provide the ability to get data about the local Client (known regions, own AZ etc)
* - provide the ability to register and access the healthcheck handler for the client
*
* @author David Liu
*/
@ImplementedBy(DiscoveryClient.class)
public interface EurekaClient extends LookupService {
在例項化DiscoveryClient後,對應的服務註冊服務就開始執行起來了,由此基本的@EnableEurekaClient原始碼講解就講完了。
下一篇文章具體講述EurekaClient服務註冊的過程。