前言
SpringBoot和SpringMVC有一个很大的改进,就是SpringBoot基于SpringMVC做了很多的事件监听模型。要弄清楚什么是Spring中的事件监听模型,首先要说明白什么是观察者设计模式。所以本篇将会从一个例子详细讲解观察者设计模式的演进。更多Spring内容进入【Spring解读系列目录】。
场景模型
为了说清楚什么是观察者设计模式,先讲一个小的场景。场景很简单:假设一个花盆里有一朵花(Flower),花朵的旁边有两个测试人员小红(XiaoHong)和李华(LiHua)。当这朵花开放(bloom)的时候小红会开心的笑(laugh),但是李华会因为花粉过敏而流泪(tear)。
基础的观察者模型
既然说到演进,就先拿LiHua做这个例子。首先要实现花开了LiHua要流泪。于是就要有一个原也就是Flower类作为被观察者,然后LiHua也要有一个类,里面有一个流泪的动作。Flower作为被观察者,一定会有动作触发LiHua流泪的动作。传统来说应该在Flower里面有一个标志,一旦有触发开花的动作改变标志位,LiHua就应该流泪。于是LiHua这里应该有一个线程不断的监视开花的动作,不然开花了LiHua不流泪就不好了。而Flower这边也应该有另一个线程去执行修改标志位的动作。因此两个类都应该做个线程去执行这些事情,所以基于上面的设计,写出了下面的Sample。
LiHua类,监听Flower的开花状态:
public class LiHua implements Runnable{
private Flower flower;
public LiHua(Flower flower) {
this.flower = flower;
}
public void tear(){
System.out.println("花开,李华流泪");
}
@Override
public void run() {
System.out.println("李华开始观察是否花开");
//无线循环监听是否开花了
while(!flower.isBloom()){
try { //每隔1s监听一次
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果开花了,跳出循环,执行流泪
tear();
}
}
Flower类,操作自己的开花状态:
public class Flower implements Runnable{
//开花的变量
private boolean bloom=false;
public boolean isBloom() {
return bloom;
}
public void bloomNow(){
//修改开花变量
bloom=true;
}
@Override
public void run() {
try {//模拟开花的时间
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
bloomNow();
}
}
测试类:
public class TestObserver {
public static void main(String[] args) {
Flower flower=new Flower();
Thread threadf=new Thread(flower);
threadf.start();
LiHua LiHua=new LiHua(flower);
Thread threadl=new Thread(LiHua);
threadl.start();
}
}
程序开始运行,当flower调用bloomNow()方法改变开花状态的时候,LiHua就会因为监听到开花了而执行tear()方法。虽然这种方法可以实现功能目标,但是其缺点也是非常明显的。我们看while(!flower.isBloom())
非常类似于Socket,在这里一直等待数据写入。也就意味着LiHua线程非常的消耗资源,无论花开不开,一直需要盯着flower。如果有别的动作,比如吃饭那就无法切换。基于此我们可以针对这个问题做一个修改,那就是当flower开始的时候,通知LiHua来看就可以了。也就是说这种极度消耗资源的设计可以修改为:当某个事件发生的时候,主动通知它的监听者,就可以优化程序耗能。
观察者模型的第一次改进:角色互换
根据上面的分析,我们需要做的就是让Flower通知LiHua开花这个事件。上面的例子实际上是让LiHua持有Flower类,并一直监听Flower中的变量。如果要让Flower主动通知LiHua,那就意味着Flower类要持有LiHua类才可以。也就是说这样的改进将会使被观察者(Flower)拥有观察者(LiHua),这点要特别注意,因为这也是观察者设计模式中非常重要的一个概念。这点和我们正常理解的观察者与被观察者的情形是相反的:在观察者设计模式被观察者是主动的,而观察者才是被动的。
由于LiHua不需要一直看着Flower,首先LiHua可以不需要单独作为线程存在,Runable接口就可以被优化掉。其次Flower作为主动方,需要添加LiHua类作为传递的参数。由于LiHua类对象能够直接被Flower持有,所以也不需要在使用标识作为判断依据,只要开花立刻调用tear()流泪方法就可以了,因此第一次的改进如下。
LiHua类:
public class LiHua{
//只保留动作
public void tear(){
System.out.println("花开,李华流泪");
}
}
Flower类:
public class Flower implements Runnable{
//持有LiHua类,并在开花动作以后,直接调用流泪方法
private LiHua LiHua;
public Flower(LiHua LiHua) {
this.LiHua = LiHua;
}
public void bloomNow(){
//由于直接通知,因此标识变量也可以优化掉
LiHua.tear();
}
@Override
public void run() {
System.out.println("开花倒计时开始");
try {//模拟开花的时间
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
bloomNow();
}
}
测试类:
public class TestObserver {
public static void main(String[] args) {
Flower flower=new Flower(new LiHua());
Thread threadf=new Thread(flower);
threadf.start();
}
}
从上面的改进中,可以看到LiHua已经不需要在持续的占用资源去监听花开的状态,只要花开LiHua就触发流泪的动作。LiHua是否进行流泪的动作,取决于Flower而不是LiHua,这样修改性能就是一个很大的提升。
但是这样的修改,还是有问题的。因为如果按照这样设计,那么LiHua除了流泪什么都做不了,而且只有LiHua能够对花开进行反应。但是设想的场景里还有XiaoHong的角色和动作都无法加入进来。因此此时又引入了观察者模式的另一个概念:事件。
观察者模型的第二次改进:抽象事件
上面的改进导致了bloomNow()中开花动作的消失,只有LiHua动作被体现出来。那么可以预期,LiHua由于开花导致的动作可以被抽象提取出来,作为一个公用的行为action。不同的行为对应不同的动作或者不同人的不同动作。因此抽象出来的东西,就被称为事件。为了同时对开花这个事件进行不同的反应,因此就有了EventListener事件监听器的概念。到了这一步,整个程序并不在多少人观察了花的状态,也不在乎观察者看了花的状态以后执行的action是什么,只需要把应该通知的内容告诉观察者即可。那么此时观察者就变成被动的监听者。因此继续修改程序如下。
FlowerListener监听者接口:
public interface FlowerListener {
public void action(FlowerEvent event);
}
LiHua类实现监听者接口:
public class LiHua implements FlowerListener{
public void action(FlowerEvent event){
if (1==event.getAction()){
System.out.println("花开,李华流泪");
}
}
}
XiaoHong类实现监听者接口:
public class XiaoHong implements FlowerListener{
public void action(FlowerEvent event){
if (1==event.getAction()){
System.out.println("花开,小红开始笑");
}
}
}
FlowerEvent开花的事件类:
public class FlowerEvent {
private int action;
public int getAction() {
return action;
}
public void setAction(int action) {
this.action = action;
}
}
Flower类作为被观察者,持有观察者(LiHua,XiaoHong)的引用:
public class Flower implements Runnable{
//为了实现一个事件多个监听,使用一个集合把所有监听的事件放进来
private List<FlowerListener> flowerListeners=new ArrayList<>();
//从外部添加事件
public void addListeners(FlowerListener listener){
flowerListeners.add(listener);
}
public void bloomNow(){
FlowerEvent event=new FlowerEvent();
event.setAction(1); //这里设置为1,当设置为2的时候可以有更多的反应
//对于某一个事件,不同人的action表达不同的反应
for (FlowerListener flowerListener : flowerListeners) {
flowerListener.action(event);
}
}
@Override
public void run() {
System.out.println("开花倒计时开始");
try {//模拟开花的时间
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
bloomNow();
}
}
测试类:
public class TestObserver {
public static void main(String[] args) {
Flower flower=new Flower();
flower.addListeners(new LiHua()); //LiHua被动监听花的状态
flower.addListeners(new XiaoHong()); //XiaoHong被动监听花的状态
Thread threadf=new Thread(flower);
threadf.start();
}
}
当程序运行,同时对不同人进行不同的action:
开花倒计时开始
花开,李华流泪
花开,小红开始笑
可以看到经过这样的改进以后,被观察者(Flower)不仅可以在改变状态以后通知观察者(LiHua),而且可以支持增加和删除观察者。那么一个观察者模式的实现就正式结束了。
总结
那么我们总结一下观察者设计模式的特点:
- 被观察者持有监听的观察者的引用。
- 被观察者支持增加和删除观察者。
- 被观察者主题状态改变,会通知观察者做相应的动作。
本篇讲观察者模式的概念和实现做了一个完整的介绍,下一篇【JDK观察者模式探究】将会讲解JDK如何实现的观察者模式的。