Spring监听器用法与原理详解(带ApplicationListener模型图)

这篇具有很好参考价值的文章主要介绍了Spring监听器用法与原理详解(带ApplicationListener模型图)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


前言

相信大家都或多或少知道Spring中的监听器,有些人还能说出它采用了观察者模式,但其实它还用到了适配器模式工厂模式等。当然,仍有不少人是完全不了解Spring的监听及其机制的,本次我们就来深入学习一下Spring监听器


一、Spring监听器是什么

Spring监听器是一种特殊的类,它们能帮助开发者监听 web 中特定的事件,比如 ServletContext, HttpSession, ServletRequest 的创建和销毁;变量的创建、销毁等等。

当Web容器启动后,Spring的监听器会启动监听,监听是否创建ServletContext的对象,如果发生了创建ServletContext对象这个事件 (当web容器启动后一定会生成一个ServletContext对象,所以监听事件一定会发生),ContextLoaderListener类会实例化并且执行初始化方法,将spring的配置文件中配置的bean注册到Spring容器中

二、观察者模式

1. 模型介绍

观察者模式(Observer Pattern)是一种行为设计模式,它用于在对象之间建立一对多的依赖关系。在该模式中,当一个对象的状态发生变化时,它会自动通知其依赖对象(称为观察者),使它们能够自动更新

观察者模式的工作原理如下:

  1. 主题对象维护一个观察者列表,并提供方法用于添加和删除观察者
  2. 当主题的状态发生变化时,它会遍历观察者列表,并调用每个观察者的通知方法
  3. 观察者接收到通知后,根据通知进行相应的更新操作。

spring监听器,Spring全家桶解析,spring,java,后端,面试

2. 观察者模式Demo

一个观察者模式demo包括以下部分:

  • 观察者实体
  • 主题实体

所以我们先写个观察者接口:

import java.util.ArrayList;
import java.util.List;

interface Observer {
    void update();
}

再构建两个观察者实现类

// 具体观察者A
class ConcreteObserverA implements Observer {
    @Override
    public void update() {
        System.out.println("ConcreteObserverA收到更新通知");
    }
}

// 具体观察者B
class ConcreteObserverB implements Observer {
    @Override
    public void update() {
        System.out.println("ConcreteObserverB收到更新通知");
    }
}

然后定义主题接口

interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

构建一个具体主题

// 具体主题
class ConcreteSubject implements Subject {
    private List<Observer> observers = new ArrayList<>();

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

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

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }

    public void doSomething() {
        System.out.println("主题执行某些操作...");
        notifyObservers(); // 执行操作后通知观察者
    }
}

测试代码

public class ObserverPatternDemo {
    public static void main(String[] args) {
        // 创建主题和观察者
        ConcreteSubject subject = new ConcreteSubject();
        Observer observerA = new ConcreteObserverA();
        Observer observerB = new ConcreteObserverB();

        // 注册观察者
        subject.registerObserver(observerA);
        subject.registerObserver(observerB);

        // 执行主题的操作,触发通知
        subject.doSomething();
    }
}

最后可以看到结果

主题执行某些操作…
ConcreteObserverA收到更新通知
ConcreteObserverB收到更新通知


三、Spring监听器应用

1. 新建监听器

1.1 实现ApplicationListener接口

在详细介绍Spring监听器前,我们以一个简单的demo来说明:

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

@Component
public class MyContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("应用程序上下文已刷新");
        // 在这里可以执行一些初始化操作
    }
}

  1. 我们创建了一个名为MyContextRefreshedListener* 的监听器,它实现了ApplicationListener接口,这意味着这个监听器监听的事件类型是ContextRefreshedEvent,并重写了 onApplicationEvent 方法。该方法在应用程序上下文被刷新时触发。

  2. 使用 @Component 注解将该监听器声明为一个Spring管理的组件,这样Spring会自动将其纳入到应用程序上下文中,并在适当的时候触发监听

