深入理解Spring IOC之扩展篇(七)、Spring中的event以及自定义event

简介: 深入理解Spring IOC之扩展篇(七)、Spring中的event以及自定义event

本篇说的是Spring中event的概念以及对它的扩展,顺便说说它的原理。

Spring中事件的顶层类是ApplicationEvent,我们自定义事件只需要继承这个类即可(更好的选择其实是继承ApplicationContextEvent),好,我们那就按照这个思路自定义一下我们自己的事件:


public class MyEvent1 extends ApplicationEvent {
    public MyEvent1(ApplicationContext source) {
        super(source);
    }
    public void doSomething(){
        System.out.println("此处发生了一件诡异的事情");
    }
}


此时,我们自己定义的这个MyEvent1在Spring容器中是没有对应的监听器的,也就是说此时Spring还不认识我们这个事件,我们还需要定义我们自己的监听器,定义监听器的姿势有三种,我们先来看看第一种:

姿势1: 实现SmartApplicationListener接口


@Component
public class MyEvent1Listener1 implements SmartApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        MyEvent1 event1 = (MyEvent1) event;
        System.out.println("Listener1 发现了一件诡异的事情");
        event1.doSomething();
    }
    // 必须重写这个逻辑,这个决定我们这个监听器是不是能识别到我们这个事件
    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        // eventType 是接收到的事件的属性
        return MyEvent1.class.equals(eventType);
    }
}


姿势2:实现GenericApplicationListener接口


@Component
public class MyEvent1Listener2 implements GenericApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        MyEvent1 event1 = (MyEvent1) event;
        System.out.println("Listener2 发现了一件诡异的事情");
        event1.doSomething();
    }
    // 必须重写这个方法,ResolvableType中的type属性就是接收到的事件的类型
    @Override
    public boolean supportsEventType(ResolvableType eventType) {
        // 接收到的事件的属性
        return MyEvent1.class.equals(eventType.getType());
    }
}


姿势3:实现ApplicationListener接口


// 注意这个泛型哈,这个是决定能不能接收到的条件
@Component
public class MyEvent1Listener3 implements ApplicationListener<MyEvent1> {
    @Override
    public void onApplicationEvent(MyEvent1 event) {
        MyEvent1 event1 = (MyEvent1) event;
        System.out.println("Listener3 发现了一件诡异的事情");
        event1.doSomething();
    }
}


配置类


@Configuration
@ComponentScan(basePackages = "com.example.demo.external7")
public class Config {
}


测试代码:


public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Config.class);
        annotationConfigApplicationContext.publishEvent(new MyEvent1(annotationConfigApplicationContext));
}


结果:


1686813727211.png


我们可以看到,姿势1和姿势2的本质都是一样的,只是在实现supportsEventType时这里传的参数不一样,姿势3看起来是最简单。它们监听的原理是什么呢,我们来具体的看一看源码:

我们先来看之前的refresh方法,注意看其中的7和9就行,这两处主要是事件广播器的初始化


public void refresh() throws BeansException, IllegalStateException {
  synchronized (this.startupShutdownMonitor) {
    // 1.加载前的准备工作
    prepareRefresh();
    // 2.获取一个全新的beanFactory实例
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    // 3.初始化beanFactory,给它设置各种
    prepareBeanFactory(beanFactory);
    try {
      // (空方法,让子类重写的)允许对beanFactory中的东西做一些前置的修改,可以是增加个BeanFactoryPostProcessors
      // 这种的,也可以给增加个BeanDefinition,也可以增加一些让beanFactory在自动装配时候忽略掉的接口,也可以增加一些特定场景使用的bean,
      // 比如有的后代就增加了新的scope bean 等等。但是重点是,我刚才说的这些都是基于一种具体场景的,因此这个抽象类里,
      // 这个方法是空的(不弄成抽象的原因是不强迫子类去实现)
      postProcessBeanFactory(beanFactory);
      // 4.触发调用所有的BeanFactoryPostProcessors
      invokeBeanFactoryPostProcessors(beanFactory);
      // 5.注册所有的BeanPostProcessor
      registerBeanPostProcessors(beanFactory);
      // 6.初始化支持国际化的东东
      initMessageSource();
      // 7. 初始化事件广播器
      initApplicationEventMulticaster();
      // 8. 初始化其他特殊的bean
      onRefresh();
      // 9. 注册监听器
      registerListeners();
      // 10. 实例化bean( 重点 )
      finishBeanFactoryInitialization(beanFactory);
      finishRefresh();
    } catch (BeansException ex) {
      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;
    }
  }
}


