Spring 观察者模式详解以及自定义监听器扩展实现

在这里插入图片描述

🔭 嗨,您好 👋 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者
📖 推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代
🌲文章所在专栏:Spring
🤔 我当前正在学习微服务领域、云原生领域、消息中间件等架构、原理知识
💬 向我询问任何您想要的东西,ID:vnjohn
🔥觉得博主文章写的还 OK,能够帮助到您的,感谢三连支持博客🙏
😄 代词: vnjohn
⚡ 有趣的事实:音乐、跑步、电影、游戏

目录

前言

在 Spring 运用中,观察者模式运用的场景很多,只不过在 Spring 内部为观察者模式定义为了抽象,使用多路广播器方式将观察者、被观察者、行为抽象了事件、监听器、事件源等名,下面对该内容进行详细分析

观察者模式/监听机制

监听器:模式中的观察者角色
多路广播器:被观察者触发事件调用,多个观察者监听到事件处理对应方法的逻辑
在 Spring 里面其实就是运用观察者模式进行设计的,它对比传统的观察者模式基础上进行了加工,概念上已经改为了基于事件驱动触发行为动作,下面对传统观察者模式、Spring 加工后的观察者模式进行介绍

观察者模式(传统)

在这里插入图片描述

观察者模式又称为发布/订阅(Publish/Subscribe)模式,在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新触发行为

观察者、被观察者共有行为

/**
 * 被观察者
 */
public interface Observable {
     public void addObserver(Observer observer);
     public void deleteObserver(Observer observer);
     public void notifyObserver(String str);
}

/**
 * 观察者
 */
public interface Observer {
    public void make(String str);
}

被观察者实现类

public class BadMan implements Observable {

    private ArrayList<Observer> observers = new ArrayList<Observer>();

    @Override
    public void addObserver(Observer observer) {
        this.observers.add(observer);
    }

    @Override
    public void deleteObserver(Observer observer) {
        this.observers.remove(observer);
    }

    @Override
    public void notifyObserver(String str) {
        for (Observer observer1 : observers) {
            observer1.make(str);
        }
    }

    public void run(){
        System.out.println("罪犯要逃跑了");
        this.notifyObserver("追击罪犯");
    }

    public void play(){
        System.out.println("罪犯在玩");
        this.notifyObserver("不动做任何事情,静观其变");
    }
}

观察者

public class Police implements Observer {
    @Override
    public void make(String str) {
        System.out.println("police(警察)开始行动");
        System.out.println("--------"+str);
    }
}

public class Soldier implements Observer {
    @Override
    public void make(String str) {
        System.out.println("军官(soldier)开始行动");
        System.out.println("====="+str);
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        // 创建被观察者
        BadMan bm = new BadMan();
        // 创建观察者
        Police gm = new Police();
        Soldier gm2 = new Soldier();
        // 向被观察者中添加观察者
        bm.addObserver(gm);
        bm.addObserver(gm2);
        // 等待罪犯触发某些行为
        bm.run();
    }
}

java.util 包下有提供 Observer 接口、Observable

  • 被观察者:存储一个观察者的集合,执行不同动作时,要调用观察者的方法进行处理
  • 观察者:看到被观察者不同行为的时候触发的反应

观察者模式(Spring)

在 Spring 中,观察者模式被理解为 “事件驱动”,通过对传统模式的理解进行细化拆分

基本概念
  • 事件:被观察者具体要执行的动作「代码中的 run、play 方法」被拆分成事件,所有事件都实现于 ApplicationEvent 接口,交给子类在不同的事件情况时去执行自己内部的逻辑
  • 监听器:作为观察者,可能存在多个,接受不同的事件去做不同的处理工作,所有监听器都实现于 ApplicationListener 接口,重写 onApplicationEvent 方法,方法内部通过不同的事件【ApplicationEvent】类型进行调用
  • 多播器:把被观察者遍历观察者列表通知的消息操作拿出来,委托给一个多播器来进行消息通知,或者说通过观察者进行不同的操作,一般是 ApplicationEventMulticaster 子类 SimpleApplicationEventMulticaster 进行事件广播操作
  • 事件源:谁来调用或者执行发布具体的事件,如在 AbstractApplicationContext 中调用 publishEvent(new ContextRefreshedEvent(this))发布事件,比如 AbstractApplicationContext 就是指的事件源,它实现了 ApplicationEventPublisher 接口,可以调用 publishEvent 方法,类图如下

