IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring系列17:Spring事件发布详解 -> 正文阅读

[Java知识库]Spring系列17:Spring事件发布详解

本文内容

  1. 实现原理和标准事件
  2. 编程式实现自定义事件
  3. 基于注解的自定义事件
  4. 通用事件

实现原理和标准事件

Spring中的事件发布本质上是标准的观察者设计模式。ApplicationContext 中的事件处理是通过 ApplicationEvent 类和 ApplicationListener 接口提供的。如果将实现 ApplicationListener 接口的bean部署到上下文中,则每次将 ApplicationEvent 发布到 ApplicationContext 时,该bean都会得到通知。

public abstract class ApplicationEvent extends EventObject {

    // 事件源
    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }

    public final long getTimestamp() {
        return this.timestamp;
    }

}
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    // 处理事件
    void onApplicationEvent(E event);

}

下表描述了 Spring 提供的标准事件:

事件解释
ContextRefreshedEvent在初始化或刷新时发布ApplicationContext(例如,通过使用接口refresh()上的方法ConfigurableApplicationContext)。这里,“初始化”意味着所有 bean 都已加载,后处理器 bean 被检测并激活,单例被预先实例化,并且ApplicationContext对象已准备好使用。只要上下文没有关闭,就可以多次触发刷新,前提是所选择的ApplicationContext实际支持这种“热”刷新。例如,XmlWebApplicationContext支持热刷新,但 GenericApplicationContext不支持。
ContextStartedEvent使用接口上的方法 ApplicationContext启动时发布。在这里,“已启动”意味着所有 bean 都接收到一个明确的启动信号。通常,此信号用于在显式停止后重新启动 bean,但它也可用于启动尚未配置为自动启动的组件(例如,尚未在初始化时启动的组件)。start()``ConfigurableApplicationContext``Lifecycle
ContextStoppedEvent使用接口上的方法 ApplicationContext停止时发布。在这里,“停止”意味着所有 的 bean 都会收到一个明确的停止信号。可以通过 调用重新启动已停止的上下文。stop()``ConfigurableApplicationContext``Lifecycle``start()
ContextClosedEventApplicationContext使用接口close()上的方法ConfigurableApplicationContext或通过 JVM 关闭挂钩关闭时发布。在这里,“关闭”意味着所有的单例 bean 都将被销毁。一旦上下文关闭,它就到了生命的尽头,无法刷新或重新启动。
RequestHandledEvent一个特定于 Web 的事件,告诉所有 bean 一个 HTTP 请求已得到服务。此事件在请求完成后发布。此事件仅适用于使用 Spring 的 Web 应用程序DispatcherServlet
ServletRequestHandledEvent它的子类RequestHandledEvent添加了 Servlet 特定的上下文信息。

编程式实现自定义事件

  1. 定义事件继承 ApplicationEvent

    public class BlockedListEvent extends ApplicationEvent {
    
        private final String address;
        private final String content;
    
        public BlockedListEvent(Object source, String address, String content) {
            super(source);
            this.address = address;
            this.content = content;
        }
    
        public String getAddress() {
            return address;
        }
    
        public String getContent() {
            return content;
        }
    }
  2. 定义事件监听器实现 ApplicationListener

    @Component
    public class BlockedListListener implements ApplicationListener<BlockedListEvent> {
        // @Async
        @Override
        public void onApplicationEvent(BlockedListEvent event) {
            System.out.println("收到邮件禁用发送通知,地址: " + event.getAddress() + "  内容: " + event.getContent());
    
        }
    }
  3. 业务类注入 ApplicationEventPublisher 用于发布事件

    @Component
    public class EmailSendService {
        @Autowired
        private ApplicationEventPublisher publisher;
        // 邮件黑名单
        private List<String> blockedList;
    
    
   public void sendEmail(String address, String content) {
       if (blockedList.contains(address)) {
           System.out.println("不允许发邮件,同步发布通知事件");
           publisher.publishEvent(new BlockedListEvent(this, address, content));
           System.out.println("同步发布通知事件结束");
           return;
       }
       // 允许
       System.out.println("允许发邮件");
   }
   public void setBlockedList(List<String> blockedList) {
       this.blockedList = blockedList;
   }

}


