Spring源码之七registerListeners()及发布订阅模式

简介: 今天带大家解读refresh()方法中的registerListeners()方法,也就是我们经常说的Spring的发布-订阅模式。文章首先举一个发布-订阅模式的样例,然后讲解了发布-订阅四个模式的原理,及对发布-订阅模式所依赖的观察者模式进行了举例,最后引出该模式在Springboot中的大量应用。

Spring源码之七registerListeners()及发布订阅模式


大家好,我是程序员田同学。


今天带大家解读refresh()方法中的registerListeners()方法,也就是我们经常说的Spring的发布-订阅模式。文章首先举一个发布-订阅模式的样例,然后讲解了发布-订阅四个模式的原理,及对发布-订阅模式所依赖的观察者模式进行了举例,最后引出该模式在Springboot中的大量应用。


照例放一份refresh()方法的源码,registerListeners()方法位于该方法的第七个位置。

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      //1、刷新前的准备
      prepareRefresh();
      // Tell the subclass to refresh the internal bean factory.
      //2、将会初始化 BeanFactory、加载 Bean、注册 Bean
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      // Prepare the bean factory for use in this context.
      //3、设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean
      prepareBeanFactory(beanFactory);
      try {
         //4、模板方法
         // Allows post-processing of the bean factory in context subclasses.
         postProcessBeanFactory(beanFactory);
         // Invoke factory processors registered as beans in the context.
         //执行BeanFactory后置处理器
         invokeBeanFactoryPostProcessors(beanFactory);
         // 5、Register bean processors that intercept bean creation.
         //注册bean后置处理器
         registerBeanPostProcessors(beanFactory);
         // Initialize message source for this context.
         //国际化
         initMessageSource();
         // Initialize event multicaster for this context.
         initApplicationEventMulticaster();
         // Initialize other special beans in specific context subclasses.
         //6、模板方法--springboot实现了这个方法
         onRefresh();
         // Check for listener beans and register them.
         //7、注册监听器
         registerListeners();
         // Instantiate all remaining (non-lazy-init) singletons.
         //8、完成bean工厂的初始化**方法重要**********************************************
         finishBeanFactoryInitialization(beanFactory);
         //9、 Last step: publish corresponding event.
         finishRefresh();
      }
      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }
         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();
         // Reset 'active' flag.
         cancelRefresh(ex);
         // Propagate exception to caller.
         throw ex;
      }


先大致看一下registerListeners()方法的源码。

protected void registerListeners() {
    // Register statically specified listeners first.
    // 首先注册静态的指定的监听器,注册的是特殊的事件监听器,而不是配置中的bean
    for (ApplicationListener<?> listener : getApplicationListeners()) {
      getApplicationEventMulticaster().addApplicationListener(listener);
    }
    // Do not initialize FactoryBeans here: We need to leave all regular beans
    // uninitialized to let post-processors apply to them!
    // 这里不会初始化FactoryBean,我们需要保留所有的普通bean
    // 不会实例化这些bean,让后置处理器可以感知到它们
    String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    for (String listenerBeanName : listenerBeanNames) {
      getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
    }
    // Publish early application events now that we finally have a multicaster...
    // 现在有了事件广播组,发布之前的应用事件
    Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
    this.earlyApplicationEvents = null;
    if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
      for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
        getApplicationEventMulticaster().multicastEvent(earlyEvent);
      }
    }
  }


这个方法要做的很简单,就是两个循环遍历,把Spring通过硬编码定义的监听器注册到容器中,然后把我们自定义的监听器注册到容器中,通过这些直接叙述有一些苍白无力,我们写一个简单的发布-订阅的例子方便理解。


在发布订阅模式用需要四个角色:

ApplicationEvent:事件,每个实现类表示一类事件,可携带数据。抽象类。

ApplicationListener:事件监听器,用于接收事件处理时间。接口。

ApplicationEventMulticaster:事件管理者,可以注册(添加)/移除/发布事件。用于事件监听器的注册和事件的广播。接口。

ApplicationEventPublisher:事件发布者,委托事件管理者ApplicationEventMulticaster完成事件发布。


事件:

@Component
public class MyEvent extends ApplicationEvent {
  private static final long serialVersionUID = 1L;
  /**
   * Create a new ApplicationEvent.
   *
   * @param source the object on which the event initially occurred (never {@code null})
   */
  public MyEvent(Object source) {
    super(source);
  }
}


事件监听器:

@Component
public class MyEventListener implements ApplicationListener<MyEvent> {
  @EventListener //@EventListener注解实现事件监听
  @Override
  public void onApplicationEvent(MyEvent event) {
    Object msg = event.getSource();
    System.out.println("自定义事件监听器(MyEventListener1)收到发布的消息: " + msg);
  }
}


事件发布者:

public static void main(String[] args) {
    System.out.println(1);
    ApplicationContext ac =new AnnotationConfigApplicationContext(MyEventListener.class);
    MyEvent myEvent=new MyEvent(new Object());
    ac.publishEvent(myEvent);
  }


那么事件的管理器跑哪去了呢?

我们在 registerListeners()方法上面打一个断点,看看我们自定义的事件是如何在Spring中起作用的。


第一个循环是Spring默认的监听器默认情况下是空。