在这里插入图片描述

事件驱动准备工作及执行过程

事件驱动的准备工作如下:

  • 提前准备好 N 多个事件
  • 初始化多播器(创建多播器对象,此多播器对象中应该包含一个监听器的集合)
  • 准备好一系列的监听器,向多播器中注册进去需要执行的监听器
  • 准备事件发布,通知多播器循环调用监听器 onApplicationEvent 方法进行相关的逻辑处理工作

Spring 相关的源码

初始化事件监听多路广播器

protected void initApplicationEventMulticaster() {
    // 获取当前bean工厂,一般是DefaultListableBeanFactory
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    // 判断容器中是否存在bdName为applicationEventMulticaster的bd,也就是说自定义的事件监听多路广播器,必须实现ApplicationEventMulticaster接口
    if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
        // 如果有,则从bean工厂得到这个bean对象
        this.applicationEventMulticaster =
            beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
        if (logger.isTraceEnabled()) {
            logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
        }
    }
    else {
        // 如果没有,则默认采用 SimpleApplicationEventMulticaster
        this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
        if (logger.isTraceEnabled()) {
            logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
                         "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
        }
    }
}

默认采用的是 SimpleApplicationEventMulticaster 多路广播器,它的父类 AbstractApplicationEventMulticaster 内部类专门使用了一个集合来存储所有加载的监听器

// 创建监听器助手类,用于存放应用程序的监听器集合,参数是否是预过滤监听器为false
private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);
private class ListenerRetriever {
    // ApplicationListener 对象集合
    public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
    // BeanFactory 中的 applicationListener 类型 Bean 名集合
    // 在设置监听器时会通过 beanName 去获取监听器,再放入 applicationListeners 集合中
    public final Set<String> applicationListenerBeans = new LinkedHashSet<>();
.......
}

注册监听器的源码:在所有注册的 bean 中查找监听器,将监听器注册到消息广播器中

protected void registerListeners() {
    // 遍历应用程序中存在的监听器集合,并将对应的监听器添加到监听器的多路广播器中
    for (ApplicationListener<?> listener : getApplicationListeners()) {
        getApplicationEventMulticaster().addApplicationListener(listener);
    }

    // 从容器中获取所有实现了ApplicationListener接口的bd的bdName
    // 放入 ApplicationListenerBeans 集合中
    String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    for (String listenerBeanName : listenerBeanNames) {
        getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
        // 可以直接获取监听器实例进去,无须在取用的时候再次 getBean	getApplicationEventMulticaster().addApplicationListener(this.getBean(listenerBeanName,ApplicationListener.class));
    }

    // 此处先发布早期的监听器集合
    Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
    this.earlyApplicationEvents = null;
    if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
        for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
            getApplicationEventMulticaster().multicastEvent(earlyEvent);
        }
    }
}

事件驱动的逻辑执行过程如下:

  • 事件源来发布不同的事件
  • 当发布事件之后会调用多播器的方法来进行事件广播操作,由多播器去触发具体的监听器去执行操作
  • 监听器接收到具体的事件之后,可以验证是否能处理当前事件的类型,如果可以,进行处理;如果不行,不做任何操作

SpringBoot 监听器加载过程

在这里插入图片描述
首先加载 META-INF/spring.factories 文件中的 key:ApplicationListener,value:全限定类名,然后获取到所有的实例存入 ApplicationContext 中,方便后续创建多播器对象时可以获取到这些监听器实例,注入:ApplicationContext 实例以后,就可以调用它来进行事件发布动作
在这里插入图片描述
通过观察以上内置监听器的源码,可以发现在这些监听器里都实现了 ApplicationListener 同时会使用到抽象类 ApplicationEvent,但这些内置监听器中的事件都是基于它抽象的实现,定义自己的事件类

事件驱动机制是基于观察者设计模式的实现,通过 ApplicationEvent 类、ApplicationListener 接口,可以通过 ApplicationContext 实现事件的发布

如下图整理了 SpringBoot 内置的监听器和事件:

在这里插入图片描述

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

前戏: 在事件准备发布时,首先会通过 getRunListeners 方法来获取我们在 META-INF/spring.factories 文件中的定义 SpringApplicationRunListener 接口实现类 EventPublishingRunListener,同时会完成该类的实例化操作,调用构造方法时,会初始化多路广播器对象:SimpleApplicationEventMulticaster,同时从上下文获取到前面加载好的 11 个监听器,进行绑定操作.

