观察者模式、监听器以及Spring中的监听器

观察者模式

观察者监听被观察者,一旦被观察者发生一定行为,观察者会被通知对该行为作出响应。例如微博中的关注与被关注关系。一对多的关系。

涉及四类角色:抽象观察者、抽象被观察者、具体观察者、具体被观察者。

关键:被观察者对象中持有观察者对象的队列,在被观察者完成指定行为后,循环观察者队列,通知每个关注自己的观察者观察者必须先注册到被观察者上,其实就是将自身添加在被观察者中的队列中。

/**
 * 被观察者/主题
 */
public abstract class ISubject {
    //维持一个观察者的队列
    List<IObserver> observers = new ArrayList<>();

    /**
     * 观察者注册
     */
    public void regiser(IObserver observer){
        observers.add(observer);
    }
    public void remove(IObserver observer){
        observers.remove(observer);
    }

    /**
     * 通知观察者,具体内容由子类实现
     */
    abstract void notifyObervers();

    /**
     * 被观察者自己的业务
     */
    abstract void work();
}

/**
 * 观察者接口
 */
public interface IObserver {
    //对被观察者发生特定行为后的响应
    void action();
}

/**
 * 具体观察者/主题
 */
public class ConcreteSubjectA extends ISubject{
    //主题名称
    private String name;

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

    /**
     * 通知观察者
     */
    @Override
    void notifyObervers() {
        //实际上此处不一定是去调用观察者的方法,也可以是主动向观察者推送消息等。
        //例如消息中间件,在收到某消息后,将消息发送给指定的订阅者(观察者)。其中observer对象中有订阅者的地址等通信信息
        for (IObserver observer:observers){
            observer.action();
        }
    }

    /**
     * 观察者自己的业务方法。完成业务后通知观察者
     */
    @Override
    void work() {
        System.out.println(name+"work");
        //do something ...
        //通知观察者
        notifyObervers();
    }
}

/**
 * 具体观察者A
 */
public class ConcreteObserverA implements IObserver{
    @Override
    public void action() {
        //do something
        System.out.println("ConcreteObserverA收到通知");
    }
}

/**
 * 具体观察者B
 */
public class ConcreateObserverB implements IObserver {
    @Override
    public void action() {
        //do something
        System.out.println("ConcreateObserverB收到通知");
    }
}

/**
 * 客户端
 */
public class Test {
    public static void main(String[] args) {
        //观察者1
        IObserver observer1 = new ConcreteObserverA();
        //观察者1
        IObserver observer2 = new ConcreateObserverB();
        //创建主体
        ISubject subject = new ConcreteSubjectA("复古主题");
        //观察者注册到指定主体
        subject.regiser(observer1);
        subject.regiser(observer2);
        //主题产生指定行为
        subject.work();
    }
}

监听者模式

监听者模式不是23中设计模式之一,这样只是方便称呼。个人认为,监听者模式和观察者模式本质是无区别的。监听者模式只是将从观察者模式多抽取出了一个事件角色。

监听器模式中涉及的角色:

事件源:指事件发生的主体,例如,一个人可以产生吃饭、睡觉、学习等事件。此处人为事件源

事件对象:对具体事件的封装

事件监听器:对某事件源的事件进行监听

//此处以人作为事件源
public class Person{  
    private List<Listener> list=new ArrayList<Listener>(); //同样持有监听者的队列
    //添加事件监听器
    public void addListener(Listener listener){  
        list.add(listener);  
    }  
    public void removeListener(Listener listener){  
        list.remove(listener);  
    }  
    //事件被触发,通知监听者
    public void sendNotification(Event event){  
        for(Listener listener:list){  
            listener.onChange(event);  
        }  
    }  
}  

public interface Listener {  
    public void onChange(Event event);  
}  

//监听器
public class MyListener implements Listener {  
    @Override  
    public void onChange(Event event) {  
        switch(event.getType()){  
            case Event.EAT:  
                System.out.println("do eat...");  
                break;  
            case Event.SLEEP :  
                System.out.println("do sleep...");  
                break;  
           	......
            default:  
                throw new IllegalArgumentException();  
        }  
    }  
}  