1.2 使用@EventListener注解

除了手动写个类外,我们也可以找个现成的类,该类不需要继承或实现任何其他类,然后在它的某个方法上加上 @EventListener 注解,如下:

@Component
public class MyListener {

	@EventListener(ContextRefreshedEvent.class)
	public void methodA(ContextRefreshedEvent event) {
 		System.out.println("应用程序上下文已刷新");
        // 在这里可以执行一些初始化操作
	}
}

  1. 在一个现有的类的某个方法上,加上@EventListener(ContextRefreshedEvent.class),Spring会在加载这个类时,为其创建一个监听器,这个监听器监听的事件类型是ContextRefreshedEvent当此事件发生时,将触发执行该方法methodA

  2. 使用 @Component 注解将该类声明为一个Spring管理的组件,这样Spring会自动将其纳入到应用程序上下文中,并在适当的时候触发监听

  3. 我们可以在这个类中写上多个方法,每个方法通过注解监听着不同的事件类型,这样我们就仅需使用一个类,却构建了多个监听器

上述两种方法的效果是一样的。那么最后,我们就完成了Spring中一个内置监听器的简单示例:当启动一个基于Spring的应用程序时,当应用程序上下文被刷新时,ContextRefreshedEvent事件将被触发,然后MyContextRefreshedListener监听器的onApplicationEvent方法将被调用。

2. 内置的事件类型

我们在demo中使用了一个 ContextRefreshedEvent 的事件,这个事件是Spring内置的事件,除了该事件,Spring还内置了一些其他的事件类型,分别在以下情况下触发:

  1. ContextRefreshedEvent
    当应用程序上下文被刷新时触发。这个事件在ApplicationContext初始化或刷新时被发布,适用于执行初始化操作和启动后的后续处理。例如,初始化缓存、预加载数据等。

  2. ContextStartedEvent
    当应用程序上下文启动时触发。这个事件在调用ApplicationContext的start()方法时被发布,适用于在应用程序启动时执行特定的操作。例如,启动定时任务、启动异步消息处理等。

  3. ContextStoppedEvent
    当应用程序上下文停止时触发。这个事件在调用ApplicationContext的stop()方法时被发布,适用于在应用程序停止时执行清理操作。例如,停止定时任务、关闭数据库连接等。

  4. ContextClosedEvent
    当应用程序上下文关闭时触发。这个事件在调用ApplicationContext的close()方法时被发布,适用于在应用程序关闭前执行最后的清理工作。例如,释放资源、保存日志等。

  5. RequestHandledEvent
    在Web应用程序中,当一个HTTP请求处理完成后触发。这个事件在Spring的DispatcherServlet处理完请求后被发布,适用于记录请求日志、处理统计数据等。

  6. ApplicationEvent
    这是一个抽象的基类,可以用于定义自定义的应用程序事件。你可以创建自定义事件类,继承自ApplicationEvent,并定义适合你的应用场景的事件类型

3. 自定义事件与监听器Demo

在学习完上面的内容后,我们现在可以手动写个Spring的事件,以及对应的监听器的demo了

3.1 构建两个自定义事件

建立继承自ApplicationEvent的自定义事件类

// 事件A
public class CustomEventA extends ApplicationEvent {
    private String message;

