Spring源码学习笔记:经典设计模式之观察者模式

1、博客内容均出自于咕泡学院架构师第三期
2、架构师系列内容:架构师学习笔记(持续更新)

0、观察者模式(Observer Pattern)

观察者模式也叫发布订阅模式。定义了对象之间的一对多依赖,让多个观察者对象同时监听一个主体对象,当主体对象发生变化时,它的所有依赖者(观察者)都会收到通知并更新。属于行为型模式。

优缺点:
优点:
1、观察者和被观察者之间建立了一个抽象的耦合。
2、观察者模式支持广播通信。
缺点:
1、观察者之间有过多的细节依赖、提高时间消耗及程序的复杂度。
2、使用要得当,要避免循环调用。

生活应用场景:
微信朋友圈动态通知,知乎邀请答主回答问题,邮件通知,广播通知,桌面程序的事件等。

1、简单案例

知乎社区邀请答主答题,我们通过jdk提供的API来实现。
首先创建Zhihu类:

package com.jarvisy.demo.pattern.observer.advice;


import lombok.Data;


import java.util.Observable;


/**
* @author :Jarvisy
* @date :Created in 2020/9/22 21:45
* @description :jdk 提供的一种观察者的实现方式   这是被观察者
*/
@Data
public class Zhihu extends Observable {


    private String name = "知乎社区";


    private Zhihu() {
    }


    private static Zhihu zhihu = null;


    public static Zhihu getInstance() {
        //这里不考虑多线程的问题
        if (null == zhihu) {
            zhihu = new Zhihu();
        }
        return zhihu;
    }


    public void publishQuestion(Question question) {
        System.out.println(question.getUserName() + "在" + this.name + "上提交了一个问题");
        setChanged();  //设置一个内部标志位注明数据发生变化
        notifyObservers(question);//会调用一个列表中的所有Observe的update方法,默认无参,会自动添加一个null,如果update方法实现中有调用arg,则在这里需要设置对象 ,否则会导致update调用的时候出现空指针异常
    }
}

创建Answerer:

package com.jarvisy.demo.pattern.observer.advice;


import java.util.Observable;
import java.util.Observer;


/**
* @author :Jarvisy
* @date :Created in 2020/9/22 22:12
* @description :这是观察者,被观察者在标志位被修改后就会通知到观察者,调用观察者的update方法
*/
public class Answerer implements Observer {


    private String name;


    public Answerer(String name) {
        this.name = name;
    }


    @Override
    public void update(Observable o, Object arg) {
        Zhihu zhihu = (Zhihu) o;
        Question question = (Question) arg;
        System.out.println("================================================");
        System.out.println(name + "答主,你好!\n" +
                "您收到了一个来自“" + zhihu.getName() + "”的提问,希望您解答,问题内容如下:\n" +
                question.getContent() + "\n" +
                "提问者:" + question.getUserName());
    }
}

创建测试代码:

package com.jarvisy.demo.pattern.observer.advice;


/**
* @author :Jarvisy
* @date :Created in 2020/9/22 22:34
* @description :
*/
public class ObserverTest {
    public static void main(String[] args) {
        Zhihu zhihu = Zhihu.getInstance();
        Answerer use1 = new Answerer("User1");
        Answerer use2 = new Answerer("User2");
        Question question = new Question();

        question.setUserName("小明");
        question.setContent("观察者设计模式适用于哪些场景");
        zhihu.addObserver(use1);//添加一个观察者  相当于@一个人
        zhihu.addObserver(use2);//添加一个观察者  相当于@一个人
        //这里需要先添加观察者,才能变更数据修改标识位,否则无法通知到观察者
        zhihu.publishQuestion(question);
    }
}

原理解读:


public class Observable {
    private boolean changed = false;  
    private Vector<Observer> obs;

    public Observable() {
        obs = new Vector<>();
    }


    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    public void notifyObservers() {
        notifyObservers(null);
    }    
       
