@EnableEurekaClient注解,從源碼的角度分析是如何work的
NetFlix Eureka client
Eureka client
負責與Eureka Server
配合向外提供注冊與發現服務接口。首先看下eureka client是怎么定義,
Netflix的 eureka client的行為在LookupService
中定義,Lookup service for finding active instances,定義了,
從outline中能看到起“規定”了如下幾個最基本的方法。服務發現必須實現的基本類:com.netflix.discovery.shared.LookupService
Eureka client與Spring Cloud Eureka Client類圖,如下所示:
前綴,帶有S
的是Spring Cloud封裝的,帶有N
是NetFlix原生的。
public class EurekaDiscoveryClient implements DiscoveryClient { public static final String DESCRIPTION = "Spring Cloud Eureka Discovery Client"; private final EurekaInstanceConfig config; private final EurekaClient eurekaClient; //Netflix中的Eureka Client public String description() { return "Spring Cloud Eureka Discovery Client"; } public ServiceInstance getLocalServiceInstance() { return new ServiceInstance() { public String getServiceId() { return EurekaDiscoveryClient.this.config.getAppname(); } public String getHost() { return EurekaDiscoveryClient.this.config.getHostName(false); } public int getPort() { return EurekaDiscoveryClient.this.config.getNonSecurePort(); } public boolean isSecure() { return EurekaDiscoveryClient.this.config.getSecurePortEnabled(); } public URI getUri() { return DefaultServiceInstance.getUri(this); } public Map<String, String> getMetadata() { return EurekaDiscoveryClient.this.config.getMetadataMap(); } }; } public List<ServiceInstance> getInstances(String serviceId) { List infos = this.eurekaClient.getInstancesByVipAddress(serviceId, false); ArrayList instances = new ArrayList(); Iterator var4 = infos.iterator(); while(var4.hasNext()) { InstanceInfo info = (InstanceInfo)var4.next(); instances.add(new EurekaDiscoveryClient.EurekaServiceInstance(info)); } return instances; } public List<String> getServices() { Applications applications = this.eurekaClient.getApplications(); if(applications == null) { return Collections.emptyList(); } else { List registered = applications.getRegisteredApplications(); ArrayList names = new ArrayList(); Iterator var4 = registered.iterator(); while(var4.hasNext()) { Application app = (Application)var4.next(); if(!app.getInstances().isEmpty()) { names.add(app.getName().toLowerCase()); } } return names; } } @ConstructorProperties({"config", "eurekaClient"}) public EurekaDiscoveryClient(EurekaInstanceConfig config, EurekaClient eurekaClient) { this.config = config; this.eurekaClient = eurekaClient; } public static class EurekaServiceInstance implements ServiceInstance { private InstanceInfo instance; EurekaServiceInstance(InstanceInfo instance) { this.instance = instance; } public InstanceInfo getInstanceInfo() { return this.instance; } public String getServiceId() { return this.instance.getAppName(); } public String getHost() { return this.instance.getHostName(); } public int getPort() { return this.isSecure()?this.instance.getSecurePort():this.instance.getPort(); } public boolean isSecure() { return this.instance.isPortEnabled(PortType.SECURE); } public URI getUri() { return DefaultServiceInstance.getUri(this); } public Map<String, String> getMetadata() { return this.instance.getMetadata(); } } }
EurekaDiscoveryClient實現了DiscoveryClient,並依賴於com.netflix.discovery.EurekaClient
點開com.netflix.discovery.EurekaClient查看代碼,可以看出EurekaClient繼承了LookupService並實現了EurekaClient接口。
@ImplementedBy(DiscoveryClient.class) public interface EurekaClient extends LookupService { //其余省略 }
com.netflix.discovery.DiscoveryClient是netflix使用的客戶端,從其class的注釋可以看到他主要做這幾件事情:
a) Registering the instance with Eureka Server
b) Renewalof the lease with Eureka Server
c) Cancellation of the lease from Eureka Server during shutdown
其中com.netflix.discovery.DiscoveryClient
實現了com.netflix.discovery.EurekaClient
,
而spring Cloud中的org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient
,
依賴於com.netflix.discovery.EurekaClient
,因此Spring Cloud與NetFlix的關系由此聯系到一起。
@Singleton public class DiscoveryClient implements EurekaClient { private static final Logger logger = LoggerFactory.getLogger(DiscoveryClient.class); // Constants public static final String HTTP_X_DISCOVERY_ALLOW_REDIRECT = "X-Discovery-AllowRedirect"; //其余省略 }
@EnableEurekaClient注解入口分析,分析主要調用鏈中的類和方法。
通過@EnableEurekaClient這個簡單的注解,在spring cloud應用啟動的時候,就可以把EurekaDiscoveryClient注入,繼而使用NetFlix提供的Eureka client。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @EnableDiscoveryClient public @interface EnableEurekaClient { }
EnableEurekaClient上面加入了另外一個注解@EnableDiscoveryClient,看看這個注解的代碼如下所示:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({EnableDiscoveryClientImportSelector.class}) public @interface EnableDiscoveryClient { }
這個注解import了EnableDiscoveryClientImportSelector.class這樣一個類,其實就是通過這個類來加載
需要用到的bean。
點開EnableDiscoveryClientImportSelector類,如下代碼:
/** * @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; } }
看到這里有覆蓋了父類SpringFactoryImportSelector
的一個方法isEnabled
,注意,默認是TRUE,也就是只要import了這個配置,就會enable。
在其父類org.springframework.cloud.commons.util.SpringFactoryImportSelector
的String[] selectImports(AnnotationMetadata metadata)
方法中正是根據這個標記類判定是否加載如下定義的類
@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()]); }
在源碼中70-71行,即在
org.springframework.core.io.support.SpringFactoriesLoader 中的109行的loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader)方法
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); List<String> result = new ArrayList<String>(); 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); } }
實際調用loadFactoryNames
其實加載META-INF/spring.factories
下的class。
具體@EnableEurekaClien注解開啟之后,服務啟動后,服務就查以注冊了