第7处的源码如下:


// 整个方法是为了初始化事件广播器的
protected void initApplicationEventMulticaster() {
        // 获取前面已经创建好的BeanFactory实例
  ConfigurableListableBeanFactory beanFactory = getBeanFactory();
  if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
      // this.applicationEventMulticaster就是spring容器中的事件广播器
      // 如果容器中已经有事件广播器,就用容器中的
      // 从这里可以看出来,如果想用自己的事件广播器,bean的名字就必须为applicationEventMulticaster
    this.applicationEventMulticaster =
        beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
    if (logger.isTraceEnabled()) {
      logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
    }
  }else {
  // 如果容器中没有,则初始化事件广播器SimpleApplicationEventMulticaster
  // 其实就是new 了一下
    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() + "]");
    }
  }
}


第9处的源码:


protected void registerListeners() {
  // 1、将容器中的所有监听器放到事件广播器中
  //  注意,此时容器所有的bean还没有初始化,因此,一般来说getApplicationListeners方法返回的都是空集合
  for (ApplicationListener<?> listener : getApplicationListeners()) {
    getApplicationEventMulticaster().addApplicationListener(listener);
  }
  // 2、把所有的listener的bean的名称加到事件广播器里面
  // 注意此时是不初始化的,因为还没到初始化的时候,初始化bean应该在refresh方法调用finishBeanFactoryInitialization时
  String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
  for (String listenerBeanName : listenerBeanNames) {
    getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
  }
  // 发布事件广播器初始化结束事件(如果有的话。。说实话我没找到在哪)
  Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
  this.earlyApplicationEvents = null;
  if (earlyEventsToProcess != null) {
    for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
      getApplicationEventMulticaster().multicastEvent(earlyEvent);
    }
  }
}


我们看了上面的代码之后发现第2步只是把所有的listener的bean放到事件广播器中,但是并没有初始化,那么是什么时候对linstener进行初始化的呢?在refresh方法中有调用一个prepareBeanFactory方法,其中有一句代码如下:


1686813713663.png


我们可以看到,prepareBeanFactory方法中给beanFactory添加了一个ApplicationListenerDetector,这玩意本质上是一个BeanPostProcessor,那它是做什么的呢?我们直接来看它的源码:


我这里仅仅列核心方法的代码了哈



@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
        // 如果实例是个ApplicationListener的实例
  if (bean instanceof ApplicationListener) {
          // 这里的this.singletonNames的key是beanName,value为key对应的bean是否是单例,是个Boolean类型的值
    Boolean flag = this.singletonNames.get(beanName);
    // 如果是单例,则以listener的方式添加到容器中,注意这里也同时会添加到事件广播器中
    if (Boolean.TRUE.equals(flag)) {
      // singleton bean (top-level or inner): register on the fly
      this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
    // 如果不是单例,那就不能添加到容器中,listener必须是单例,不然一个事件就有被多个相同的listener多次监听的危险。。
    }else if (Boolean.FALSE.equals(flag)) {
      if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {
        // ....打日志代码省略....
      }
      this.singletonNames.remove(beanName);
    }
  }
  return bean;
}


初始化事件监听器的逻辑说完了,我们来看看发布事件的源码,看完这块,你会明白上面三种姿势的不同之处。

我需要先说明的是,spring的事件监听机制就是一个很简单的监听者模式,我不会在这里说这个设计模式,不知道的请自行百度。

这些代码位于AbstractApplicationContex中,注意因为ApplicationContex也继承了ApplicationEventPublisher,因此它也是个事件广播器。


