月夜星空-liulq

--愿美好接踵而至

观察者模式

    观察者模式是一种十分经典的设计模式,概念在这里就不多说了,相信大家都对概念有所了解,简单的来说,观察者模式分为两部分:观察者(Observer)和被观察者(Subject),观察者注册到被观察者的容器中,当被观察者发生改变时,通知容器中的所有的观察者,观察者做出响应。

    先举个例子,一个网上十分经典的“猫叫”的例子吧。假设有这么一个场景:深夜,猫看见了老鼠,猫去追老鼠,然后引起了狗叫,狗的叫声又导致了熟睡的孩子醒来大哭,主人起床查看。这个场景用代码怎么实现呢,有的同学可能会很直观的说“每一个事情都写入一个方法或者单独定义一个类,然后直接逐个调用就行了”。你说的没错,确实可以,但是试想一下,这种做法是不是把控制权完全的交给了我们的主函数来做,增加了代码间的耦合度了呢。从软件的设计角度来讲,高耦合是不可取的,那怎么解决这个问题呢。好了,不废话了,语言描述在代码示例面前永远都显得苍白无力,直接上代码吧。

//定义一个观察者的接口
public interface IObserver {
    void update();
}
//真实的观察者类 实现了观察者的接口 并做出了老鼠跑的动作
public class RealObserverMouse implements IObserver {
    @Override
    public void update() {
        System.out.println("老鼠跑 ");
    }
}
//真实的观察者类 实现了观察者的接口 并做出了狗叫的动作
public class RealObserverDog implements IObserver {
    @Override
    public void update() {
        System.out.println("狗叫 ");
    }
}
//真实的观察者类 实现了观察者的接口 并做出了孩子哭的动作
public class RealObserverBaby implements IObserver {
    @Override
    public void update() {
        System.out.println("孩子哭 ");
    }
}
//真实的观察者类 实现了观察者的接口 并做出了主人起来查看的动作
public class RealObserverMan implements IObserver {
    @Override
    public void update() {
        System.out.println("主人起来 ");
    }
}
//定义一个被观察者的接口
public interface ISubject {
    //用于把观察者装入容器
    void Add(IObserver observer);
    //用于把观察者从容器中移除
    void delete (IObserver observer);
    //通知观察者
    void notice();
}
//真实的被观察者
public class RealSubject implements ISubject {
    //容器,用于装载观察者
    private List<IObserver> list = new ArrayList<>();
    @Override
    public void Add(IObserver observer) {
        list.add(observer);
    }
    @Override
    public void delete(IObserver observer) {
        list.remove(observer);
    }
    //当被观察者执行notice方法时,循环容器,逐个执行观察者的update方法,起到通知的作用
    @Override
    public void notice() {
        System.out.println("猫看见老鼠,猫叫,追老鼠");
        for (IObserver observer : list) {
            observer.update();
        }
    }
}
public class gczApplication {
    //测试方法
    public static void main(String[] args) {
        //实例化了观察者
        IObserver observerMouse = new RealObserverMouse();
        IObserver observerDog = new RealObserverDog();
        IObserver observerBaby = new RealObserverBaby();
        IObserver observerMan = new RealObserverMan();
        //实例化了被观察者
        ISubject subject = new RealSubject();
        //把观察者装载到容器
        subject.Add(observerMouse);
        subject.Add(observerDog);
        subject.Add(observerBaby);
        subject.Add(observerMan);
        //执行这个方法的时候,循环容器,逐个调用了观察者的方法
        subject.notice();
    }
}

 

    看一下这段代码的执行结果:

 

     我们看下这段代码的逻辑:

    1、定义好老鼠等四个类,都实现了我们的IObever接口;

    2、主函数实例化了老鼠、狗、孩子、主人四个类;

    3、把这四个实例扔进被观察者的容器;

    4、调用被观察者的notice方法即可;

