今天有时间debug了下spring boot启动的源码,没跟几行code就看到了一个标准的监听者模式。索性花点时间写写监听者模式。
JDK对监听者的支持
以下是jdk对监听者模式提供的支持,后面的例子都会用到。
- 事件类:java.util.EventObject 自带对象source,注意source不能为空。
public class EventObject implements java.io.Serializable {
protected transient Object source;
public EventObject(Object source) {
if (source == null)
throw new IllegalArgumentException("null source");
this.source = source;
}
public Object getSource() {
return source;
}
}
- 监听接口:public interface EventListener 没有任何属性方法。
小王的苦逼生活
场景:小王每月工资1.5W,每年奖金3W。每月信用卡还款1W,一有钱就要还信用卡;另外,还想着发奖金的时候购物(买个7手奥拓,先定价2W吧,哥也不了解行情)。
下面开始用监听者模式模拟场景。
- 先定义工资和奖金类(get和set方法忽略)。
public class Salary {
private int month;
private double money;
public Salary(int month, double money) {
this.month = month;
this.money = money;
}
}
public class Bonus extends Salary {
public Bonus(double money) {
super(0, money);
}
public Bonus(int month, double money) {
super(month, money);
}
}
- 自定义事件,包括发工资和发奖金两种事件。将source类型定义为salary,方便事件的监听者使用。
public abstract class CustomEvent extends EventObject{
public CustomEvent(Salary source) {
super(source);
}
public Salary getSource() {
return (Salary)source;
}
}
public class PaySalaryEvent extends CustomEvent {
public PaySalaryEvent(Salary source) {
super(source);
}
}
public class PayBonusEvent extends CustomEvent {
public PayBonusEvent(Bonus source) {
super(source);
}
}
- 定义两个监听器,分别是信用卡还款监听器和购物车监听器。
public interface CustomEventListener<E extends CustomEvent> extends EventListener{
public void handleEvent(CustomEvent event);
}
//信用卡还款监听器要做的事情就是发现小王发了钱(包括工资和奖金),立即扣除。
public class CreditCardRepayListener implements CustomEventListener<CustomEvent>{
private static double defaultPurchases = 10000;
@Override
public void handleEvent(CustomEvent event) {
if(event.getSource().getMoney()< defaultPurchases) {
System.out.println("Repay credit card, You are a poor man!");
event.getSource().setMoney(0);
}else {
event.getSource().setMoney(event.getSource().getMoney() - defaultPurchases);
System.out.println("Repay credit card, Surplus is " + event.getSource().getMoney());
}
}
}
//购物车监听器做的事情是发现小王发了奖金去happy。
public class ShoppingCartListener implements CustomEventListener<CustomEvent> {
private static double defaultShoppingMoney = 20000;
@Override
public void handleEvent(CustomEvent event) {
if(event instanceof PayBonusEvent) {
if(event.getSource().getMoney()< defaultShoppingMoney) {
System.out.println("Do not want to go shopping!");
}else {
event.getSource().setMoney(event.getSource().getMoney() - defaultShoppingMoney);
System.out.println("After go shopping, Surplus is " + event.getSource().getMoney());
}
}
}
}
- 现在事件和监听器都有了,我们还需要建立两者之间的关系。这里使用一个事件管理员来维护这个关系。管理员能够使用doEvent发送事件。
public class EventManager {
private List<CustomEventListener<?>> listeners = Arrays.asList(new CreditCardRepayListener(), new ShoppingCartListener());
public void addListener(CustomEventListener<?> listener) {
listeners.add(listener);
}
public void removeListener(CustomEventListener<?> eventListener){
listeners.remove(eventListener);
}
public void doEvent(CustomEvent event){
listeners.stream().forEach(listener -> listener.handleEvent(event));
}
}
- 最后写测试类:
public class TestClass {
public static void main(String[] args) {
EventManager eventManager =new EventManager();
System.out.println("Pay January salary, 15000");
eventManager.doEvent(new PaySalaryEvent(new Salary(0, 15000)));
System.out.println("--------------------------------------------");
System.out.println("Pay Bonus, 30000");
eventManager.doEvent(new PayBonusEvent(new Bonus(30000)));
}
}
- 打印结果:
Pay January salary, 15000
Repay credit card, Surplus is 5000.0
--------------------------------------------
Pay Bonus, 30000
Repay credit card, Surplus is 20000.0
After go shopping, Surplus is 0.0
监听者模式在spring boot里面的应用
spring boot是通过@SpringBootApplication和SpringApplication.run(×××,args)启动的。我们debug SpringApplication源码会进入如下:
public ConfigurableApplicationContext run(String... args) {
......
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
......
}
方法getRunListeners返回值中默认只有一个对象org.springframework.boot.context.event.EventPublishingRunListener(配置文件spring.factories)。直接debug到主题类:SimpleApplicationEventMulticaster的multicastEvent方法。
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
@Override
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(new Runnable() {
@Override
public void run() {
invokeListener(listener, event);
}
});
}
else {
invokeListener(listener, event);
}
}
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
..... }
我们能看到入参就是一个事件对象,做的事情就是找到事件监听器然后发消息,这个和我们在小王的场景中使用的EventManager类做的事情相似。不同的是在getApplicationListeners(event, type))方法中,加了些事件监听器的过滤。
再来看看它的事件对象ApplicationEvent和事件监听器ApplicationListener。
public abstract class ApplicationEvent extends EventObject....
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
ApplicationEvent和我们在小王的场景中事件的CustomEvent是一样的。ApplicationListener对应CustomEventListener。
监听器的实现类是来自于spring boot配置文件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
我们可以打开一个LoggingApplicationListener源码,看看它做了什么。
首先,我们会看看两个钩子方法。这两个方法的大体意思是检查这个监听器支持监听哪些事件,事件源可以通过这两个方法过滤监听器。
@Override
public boolean supportsEventType(ResolvableType resolvableType) {
return isAssignableFrom(resolvableType.getRawClass(), EVENT_TYPES);
}
@Override
public boolean supportsSourceType(Class<?> sourceType) {
return isAssignableFrom(sourceType, SOURCE_TYPES);
}
下面就是事件响应方法了,按事件干活。
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationStartingEvent) {
onApplicationStartingEvent((ApplicationStartingEvent) event);
}
else if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
else if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
.getApplicationContext().getParent() == null) {
onContextClosedEvent();
}
else if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
以上就是简单的监听者模式,类图就不画了。
- 监听者模式包含EventObject ,EventListener,EventSource(上面例子中对应的是事件管理者),事件源维护事件监听器,并且可以每个监听器发送事件对象。
- 事件对象和监听器没有必然关系,全靠事件源来联系它们从而做到解耦。