zoukankan      html  css  js  c++  java
  • @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注解开启之后,服务启动后,服务就查以注册了

  • 相关阅读:
    C++ template —— 模板中的名称(三)
    关于烂代码的那些事(下)
    关于烂代码的那些事(中)
    关于烂代码的那些事(上)
    比尔的村庄:创业是选择做赚钱的事,还是值钱的事?
    C++ template —— 深入模板基础(二)
    依赖倒置,控制反转,依赖注入
    JAVA中限制接口流量、并发的方法
    SVN同步时忽略特定文件或文件夹
    MySQL中查询表及索引大小的方法
  • 原文地址:https://www.cnblogs.com/zhangjianbin/p/6616866.html
Copyright © 2011-2022 走看看