//事件
public class Event {  
    public static final int EAT=1;   
    public static final int SLEEP=2;   
    public static final int STUDY=3;   
    public static final int RUN=4;   
    private int type ;  
    private Object source ;  
    //type:事件类型,source:事件源
    public Event(int type, Object source) {  
        this.type = type;  
        this.source = source;  
    }  
    public int getType() {  
        return type;  
    }  
    public Object getSource() {  
        return source;  
    }  
}  

public class Test {  
    public static void main(String[] args) {  
        Person person = new Person();
        Listener listener=new MyListener();  
        //添加监听者  
        person.addListener(listener);  
        //模拟人睡觉完成事件触发  
        person.sendNotification(new Event(Event.INSTALLED, person)); //将睡觉行为封装成事件对象,通知给监听者,监听者对该事件具体采取何种响应由自己决定
    }  
  
}  
上例实际是对事件源做了监听,事件源会将发生的所有事件都通知给了事件监听器,可以将监听粒度缩小到事件,在事件源内部可以为每类事件创建不同的队列,某事件发生时只通知在该事件上注册的监听器。

以上都只是基本思想,可以根据业务需求做出不同的变形。

观察者模式与监听者模式关系

最明显就是,监听者模式中通知监听者时需要传递一个事件对象,而观察者模式不需要

观察者=事件监听器,被观察者=事件源+事件

图片来自:https://blog.csdn.net/axuan_k/article/details/78803382

监听者模式中的例子如果用观察者模式实现,则需要在被观察者对象完成特定业务后,调用观察者对象的对应方法,需要知道要调用被观察者的哪个方法。而监听者模式告诉监听者自己做了什么事(通过将行为封装成事件传递给监听者),监听者做出什么响应由其自身决定。

Spring中的监听器

ApplicationContext基于Observer模式(java.util包中有对应实现),提供了针对Bean的事件传
播功能。通过Application. publishEvent方法,我们可以将事件通知系统内所有的
ApplicationListener。

几个角色:ApplicationEvent(事件类)、ApplicationListener(监听者类)、ApplicationContext(事件发布类)

ApplicationEvent继承自JDK中的EventObject,EventObject,为JavaSE提供的事件类型基类,任何自定义的事件都继承自该类,例如上图中右侧灰色的各个事件。

public abstract class ApplicationEvent extends EventObject {  
   
    private static final long serialVersionUID = 7099057708183571937L; 
 
    private final long timestamp;  
  
    public ApplicationEvent(Object source) {  
        super(source);  
        this.timestamp = System.currentTimeMillis();  
    }  
    
    public final long getTimestamp() {  
        return this.timestamp;  
    }  
  
}  

ApplicationListener:继承自JDK中的EventListener。EventListener为JavaSE提供的事件监听者接口,任何自定义的事件监听者都实现了该接口。

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {  
  
    void onApplicationEvent(E event);  
  
} 

ApplicationContext:JavaSE中未提供事件发布者这一角色类,由各个应用程序自行实现事件发布者这一角色。Spring中提供了ApplicationEventPublisher接口作为事件发布者,并且ApplicationContext实现了这个接口,担当起了事件发布者这一角色。可通过ApplicationContext.publishEvent(ApplicationEvent event)发布事件。可猜想ApplicationContext中维护了一个监听者的列表,事件发生时通知所有监听者。

具体用法,from:https://blog.csdn.net/erbao_2014/article/details/68924231?locationNum=9&fps=1

//自定义事件
public class MyTestEvent extends ApplicationEvent{
    private String msg ;

    public MyTestEvent(Object source,String msg) {
        super(source);
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
} 
//自定义监听器
@Component
public class MyNoAnnotationListener implements ApplicationListener<MyTestEvent>{

    @Override
    public void onApplicationEvent(MyTestEvent event) {
        System.out.println("非注解监听器:" + event.getMsg());
    }

} 
//自定义发布类
@Component
public class MyTestEventPubLisher {
    @Autowired
    private ApplicationContext applicationContext;

