小编给大家分享一下Spring事件发布与监听机制的用法示例,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!
ApplicationContext 事件机制采用观察者设计模式来实现,通过 ApplicationEvent
事件类和 ApplicationListener
监听器接口,可以实现 ApplicationContext
事件发布与处理。
每当 ApplicationContext 发布 ApplicationEvent 时,如果 Spring 容器中有 ApplicationListener bean,则监听器会被触发执行相应的处理。当然,ApplicationEvent 事件的发布需要显示触发,要么 Spring 显示触发,要么我们显示触发。
定义应用监听器需要实现的接口。此接口继承了 JDK 标准的事件监听器接口 EventListener
,EventListener 接口是一个空的标记接口,推荐所有事件监听器必须要继承它。
package org.springframework.context; import java.util.EventListener; @FunctionalInterface public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { /** * 处理应用事件 */ void onApplicationEvent(E event); }
package java.util; public interface EventListener { }
ApplicationListener 是个泛型接口,我们自定义此接口的实现类时,如果指定了泛型的具体事件类,那么只会监听此事件。如果不指定具体的泛型,则会监听 ApplicationEvent 抽象类的所有子类事件。
如下我们定义一个监听器,监听具体的事件,例如监听 ApplicationStartedEvent 事件。
package com.chenpi; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; @Slf4j @Component public class MyApplicationListener implements ApplicationListener<ApplicationStartedEvent> { @Override public void onApplicationEvent(ApplicationStartedEvent event) { log.info(">>> MyApplicationListener:{}", event); } }
启动服务,会发现在服务启动后,此监听器被触发了。
如果不指定具体的泛型类,则会监听 ApplicationEvent 抽象类的所有子类事件。如下所示:
package com.chenpi; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; @Slf4j @Component public class MyApplicationListener implements ApplicationListener { @Override public void onApplicationEvent(ApplicationEvent event) { log.info(">>> MyApplicationListener:{}", event); } }
注意,监听器类的 bean 要注入到 Spring 容器中,不然不会生效。一种是使用注解注入,例如 @Component
。另外可以使用 SpringApplicationBuilder.listeners()
方法添加,不过这两种方式有区别的,看以下示例。
首先我们使用 @Component 注解方式,服务启动时,监视到了2个事件:
ApplicationStartedEvent
ApplicationReadyEvent
package com.chenpi; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.SpringApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; @Slf4j @Component public class MyApplicationListener implements ApplicationListener<SpringApplicationEvent> { @Override public void onApplicationEvent(SpringApplicationEvent event) { log.info(">>> MyApplicationListener:{}", event); } }
而使用 SpringApplicationBuilder.listeners()
方法添加监听器,服务启动时,监听到了5个事件:
ApplicationEnvironmentPreparedEvent
ApplicationContextInitializedEvent
ApplicationPreparedEvent
ApplicationStartedEvent
ApplicationReadyEvent
package com.chenpi; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class Application { public static void main(String[] args) { // SpringApplication.run(Application.class, args); SpringApplication app = new SpringApplicationBuilder(Application.class) .listeners(new MyApplicationListener()).build(); app.run(args); } }
其实这是和监听器 bean 注册的时机有关,@component 注解的监听器 bean 只有在 bean 初始化注册完后才能使用;而通过 SpringApplicationBuilder.listeners() 添加的监听器 bean 是在容器启动前,所以监听到的事件比较多。但是注意,这两个不要同时使用,不然监听器会重复执行两遍。
如果你想在监听器 bean 中注入其他 bean(例如 @Autowired),那最好是使用注解形式,因为如果太早发布监听器,可能其他 bean 还未初始化完成,可能会报错。
ApplicationEvent 是所有应用事件需要继承的抽象类。它继承了 EventObject
类,EventObject 是所有事件的根类,这个类有个 Object 类型的对象 source,代表事件源。所有继承它的类的构造函数都必须要显示传递这个事件源。
package org.springframework.context; import java.util.EventObject; public abstract class ApplicationEvent extends EventObject { private static final long serialVersionUID = 7099057708183571937L; // 发布事件的系统时间 private final long timestamp; public ApplicationEvent(Object source) { super(source); this.timestamp = System.currentTimeMillis(); } public final long getTimestamp() { return this.timestamp; } }
package java.util; public class EventObject implements java.io.Serializable { private static final long serialVersionUID = 5516075349620653480L; protected transient Object source; public EventObject(Object source) { if (source == null) throw new IllegalArgumentException("null source"); this.source = source; } public Object getSource() { return source; } public String toString() { return getClass().getName() + "[source=" + source + "]"; } }
在 Spring 中,比较重要的事件类是 SpringApplicationEvent
。Spring 有一些内置的事件,当完成某种操作时会触发某些事件。这些内置事件继承 SpringApplicationEvent
抽象类。SpringApplicationEvent 继承 ApplicationEvent 并增加了字符串数组参数字段 args。
@SuppressWarnings("serial") public abstract class SpringApplicationEvent extends ApplicationEvent { private final String[] args; public SpringApplicationEvent(SpringApplication application, String[] args) { super(application); this.args = args; } public SpringApplication getSpringApplication() { return (SpringApplication) getSource(); } public final String[] getArgs() { return this.args; } }
我们可以编写自己的监听器,然后监听这些事件,实现自己的业务逻辑。例如编写 ApplicationListener
ContextRefreshedEvent:ApplicationContext 被初始化或刷新时,事件被发布。ConfigurableApplicationContext接口中的 refresh() 方法被调用也会触发事件发布。初始化是指所有的 Bean 被成功装载,后处理 Bean 被检测并激活,所有单例 Bean 被预实例化,ApplicationContext 容器已就绪可用。
ContextStartedEvent:应用程序上下文被刷新后,但在任何 ApplicationRunner 和 CommandLineRunner 被调用之前,发布此事件。
ApplicationReadyEvent:此事件会尽可能晚地被发布,以表明应用程序已准备好为请求提供服务。事件源是SpringApplication 本身,但是要注意修改它的内部状态,因为到那时所有初始化步骤都已经完成了。
ContextStoppedEvent:ConfigurableApplicationContext 接口的 stop() 被调用停止 ApplicationContext 时,事件被发布。
ContextClosedEvent:ConfigurableApplicationContext 接口的 close() 被调用关闭 ApplicationContext 时,事件被发布。注意,一个已关闭的上下文到达生命周期末端后,它不能被刷新或重启。
ApplicationFailedEvent:当应用启动失败后发布事件。
ApplicationEnvironmentPreparedEvent:事件是在 SpringApplication 启动时发布的,并且首次检查和修改 Environment 时,此时上 ApplicationContext 还没有创建。
ApplicationPreparedEvent:事件发布时,SpringApplication 正在启动,ApplicationContext 已经完全准备好,但没有刷新。在这个阶段,将加载 bean definitions 并准备使用 Environment。
RequestHandledEvent:这是一个 web 事件,只能应用于使用 DispatcherServlet 的 Web 应用。在使用 Spring 作为前端的 MVC 控制器时,当 Spring 处理用户请求结束后,系统会自动触发该事件。
前面介绍了自定义监听器,然后监听 Spring 原有的事件。下面介绍自定义事件和自定义监听器,然后在程序中发布事件,触发监听器执行,实现自己的业务逻辑。
首先自定义事件,继承 ApplicationEvent
,当然事件可以自定义自己的属性。
package com.chenpi; import lombok.Getter; import lombok.Setter; import lombok.ToString; import org.springframework.context.ApplicationEvent; @Getter @Setter public class MyApplicationEvent extends ApplicationEvent { // 事件可以增加自己的属性 private String myField; public MyApplicationEvent(Object source, String myField) { // 绑定事件源 super(source); this.myField = myField; } @Override public String toString() { return "MyApplicationEvent{" + "myField='" + myField + '\'' + ", source=" + source + '}'; } }
然后自定义监听器,监听我们自定义的事件。
package com.chenpi; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationListener; @Slf4j public class MyApplicationListener implements ApplicationListener<MyApplicationEvent> { @Override public void onApplicationEvent(MyApplicationEvent event) { log.info(">>> MyApplicationListener:{}", event); } }
注册监听器和发布事件。注册监听器上面讲解了有两种方式。事件的发布可以通过 ApplicationEventPublisher.publishEvent()
方法。此处演示直接用 configurableApplicationContext 发布,它实现了 ApplicationEventPublisher 接口。
package com.chenpi; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class Application { public static void main(String[] args) { // SpringApplication.run(Application.class, args); // 注册监听器 SpringApplication app = new SpringApplicationBuilder(Application.class) .listeners(new MyApplicationListener()).build(); ConfigurableApplicationContext configurableApplicationContext = app.run(args); // 方便演示,在项目启动后发布事件,当然也可以在其他操作和其他时间点发布事件 configurableApplicationContext .publishEvent(new MyApplicationEvent("我是事件源,项目启动成功后发布事件", "我是自定义事件属性")); } }
启动服务,结果显示确实监听到发布的事件了。
2021-06-26 16:15:09.584 INFO 10992 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8081 (http) with context path ''
2021-06-26 16:15:09.601 INFO 10992 --- [ main] com.chenpi.Application : Started Application in 2.563 seconds (JVM running for 4.012)
2021-06-26 16:15:09.606 INFO 10992 --- [ main] com.chenpi.MyApplicationListener : >>> MyApplicationListener:MyApplicationEvent{myField='我是自定义事件属性', source=我是事件源,项目启动成功后发布事件}
事件监听机制能达到分发,解耦效果。例如可以在业务类中发布事件,让监听在此事件的监听器执行自己的业务处理。例如:
package com.chenpi; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.stereotype.Service; @Service public class MyService implements ApplicationEventPublisherAware { private ApplicationEventPublisher applicationEventPublisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } public void testEvent() { applicationEventPublisher .publishEvent(new MyApplicationEvent("我是事件源", "我是自定义事件属性")); } }
除了实现 ApplicationListener 接口创建监听器外,Spring 还提供了注解 @EventListener
来创建监听器。
package com.chenpi; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; @Slf4j @Component public class MyApplicationListener01 { @EventListener public void onApplicationEvent(MyApplicationEvent event) { log.info(">>> MyApplicationListener:{}", event); } }
而且注解还可以通过条件过滤只监听指定条件的事件。例如事件的 myField 属性的值等于"陈皮"的事件。
package com.chenpi; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; @Slf4j @Component public class MyApplicationListener01 { @EventListener(condition = "#event.myField.equals('陈皮')") public void onApplicationEvent(MyApplicationEvent event) { log.info(">>> MyApplicationListener:{}", event); } }
还可以在同一个类中定义多个监听,对同一个事件的不同监听还可以指定顺序。order 值越小越先执行。
package com.chenpi; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; @Slf4j @Component public class MyApplicationListener01 { @Order(2) @EventListener public void onApplicationEvent(MyApplicationEvent event) { log.info(">>> onApplicationEvent order=2:{}", event); } @Order(1) @EventListener public void onApplicationEvent01(MyApplicationEvent event) { log.info(">>> onApplicationEvent order=1:{}", event); } @EventListener public void otherEvent(YourApplicationEvent event) { log.info(">>> otherEvent:{}", event); } }
执行结果如下:
>>> onApplicationEvent order=1:MyApplicationEvent{myField='陈皮', source=我是事件源}
>>> onApplicationEvent order=2:MyApplicationEvent{myField='陈皮', source=我是事件源}
>>> otherEvent:MyApplicationEvent{myField='我是自定义事件属性01', source=我是事件源01}
事件的监听处理是同步的,如下:
package com.chenpi; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.stereotype.Service; @Service @Slf4j public class MyService implements ApplicationEventPublisherAware { private ApplicationEventPublisher applicationEventPublisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } public void testEvent() { log.info(">>> testEvent begin"); applicationEventPublisher.publishEvent(new MyApplicationEvent("我是事件源", "陈皮")); applicationEventPublisher.publishEvent(new YourApplicationEvent("我是事件源01", "我是自定义事件属性01")); log.info(">>> testEvent end"); } }
执行结果如下:
2021-06-26 20:34:27.990 INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyService : >>> testEvent begin
2021-06-26 20:34:27.990 INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyApplicationListener01 : >>> onApplicationEvent order=1:MyApplicationEvent{myField='陈皮', source=我是事件源}
2021-06-26 20:34:27.991 INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyApplicationListener01 : >>> onApplicationEvent order=2:MyApplicationEvent{myField='陈皮', source=我是事件源}
2021-06-26 20:34:27.992 INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyApplicationListener01 : >>> otherEvent:MyApplicationEvent{myField='我是自定义事件属性01', source=我是事件源01}
2021-06-26 20:34:27.992 INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyService : >>> testEvent end
不过,我们也可以显示指定异步方式去执行监听器,记得在服务添加 @EnableAsync
注解开启异步注解。
package com.chenpi; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; @Slf4j @Component public class MyApplicationListener01 { @Async @Order(2) @EventListener public void onApplicationEvent(MyApplicationEvent event) { log.info(">>> onApplicationEvent order=2:{}", event); } @Order(1) @EventListener public void onApplicationEvent01(MyApplicationEvent event) { log.info(">>> onApplicationEvent order=1:{}", event); } @Async @EventListener public void otherEvent(YourApplicationEvent event) { log.info(">>> otherEvent:{}", event); } }
执行结果如下,注意打印的线程名。
2021-06-26 20:37:04.807 INFO 9092 --- [nio-8081-exec-1] com.chenpi.MyService : >>> testEvent begin
2021-06-26 20:37:04.819 INFO 9092 --- [nio-8081-exec-1] com.chenpi.MyApplicationListener01 : >>> onApplicationEvent order=1:MyApplicationEvent{myField='陈皮', source=我是事件源}
2021-06-26 20:37:04.831 INFO 9092 --- [ task-1] com.chenpi.MyApplicationListener01 : >>> onApplicationEvent order=2:MyApplicationEvent{myField='陈皮', source=我是事件源}
2021-06-26 20:37:04.831 INFO 9092 --- [nio-8081-exec-1] com.chenpi.MyService : >>> testEvent end
2021-06-26 20:37:04.831 INFO 9092 --- [ task-2] com.chenpi.MyApplicationListener01 : >>> otherEvent:MyApplicationEvent{myField='我是自定义事件属性01', source=我是事件源01}
以上是“Spring事件发布与监听机制的用法示例”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。