@Override
// 调用的是最下面的方法
public void publishEvent(ApplicationEvent event) {
  publishEvent(event, null);
}
@Override
// 调用的是最下面的方法
public void publishEvent(Object event) {
  publishEvent(event, null);
}
// 上面的两个方法都是调用的这里的方法
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        // 要发布的事件必须不能为null
        // 可以学习学习这个处理null的方式
  Assert.notNull(event, "Event must not be null");
  // Decorate event as an ApplicationEvent if necessary
  ApplicationEvent applicationEvent;
  // 如果event是spring中的ApplicationEvent的实例,则把Object类型转成ApplicationEvent
  if (event instanceof ApplicationEvent) {
    applicationEvent = (ApplicationEvent) event;
  } else {
          // 这里的封装,主要是为了获取类型,因为PayloadApplicationEvent中的ResolvableType就是event的实际类型
    applicationEvent = new PayloadApplicationEvent<>(this, event);
    // 方法参数传过来的eventType是null时就用刚刚解析出来的类型
    if (eventType == null) {
      eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
    }
  }
  // this.earlyApplicationEvents 到这里一般都是空
  if (this.earlyApplicationEvents != null) {
    this.earlyApplicationEvents.add(applicationEvent);
  } else {
  // 使用我们刚刚创建的事件广播器来发布事件
    getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
  }
  // 如果父IOC容器也存在的话,同时调用父容器发布事件
  if (this.parent != null) {
    if (this.parent instanceof AbstractApplicationContext) {
      ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
    } else {
      this.parent.publishEvent(event);
    }
  }
}


我们来看一下事件广播器SimpleApplicationEventMulticaster 中 广播事件的 multicastEvent方法:(这里是个监听者模式)


@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    // 这时候传过来的eventType已经不是null了
  ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
  // 获取执行器,说白了就是个线程池
  Executor executor = getTaskExecutor();
  // 1、获取该eventType对应的listener,并循环调用listener监听该事件的方法
  for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
      // 如果有多线程线程池就并发的去执行,如果没有就一个一个来
    if (executor != null) {
      executor.execute(() -> invokeListener(listener, event));
    }
    else {
      invokeListener(listener, event);
    }
  }
}


上面代码块中的第1处调用的getApplicationListeners方法,会返回这个eventType对应的listener,这个方法中是怎样获取的呢?注意这里非常重要,我们一起来看看:


