概述
一直关注spring cloud,以后微服务必将其发扬光大,所以结合源码和网上的文章详细研究一下。
本文主要是分析eureka(EurekaClient)的初始化过程。当需要使用EurekaClient的时候,会在SpringBootApplication上增加EnableEurekaClient注解,那么我们就从spring boot项目中的EnableEurekaClient注解开始(EurekaServer启动过程也类似)。
1. 加载过程
1.1 分析过程
EnableEurekaClient ->
EnableDiscoveryClient ->
EnableDiscoveryClientImportSelector ->
EnableDiscoveryClientImportSelector(selectImports方法) ->
SpringFactoriesLoader(loadFactoryNames添加spring.factories下的类,spring启动初始化这些类)
1.2 spring 启动过程
spring refresh ->
annotation.ConfigurationClassParser.processImports() ->
EurekaClientAutoConfiguration ->
DiscoveryClient(spring boot 包) ->
CloudEurekaClient(eureka包)
2. EnableEurekaClient 注解
@EnableDiscoveryClient
public @interface EnableEurekaClient
3. EnableDiscoveryClient 注解
Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient
4. EnableDiscoveryClientImportSelector
该类会调用父类的方法selectImports,父类中的selectImports会调用spring的loadFactoryNames,将配置文件下的类加载,先放到指定的map中等待spring初始化,实际初始化这些类是在org.springframework.context.annotation.ConfigurationClassParser.processImports()
4.1 EnableDiscoveryClientImportSelector
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector {
@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 importsList = new ArrayList<>(Arrays.asList(imports));
importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
imports = importsList.toArray(new String[0]);
}
return imports;
}
@Override
protected boolean isEnabled() {
return new RelaxedPropertyResolver(getEnvironment()).getProperty(
"spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE);
}
@Override
protected boolean hasDefaultFactory() {
return true;
}
}
4.2 SpringFactoryImportSelector
EnableDiscoveryClientImportSelector的父类,调用SpringFactoriesLoader.loadFactoryNames加载配置文件下的类。
@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 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()]);
}
4.3 SpringFactoriesLoader
该类的方法loadFactoryNames,将FACTORIES_RESOURCE_LOCATION下的类添加到一个className的列表中。
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List loadFactoryNames(Class> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List result = new ArrayList();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
5. EurekaClient的初始化
** eureka包中resource下的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
EurekaClientAutoConfiguration 会初始化EurekaClientConfigBean、 EurekaInstanceConfigBean和DiscoveryClient(DiscoveryClient实际是对eurake中的DiscoveryClient的封装)
5.1 EurekaClientAutoConfiguration
该类中初始化了EurekaClientConfigBean、 EurekaInstanceConfigBean和DiscoveryClient
** EurekaClientConfigBean类 **
@Bean
@ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)
public EurekaClientConfigBean eurekaClientConfigBean() {
EurekaClientConfigBean client = new EurekaClientConfigBean();
if ("bootstrap".equals(this.env.getProperty("spring.config.name"))) {
// We don't register during bootstrap by default, but there will be another
// chance later.
client.setRegisterWithEureka(false);
}
return client;
}
** EurekaInstanceConfigBean类 **
@Bean
@ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT)
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils) {
EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);
instance.setNonSecurePort(this.nonSecurePort);
instance.setInstanceId(getDefaultInstanceId(this.env));
if (this.managementPort != this.nonSecurePort && this.managementPort != 0) {
if (StringUtils.hasText(this.hostname)) {
instance.setHostname(this.hostname);
}
RelaxedPropertyResolver relaxedPropertyResolver = new RelaxedPropertyResolver(env, "eureka.instance.");
String statusPageUrlPath = relaxedPropertyResolver.getProperty("statusPageUrlPath");
String healthCheckUrlPath = relaxedPropertyResolver.getProperty("healthCheckUrlPath");
if (StringUtils.hasText(statusPageUrlPath)) {
instance.setStatusPageUrlPath(statusPageUrlPath);
}
if (StringUtils.hasText(healthCheckUrlPath)) {
instance.setHealthCheckUrlPath(healthCheckUrlPath);
}
String scheme = instance.getSecurePortEnabled() ? "https" : "http";
instance.setStatusPageUrl(scheme + "://" + instance.getHostname() + ":"
+ this.managementPort + instance.getStatusPageUrlPath());
instance.setHealthCheckUrl(scheme + "://" + instance.getHostname() + ":"
+ this.managementPort + instance.getHealthCheckUrlPath());
}
return instance;
}
** DiscoveryClient类 **
@Bean
public DiscoveryClient discoveryClient(EurekaInstanceConfig config,
EurekaClient client) {
return new EurekaDiscoveryClient(config, client);
}
5.2 EurekaClient的创建
在EurekaClientAutoConfiguration中的静态类EurekaClientConfiguration中,创建了CloudEurekaClient。
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
public EurekaClient eurekaClient(ApplicationInfoManager manager,
EurekaClientConfig config) {
return new CloudEurekaClient(manager, config, this.optionalArgs,
this.context);
}
5.3 CloudEurekaClient
这个类实际继承了 DiscoveryClient(eureka-client包中的),其实spring boot中的EurekaClient最终也就是对eurekaClient(eureka-client)的封装
public CloudEurekaClient(ApplicationInfoManager applicationInfoManager,
EurekaClientConfig config,
DiscoveryClientOptionalArgs args,
ApplicationEventPublisher publisher) {
super(applicationInfoManager, config, args);
this.applicationInfoManager = applicationInfoManager;
this.publisher = publisher;
this.eurekaTransportField = ReflectionUtils.findField(DiscoveryClient.class, "eurekaTransport");
ReflectionUtils.makeAccessible(this.eurekaTransportField);
}
6. 实例化
spring启动的时候,调用spring的refresh(AbstractApplicationContext),fresh中会调用西面的方法来进行对imports的类进行实例化。
org.springframework.context.annotation.ConfigurationClassParser.processImports()
参考:http://www.idouba.net/spring-cloud-source-load-eureka-client-by-annotation/