4. 测试程序和结果

   ```java
   @Test
   public void test_synchronous() {
       AnnotationConfigApplicationContext context =
               new AnnotationConfigApplicationContext(AppConfig.class);
       EmailSendService sendService = context.getBean(EmailSendService.class);
       sendService.setBlockedList(Arrays.asList("123"));
       sendService.sendEmail("123","ooo");
       sendService.sendEmail("456","ooo");
   }
   不允许发邮件,同步发布通知事件
   收到邮件禁用发送通知,地址: 123  内容: ooo
   同步发布通知事件结束
   允许发邮件

从结果分析,publishEvent()发布事件的方式是阻塞同步的,期间会等待所有的事件监听器完成处理。

基于注解的自定义事件

从Spring 4.2开始,事件基础结构得到了显著的改进,并提供了一个基于注释的模型,以及发布任何任意事件(也就是说,一个不一定是从ApplicationEvent扩展而来的对象)的能力。当这样一个对象被发布时会被包装在一个事件中。

可以使用 @EventListener 注释在托管 bean 的任何方法上注册事件监听器。

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EventListener {

   /**
    * Alias for {@link #classes}.
    */
   @AliasFor("classes")
   Class<?>[] value() default {};

   // 监听哪些事件
   // 如果使用单个值指定该属性,则带注释的方法可以选择接受单个参数。
   // 但是,如果使用多个值指定此属性,则带注释的方法不能声明任何参数
   @AliasFor("value")
   Class<?>[] classes() default {};

   // SpEL表达式条件
   String condition() default "";

}

监听单个事件

@Component
public class AnnotatedBlockedListListener{

    @EventListener(value = BlockedListEvent.class)
    public void onApplicationEvent(BlockedListEvent event) {
        System.out.println("AnnotatedBlockedListListener收到邮件禁用发送通知,地址: " + event.getAddress() + "  内容: " + event.getContent());
    }
}

监听多个事件

如果使用多个值指定监听多个事件,则带注释的方法不能声明任何参数。

@Component
public class MultiEventListener {
    @EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
    public void handleContextStart() {
        System.out.println("收到 ContextStartedEvent 或 ContextRefreshedEvent");
    }
}

监听方法结果作为新事件发布

监听方法处理后返回值如果是 ApplicationEvent 的实现类,Spring会将结果当做新事件来发布。支持单个或是集合形式,集合形式的返回值会拆分成单个依次发送。直接看案例。

  1. 定义一个结果事件 ResultEvent 继承 ApplicationEvent

    public class ResultEvent extends ApplicationEvent {
    
        private final String result;
    
        public ResultEvent(Object source,  String result) {
            super(source);
            this.result = result;
        }
    
    
   public String getResult() {
       return result;
   }

}


2. 定义 BlockedListEvent 的监听器返回值是单个 ApplicationEvent 

   ```java
   @Component
   public class AnnotatedBlockedListListener2 {

       @EventListener(value = BlockedListEvent.class)
       public ResultEvent onApplicationEvent(BlockedListEvent event) {
           System.out.println("邮件处理完毕,返回单个通知ResultEvent事件");
           return new ResultEvent(event.getSource(), "success");
       }
   }
  1. 定义 BlockedListEvent 的监听器返回值集合形式的 ApplicationEvent

    @Component
    public class AnnotatedBlockedListListener3 {
    
        @EventListener(value = BlockedListEvent.class)
        public List<ResultEvent> onApplicationEvent(BlockedListEvent event) {
            System.out.println("邮件处理完毕,返回多个通知ResultEvent事件");
            List<ResultEvent > resultEvents = new ArrayList<>(5);
            for (int i = 0; i < 5; i++) {
                resultEvents.add(new ResultEvent(event.getSource(), "success:" + i));
            }
            return resultEvents;
        }
    }
    
  2. 定义 ResultEvent 的监听器

    @Component
    public class ResultEventListener {
    
        @EventListener(ResultEvent.class)
        public void onApplicationEvent(ResultEvent event) {
            System.out.println("收到ResultEvent,结果: " + event.getResult());
        }
    }
  3. 运行结果

    AnnotatedBlockedListListener收到邮件禁用发送通知,地址: 123  内容: ooo
    邮件处理完毕,返回单个通知ResultEvent事件
    收到ResultEvent,结果: success
    邮件处理完毕,返回多个通知ResultEvent事件
    收到ResultEvent,结果: success:0
    收到ResultEvent,结果: success:1
    收到ResultEvent,结果: success:2
    收到ResultEvent,结果: success:3
    收到ResultEvent,结果: success:4
    收到邮件禁用发送通知,地址: 123  内容: ooo
    同步发布通知事件结束
    允许发邮件

    从结果看,AnnotatedBlockedListListener2 和 AnnotatedBlockedListListener3 的监听方法结果被当做新的事件发布了,并被 ResultEventListener 监听到后处理。

使用SpEL表达式

@EventListener 中的 condition 可以指定SpEL表达式条件。如当事件内容是“ooo”时才监听器方法才处理该事件。实现如下