protected Collection<ApplicationListener<?>> getApplicationListeners(
      ApplicationEvent event, ResolvableType eventType) {
    // 获取事件源,这里一般都是ApplicationContext
  Object source = event.getSource();
  Class<?> sourceType = (source != null ? source.getClass() : null);
  // 构建拿缓存的key
  ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
  // 先从上面构造的key从缓存中拿
  ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
  // 如果缓存中有则返回
  if (retriever != null) {
    return retriever.getApplicationListeners();
  }
    // 能走到这里说明缓存中是没有的,所以需要我们
    // if里的条件总的来说是在检查event对应的class有没有被加载
  if (this.beanClassLoader == null ||
      (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
          (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
    // 加锁进行操作
    synchronized (this.retrievalMutex) {
        // 加上锁之后再获取一次,因为可能别的线程在上面的获取到加锁的这段操作已经设置好了缓存
        // 这个也是并发编程中需要注意的一个很重要的点
      retriever = this.retrieverCache.get(cacheKey);
      // 缓存中有则返回
      if (retriever != null) {
        return retriever.getApplicationListeners();
      }
      retriever = new ListenerRetriever(true);
      // 1、这里才是真正的去拿listener的逻辑
      Collection<ApplicationListener<?>> listeners =
          retrieveApplicationListeners(eventType, sourceType, retriever);
      // 把刚刚拿出来的listener放到缓存中
      this.retrieverCache.put(cacheKey, retriever);
      return listeners;
    }
  }
  else {
    // No ListenerRetriever caching -> no synchronization necessary
    return retrieveApplicationListeners(eventType, sourceType, null);
  }
}


我们来看看1处的这个retrieveApplicationListeners方法:


private Collection<ApplicationListener<?>> retrieveApplicationListeners(
      ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {
        // 代表结果的集合
  List<ApplicationListener<?>> allListeners = new ArrayList<>();
  Set<ApplicationListener<?>> listeners;
  Set<String> listenerBeans;
  synchronized (this.retrievalMutex) {
  // 添加默认的listener
    listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
    listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
  }
  for (ApplicationListener<?> listener : listeners) {
  // 这里的supportsEvent方法,是使用GenericApplicationListener或SmartApplicationListener中的supportsEventType来进行判断的,如果不是这两个的实例,就会根据泛型找(取决于listener是哪个的实例)
    if (supportsEvent(listener, eventType, sourceType)) {
      if (retriever != null) {
        retriever.applicationListeners.add(listener);
      }
      allListeners.add(listener);
    }
  }
  if (!listenerBeans.isEmpty()) {
    BeanFactory beanFactory = getBeanFactory();
    for (String listenerBeanName : listenerBeans) {
      try {
        Class<?> listenerType = beanFactory.getType(listenerBeanName);
        // 注意这里这个supportsEvent方法,是为了判断当前bean是不是一个支持监听事件的listener,和上面的判断方法不一样
        if (listenerType == null || supportsEvent(listenerType, eventType)) {
          ApplicationListener<?> listener =
              beanFactory.getBean(listenerBeanName, ApplicationListener.class);
          // 这个判断逻辑和上面一样
          if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
            if (retriever != null) {
              if (beanFactory.isSingleton(listenerBeanName)) {
                retriever.applicationListeners.add(listener);
              }
              else {
                retriever.applicationListenerBeans.add(listenerBeanName);
              }
            }
            allListeners.add(listener);
          }
        }
      }
      catch (NoSuchBeanDefinitionException ex) {
        // Singleton listener instance (without backing bean definition) disappeared -
        // probably in the middle of the destruction phase
      }
    }
  }
  // 排序并加入结果集合
  AnnotationAwareOrderComparator.sort(allListeners);
  if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
    retriever.applicationListeners.clear();
    retriever.applicationListeners.addAll(allListeners);
  }
  return allListeners;
}


我们可以看出来,其实三种姿势实现的listener也就是在第一次监听事件的时候性能不一样,因为泛型是需要反射去拿的,后边再使用的时候就一样了,因为已经有缓存了,其他的都是一样的。

目录
相关文章
|
4天前
|
XML Java 数据格式
Spring框架入门:IoC与DI
【5月更文挑战第15天】本文介绍了Spring框架的核心特性——IoC(控制反转)和DI(依赖注入)。IoC通过将对象的创建和依赖关系管理交给容器,实现解耦。DI作为IoC的实现方式,允许外部注入依赖对象。文章讨论了过度依赖容器、配置复杂度等常见问题,并提出通过合理划分配置、使用注解简化管理等解决策略。同时,提醒开发者注意过度依赖注入和循环依赖,建议适度使用构造器注入和避免循环引用。通过代码示例展示了注解实现DI和配置类的使用。掌握IoC和DI能提升应用的灵活性和可维护性,实践中的反思和优化至关重要。
18 4
|
4天前
|
Java 测试技术 开发者
Spring IoC容器通过依赖注入机制实现控制反转
【4月更文挑战第30天】Spring IoC容器通过依赖注入机制实现控制反转
22 0
|
4天前
|
XML Java 程序员
Spring特性之二——IOC控制反转
Spring特性之二——IOC控制反转
16 4
|
4天前
|
安全 Java 开发者
在Spring框架中,IoC和AOP是如何实现的?
【4月更文挑战第30天】在Spring框架中,IoC和AOP是如何实现的?
24 0
|
4天前
|
XML Java 程序员
什么是Spring的IoC容器?
【4月更文挑战第30天】什么是Spring的IoC容器?
20 0
|
Java 程序员 API
spring4.1.8扩展实战之八:Import注解
spring框架下做开发时,@Import是常见的注解,可以用来动态创建bean,今天我们先从源码分析原理,再用实战来验证Import的作用
305 0
spring4.1.8扩展实战之八:Import注解
|
4天前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
60 0
|
4天前
|
缓存 安全 Java
Spring Boot 面试题及答案整理,最新面试题
Spring Boot 面试题及答案整理,最新面试题
142 0
|
4天前
|
存储 JSON Java
SpringBoot集成AOP实现每个接口请求参数和返回参数并记录每个接口请求时间
SpringBoot集成AOP实现每个接口请求参数和返回参数并记录每个接口请求时间
49 2
|
4天前
|
前端开发 搜索推荐 Java
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革