前戏工作做完以后,就到了发布事件的时候了
SimpleApplicationEventMulticaster#multicastEvent:在容器启动时会调用 listener#starting 方法(事件名:ApplicationStartingEvent)、环境对象准备前会调用 listener#environmentPrepared(事件名:ApplicationEnvironmentPreparedEvent)

以 ConfigFileApplicationListener 为例,会在其内部处理配置文件的解析工作,接受 ApplicationEnvironmentPreparedEvent 事件的处理,源码如下:

public void onApplicationEvent(ApplicationEvent event) {
      if (event instanceof ApplicationEnvironmentPreparedEvent) {
          this.onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event);
      }
      if (event instanceof ApplicationPreparedEvent) {
          this.onApplicationPreparedEvent(event);
      }
  }

自定义监听器扩展实现

通过几个不同类型自定义事件案例来加深对事件驱动机制的理解

监听所有事件

先创建一个自定义监听器,来监听所有的事件;创建一个 Java 类,实现 ApplicationListener 接口在泛型中指定要监听的事件类型即可,如果要监听所有的事件,那么泛型就写 ApplicationEvent

public class MySpringApplicationListener implements ApplicationListener<ApplicationEvent> {
	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		System.out.println("自定义监听器--->" + event);
	}
}

之后为了在容器启动中能够发现我们的监听器并且添加到 SimpleApplicationEventMulticaster 中,我们需要在 spring.factories/META-INF 中注册自定义的监听器

org.springframework.context.ApplicationListener=\
com.vnjohn.demo.listener.MySpringApplicationListener

这样当我们启动服务的时候就可以看到相关事件发布,我们的监听器被触发了,会打印对应的信息

监听特定事件

如果是监听特定的事件,我们只需要在泛型出指定类型即可

public class MySpringApplicationStartingListener implements ApplicationListener<ApplicationStartingEvent> {
	@Override
	public void onApplicationEvent(ApplicationStartingEvent event) {
		// 该事件是启动启动时就会进行发布的,查询容器启动日志信息即可 
		System.out.println("MySpringApplicationStartingListener--------->" + event);
	}
}
org.springframework.context.ApplicationListener=\
com.vnjohn.demo.listener.MySpringApplicationListener,\
com.vnjohn.demo.listener.MySpringApplicationStartingListener

启动服务时可以看到相关的事件发布

自定义事件

若我们想要通过自定义的监听器来监听自定义的事件呢?首先创建自定义的事件类,非常简单,只需要继承 ApplicationEvent 即可

public class MyEvent extends ApplicationEvent {
	public MyEvent(Object source) {
		super(source);
	}
}

然后在自定义的监听器中监听自定义的事件

public class MyCustomerEventListener implements ApplicationListener<MyEvent> {
	@Override
	public void onApplicationEvent(MyEvent event) {
		System.out.println("MyCustomerEventListener ---》自定义事件触发" + event);
		// 触发对应的事件后 业务处理
		new Thread(()->{
			// 业务....
		}).start();
	}
}

事件的监听和发布是同步执行的,如果想让其异步的进行,可以抛给一个线程进行处理

org.springframework.context.ApplicationListener=\
com.vnjohn.demo.listener.MySpringApplicationListener,\
com.vnjohn.demo.listener.MySpringApplicationStartingListener,\
com.vnjohn.demo.listener.MyCustomerEventListener

之后我们就可以在我们特定的业务场景中类发布对应的事件了

@Component
public class MyApplicationContextAware implements ApplicationContextAware {
	private static ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		MyApplicationContextAware.applicationContext = applicationContext;
	}

	public static void publishEvent(ApplicationEvent applicationEvent) {
		applicationContext.publishEvent(applicationEvent);
	}
}
@RestController
public class HelloController {
	
	@GetMapping("/hello")
	public String hello(){
		MyApplicationContextAware.publishEvent(new MyEvent(new Object()));
		return "hello---";
	}
}

当提交请求后,对应的监听器就触发了,这样一来不光搞清楚了 SpringBoot 中的监听机制,而且也可以扩展使用到我们业务开发中了

总结

希望对你有所帮助,您的支持是对我最大的鼓励,关注+赞+收藏三连,感谢!

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!
大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

vnjohn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值