@Component
public class ConditionalBlockedListListener{
    // 当事件内容是ooo 才处理
    @EventListener(value = BlockedListEvent.class, condition = "#event.content == 'ooo'")
    public void onApplicationEvent(BlockedListEvent event) {
        System.out.println("当事件内容是ooo 才处理");
    }
}

下表列出了可用于上下文的项目,可以用于condition 中:

名称位置描述例子
事件root objectApplicationEvent对象#root.event or event
参数数组root object方法的参数数组#root.args 或者args; args[0] 按照索引取
参数名称context任何方法参数的名称。如果,由于某些原因,名称不可用(例如,因为编译后的字节码中没有调试信息),则使用#a<#arg>语法也可以使用单个参数,其中<#arg>表示参数索引(从0开始)。#blEvent 或是#a0

异步事件

默认情况下,上面的案例的事件发布和事件处理都是同步阻塞完成的。可以使用 @Async 来完成异步处理事件。

@Component
@EnableAsync // 启用Spring异步处理
public class AnnotatedBlockedListListener4 {

    @Async // 标记方法为异步
    @EventListener(value = BlockedListEvent.class)
    public void onApplicationEvent(BlockedListEvent event) {
        System.out.println("异步处理收到邮件禁用发送通知,地址: " + event.getAddress() + "  内容: " + event.getContent());
    }
}

使用异步事件时请注意以下限制:

  • 如果异步事件侦听器抛出异常,它不会传播给调用者。需要使用 AsyncUncaughtExceptionHandler 来处理异常,后面专门详解。
  • 异步事件侦听器方法不能通过返回值来发布后续事件。如果需要发布另一个事件作为处理的结果,请注入一个 ApplicationEventPublisher 以手动发布该事件。

使用@Order定义同一事件监听器

同一个事件多个监听器时候,@Order 值较小的先收到。

    @Order(3)
    @EventListener(value = BlockedListEvent.class)
    public void onApplicationEvent(BlockedListEvent event) {
        System.out.println("AnnotatedBlockedListListener收到邮件禁用发送通知,地址: " + event.getAddress() + "  内容: " + event.getContent());
    }

   @Order(2)
    @EventListener(value = BlockedListEvent.class)
    public ResultEvent onApplicationEvent(BlockedListEvent event) {
        System.out.println("邮件处理完毕,返回单个通知ResultEvent事件");
        return new ResultEvent(event.getSource(), "success");
    }

    @Order(1)
    @EventListener(value = BlockedListEvent.class)
    public List<ResultEvent> onApplicationEvent(BlockedListEvent event) {
        System.out.println("邮件处理完毕,返回多个通知ResultEvent事件");
        List<ResultEvent > resultEvents = new ArrayList<>(5);
        for (int i = 0; i < 5; i++) {
            resultEvents.add(new ResultEvent(event.getSource(), "success:" + i));
        }
        return resultEvents;
    }

观察下输出

邮件处理完毕,返回多个通知ResultEvent事件
邮件处理完毕,返回单个通知ResultEvent事件
AnnotatedBlockedListListener收到邮件禁用发送通知,地址: 123  内容: ooo

泛型化事件结构

使用泛型事件

可以使用泛型进一步定义事件的结构。直接看案例。

实体创建事件使用 EntityCreatedEvent<T>,其中 T 是创建的实际实体的类型

public class EntityCreatedEvent<T> extends ApplicationEvent {

    public EntityCreatedEvent(T source) {
        super(source);
    }
}

假设有2个是实体 Order 和 Person, 则它们的创建事件监听方法如下:

    @EventListener(PersonEntityCreatedEvent.class)
    public void onPersonCreated(EntityCreatedEvent<Person> event) {
        System.out.println("onPersonCreated");
    }

    @EventListener(OrderEntityCreatedEvent.class)
    public void onOrderCreated(EntityCreatedEvent<Order> event) {
        System.out.println("onOrderCreated");
    }

由于Java中的类型擦除,这仅在触发的事件解析事件侦听器过滤的通用参数时才有效。对应的定义一下2个类:

public class OrderEntityCreatedEvent extends  EntityCreatedEvent<Order>{
    public OrderEntityCreatedEvent(Order source) {
        super(source);
    }
}

public class PersonEntityCreatedEvent extends EntityCreatedEvent<Person>{
    public PersonEntityCreatedEvent(Person source) {
        super(source);
    }
}

测试程序和测试结果

public class EntityCreatedEventTest {

    @Test
    public void test() {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(AppConfig.class);
        context.publishEvent(new OrderEntityCreatedEvent(new Order()));
        context.publishEvent(new PersonEntityCreatedEvent(new Person()));
        context.close();
    }
}
onOrderCreated
onPersonCreated

使用ResolvableTypeProvider优化