    public CustomEventA(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
// 事件B
public class CustomEventB extends ApplicationEvent {
    private String message;

    public CustomEventB(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

3.2 构建监听

我们选用@EventListener注解来实现监听

@Component
public class MyListener {

	@EventListener(CustomEventA.class)
	public void methodA(CustomEventA event) {
 		System.out.println("========我监听到事件A了:" + event.getMessage());
        // 在这里可以执行一些其他操作
	}

	@EventListener(CustomEventB.class)
	public void methodB(CustomEventB event) {
 		System.out.println("========我监听到事件B了:" + event.getMessage());
        // 在这里可以执行一些其他操作
	}
}

3.3 发布事件

@Component
public class CustomEventPublisher implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent> {
    
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    
    // 利用容器刷新好的消息为触发,发布两条自定义的事件
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        CustomEventA eventA = new CustomEventA(applicationContext , "我是AAAA");
        CustomEventB eventB = new CustomEventB(applicationContext , "我是BBBB");
        applicationContext.publishEvent(eventA);
        applicationContext.publishEvent(eventB);
    }
}

spring监听器,Spring全家桶解析,spring,java,后端,面试

四、Spring监听器原理

1. Spring监听器模型

前面我们讲了观察者模式的模型,它的模型主要是由 观察者实体主题实体 构成,而Spring的监听器模式则结合了Spring本身的特征,也就是容器化。在Spring中,监听器实体全部放在ApplicationContext中,事件也是通过ApplicationContext来进行发布,具体模型如下:
spring监听器,Spring全家桶解析,spring,java,后端,面试
我们不难看到,虽说是通过ApplicationContext发布的事件,但其并不是自己进行事件的发布,而是引入了一个处理器—— EventMulticaster,直译就是事件多播器,它负责在大量的监听器中,针对每一个要广播的事件,找到事件对应的监听器,然后调用该监听器的响应方法,图中就是调用了监听器1、3、6。

PS: 只有在某类事件第一次广播时,EventMulticaster才会去做遍历所有监听器的事,当它针对该类事件广播过一次后,就会把对应监听器保存起来了,最后会形成一个缓存Map,下一次就能直接找到这些监听器

final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);

2. @EventListener原理

直接实现监听器接口,然后注册成Bean,这种方式比较好理解,因为我们自己写的实现类就是监听器。但是使用 @EventListener 时,监听器又是怎么产生呢?我们以上面的【自定义事件与监听器Demo】为例,来看一下关键代码。

我们知道,在生成流程中,会对每个Bean都使用PostProcessor来进行加工,而其中就有这么一个类EventListenerMethodProcessor这个类会在Bean实例化后进行一系列操作
(PS: 首先,不了解Bean生成过程的同学,可以先去看看另一篇文章:SpringBean生成流程详解 )

private void processBean(final String beanName, final Class<?> targetType) {
          ......省略前面代码
	// Non-empty set of methods
	ConfigurableApplicationContext context = this.applicationContext;
	Assert.state(context != null, "No ApplicationContext set");
	List<EventListenerFactory> factories = this.eventListenerFactories;
	Assert.state(factories != null, "EventListenerFactory List not initialized");
	// 遍历该Bean中有EventListener注解的方法,此例中即methodA、methodB
	for (Method method : annotatedMethods.keySet()) {
	    // 遍历监听器工厂,这类工厂是专门用来创建监听器的,此处起作用的是默认工厂DefaultEventListenerFactory
		for (EventListenerFactory factory : factories) {
		    // DefaultEventListenerFactory是永远返回true的
			if (factory.supportsMethod(method)) {
				Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
				// 利用该Bean名、Bean类型、方法来创建监听器
				ApplicationListener<?> applicationListener =
						factory.createApplicationListener(beanName, targetType, methodToUse);
				if (applicationListener instanceof ApplicationListenerMethodAdapter) {
					((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
				}
				// 把监听器存入容器
				context.addApplicationListener(applicationListener);
				break;
			}
		}
	}
	......省略后面代码
}

如上,遍历Bean每个带@EventListener注解的方法,然后利用DefaultEventListenerFactory开始创建监听器,实际上这些监听器类型都是一个适配器类——ApplicationListenerMethodAdapter,只是因为这些监听器具体的参数不一样,所以可以监听不同的事件,做不同的响应

public class DefaultEventListenerFactory implements EventListenerFactory, Ordered {
	// 省略其余代码
	@Override
	public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
		// 可以看到,每次都是返回一个新对象,所以我们在MyListener里的两个方法都加了@EventListener,其实就会返回两个监听器
		return new ApplicationListenerMethodAdapter(beanName, type, method);
	}

}

最后效果如图,成功的创建了两个监听器
spring监听器,Spring全家桶解析,spring,java,后端,面试

3. @EventListener错误尝试

知道了@EventListener的原理,我们其实可以做一些猜测,如下:
methodA是正常的用法;
methodB方法的修饰符是private;
methodC则是监听的ContextRefreshedEvent,但下面方法的入参却是ContextClosedEvent;
spring监听器,Spring全家桶解析,spring,java,后端,面试
后两者都有问题:
可以看到,编译器直接黄底提示了methodB的@EventListener注解,其实从前面我们已经猜到,因为最后我们的调用是由监听器ApplicationListenerMethodAdapter对象直接调用的方法ABC,所以方法必须可被其他对象调用,即public
spring监听器,Spring全家桶解析,spring,java,后端,面试
而后者会在执行广播响应事件时报参数非法异常也是意料之中。

五、同步与异步

通过模型,我们不难看出,事件的发布其实由业务线程来发起,那么哪些监听器的触发呢,是仍由业务线程一个个同步地去通知监听器,还是有专门的线程接手,收到事件后,再转手通知监听器们?

1. 默认同步通知

其实,因为spring默认的多播器没有设置执行器,所以默认采用的是第一种情况,即哪个线程发起的事件,则由哪个线程去通知监听器们,关键代码如下所示

	// SimpleApplicationEventMulticaster.java
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				// 默认走此分支,由发出事件的线程来执行
				invokeListener(listener, event);
			}
		}
	}
	
	@Nullable
	protected Executor getTaskExecutor() {
		return this.taskExecutor;
	}