    //核心方法就在这里
    public void notifyObservers(Object arg) {
       
        Object[] arrLocal;


        synchronized (this) {
          
            if (!changed)  //这里就是setChanged方法设置数据变更标识位,默认为false,如果不调用setChanged 则不会发送通知
                return;
            arrLocal = obs.toArray();
            clearChanged();//通知后又把标志位还原回来
        }


        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);  //这里就是调用通知,如果在标识位变更前不进行添加通知者,这里进不会进行for循环。 arg如果有调用的话,在notifyObservers方法就需要设置对象,否则会失败
    }

    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }
    protected synchronized void clearChanged() {
        changed = false;
    }

    protected synchronized void setChanged() {
        changed = true;
    }

}

2、模拟java.awt.Event观察者模式实现

java.awt.Event 就是观察者模式的一种,只不过 Java 很少被用来写桌面程序。我们自己用代码来实现一下。
首先创建Event类:

package com.jarvisy.demo.pattern.observer.events.core;


import java.lang.reflect.Method;


/**
* @author :Jarvisy
* @date :Created in 2020/9/22 23:15
* @description :监听器的一种包装,标准事件源格式的定义
*/
public class Event {
    //事件源,事件是由谁发起的保存起来
    private Object source;
    //事件触发,要通知谁
    private Object target;
    //事件触发,要做什么动作,回调
    private Method callback;
    //事件的名称,触发的是什么事件
    private String trigger;
    //事件触发的时间
    private long time;


    public Event(Object target, Method callback) {
        this.target = target;
        this.callback = callback;
    }


    public Event setSource(Object source) {
        this.source = source;
        return this;
    }


    public Event setTime(long time) {
        this.time = time;
        return this;
    }


    public Object getSource() {
        return source;
    }


    public Event setTrigger(String trigger) {
        this.trigger = trigger;
        return this;
    }


    public long getTime() {
        return time;
    }


    public Object getTarget() {
        return target;
    }


    public Method getCallback() {
        return callback;
    }


    @Override
    public String toString() {
        return "Event{" + "\n" +
                "\tsource=" + source.getClass() + ",\n" +
                "\ttarget=" + target.getClass() + ",\n" +
                "\tcallback=" + callback + ",\n" +
                "\ttrigger='" + trigger + "',\n" +
                "\ttime=" + time + "'\n" +
                '}';
    }
}

创建 EventLisenter (相当于Observable类)类:

package com.jarvisy.demo.pattern.observer.events.core;




import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;


/**
* @author :Jarvisy
* @date :Created in 2020/9/22 23:13
* @description :监听器,它就是观察者
*/
public class EventListener {


    //JDK底层的Listener通常也是这样来设计的
    protected Map<String, Event> events = new HashMap<String, Event>();