    // 事件发布方法
    public void pushListener(String msg) {
        applicationContext.publishEvent(new MyTestEvent(this, msg));
    }

}
//测试类
@Controller
public class TestEventListenerController {
    @Autowired
    private MyTestEventPubLisher publisher;

    @RequestMapping(value = "/test/testPublishEvent1" )
    public void testPublishEvent(){
        publisher.pushListener("我来了!");
    }
}

注解方式:在监听器中@EventListener来做方法级别的注解。其余不变

@Component
public class MyAnnotationListener {
    @EventListener
    public void listener1(MyTestEvent event) {
        System.out.println("注解监听器1:" + event.getMsg());
    }
}

补充:如何在自定义Listener(非实现spring的ApplicationListener)中使用Spring容器管理的bean。

form:https://www.cnblogs.com/fjdingsd/p/5731982.html

1.在java web项目中我们通常会有这样的需求:当项目启动(非spring启动)时执行一些初始化操作,例如从数据库加载全局配置文件等,通常情况下我们会用javaee规范中的Listener去实现。ServletContextListener是javaee下的类

1 public class ConfigListener implements ServletContextListener {
2     @Override
3     public void contextInitialized(ServletContextEvent sce) {
4           //执行初始化操作
5     }
6     @Override
7     public void contextDestroyed(ServletContextEvent sce) {
8     }
9 }

2.这样当servlet容器初始化完成后便会调用contextInitialized方法。但是通常我们在执行初始化的过程中会调用service和dao层提供的方法,service和dao是由spring框架来管理,我们想当然会像下面这么写,假设执行初始化的过程中需要调用ConfigService的initConfig方法,而ConfigService由spring容器管理

public class ConfigListener implements ServletContextListener {
 
    @Autowired
    private ConfigService configService;
     
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        configService.initConfig();
    }
 
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    }
}

3.以上代码会在项目启动时抛出空指针异常。ConfigService实例并没有成功注入。这是为什么呢?要理解这个问题,首先要区分Listener的生命周期和spring管理的bean的生命周期。

(1)Listener的生命周期是由servlet容器(例如tomcat)管理的项目启动时上例中的ConfigListener是由servlet容器实例化并调用其contextInitialized方法,而servlet容器并不认得@Autowired注解,因此导致ConfigService实例注入失败。

(2)而spring容器中的bean的生命周期是由spring容器管理的。

4.那么该如何在spring容器外面获取到spring容器bean实例的引用呢?这就需要用到spring为我们提供的WebApplicationContextUtils工具类,该工具类的作用是获取到spring容器的引用,进而获取到我们需要的bean实例。代码如下

public class ConfigListener implements ServletContextListener {
     
    @Override
    public void contextInitialized(ServletContextEvent sce) {   
        ConfigService configService = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()).getBean(ConfigService.class);
        configService.initConfig();
    }
 
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    }
 
}

注意:以上代码有一个前提,那就是servlet容器在实例化ConfigListener并调用其方法之前,要确保spring容器已经初始化完毕!而spring容器的初始化也是由Listener(ContextLoaderListener)完成,因此只需在web.xml中先配置初始化spring容器的Listener,然后在配置自己的Listener,配置如下

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring.xml</param-value>
</context-param>
 
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
 
<listener>
    <listener-class>example.ConfigListener</listener-class>
</listener>

若在ConfigListener上加上@Component注解会怎样?

ConfigService还是无法注入,因为ConfigListener实现的是ServletContextListener,是受web容器才管理,在web容器初始化完成后创建ConfigListener实例并调用其contextInitialized()方法的。web容器完全不认识@Autowired注解。(此时的ConfigListener实例由web容器创建

但是如果当项目完全启动后,再另个一类中注入ConfigListener,会发现ConfigService已经被成功注入ConfigListener类,这是因为,此时用到的ConfigListener是spring管理下的bean,所以ConfigService也可以被注入了。(此时的ConfigListener实例由spring容器创建

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值