1. 程式人生 > >Spring cloud 中@EnableEurekaClient原始碼分析

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服務註冊的過程。