SpringCloudConfigClient
Spring Cloud 技术体系

Spring Cloud Config Client
学习 SpringCloud 预备知识
发布/订阅模式
java.util.Observable 是一个发布者
java.util.Observer 是订阅者
发布者和订阅者:1 : N
发布者和订阅者:N : M
java 实现
创建观察者
增加订阅者,并定义对应处理方式
观察者发布通知
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"); } }这里无法打印 “hello world”,由于在 notifyObservers 中判断了状态,没有发生改变则直接返回,没有调用后面的 update 方法进行处理

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

定义 MyObservable 继承自 Observable
static class MyObservable extends Observable{ @Override protected synchronized void setChanged() { super.setChanged(); } }使用 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(); } } }订阅者收到通知,并处理

事件/监听模式
java.util.EventObject :事件对象
- 事件对象总是关联着事件源(source)
java.util.EventListener :事件监听接口(标记)
java 实现
java事件机制包括三个部分:事件、事件监听器、事件源。
定义事件。继承 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; } }定义事件监听器。实现 EventListener 接口,注册在事件源上,当事件源的属性或状态改变时,取得相应的监听器调用其内部的回调方法。
public class StateChangeListener implements EventListener { public void onChange(MyEvent event) { System.out.println("触发状态变更的事件。。。"); System.out.println("当前事件源状态为:" + event.getSourceState()); System.out.println("。。。。。。。。。。。。。。。。。。。。。。。"); } }定义事件源。事件发生的地方,由于事件源的某项属性或状态发生了改变(比如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; } }测试事件机制
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("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); } }结果

Spring 事件/监听
ApplicationEvent : 应用事件

ApplicationListener : 应用监听器

自定义 Spring 事件监听
定义事件继承 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; } }注册监听器,监听事件
发布事件
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)); }发布事件之前要 refresh,否则出现如下错误

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

Spring Boot 事件/监听器
SpringBoot 核心事件
- ApplicationEnvironmentPreparedEvent
- ApplicationPreparedEvent
- ApplicationStartingEvent
- ApplicationReadyEvent
- ApplicationFailedEvent
ConfigFileApplicationListener
管理配置文件,比如:application.properties 以及 application.yaml
application-{profile}.properties:
profile = dev 、test
application-{profile}.properties- 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优先
- 负责加载
bootstrap.properties或者bootstrap.yaml - 负责初始化 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
注意:
在配置文件
application.properties中定义上述配置,无法生效由于
application.properties是由ConfigFileApplicationListener加载的,优先级较低。BootstrapApplicationListener 加载的优先级 高于
ConfigFileApplicationListener,所以 application.properties 文件即使定义也配置不到!原因在于:
BootstrapApplicationListener第6优先ConfigFileApplicationListener第11优先如果需要生效,可在
program-arguments中配置

ConfigurableApplicationContext
标准实现类:AnnotationConfigApplicationContext
Env 端点
EnvironmentEndpoint

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
AbstractEnvironmentStandardEnvironment
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
实现自定义配置
实现
PropertySourceLocator暴露该实现作为一个Spring Bean
实现
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; } }定义并且配置 /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);
问题
.yml和.yaml是啥区别?
答:没有区别,就是文件扩展名不同
自定义的配置在平时使用的多吗 一般是什么场景
答:不多,一般用于中间件的开发
Spring 里面有个
@EventListener和ApplicationListener什么区别答:没有区别,前者是 Annotation 编程模式,后者 接口编程
小马哥 可以讲课的时候简单的实现一个小项目,在讲原理和源码吧,直接上源码,感觉讲得好散,听起来好累
答:从第三节开始直接开始从功能入
/env端点的使用场景 是什么答:用于排查问题,比如要分析
@Value("${server.port}")里面占位符的具体值Spring cloud 会用这个实现一个整合起来的高可用么
答:Spring Cloud 整体达到一个目标,把 Spring Cloud 的技术全部整合到一个项目,比如负载均衡、短路、跟踪、服务调用等
怎样防止Order一样
答:Spring Boot 和 Spring Cloud 里面没有办法,在 Spring Security 通过异常实现的。
服务监控跟鹰眼一样吗
答:类似
bootstrapApplicationListener是引入cloud组件来有的吗
答:是的
pom.xml引入哪个cloud组件了?
答:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
书籍推荐
翟永超《Spring Cloud 微服务实战》