@EnableEurekaClient源碼分析


@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注解開啟之后,服務啟動后,服務就查以注冊了

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM