跳至主要內容

SpringCloudConfigClient

soulballad微服务SpringCloud NetfilxSpringCloud约 2014 字大约 7 分钟

Spring Cloud 技术体系

1570103465361

Spring Cloud Config Client

学习 SpringCloud 预备知识

发布/订阅模式

java.util.Observable 是一个发布者

java.util.Observer 是订阅者

发布者和订阅者:1 : N

发布者和订阅者:N : M

java 实现

  1. 创建观察者

  2. 增加订阅者,并定义对应处理方式

  3. 观察者发布通知

    public class ObserverDemo {
    
        public static void main(String[] args) {
    
            Observable observable = new Observable();
            // 增加订阅者
            observable.addObserver(new Observer() {
                @Override
                public void update(Observable o, Object arg) {
                    System.out.println(arg);
                }
            });
    
            // 发布者通知,订阅者是被动感知(推模式)
            observable.notifyObservers("hello world");
        }
    }
    
  4. 这里无法打印 “hello world”,由于在 notifyObservers 中判断了状态,没有发生改变则直接返回,没有调用后面的 update 方法进行处理

1570103854911

  1. 所以需要手动变更状态,Observable 提供了一个方法 setChanged,但这个方法是 protected,无法在外部直接使用,所以使用一个类来继承它

    1570103926306

  2. 定义 MyObservable 继承自 Observable

    static class MyObservable extends Observable{
        @Override
        protected synchronized void setChanged() {
            super.setChanged();
        }
    }
    
  3. 使用 MyObservable 创建观察者,并调用 setChanged 方法,最终方法如下

    public class ObserverDemo {
    
        public static void main(String[] args) {
    
            MyObservable observable = new MyObservable();
            // 增加订阅者
            observable.addObserver(new Observer() {
                @Override
                public void update(Observable o, Object arg) {
                    System.out.println(arg);
                }
            });
    
            observable.setChanged();
            // 发布者通知,订阅者是被动感知(推模式)
            observable.notifyObservers("hello world");
        }
    
        static class MyObservable extends Observable{
            @Override
            protected synchronized void setChanged() {
                super.setChanged();
            }
        }
    }
    
  4. 订阅者收到通知,并处理

    1570104199542

事件/监听模式

java.util.EventObject :事件对象

  • 事件对象总是关联着事件源(source)

java.util.EventListener :事件监听接口(标记)

java 实现

