xinrong2019 / xinrong2019.github.io

My Blog
https://xinrong2019.github.io
1 stars 1 forks source link

20190429,SpringBoot应用是如何注册到注册中心的?(一)EnableDiscoveryClient注解源码走读,自动注解导入了什么配置? #7

Open xinrong2019 opened 5 years ago

xinrong2019 commented 5 years ago

#

xinrong2019 commented 5 years ago

32G内存,搜狗输入法占了20多G,果断卸载了。也不想用原生的,于是下载了百度输入法,先用着,目前观察没有异常,内存消耗77兆

xinrong2019 commented 5 years ago

Spring Cloud Config Server原理剖析

学习目的

为了弄清楚如下几个问题:

xinrong2019 commented 5 years ago

1、关于环境的搭建不属于这次分析的内容,不会过多记录;

2、版本(安装包)声明:

consul:consul_1.4.4_darwin_amd64

Spring Cloud版本:Dalston.RELEASE

Spring Boot版本:1.5.14.RELEASE

3、由于Spring Cloud的使用是基于SpringBoot自动注解配置,所以入口都是全局搜索注解相关内容,下面会依次展开说明。

xinrong2019 commented 5 years ago

Spring Cloud Config Server是如何注册到Consul注册中心的?

一个Config Server应用,需要在启动类上加三个注解:

@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigServer//开启配置中心功能

1、从EnableDiscoveryClient注解开始

关于如何注册到注册中心,看EnableDiscoveryClient这个注解,这个注解类属于spring-cloud-commons模块下。

看一下这个注解的源码及JavaDoc

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

    /**
     * If true, the ServiceRegistry will automatically register the local server.
     */
    boolean autoRegister() default true;
}

注释上说了,这是一个启动DiscoveryClient实现的注解。

它有个autoRegister方法,默认是true,表示自动注册本地服务到注册中心。

有个import注解,导入了一个EnableDiscoveryClientImportSelector类,接下来看看这个类。

2、EnableDiscoveryClientImportSelector

先看源码:

/**
 * 这个类没有注释,差评!
 * @author Spencer Gibb
 */
@Order(Ordered.LOWEST_PRECEDENCE - 100)//应该和什么顺序有关,先不管,感觉不是这次分析的重点。
public class EnableDiscoveryClientImportSelector
        extends SpringFactoryImportSelector<EnableDiscoveryClient> {//继承自SpringFactoryImportSelector类,继承了父类,父类应该也很重要,知道父类的一些信息更容易了解子类。

    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        String[] imports = super.selectImports(metadata);

        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
                metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));

        boolean autoRegister = attributes.getBoolean("autoRegister");

        if (autoRegister) {
            List<String> importsList = new ArrayList<>(Arrays.asList(imports));
            importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
            imports = importsList.toArray(new String[0]);
        }

        return imports;
    }

    @Override
    protected boolean isEnabled() {
        return new RelaxedPropertyResolver(getEnvironment()).getProperty(
                "spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE);
    }

    @Override
    protected boolean hasDefaultFactory() {
        return true;
    }

}
/**
 * Selects configurations to load defined by the generic type T. Loads implementations
 * using {@link SpringFactoriesLoader}.
 * 选择泛型指定的需要加载的配置,使用SpringFactoriesLoader类加载实现
 * @author Spencer Gibb
 * @author Dave Syer
 */
@CommonsLog//lombok注解,和日志相关,忽略
public abstract class SpringFactoryImportSelector<T>
        implements DeferredImportSelector, BeanClassLoaderAware, EnvironmentAware {

    private ClassLoader beanClassLoader;

    private Class<T> annotationClass;

    private Environment environment;

    @SuppressWarnings("unchecked")
    protected SpringFactoryImportSelector() {
        this.annotationClass = (Class<T>) GenericTypeResolver
                .resolveTypeArgument(this.getClass(), SpringFactoryImportSelector.class);
    }

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

    protected boolean hasDefaultFactory() {
        return false;
    }

    protected abstract boolean isEnabled();

    protected String getSimpleName() {
        return this.annotationClass.getSimpleName();
    }

    protected Class<T> getAnnotationClass() {
        return this.annotationClass;
    }

    protected Environment getEnvironment() {
        return this.environment;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.beanClassLoader = classLoader;
    }

}

SpringFactoryImportSelector主要实现了DeferredImportSelector,这个类是在所有配置类处理完后运行。

/**
 * A variation of {@link ImportSelector} that runs after all {@code @Configuration} beans
 * have been processed. This type of selector can be particularly useful when the selected
 * imports are {@code @Conditional}.
 *
 * <p>Implementations can also extend the {@link org.springframework.core.Ordered}
 * interface or use the {@link org.springframework.core.annotation.Order} annotation to
 * indicate a precedence against other {@link DeferredImportSelector}s.
 *
 * @author Phillip Webb
 * @since 4.0
 */
public interface DeferredImportSelector extends ImportSelector {

}

DeferredImportSelector继承自ImportSelector接口:


/*
 * Interface to be implemented by types that determine which @Configuration class(es) should be imported based on a given selection criteria, usually one or more annotation attributes.
An ImportSelector may implement any of the following Aware interfaces, and their respective methods will be called prior to selectImports(org.springframework.core.type.AnnotationMetadata):
EnvironmentAware
BeanFactoryAware
BeanClassLoaderAware
ResourceLoaderAware
ImportSelectors are usually processed in the same way as regular @Import annotations, however, it is also possible to defer selection of imports until all @Configuration classes have been processed (see DeferredImportSelector for details).
 *
*/
public interface ImportSelector {

    /**
     * Select and return the names of which class(es) should be imported based on the AnnotationMetadata of the importing @Configuration class.
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);

}

selectImports方法表示选择需要被导入的类。实现在SpringFactoryImportSelector中,所以回过头来看上面的SpringFactoryImportSelector的方法selectImports,选择需要导入哪些类。

@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  <1>
        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()]);
    }
<1> 看到这个地方,通过SpringFactoriesLoader加载一些工厂类,SpringFactoriesLoader类属于Spring-core模块下。SpringFactoriesLoader的loadFactoryNames方法干了什么? ```java public static List loadFactoryNames(Class factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); try { Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); List result = new ArrayList(); 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); } } ``` 看源码,知道了是在META-INF/spring.factories下找工厂实现类,怎么找的,看参数,传递了一个factoryClass,就是我们的注解类org.springframework.cloud.client.discovery.EnableDiscoveryClient,还有一个类加载器,这里是AppClassLoader应用类加载器。 这里我看到一个技巧,不知道算不算技巧,反正可以记一下,就是如果获取不到,返回一个0长度的字符串数组。 ```java public static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) { if (str == null) { return new String[0]; } ``` 一般可能想在while循环里写if null判断,显得不够优雅。但是有另外一个问题,过早的判断非空,不是可以尽量少的进入一些方法栈么。 上面这个while循环处理的东西还是挺多的,我使用了条件debug模式,但是没debug到,不知道为什么。 ![image](https://user-images.githubusercontent.com/48017262/56902736-7aa53c80-6acd-11e9-834a-84f67b64361f.png) 不过先也不纠结了。因为我知道了loadFactoryNames做的事情: 就是在classpath找到org.springframework.cloud.client.discovery.EnableDiscoveryClient的实现,最后找的是org.springframework.cloud.consul.discovery.ConsulDiscoveryClientConfiguration,配置在spring-cloud-consul-discovery模块下。 所以上面大费周章就是导入了org.springframework.cloud.consul.discovery.ConsulDiscoveryClientConfiguration。 回到EnableDiscoveryClientImportSelector类中, ```java if (autoRegister) { List importsList = new ArrayList<>(Arrays.asList(imports)); importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration"); imports = importsList.toArray(new String[0]); } ``` 判断是否是自动注册,默认是,除了导入org.springframework.cloud.consul.discovery.ConsulDiscoveryClientConfiguration,还添加了org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration这个配置类。 接着就是加载这两个配置类中的配置。 后面我通过断点单步调试,跟到ConfigurationClassParser这个类,该类属于spring-context模块,做了一系列配置类加载、初始化等逻辑,下次再跟。
xinrong2019 commented 5 years ago

总结:

1、学习了一个比较重要的接口,ImportSelector接口

这个接口的实现类,需要实现导入哪些注解配置

2、学到了一个重要配置加载的机制,通过SpringFactoriesLoader的loadFactoryNames加载META-INF/spring.factories文件中的实现类

3、使用Consul做服务发现,需要导入两个重要的配置,ConsulDiscoveryClientConfiguration和AutoServiceRegistrationConfiguration

xinrong2019 commented 5 years ago

github上star了一些项目学习,是http://www.youliao.com.cn/这个网站的,都是电商业务。

基于SpringBoot+MyBatis的电商系统,包括前台商城系统及后台管理系统。

基于SOA架构的分布式电商购物商城 前后端分离 前台商城:Vue全家桶 后台管理系统:Dubbo/SSM/Elasticsearch/Redis/MySQL/ActiveMQ/Shiro/Zookeeper等

SpringCloud(Finchley.RELEASE)+SpringBoot(2.0.7)+LCN(5.0.2.RELEASE)项目骨架,eureka+config+bus+feign+ribbon+hystrix+zuul等组件支持,MyBatis+Redis+MongoDB+RabbitMQ+Elasticsearch等集群配置,LCN(5.0.2.RELEASE)分布式事务框架,支持Docker部署