前面的案例,每个实体类A都需要定义一个对应的具体事件类EntityCreatedEvent<A>,这样模板化的机械化的工作某种程度上非常乏味。在这种情况下,可以实现 ResolvableTypeProvider 来引导框架超出运行时环境提供的范围,就是运行时指定类型。

泛型事件实现 ResolvableTypeProvider

/**
 * 泛型的实体创建事件
 * @author zfd
 * @version v1.0
 * @date 2022/1/26 15:09
 * @关于我 请关注公众号 螃蟹的Java笔记 获取更多技术系列
 */
public class ResolvableEntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {

    public ResolvableEntityCreatedEvent(T source) {
        super(source);
    }

    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
    }
}

省去了实体对应具体事件的定义,直接上监听方法

@Component
public class ResolvableEntityCreatedEventListener {

    @EventListener
    public void onPersonCreated(ResolvableEntityCreatedEvent<Person> event) {
        System.out.println("onPersonCreated Resolvable");
    }

    @EventListener
    public void onOrderCreated(ResolvableEntityCreatedEvent<Order> event) {
        System.out.println("onOrderCreated Resolvable");
    }
}

观察下结果

@Test
public void test_resolvable() {
    AnnotationConfigApplicationContext context =
            new AnnotationConfigApplicationContext(AppConfig.class);
    context.publishEvent(new ResolvableEntityCreatedEvent<>(new Order()));
    context.publishEvent(new ResolvableEntityCreatedEvent(new Person()));
    context.close();
}
// 结果符合预期
onOrderCreated Resolvable
onPersonCreated Resolvable

事件发布任意对象

原理分析

从Spring 4.2开始,事件基础结构得到了显著的改进,并提供了一个基于注释的模型,以及发布任何任意事件(也就是说,一个不一定是从ApplicationEvent扩展而来的对象)的能力。当这样一个对象被发布时会被包装在一个事件中。

调用 ApplicationEventPublisher#publishEvent(event) 发布事件时,如果event本身继承了 ApplicationEvent正常发送,如果 event 没有继承 ApplicationEvent 则将被包装成 PayloadApplicationEvent 来发送。

看下源码实现 AbstractApplicationContext#publishEvent(Object event, @Nullable ResolvableType eventType)

    protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        Assert.notNull(event, "Event must not be null");

        // Decorate event as an ApplicationEvent if necessary
        ApplicationEvent applicationEvent;
        // 1 本身实现了ApplicationEvent
        if (event instanceof ApplicationEvent) {
            applicationEvent = (ApplicationEvent) event;
        }
        else {
            // 本身没有实现 ApplicationEvent 包装成  PayloadApplicationEvent
            applicationEvent = new PayloadApplicationEvent<>(this, event);
            if (eventType == null) {
                eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
            }
        }

        // Multicast right now if possible - or lazily once the multicaster is initialized
        if (this.earlyApplicationEvents != null) {
            this.earlyApplicationEvents.add(applicationEvent);
        }
        else {
            // 3 广播发送
            getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
        }

那么实现任意对象发布事件的关键是 PayloadApplicationEvent ,来看下其定义:

  • 实现了 ApplicationEvent 可作为事件发布
  • 泛型类,可包装任意事件内容
  • ResolvableTypeProvider 上面提到的类型解析接口,实现任意发布的关键!
public class PayloadApplicationEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {}

使用案例

  1. 定义任意的发布对象,不继承 ApplicationEvent

    public class ArbitraryObject {
        private String name;
    
        public ArbitraryObject(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "ArbitraryObject{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
  2. 定义监听器监听 PayloadApplicationEvent 事件,指定泛型

    @Component
    public class PayloadApplicationEventListener {
    
        @EventListener
        public void onPayloadApplicationEvent(PayloadApplicationEvent<ArbitraryObject> event) {
    
            System.out.println("收到PayloadApplicationEvent: " + event.getPayload());
        }
    
    }
  3. 发送事件观察结果

    @Test
    public void test_arbitrary() {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(AppConfig.class);
        context.publishEvent(new ArbitraryObject("随意对象非ApplicationEvent事件"));
        context.close();
    }

    监听到的事件内容输出如下

    收到PayloadApplicationEvent: ArbitraryObject{name='随意对象非ApplicationEvent事件'}

总结

本文详解分析了Spring的事件发布原理,自定义事件可通过编程式或是注解方式。也介绍了泛型事件结构以及如何发送任意的事件对象。案例比较多,建议实践一下。

本篇源码地址: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo19 知识分享,转载请注明出处。学无先后,达者为先!

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-02-19 01:00:55  更:2022-02-19 01:02:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 12:06:42-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码