监听器

    看完上面的这段代码后,肯定会有些朋友想,这也没有解耦啊,我们的主函数还是实例化了老鼠等四个类,你又说对了。那有什么办法解决吗?这里就先不贴代码了,写起来有些复杂,我就用苍白的语言描述吧:

    1、同样定义好老鼠等四个类,都实现了我们的IObever接口;

    2、定义一个配置文件,在配置文件中写上这四个类的类名;

    3、主函数调用,这次不是直接实例化那四个类,而是读取配置文件,通过反射的方式完成实例化;

    4、把这四个实例扔进被观察者的容器(这步开始就和上面代码一样了);

    5、调用被观察者的notice方法即可;

    这就是我们现在SpringBoot中监听器的逻辑,但是又略有不同。不同的就是SpringBoot从配置文件中读取监听器(就是我们的观察者),把监听器写入到一个多播器的容器当中,监听器监听了事件(事件可以理解为我们上面的被观察者),当多播器发布事件时(就是执行我们的上面代码中notice方法),会从多播器的容器中查找监听该事件的监听器,然后做出响应。这样说可能有些蒙圈,老规矩,上代码:

    我们读SpringBoot源码时,有这样一行代码:

SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();

     再看看这个方法里面:

    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
        return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    }

    简单的来说,就是从我们的配置文件META-INF/spring.factories中,找到实现了SpringApplicationRunListener接口的类,我们从配置文件中可知(或者跟踪代码也能看到),框架本身只有一个EventPublishingRunListener类实现了这个接口,然后对这个类进行了实例化,我们看看这个类的构造函数和它的starting方法都做了什么:

//构造方法
public EventPublishingRunListener(SpringApplication application, String[] args) {
    this.application = application;
    this.args = args;
    //实例化了一个多播器,用来装载监听器和发布事件的
    this.initialMulticaster = new SimpleApplicationEventMulticaster();
    //获取到我们在 实例化SpringApplication时,加载的监听器(《SpringBoot源码系列(二)》当中的this.setListeners加载的监听器)
    Iterator var3 = application.getListeners().iterator();

    while(var3.hasNext()) {
        ApplicationListener<?> listener = (ApplicationListener)var3.next();
        //装载了监听器,当发布相应事件时做出相应处理
        this.initialMulticaster.addApplicationListener(listener);
    }

}
 public void starting() {
        //发布了ApplicationStartingEvent事件
        this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
    }

    先来说说这个构造函数,通过我上面贴的代码中的注释也就可以理解,主要的工作就是把前面加载的监听器全都装载到多播器的容器里面,等到发布事件时做出响应。

    再看看这个starting方法,这就发布了一个ApplicationStartingEvent的事件,之前的容器中的那些监听器,如果有监听了这个事件的监听器,就会做出响应了,那么监听器是怎么监听的,多播器这个multicastEvent方法又是怎么发布事件从而让监听者收到通知的呢?那我们就从这个multicastEvent方法说起吧:

public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
        Iterator var4 = this.getApplicationListeners(event, type).iterator();

        while(var4.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var4.next();
            Executor executor = this.getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> {
                    this.invokeListener(listener, event);
                });
            } else {
                //执行 listner 
                this.invokeListener(listener, event);
            }
        }

    }

    这里最主要的就是this.invokeListener(listener,event)这行代码,如果继续向方法内部跟踪的话,会看到listener.onApplicationEvent(event);,调用了监听器的onApplicationEvent,然后在监听器的实现类方法里面判断是否有其监听的事件,我们随便找个监听器看看:

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {... ...}
}

    回过头再看看EventPublishingRunListener的starting方法,调用了多播器的multicastEvent方法,同时传递了一个事件的实例作为参数,就可以发布这个事件,让监听器接收到通知了,我们再看看这个类里面的其他方法,类似starting的还很多,都是这种方式让监听器做出响应的,这里不贴代码了,感兴趣的同学自己到这个类里面查看吧。

 

posted on 2022-01-16 17:10  月夜星空-liulq  阅读(177)  评论(0)    收藏  举报