java中的事件机制
发布订阅模式
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
/** Construct an Observable with zero Observers. */
public Observable() {
obs = new Vector<>();
}
java.util.Observable
是一个a发布者
java.util.Observable
是一个订阅者
发布者和订阅者的关系可以是一对多,也可以是多对多
我们先来初始化一个类
/**
*@Description TODO
*@author apdoer
*@CreateDate 2019/6/15-20:20
*@Version 1.0
*===============================
**/
public class Demo {
public static void main(String[] args) {
//创建发布者
Observable observable = new Observable();
//添加订阅者
observable.addObserver(new Observer() {
/**
* @param o 发布者对象
* @param value 订阅的内容
*/
@Override
public void update(Observable o, Object value) {
System.out.println(value);
}
});
//发布消息
observable.notifyObservers("hello,apdoer");
}
}
运行结果发现什么都没有发生 , 为什么呢 ? 明明创建了发布者也添加了订阅者啊,而且也发布了消息啊
因为我们没有触发机制
怎么触发?
- 要么发布者去推送 (推的模式)
- 要么订阅者主动去拉取 (拉的模式)
推送
回过头去看, Observable
发布者有个成员变量 changed
,这个变量控制着发布者是否发生了改变,这也是正常的思维,改变了就去推送,默认是没有改变
/**
* Marks this <tt>Observable</tt> object as having been changed; the
* <tt>hasChanged</tt> method will now return <tt>true</tt>.
*/
protected synchronized void setChanged() {
changed = true;
}
但是通过源码我们可以看到这个方法的级别我们在自己的测试demo中应该是调用不到的,那么怎么办呢,我们提升一下这个方法的访问权限
/**
* @author apdoer
* @Description TODO
* @CreateDate 2019/6/15-20:20
* @Version 1.0
* ===============================
**/
public class Demo {
public static void main(String[] args) {
//创建发布者
MyObservable observable = new MyObservable();
//添加订阅者
observable.addObserver(new Observer() {
/**
*
* @param o 发布者对象
* @param value 订阅的内容
*/
@Override
public void update(Observable o, Object value) {
System.out.println(value);
}
});
observable.setChanged();
//发布消息
observable.notifyObservers("hello,apdoer");
}
static class MyObservable extends Observable {
@Override
public void setChanged() {
super.setChanged();
}
}
}
这样一来,就可以提升了setChanged
方法的访问权限,这是一种思想,大家在别的地方可以举一反三
重要的事情只说一遍
我们再次启动程序,发现订阅者是拿到了发布者发布的消息的
这种模式对订阅者来说是被动感知的
拉取
List<Integer> values = Arrays.asList(1, 2, 3, 4, 5);
Iterator<Integer> iterator = values.iterator();
while (iterator.hasNext()){//通过循环,去主动获取
System.out.println(iterator.next());
}
这里只是举一个简单的类似,主动获取,也叫迭代器模式,
事件监听模式
public class EventObject implements java.io.Serializable {
private static final long serialVersionUID = 5516075349620653480L;
/**
* The object on which the Event initially occurred.
*/
protected transient Object source;
java.util.EventObject
: 事件对象
- 事件对象始终关联着事件源
source
java.util.EventListener
: 事件监听接口(标记接口)
spring 事件/监听
public abstract class ApplicationEvent extends EventObject {
private static final long serialVersionUID = 7099057708183571937L;
private final long timestamp = System.currentTimeMillis();
public ApplicationEvent(Object source) {
super(source);
}
public final long getTimestamp() {
return this.timestamp;
}
}
ApplicationEvent
: 应用事件,继承自 EventObject
通过下面一段代码,测试spring事件监听
/**
* @Description Spring 自定义 事件/监听器
* @Author apdoer
* @Date 2019/5/4 23:42
* @Version 1.0
*/
public class SpringListenerDemo {
public static void main(String[] args) {
// Annotation驱动的spring上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//注册监听器,只关注MyApplicationEvent事件
context.addApplicationListener(new ApplicationListener<MyApplicationEvent>() {
/**
* 监听器得到事件
* @param applicationEvent
*/
@Override
public void onApplicationEvent(MyApplicationEvent applicationEvent) {
System.out.println("【接收到事件】"+applicationEvent.getSource()+"@"+applicationEvent.getApplicationContext());
}
});
//监听器得到事件
context.refresh();
//发布事件
context.publishEvent(new MyApplicationEvent(context,"hello world"));
context.publishEvent(new MyApplicationEvent(context,1));
context.publishEvent(new MyApplicationEvent(context,new Integer(100)));
}
//自定义事件
private static class MyApplicationEvent extends ApplicationEvent{
private final ApplicationContext applicationContext;//这样就可以通过事件获取上下文
public MyApplicationEvent(ApplicationContext applicationContext, Object source) {
super(source);
this.applicationContext = applicationContext;
}
public ApplicationContext getApplicationContext() {
return applicationContext;
}
}
}
接下来 重点来了
springboot
核心事件
ApplicationEnvironmentPreparedEvent
ApplicationPreparedEvent
ApplicationStartingEvent
ApplicationReadyEvent
ApplicationFailedEvent
Springboot 事件/监听器
ConfigFileApplicationListener
: 管理配置文件,比如application.properties
,以及application.yml
springboot默认对properties
和yml
也是在这个类里面有对应的加载器- 当
application-{profile}.yml
中的profile
存在时,会被先加载 - 顺序从前往后
application-{profile}.yml
,application.yml
- 当
Springboot 在对于ClassPath: /META-INF/spring.factories
- 这里是Spring的SPI
- 不是java的SPI : java.util.ServiceLoader
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
# Application Listeners
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.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer
# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
这里就好比是前面的demo 中,把所有的事件一个个的塞进去,
如何控制顺序的
- 实现
Ordered
以及@Order
注解,可以看到,Spring中的很多事件监听都实现了这个接口- 在Spring 中 数值越小,优先级越高
同样的,我们看一看 springcloud 的spring.factories,可以发现BootstrapApplicationListener
/**
* A listener that prepares a SpringApplication (e.g. populating its Environment) by
* delegating to {@link ApplicationContextInitializer} beans in a separate bootstrap
* context. The bootstrap context is a SpringApplication created from sources defined in
* spring.factories as {@link BootstrapConfiguration}, and initialized with external
* config taken from "bootstrap.properties" (or yml), instead of the normal
* "application.properties".
*
* @author Dave Syer
*
*/
public class BootstrapApplicationListener
implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
/**
* Property source name for bootstrap.
*/
public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = "bootstrap";
/**
* The default order for this listener.
*/
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 5;
/**
* The name of the default properties.
*/
public static final String DEFAULT_PROPERTIES = "defaultProperties";
private int order = DEFAULT_ORDER;
在springcloud 中的事件/监听器
- BootstrapApplicationListener : 根事件,负责加载
bootstrap.yml
或者bootstrap.properties
BoostrapApplicationListener
加载的优先级高于ConfigFileApplicationListener
,所以application.properties
即使定义也配置不到,原因在于:前者始终比后者优先级高5级- 负责加载boostrap.yml或者boostrap.properties
- 负责初始化boostrap ApplicationContext id=“boostrap”
ConfigurableApplicationContext context = builder.run();
- boostrap是一个根spring上下文,parent=null
- 联想到classloader
加入actuator依赖,启动项目,访问localhost:8080/beans,可以看到项目启动加载的所有bean
springcloud上下文
springcloud不同于springboot,有两个上下文
- application
- bootstrap
configurableApplicationcontext
标准实现类AnnotationConfigApplicationContext
- Env端点:
EnvironmentEndPoint
- environment关联多个带名称的propertySource
- 源码如下:
protected void initPropertySource(){ ConfigurableEnvironment env = getEnvironment(); if(env.instanceof ConfigurableWebEnvironment){ (ConfigurableWebEnvironment)env.initPropertySource(this.servletContext,this.servletConfig); } }
Environment
有两种实现方式- 普通类型 :
StandardEnvironment
- web类型 :
StandardServletEnvironment
- 普通类型 :
environment
关联着一个propertySources
实例propertySources
关联着多个propertySource
,并且有优先级- 实现自定义配置
- 实现
PropertySourceLocator
- 暴露该实现作为一个
springbean
- 实现
PropertySource
public static class MyPropertySourceLocator implements PropertySourceLocator{ 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.boostrap.BoostrapConfiguration=\org.apdoer.springcloudconfigClient.SpringCloudConfigClienApplication.MyPropertySourceLocator
environment
允许同名的,有先后- 问题
- yml和yaml的区别
- 没有区别,扩展名不同而已
- 自定义配置在哪里用到
- 用的不多,一般用于中间件开发
spring
有个@EventLister
和ApplicationListener
区别- 没有区别,前者是
annotation
编程模式,后者是接口
- 没有区别,前者是
- /env端点的使用场景是什么
- 用于排查问题,比如要分析@value里面占位符的具体的值
- 怎么样防止
order
一样springboot
和springcloud
中没办法,SpringSecurity
中通过异常实现
boostrapApplicationListener
是引入cloud
组件来使用的吗
是的pom.xml
引入哪个cloud
组件了spring-cloud-starter-config
- yml和yaml的区别