我们可以看到,对于每个监听器的调用是同步还是异步,取决于多播器内部是否含有一个执行器,如果有则交给执行器去执行,如果没有,只能让来源线程一一去通知了。

2. 异步通知设置

两种方式,一种是在多播器创建时内置一个线程池,使多播器能够调用自身的线程池去执行事件传播。另一种是不再多播器上做文章,而是在每个监视器的响应方法上标注异步@Async,毫无疑问,第一种才是正道,我们来看看如何做到。其实有多种方法,我们这里说两种。

第一种,直接自定义一个多播器,然后顶替掉Spring自动创建的多播器

@Configuration
public class EventConfig {
    @Bean("taskExecutor")
    public Executor getExecutor() {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10,
                15,
                60,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue(2000));
        return executor;
    }
    
    // 这其实就是spring-boot自动配置的雏形,所谓的自动配置其实就是通过各种配置类,顶替原有的简单配置
    
    @Bean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
    public ApplicationEventMulticaster initEventMulticaster(@Qualifier("taskExecutor") Executor taskExecutor) {
        SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster();
        simpleApplicationEventMulticaster.setTaskExecutor(taskExecutor);
        return simpleApplicationEventMulticaster;
    }
}

第二种,为现成的多播器设置设置一个线程池

@Component
public class WindowsCheck implements ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SimpleApplicationEventMulticaster caster = (SimpleApplicationEventMulticaster)applicationContext
                .getBean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10,
                15,
                60,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue(2000));
        caster.setTaskExecutor(executor);
    }
}

当然,这里推荐第一种,第二种方法会在spring启动初期的一些事件上,仍采用同步的方式。直至被注入一个线程池后,其才能使用线程池来响应事件。而第一种方法则是官方暴露的位置,让我们去构建自己的多播器。

六、总结

我们可以看到,一个Spring监听器内容其实并不少,而且用到了观察者模式工厂模式(EventListenerFactory)适配器模式(ApplicationListenerMethodAdapter)。除了这些设计模式,还需要对Spring的基础有些了解,比如Bean生成过程(PostProcessor),不过,相信你看完了本篇,已经对Spring监听器用法及原理已经有了相当的理解了,只需要在后续开发实践中,注意相互印证即可。文章来源地址https://www.toymoban.com/news/detail-734579.html