我们自定义的事件监听器在第二个循环里面被加载到了ApplicationEventMulticaster中,已经很明显了,ApplicationEventMulticaster就是我们的事件管理者。

我们在把他们之间的关系详细串一下。


注:广播器和上面的管理者是一个意思

到这个阶段,事件管理者和监听器都在Spring容器里初始化和注册了,之后就可以实现监听者模式了,对事件的发布进行监听然后处理。

在就是ac.publishEvent(myEvent);方法发布事件后进行的业务处理。

ApplicationContext ac =new AnnotationConfigApplicationContext(MyEventListener.class);
    MyEvent myEvent=new MyEvent(new Object());
    ac.publishEvent(myEvent);


事件监听机制实际就是主题-订阅模式(观察者模式)的实现,能够降低代码耦合。

本文顺便把观察者模式简要的叙述一下,方便读者可以更好的理解。


观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

public class RMBrateTest {
    public static void main(String[] args) {
        Rate rate = new RMBrate();
        Company watcher1 = new ImportCompany();
        Company watcher2 = new ExportCompany();
        rate.add(watcher1);
        rate.add(watcher2);
        rate.change(10);
        rate.change(-9);
    }
}
//抽象目标:汇率
abstract class Rate {
    protected List<Company> companys = new ArrayList<Company>();
    //增加观察者方法
    public void add(Company company) {
        companys.add(company);
    }
    //删除观察者方法
    public void remove(Company company) {
        companys.remove(company);
    }
    public abstract void change(int number);
}
//具体目标:人民币汇率
class RMBrate extends Rate {
    public void change(int number) {
        for (Company obs : companys) {
            ((Company) obs).response(number);
        }
    }
}
//抽象观察者:公司
interface Company {
    void response(int number);
}
//具体观察者1:进口公司
class ImportCompany implements Company {
    public void response(int number) {
        if (number > 0) {
            System.out.println("人民币汇率升值" + number + "个基点,降低了进口产品成本,提升了进口公司利润率。");
        } else if (number < 0) {
            System.out.println("人民币汇率贬值" + (-number) + "个基点,提升了进口产品成本,降低了进口公司利润率。");
        }
    }
}
//具体观察者2:出口公司
class ExportCompany implements Company {
    public void response(int number) {
        if (number > 0) {
            System.out.println("人民币汇率升值" + number + "个基点,降低了出口产品收入,降低了出口公司的销售利润率。");
        } else if (number < 0) {
            System.out.println("人民币汇率贬值" + (-number) + "个基点,提升了出口产品收入,提升了出口公司的销售利润率。");
        }
    }
}


汇率变化就是一个事件,当该事件发生时,它的观察者出口公司和进口公司就要做相应的变化。


书归正传重新回到registerListeners()方法中,这时所有的监听器都注册到了容器中,只等publishEvent()发布事件以后,就会执行我们监听器中的业务逻辑。


据说在Springboot中应用了大量的发布订阅模式,抱着求知若渴的态度我们去Springboot中搂一眼,在registerListeners()上面打一个断点,看看和Spring上面的断点有什么区别。


在Spirng中监听器默认是空,当时在Springboot中有15个之多,不愧是加强版的Spring。具体这些监听器是干嘛的我们就不深究了,在Springboot中会对其逐步拆解的。


相关文章
|
2天前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
2天前
|
XML 缓存 Java
手写Spring源码(简化版)
Spring包下的类、手写@ComponentScan注解、@Component注解、@Autowired注解、@Scope注解、手写BeanDefinition、BeanNameAware、InitializingBean、BeanPostProcessor 、手写AnnotationConfigApplicationContext
手写Spring源码(简化版)
|
2天前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
2月前
|
设计模式 监控 Java
解析Spring Cloud中的断路器模式原理
解析Spring Cloud中的断路器模式原理
|
19天前
|
人工智能 前端开发 Java
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
本文介绍了如何使用 **Spring Cloud Alibaba AI** 构建基于 Spring Boot 和 uni-app 的聊天机器人应用。主要内容包括:Spring Cloud Alibaba AI 的概念与功能,使用前的准备工作(如 JDK 17+、Spring Boot 3.0+ 及通义 API-KEY),详细实操步骤(涵盖前后端开发工具、组件选择、功能分析及关键代码示例)。最终展示了如何成功实现具备基本聊天功能的 AI 应用,帮助读者快速搭建智能聊天系统并探索更多高级功能。
150 2
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
|
2月前
|
Java Spring 容器
Spring Boot 启动源码解析结合Spring Bean生命周期分析
Spring Boot 启动源码解析结合Spring Bean生命周期分析
79 11
|
25天前
|
设计模式 Java 程序员
学习 Spring 源码的意义是什么呢?
研究Spring源码能深化框架理解,提升代码分析与设计能力,助您掌握设计模式及最佳实践,增强解决问题的效率,促进职业生涯发展,并激发技术热情。选择稳定版本,从核心模块开始,结合实际项目并参与社区,让学习之旅既充实又具乐趣。
|
2月前
|
缓存 Java 程序员
spring IoC 源码
spring IoC 源码
47 3
|
2月前
|
存储 设计模式 Java
Spring Boot中的事件溯源模式
Spring Boot中的事件溯源模式
|
2月前
|
JavaScript Java 数据安全/隐私保护
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码(2)
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码
64 0
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码(2)