• Spring的事件通知


    一、万里长征第一步(特性概述)

            说到Spring的事件通知也许大家都比较陌生,但说到设计模式中的观察者模式大家肯定比较熟悉,它是GoF23设计模式之一,它的概念我就不再此处赘述了引用百度百科原话:一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。出处:观察者模式_百度百科观察者模式(有时又被称为模型(Model)-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。https://baike.baidu.com/item/%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F/5881786?fr=aladdin。Spring的事件通知其实就是观察者模式的一种体现。观察者模式三大核心:观察者、被观察主题、订阅者。

    二、初出茅庐(观察者模式与Spring事件通知)

            观察者:在spring中IOC容器其实就是事件广播器可以理解为就是观察者【即ApplicationContext】。

            事件源:就是需要被观察的主题。

            监听器:其实就是订阅者。

            也许这么说还是比较迷糊,上图(图画的比较粗糙):

    三、 小试牛刀(快速掌握Spring事件监听器)

            啰嗦了这么多下面介绍下Spring事件监听器的使用,Sping内置的事件监听器是ApplicationListener,Spring源码片段:

    1. @FunctionalInterface
    2. public interface ApplicationListenerextends ApplicationEvent> extends EventListener {
    3. /**
    4. * Handle an application event.
    5. * @param event the event to respond to
    6. */
    7. void onApplicationEvent(E event);
    8. /**
    9. * Create a new {@code ApplicationListener} for the given payload consumer.
    10. * @param consumer the event payload consumer
    11. * @param the type of the event payload
    12. * @return a corresponding {@code ApplicationListener} instance
    13. * @since 5.3
    14. * @see PayloadApplicationEvent
    15. */
    16. static ApplicationListener> forPayload(Consumer consumer) {
    17. return event -> consumer.accept(event.getPayload());
    18. }
    19. }

    我们主要看方法一onApplicationEvent:字面意思处理要监听的事件;大概看了下里面有一个泛型,大概明白他的用意了,这个泛型代表我们想监听的事件。我们如果要实现事件监听实现ApplicationListener接口即可。

     Spring也支持注解形式的 EventListener  Spring源代码片段:

    1. /**
    2. * @author Stephane Nicoll
    3. * @author Sam Brannen
    4. * @since 4.2
    5. * @see EventListenerMethodProcessor
    6. * @see org.springframework.transaction.event.TransactionalEventListener
    7. */
    8. @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
    9. @Retention(RetentionPolicy.RUNTIME)
    10. @Documented
    11. public @interface EventListener {
    12. /**
    13. * Alias for {@link #classes}.
    14. */
    15. @AliasFor("classes")
    16. Class[] value() default {};
    17. /**
    18. * The event classes that this listener handles.
    19. *

      If this attribute is specified with a single value, the

    20. * annotated method may optionally accept a single parameter.
    21. * However, if this attribute is specified with multiple values,
    22. * the annotated method must not declare any parameters.
    23. */
    24. @AliasFor("value")
    25. Class[] classes() default {};
    26. /**
    27. * Spring Expression Language (SpEL) expression used for making the event
    28. * handling conditional.
    29. *

      The event will be handled if the expression evaluates to boolean

    30. * {@code true} or one of the following strings: {@code "true"}, {@code "on"},
    31. * {@code "yes"}, or {@code "1"}.
    32. *

      The default expression is {@code ""}, meaning the event is always handled.

    33. *

      The SpEL expression will be evaluated against a dedicated context that

    34. * provides the following metadata:
    35. *
      • *
      • {@code #root.event} or {@code event} for references to the
    36. * {@link ApplicationEvent}
  • *
  • {@code #root.args} or {@code args} for references to the method
  • * arguments array
  • *
  • Method arguments can be accessed by index. For example, the first
  • * argument can be accessed via {@code #root.args[0]}, {@code args[0]},
  • * {@code #a0}, or {@code #p0}.
  • *
  • Method arguments can be accessed by name (with a preceding hash tag)
  • * if parameter names are available in the compiled byte code.
  • *
  • */
  • String condition() default "";
  • /**
  • * An optional identifier for the listener, defaulting to the fully-qualified
  • * signature of the declaring method (e.g. "mypackage.MyClass.myMethod()").
  • * @since 5.3.5
  • * @see SmartApplicationListener#getListenerId()
  • * @see ApplicationEventMulticaster#removeApplicationListeners(Predicate)
  • */
  • String id() default "";
  • }
  • 从类注释中了解到,大概看了下它是在Spring4.2引入的注解(小于4.2版本的的小伙伴不用找啦),注解具体的处理逻辑在这里EventListenerMethodProcessor,注解可以用于方法上,参数可以是classs数组,可以写多个事件,等等;本节的主要目的是了解加使用这里就不再赘述了。

    3.1Spring事件使用(继承ApplicationListener)

    为了快速上手我们来用ContextRefreshedEvent(它代表Spring容器刷新完毕)来编写一个事件监听器:代码如下:

    1. @Component
    2. public class ContextRefreshedEventApplicationListener implements ApplicationListener{
    3. /**
    4. * @Title: onApplicationEvent
    5. * @Description: 这是一个事件监听
    6. * @param: event 要监听的事件
    7. * @throws
    8. */
    9. @Override
    10. public void onApplicationEvent(ContextRefreshedEvent event) {
    11. System.out.println("ContextRefreshedEventApplicationListener监听到ContextRefreshedEvent刷新事件!");
    12. }
    13. }

    写一个启动类:

    1. package com.example.demo.listener;
    2. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    3. public class ContextRefreshedEventListenerTest {
    4. public static void main(String[] args) {
    5. System.out.println("初始化容器");
    6. AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext("com.example.demo");
    7. System.out.println("初始化容器完成");
    8. ctx.close();
    9. System.out.println("IOC容器被关闭了");
    10. }
    11. }

    最终输出结果

    1. ContextRefreshedEventApplicationListener监听到ContextRefreshedEvent刷新事件!
    2. 初始化容器完成
    3. 18:32:54.135 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@12c8a2c0, started on Sat Aug 27 18:32:53 CST 2022
    4. IOC容器被关闭了

    3.2Spring事件使用(使用EventListener注解)

    编写一个注解形式的事件监听

    1. package com.example.demo.listener;
    2. import org.springframework.context.event.ContextRefreshedEvent;
    3. import org.springframework.context.event.EventListener;
    4. import org.springframework.stereotype.Component;
    5. @Component
    6. public class AnnotationContextRefreshedEventApplicationListener{
    7. /**
    8. * @Title: onApplicationEvent
    9. * @Description: 这是一个事件监听
    10. * @param: event 要监听的事件
    11. * @throws
    12. */
    13. @EventListener
    14. public void onApplicationEvent(ContextRefreshedEvent event) {
    15. System.out.println("ContextRefreshedEventApplicationListener注解形式监听到ContextRefreshedEvent刷新事件!");
    16. }
    17. }

    还是调用上方的ContextRefreshedEventListenerTest执行结果如下:

    1. ContextRefreshedEventApplicationListener监听到ContextRefreshedEvent刷新事件!
    2. ContextRefreshedEventApplicationListener注解形式监听到ContextRefreshedEvent刷新事件!
    3. 初始化容器完成
    4. 19:07:15.491 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@12c8a2c0, started on Sat Aug 27 19:07:14 CST 2022
    5. IOC容器被关闭了

    四、渐入佳境(实际场景使用)

            在很多网站用户注册完成后会有短信通知或者邮件通知用户等事件发布,如果用硬编码的形式大家肯定会在注册完成后写如下方法,这样一直堆积的后果就是(我和上帝的秘密+祖传代码勿动)。

    4.1注册成功事件

    是不是觉得很简单?对只是继承下就完了。

    1. package com.example.demo.listener;
    2. import org.springframework.context.ApplicationEvent;
    3. /**
    4. * @ClassName: RegisterEvent
    5. * @Description:注册成果的事件
    6. * @date: 2022年8月27日 下午7:17:48
    7. *
    8. */
    9. public class RegisterEvent extends ApplicationEvent {
    10. public RegisterEvent(Object source) {
    11. super(source);
    12. }
    13. }

    4.2监听事件发送短信(使用实现ApplicationListener)

    1. package com.example.demo.listener;
    2. import org.springframework.context.ApplicationListener;
    3. import org.springframework.stereotype.Component;
    4. @Component
    5. public class ShortMsgListener implements ApplicationListener {
    6. @Override
    7. public void onApplicationEvent(RegisterEvent event) {
    8. System.out.println("监听到用户注册成功,发送短信");
    9. }
    10. }

    4.3监听事件发送邮件(使用方法注解@EventListener)

    1. package com.example.demo.listener;
    2. import org.springframework.context.event.EventListener;
    3. import org.springframework.stereotype.Component;
    4. @Component
    5. public class EmailListener{
    6. @EventListener
    7. public void onApplicationEvent(RegisterEvent event) {
    8. System.out.println("监听到用户注册成功,发送邮件");
    9. }
    10. }

    发现这些做完了但是我怎样发布这个事件呢?

    4.4发布事件

    1. package com.example.demo.service;
    2. import org.springframework.context.ApplicationEventPublisher;
    3. import org.springframework.context.ApplicationEventPublisherAware;
    4. import org.springframework.stereotype.Service;
    5. import com.example.demo.listener.RegisterEvent;
    6. @Service
    7. public class RegisterService implements ApplicationEventPublisherAware {
    8. ApplicationEventPublisher publisher;
    9. public void register(String id) {
    10. //注册逻辑
    11. System.out.println("用户id:"+id+"注册成功!");
    12. //发布事件
    13. publisher.publishEvent(new RegisterEvent(id));
    14. }
    15. //这里采用了回调注入的方式,也可以用set方式直接传入
    16. @Override
    17. public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
    18. this.publisher=publisher;
    19. }
    20. }

    执行测试类

    1. package com.example.demo.listener;
    2. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    3. import com.example.demo.service.RegisterService;
    4. public class RegisterEventListenerTest {
    5. public static void main(String[] args) {
    6. AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext("com.example.demo");
    7. RegisterService registerService = ctx.getBean(RegisterService.class);
    8. registerService.register("00001");
    9. }
    10. }

    执行结果,发现执行成功了,但是并没有解耦啊,上面只是简单列举了下,可以使用aop切面在方法中编写发布事件哦。相比堆积方法是不是清晰了很多了呢?

    1. 用户id:00001注册成功!
    2. 监听到用户id:00001注册成功,发送短信
    3. 监听到用户id:00001注册成功,发送邮件

    五、蓦然回首(写在最后)

            代码不是炫技,以上列举的只是简单的场景实现,实际场景相信会有比上述场景较复杂的业务还有很多,引入特性和设计模式的初衷不是用来表现的很高级,最终的目的还是优雅的实现业务,保证项目的稳定运行才是王道。最后也希望大家可以看完这个文章对自己有真的帮助。

  • 相关阅读:
    每天五分钟机器学习:支持向量机和逻辑回归损失函数的区别和联系
    Prometheus 监控告警系统搭建(对接飞书告警)
    qml保姆级教程五:视图组件
    Cesium之home键开关及相机位置设置
    Python--练习:使用while循环求1..100的和
    Vue使用ts的枚举类型
    有属性的自定义注解,如何获取到post请求中RequestBody中对象的一个属性值?
    【Java进阶篇】第一章 面向对象
    JAVA创建线程案例
    【毕业设计】图像风格迁移算法实现 - 机器视觉 深度学习 卷积神经网络
  • 原文地址:https://blog.csdn.net/m0_37506254/article/details/126560316