欢迎访问Spring Cloud中国社区

《重新定义Spring Cloud实战》由Spring Cloud中国社区倾力打造,基于Spring Cloud的Finchley.RELEASE版本,本书内容宽度足够广、深度足够深,而且立足于生产实践,直接从生产实践出发,包含大量生产实践的配置。欢迎加微信Software_King进群答疑,国内谁在使用Spring Cloud?欢迎登记

Spring Cloud中@EnableEurekaClient源码分析

xujin · 5月前 · 770 ·

摘要:在这篇文章中主要介绍一下Spring Cloud中的@EnableEurekaClient注解,从源码的角度分析是如何work的,让大家能了解Spring Cloud如何通过@EnableEurekaClient注解对NetFlix Eureka Client进行封装为它所用。

1.NetFlix Eureka client简介

1.1 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<T>,可以自行查看源码。

2. Eureka client与Spring Cloud类关系

Eureka client与Spring Cloud Eureka Client类图,如下所示:
类关系图

在上图中,我加了前缀,带有S的是Spring Cloud封装的,带有N是NetFlix原生的。

1.org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient中49行的eurekaClient就是com.netflix.discovery.EurekaClient,代码如下所示:

@RequiredArgsConstructor
public class EurekaDiscoveryClient implements DiscoveryClient {
 public static final String DESCRIPTION = "Spring Cloud Eureka Discovery Client";
 private final EurekaInstanceConfig config;
 // Netflix中的Eureka Client
 private final EurekaClient eurekaClient;
 //其余省略
}

Tips:org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient实现了DiscoveryClient,并依赖于com.netflix.discovery.EurekaClient

2.点开com.netflix.discovery.EurekaClient查看代码,可以看出EurekaClient继承了LookupService并实现了EurekaClient接口。

@ImplementedBy(DiscoveryClient.class)
public interface EurekaClient extends LookupService {
  //其余省略
}

3.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";
    //其余省略
}

3. @EnableEurekaClient注解入口分析

在上面小节中,理清了NetFlix EurekaSpring cloud中类的依赖关系,下面将以@EnableEurekaClient为入口,分析主要调用链中的类和方法。

3.1 @EnableEurekaClient使用

1.用过spring cloud的同学都知道,使用@EnableEurekaClient就能简单的开启Eureka Client中的功能,如下代码所示。

@EnableEurekaClient
@SpringBootApplication
public class CloudEurekaClientApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(CloudEurekaClientApplication.class).web(true).run(args);
    }

}

通过@EnableEurekaClient这个简单的注解,在spring cloud应用启动的时候,就可以把EurekaDiscoveryClient注入,继而使用NetFlix提供的Eureka client。

2.打开EnableEurekaClient这个类,可以看到这个自定义的annotation @EnableEurekaClient里面没有内容。它的作用就是开启Eureka discovery的配置,正是通过这个标记,autoconfiguration就可以加载相关的Eureka类。那我们看下它是怎么做到的。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableDiscoveryClient
public @interface EnableEurekaClient {

}

3.在上述代码中,我们看到,EnableEurekaClient上面加入了另外一个注解@EnableDiscoveryClient,看看这个注解的代码如下所示:

/**
 * Annotation to enable a DiscoveryClient implementation.
 * @author Spencer Gibb
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {

}
  1. 这个注解import了EnableDiscoveryClientImportSelector.class这样一个类,其实就是通过这个类来加载需要用到的bean。
    点开EnableDiscoveryClientImportSelector类,如下代码:
@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)方法中正是根据这个标记类判定是否加载如下定义的类。在源码第59行,局部代码如下所示。

@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 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);
        }
    }

5.实际调用loadFactoryNames其实加载META-INF/spring.factories下的class。

  /**
    * 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";

而在spring-cloud-netflix-eureka-client\src\main\resources\META-INF\spring.factories中配置,用于加载一系列配置信息和Dependences Bean

spring.factories

可以看到EnableAutoConfiguration的包含了EurekaClientConfigServerAutoConfiguration

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

打开org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration可以看到
EurekaClientAutoConfiguration具体的注入信息。

具体@EnableEurekaClien注解开启之后,服务启动后,是服务怎么注册的请参考,下面链接:
http://xujin.org/sc/sc-eureka-register/

4.其它源码分析链接

Spring Cloud中@EnableEurekaClient源码分析:
http://xujin.org/sc/sc-enableEurekaClient-annonation/
Spring Cloud Eureka服务注册源码分析:
http://xujin.org/sc/sc-eureka-register/
Spring Cloud Eureka服务续约(Renew)源码分析
http://xujin.org/sc/sc-eureka-renew/
Spring Cloud Eureka服务下线(Cancel)源码分析
http://xujin.org/sc/sc-eureka-cancle/