到了这里,关于Spring监听器用法与原理详解(带ApplicationListener模型图)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Kafka 监听器详解

    Kafka Assistant 是一款 Kafka GUI 管理工具——管理Broker,Topic,Group、查看消费详情、监控服务器状态、支持多种消息格式。 你需要将 advertised.listeners (如果你使用Docker镜像,则为 KAFKA_ADVERTISED_LISTENERS )设置为外部地址(host/IP),以便客户端可以正确地连接到它。否则,他们会尝试

    2024年02月06日
    浏览(39)
  • JavaEE中的监听器的作用和工作原理

    在JavaEE(Java Platform, Enterprise Edition)中,监听器(Listener)是一种重要的组件,用于监听和响应Web应用程序中的事件。监听器的作用是在特定的事件发生时执行一些自定义的逻辑。常见的监听器包括ServletContext监听器、HttpSession监听器和ServletRequest监听器。以下是监听器的作用

    2024年01月22日
    浏览(41)
  • Spring中最简单的过滤器和监听器

            Filter也称之为过滤器,它是Servlet技术中最实用的技术,Web开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息

    2024年02月14日
    浏览(39)
  • Spring高手之路15——掌握Spring事件监听器的内部逻辑与实现

    在阅读本文之前需要你已经对事件监听器有了简单的了解,或去阅读前面的文章《 Spring高手之路7——事件机制与监听器的全面探索 》   在 Spring 中, ApplicationContext 可以形成一个层次结构,通常由主容器和多个子容器组成。一个常见的疑问是:当一个事件在其中一个容器

    2024年02月06日
    浏览(44)
  • Spring高手之路7——事件机制与监听器的全面探索

      观察者模式是一种行为设计模式,它定义了对象之间的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。在这个模式中,改变状态的对象被称为主题,依赖的对象被称为观察者。 举个实际的例子: 事件源(Event Source) :可以视

    2024年02月11日
    浏览(41)
  • Spring项目配置文件中RabbitMQ监听器各个参数的作用

    spring.rabbitmq.listener.simple.concurrency :设置监听器容器的并发消费者数量,默认为1,即单线程消费。 spring.rabbitmq.listener.simple.max-concurrency :设置监听器容器的最大并发消费者数量。 spring.rabbitmq.listener.simple.prefetch :设置每个消费者从RabbitMQ服务器获取的消息数量,即每次从队列

    2024年02月16日
    浏览(37)
  • Spring Boot实战:拦截器和监听器的应用指南

    当使用Spring Boot时,我们可以通过拦截器(Interceptor)和监听器(Listener)来实现对请求和响应的处理。拦截器和监听器提供了一种可插拔的机制,用于在请求处理过程中进行自定义操作,例如记录日志、身份验证、权限检查等。下面通过提供一个示例,展示如何使用拦截器和

    2024年02月09日
    浏览(45)
  • 【Vue2.x源码系列07】监听器watch原理

    上一章 Vue2计算属性原理,我们介绍了计算属性是如何实现的?计算属性缓存原理?以及洋葱模型是如何应用的? 本章目标 监听器是如何实现的? 监听器选项 - immediate、deep 内部实现 在 Vue初始化实例的过程中,如果用户 options选项中存在侦听器,则初始化侦听器 watch 类型:

    2023年04月20日
    浏览(48)
  • Zookeeper快速入门(Zookeeper概述、安装、集群安装、选举机制、命令行操作、节点类型、监听器原理)

    1.1 概述 Zookeeper是一个开源的分布式的,为分布式框架提供协调服务的Apache项目。 1、Zookeeper工作机制 Zookeeper从设置模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责储存和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生

    2024年03月28日
    浏览(54)
  • 【三十天精通Vue 3】第六天 Vue 3 计算属性和监听器详解

    ✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: 三十天精通 Vue 3

    2024年02月16日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包