    //事件名称和一个目标对象来触发事件
    public void addListener(String eventType, Object target) {
        try {
            if (null == target) return;//如果回调事件为null ,则不添加
            this.addListener(eventType, target, target.getClass().getMethod("on" + toUpperFirstCase(eventType), Event.class));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public void addListener(String eventType, Object target, Method callback) {
        //注册事件
        events.put(eventType, new Event(target, callback));
    }




    //触发,只要有动作就触发
    private void trigger(Event event) {
        event.setSource(this);
        event.setTime(System.currentTimeMillis());


        try {
            //发起回调
            if (event.getCallback() != null) {
                //用反射调用它的回调函数
                event.getCallback().invoke(event.getTarget(), event);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    //事件名称触发
    protected void trigger(String trigger) {
        if (!this.events.containsKey(trigger)) {
            return;
        }
        trigger(this.events.get(trigger).setTrigger(trigger));
    }


    //逻辑处理的私有方法,首字母大写
    private String toUpperFirstCase(String str) {
        char[] chars = str.toCharArray();
        chars[0] -= 32;
        return String.valueOf(chars);
    }


}

创建MouseEventType:

package com.jarvisy.demo.pattern.observer.events.mouseevent;


/**
* @author :Jarvisy
* @date :Created in 2020/9/22 23:17
* @description :这里可以换成枚举类,只是单纯定义属性,没有其他意义
*/
public class MouseEventType {
    //单击
    public static String ON_CLICK = "click";
    
    //获焦
    public static String ON_FOCUS = "focus";
}

创建Mouse类:

package com.jarvisy.demo.pattern.observer.events.mouseevent;


import com.jarvisy.demo.pattern.observer.events.core.EventListener;


/**
* @author :Jarvisy
* @date :Created in 2020/9/22 23:16
* @description :
*/
public class Mouse extends EventListener {


    public void click() {
        System.out.println("调用单击方法");
        this.trigger(MouseEventType.ON_CLICK);
    }
    public void focus() {
        System.out.println("调用失焦方法");
        this.trigger(MouseEventType.ON_FOCUS);
    }
}

创建回调方法MouseEventCallback类:

package com.jarvisy.demo.pattern.observer.events.mouseevent;


import com.jarvisy.demo.pattern.observer.events.core.Event;


/**
* @author :Jarvisy
* @date :Created in 2020/9/22 23:17
* @description :自己写的逻辑,用于回调
*/
public class MouseEventCallback {


    public void onClick(Event e) {
        System.out.println("===========触发鼠标单击事件==========" + "\n" + e);
    }
    public void onFocus(Event e) {
        System.out.println("===========触发鼠标获焦事件==========" + "\n" + e);
    }


}

测试代码:

package com.jarvisy.demo.pattern.observer.events;


import com.jarvisy.demo.pattern.observer.events.mouseevent.Mouse;
import com.jarvisy.demo.pattern.observer.events.mouseevent.MouseEventCallback;
import com.jarvisy.demo.pattern.observer.events.mouseevent.MouseEventType;


/**
* @author :Jarvisy
* @date :Created in 2020/9/22 23:19
* @description :
*/
public class MouseEventTest {
    public static void main(String[] args) {
        MouseEventCallback callback = new MouseEventCallback();

        Mouse mouse = new Mouse();

        mouse.addListener(MouseEventType.ON_CLICK, callback);
        mouse.addListener(MouseEventType.ON_FOCUS, callback);

        mouse.click();

        mouse.focus();
    }
}

3、观察者模式在源码中的应用

Spring 中的 ContextLoaderListener 实现了 ServletContextListener 接口,ServletContextListener 接口又继承了 EventListener,在 JDK 中 EventListener 有非常广泛的应用。我们可以看一下源代码,ContextLoaderListener:


public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

   public ContextLoaderListener() {
   }
   public ContextLoaderListener(WebApplicationContext context) {
      super(context);
   }

   @Override
   public void contextInitialized(ServletContextEvent event) {
      initWebApplicationContext(event.getServletContext());
   }
   @Override
   public void contextDestroyed(ServletContextEvent event) {
      closeWebApplicationContext(event.getServletContext());
      ContextCleanupListener.cleanupAttributes(event.getServletContext());
   }

}

ServletContextListener:

package javax.servlet;
import java.util.EventListener;
public interface ServletContextListener extends EventListener {
    public void contextInitialized(ServletContextEvent sce);
    public void contextDestroyed(ServletContextEvent sce);
}

EventListener:

package java.util;
public interface EventListener {
}

4、基于GuavaApi轻松实现观察者模式

Guava 是一个实现观察者模式非常好的框架。
maven:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>20.0</version>
</dependency>

创建侦听事件:

package com.jarvisy.demo.pattern.observer.guava;


import com.google.common.eventbus.Subscribe;


/**
* @author :Jarvisy
* @date :Created in 2020/9/22 23:49
* @description :
*/
public class GuavaEvent {


    @Subscribe
    public void subscribe(String str) {
        System.out.println("执行subscribe方法,传入的参数是:" + str);
    }


}

创建测试类:

package com.jarvisy.demo.pattern.observer.guava;


import com.google.common.eventbus.EventBus;
import org.springframework.web.context.ContextLoaderListener;


/**
* @author :Jarvisy
* @date :Created in 2020/9/22 23:50
* @description :
*/
public class GuavaEventTest {


    public static void main(String[] args) {
        //消息总线
        EventBus eventBus = new EventBus();
        GuavaEvent guavaEvent = new GuavaEvent();
        eventBus.register(guavaEvent);
        eventBus.post("Jarvis");
    }
}

Guava 的简单应用,这里不再做更深的研究。以后有时间在看看。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值