java事件机制包括三个部分:事件、事件监听器、事件源。

  1. 定义事件。继承 EventObject 类,封装了事件源对象及跟事件相关的信息

    public class MyEvent extends EventObject {
        private static final long serialVersionUID = 1L;
        private int sourceState;
    
        // source: 事件源
        public MyEvent(Object source) {
            super(source);
            sourceState = ((Source) source).getFlag();
        }
    
        public int getSourceState() {
            return sourceState;
        }
    }
    
  2. 定义事件监听器。实现 EventListener 接口,注册在事件源上,当事件源的属性或状态改变时,取得相应的监听器调用其内部的回调方法。

    public class StateChangeListener implements EventListener {
        public void onChange(MyEvent event) {
            System.out.println("触发状态变更的事件。。。");
            System.out.println("当前事件源状态为:" + event.getSourceState());
            System.out.println("。。。。。。。。。。。。。。。。。。。。。。。");
        }
    }
    
  3. 定义事件源。事件发生的地方,由于事件源的某项属性或状态发生了改变(比如BUTTON被单击、TEXTBOX的值发生改变等等)导致某项事件发生。换句话说就是生成了相应的事件对象。因为事件监听器要注册在事件源上,所以事件源类中应该要有盛装监听器的容器(List,Set等等)。

    public class Source {
    
        private int flag = 0;
        Set<EventListener> listeners = new HashSet<EventListener>();
    
        /**
         * 注册事件监听器
         * @param changeListener
         */
        public void addStateChangeListener(StateChangeListener changeListener) {
            listeners.add(changeListener);
        }
    
        /**
         * 通知监听器
         */
        public void notifyListeners() {
            for (EventListener listener : listeners) {
                try {
                    ((StateChangeListener)listener).onChange(new MyEvent(this));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 改变状态
         */
        public void changeFlag() {
            flag = (flag == 0 ? 1 : 0);
            notifyListeners();
        }
    
        public int getFlag() {
            return flag;
        }
    }
    
  4. 测试事件机制

    public class EventTest {
    
        public static void main(String[] args) {
    
            Source source = new Source();
            source.addStateChangeListener(new StateChangeListener());
            source.changeFlag();
            System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
            source.changeFlag();
            System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
    
        }
    }
    
  5. 结果

    1570104610656

Spring 事件/监听

ApplicationEvent : 应用事件

1570104714308

ApplicationListener : 应用监听器

1570104778369

自定义 Spring 事件监听

  1. 定义事件继承 ApplicationEvent

    private static class MyApplicationEvent extends ApplicationEvent {
        // 把上下文作为事件的属性,可以在监听事件时获取到上下文
        private final ApplicationContext applicationContext;
        public MyApplicationEvent(Object source, 
                                  ApplicationContext applicationContext) {
            super(source);
            this.applicationContext = applicationContext;
        }
    
        public ApplicationContext getApplicationContext() {
            return applicationContext;
        }
    }
    
  2. 注册监听器,监听事件

  3. 发布事件

    public static void main(String[] args) {
    
        AnnotationConfigApplicationContext context = 
            new AnnotationConfigApplicationContext();
        // 注册监听器
        context.addApplicationListener(
            new ApplicationListener<MyApplicationEvent>() {
                /**
                 * // 监听器得到事件
                 * @param event
                 */
                @Override
                public void onApplicationEvent(MyApplicationEvent event) {
                    // 获取上下文
                    System.err.println("receive event: " + event.getSource() + "@" + event.getApplicationContext());
                }
            });
    
        context.refresh();
        // 发布事件
        context.publishEvent(new MyApplicationEvent("hello world", context));
        context.publishEvent(new MyApplicationEvent(1, context));
        context.publishEvent(new MyApplicationEvent(new Integer(100), context));
    }
    
  4. 发布事件之前要 refresh,否则出现如下错误

    1570105220125

  5. 正确调用后,出现如下结果

    1570105328906

Spring Boot 事件/监听器

SpringBoot 核心事件
  • ApplicationEnvironmentPreparedEvent
  • ApplicationPreparedEvent
  • ApplicationStartingEvent
  • ApplicationReadyEvent
  • ApplicationFailedEvent
ConfigFileApplicationListener

管理配置文件,比如:application.properties 以及 application.yaml

application-{profile}.properties:

profile = dev 、test

  1. application-{profile}.properties
  2. application.properties

Spring Boot 在相对于 ClassPath : /META-INF/spring.factories

Java SPI : java.util.ServiceLoader

Spring SPI:

Spring Boot "/META-INF/spring.factories"

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener
如何控制顺序

实现 Ordered 以及 标记 @Order

在 Spring 里面,数值越小,越优先

Spring Cloud 事件/监听器

BootstrapApplicationListener

Spring Cloud "/META-INF/spring.factories":

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\
org.springframework.cloud.context.restart.RestartListener

加载的优先级 高于 ConfigFileApplicationListener,所以 application.properties 文件即使定义也配置不到!

原因在于:

BootstrapApplicationListener 第6优先

ConfigFileApplicationListener 第11优先

  1. 负责加载bootstrap.properties 或者 bootstrap.yaml
  2. 负责初始化 Bootstrap ApplicationContext ID = "bootstrap"
ConfigurableApplicationContext context = builder.run();

Bootstrap 是一个根 Spring 上下文,parent = null

联想 ClassLoader:

ExtClassLoader <- AppClassLoader <- System ClassLoader -> Bootstrap Classloader(null)

Bootstrap 配置属性
  • Bootstrap 配置文件路径
    • spring.cloud.bootstrap.location
  • 覆盖远程配置属性
    • spring.cloud.config.allowOverride
  • 自定义 Bootstrap 配置
    • @BootstrapConfiguration
  • 自定义 Bootstrap 配置属性源
    • PropertySourceLocator

注意

  1. 在配置文件 application.properties 中定义上述配置,无法生效

    由于 application.properties 是由 ConfigFileApplicationListener 加载的,优先级较低。

    BootstrapApplicationListener 加载的优先级 高于 ConfigFileApplicationListener,所以 application.properties 文件即使定义也配置不到!

    原因在于:

    BootstrapApplicationListener 第6优先

    ConfigFileApplicationListener 第11优先

  2. 如果需要生效,可在 program-arguments 中配置

    1570162466054

    1570162575029

ConfigurableApplicationContext

标准实现类:AnnotationConfigApplicationContext

Env 端点

EnvironmentEndpoint

1570164533169

Environment 关联多个带名称的 PropertySource

可以参考一下Spring Framework 源码:

AbstractRefreshableWebApplicationContext

protected void initPropertySources() {
    ConfigurableEnvironment env = getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, this.servletConfig);
    }
}

Environment 有两种实现方式:

  • 普通类型:StandardEnvironment
  • Web类型:StandardServletEnvironment

Environment

  • AbstractEnvironment
  • StandardEnvironment

Enviroment 关联着一个PropertySources 实例

PropertySources 关联着多个PropertySource,并且有优先级

其中比较常用的PropertySource 实现:

Java System#getProperties 实现: 名称"systemProperties",对应的内容 System.getProperties()

Java System#getenv 实现(环境变量): 名称"systemEnvironment",对应的内容 System.getEnv()

关于 Spring Boot 外部配置优先级顺序,可以参考:https://docs.spring.io/spring-boot/docs/2.0.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-external-config

实现自定义配置
  1. 实现PropertySourceLocator

  2. 暴露该实现作为一个Spring Bean

  3. 实现PropertySource:

    public static class MyPropertySourceLocator implements PropertySourceLocator {
    
        @Override
        public PropertySource<?> locate(Environment environment) {
            Map<String, Object> source = new HashMap<>();
            source.put("server.port","9090");
            MapPropertySource propertySource =
                new MapPropertySource("my-property-source", source);
            return propertySource;
        }
    }
    
  4. 定义并且配置 /META-INF/spring.factories:

    org.springframework.cloud.bootstrap.BootstrapConfiguration=\
    com.gupao.springcloudconfigclient.SpringCloudConfigClientApplication.MyPropertySourceLocator
    

注意事项:

Environment 允许出现同名的配置,不过优先级高的胜出

内部实现:MutablePropertySources 关联代码:

List<PropertySource<?>> propertySourceList = 
    new CopyOnWriteArrayList<PropertySource<?>>();

propertySourceList FIFO,它有顺序

可以通过 MutablePropertySources#addFirst 提高到最优先,相当于调用:

List#add(0,PropertySource);

问题

  1. .yml和.yaml是啥区别?

    答:没有区别,就是文件扩展名不同

  2. 自定义的配置在平时使用的多吗 一般是什么场景

    答:不多,一般用于中间件的开发

  3. Spring 里面有个@EventListenerApplicationListener什么区别

    答:没有区别,前者是 Annotation 编程模式,后者 接口编程

  4. 小马哥 可以讲课的时候简单的实现一个小项目,在讲原理和源码吧,直接上源码,感觉讲得好散,听起来好累

    答:从第三节开始直接开始从功能入

  5. /env 端点的使用场景 是什么

    答:用于排查问题,比如要分析@Value("${server.port}")里面占位符的具体值

  6. Spring cloud 会用这个实现一个整合起来的高可用么

    答:Spring Cloud 整体达到一个目标,把 Spring Cloud 的技术全部整合到一个项目,比如负载均衡、短路、跟踪、服务调用等

  7. 怎样防止Order一样

    答:Spring Boot 和 Spring Cloud 里面没有办法,在 Spring Security 通过异常实现的。

  8. 服务监控跟鹰眼一样吗

    答:类似

  9. bootstrapApplicationListener是引入cloud组件来有的吗

    答:是的

  10. pom.xml引入哪个cloud组件了?

    答:

    <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    

书籍推荐

翟永超《Spring Cloud 微服务实战》

上次编辑于:
贡献者: soulballad