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服务注册的过程。