D5E09CE4-spring.png

Spring 事件监听机制也是基于 JDK 规范在框架中进行扩展和完善。
同样也包含三个部分:

  • ApplicationEvent:这个抽象类继承了 EventObject
  • ApplicationListener:函数式接口继承了 EventListener
  • ApplicationEventPublisher:函数式接口,通过 publishEvent 发布事件

    简单事件监听

    模拟用户注册后,发送短信和邮件。
    先定义事件

    1. /**
    2. * 用户注册实体
    3. *
    4. * @author chenghao.li
    5. */
    6. @Getter
    7. @Setter
    8. public class UserRegister extends ApplicationEvent {
    9. private static final long serialVersionUID = -8392057501920248680L;
    10. private String userName;
    11. private String telPhone;
    12. private String email;
    13. public UserRegister(Object source) {
    14. super(source);
    15. }
    16. }

注册监听,有两种方式

  1. /**
  2. * 注解监听器
  3. *
  4. * @author chenghao.li
  5. */
  6. @Component
  7. public class UserRegisterSendMsgListener {
  8. @EventListener
  9. public void handleUserRegisterEvent(UserRegister userRegister) {
  10. System.out.println("监听到用户注册,准备发送短信:" + userRegister.getTelPhone());
  11. }
  12. }
  13. /**
  14. * 监听器(接口实现)
  15. *
  16. * @author chenghao.li
  17. */
  18. @Component
  19. public class UserRegisterSendEmailListener implements ApplicationListener<UserRegister> {
  20. @Override
  21. public void onApplicationEvent(UserRegister userRegister) {
  22. System.out.println("监听到用户注册,准备发送邮件:" + userRegister.getEmail());
  23. }
  24. }

测试事件发布

  1. @RequestMapping("/register")
  2. public String register() {
  3. UserRegister userRegister = new UserRegister(this);
  4. userRegister.setUserName("film");
  5. userRegister.setTelPhone("13333334444");
  6. userRegister.setEmail("123@admin.com");
  7. eventPublisher.publishEvent(userRegister);
  8. return "success";
  9. }

上面的代码发生注册事件后,会发现监听会被执行两次。也就是说监听被注册了两次。
原因:
web 项目中如果同时集成了 spring 和 springMVC 的话,上下文中会存在两个容器,即 spring 的applicationContext.xml 父容器和 springMVC 的 applicationContext-mvc.xml 子容器。在通过 ApplicationEventPublisher 发布事件的时候,事件会被两个容器发布,造成上述情况。
解决:
通过父容器发布

  1. @RequestMapping("/register")
  2. public String register() {
  3. UserRegister userRegister = new UserRegister(this);
  4. userRegister.setUserName("film");
  5. userRegister.setTelPhone("13333334444");
  6. userRegister.setEmail("123@admin.com");
  7. try {
  8. WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
  9. if (context != null) {
  10. context.publishEvent(userRegister);
  11. }
  12. } catch (Exception e) {
  13. System.out.println("error");
  14. }
  15. return "success";
  16. }

监听器的顺序

默认情况下按照 bean 的加载顺序执行监听器。

@Order

可以通过 @Order 指定执行顺序,数字越小,优先级越大

  1. @Order(1)
  2. @Component
  3. public class UserRegisterSendMsgListener {
  4. @EventListener
  5. public void handleUserRegisterEvent(UserRegister userRegister) {
  6. System.out.println("监听到用户注册,准备发送短信:" + userRegister.getTelPhone());
  7. }
  8. }
  9. @Order(2)
  10. @Component
  11. public class UserRegisterSendEmailListener implements ApplicationListener<UserRegister> {
  12. @Override
  13. public void onApplicationEvent(UserRegister userRegister) {
  14. System.out.println("监听到用户注册,准备发送邮件:" + userRegister.getEmail());
  15. }
  16. }

SmartApplicationListener

通过实现 SmartApplicationListener 接口的 getOrder 方法来指定顺序。

  1. /**
  2. * 监听器
  3. *
  4. * @author chenghao.li
  5. */
  6. @Component
  7. public class UserRegisterOtherListener implements SmartApplicationListener {
  8. @Override
  9. public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
  10. return true;
  11. }
  12. @Override
  13. public void onApplicationEvent(ApplicationEvent applicationEvent) {
  14. UserRegister event = (UserRegister) applicationEvent;
  15. System.out.println("监听到用户注册,干点其他的:" + event.getUserName());
  16. }
  17. @Override
  18. public int getOrder() {
  19. return 3;
  20. }
  21. }

异步监听

Spring 事件通知默认是同步的,也就是说需要等待所有的监听都执行完毕后才能继续后序的处理。
可以验证下,在任意监听器下睡一会即可。

  1. @Override
  2. public void onApplicationEvent(ApplicationEvent applicationEvent) {
  3. UserRegister event = (UserRegister) applicationEvent;
  4. System.out.println("监听到用户注册,干点其他的:" + event.getUserName());
  5. try {
  6. TimeUnit.SECONDS.sleep(5);
  7. } catch (InterruptedException e) {
  8. System.err.println(e);
  9. }
  10. }

开启异步支持,并配置异步任务的线程池。

  1. /**
  2. * 线程池配置
  3. *
  4. * @author chenghao.li
  5. */
  6. @EnableAsync
  7. @Configuration
  8. public class ThreadPoolConfig implements AsyncConfigurer {
  9. @Bean("eventListenerThreadPool")
  10. @Override
  11. public Executor getAsyncExecutor() {
  12. return new ThreadPoolExecutor(5, 10, 60L, TimeUnit.SECONDS
  13. , new ArrayBlockingQueue<>(50)
  14. , new MyThreadFactory());
  15. }
  16. static class MyThreadFactory implements ThreadFactory {
  17. final AtomicInteger threadNumber = new AtomicInteger(0);
  18. @Override
  19. public Thread newThread(Runnable r) {
  20. Thread t = new Thread(new ThreadGroup("EventListener"), r);
  21. t.setName("async-thread-" + threadNumber.getAndIncrement());
  22. t.setDaemon(true);
  23. return t;
  24. }
  25. }
  26. }

监听上使用异步 @Async(“eventListenerThreadPool”) 注解指定配置好的线程池即可。

  1. /**
  2. * 监听器(接口实现)
  3. *
  4. * @author chenghao.li
  5. */
  6. @Order(2)
  7. @Component
  8. @Async("eventListenerThreadPool")
  9. public class UserRegisterSendEmailListener implements ApplicationListener<UserRegister> {
  10. @Override
  11. public void onApplicationEvent(UserRegister userRegister) {
  12. System.out.println("监听到用户注册,准备发送邮件:" + userRegister.getEmail());
  13. }
  14. }
  15. /**
  16. * 注解监听器
  17. *
  18. * @author chenghao.li
  19. */
  20. @Order(1)
  21. @Component
  22. @Async("eventListenerThreadPool")
  23. public class UserRegisterSendMsgListener {
  24. @EventListener
  25. public void handleUserRegisterEvent(UserRegister userRegister) {
  26. try {
  27. TimeUnit.SECONDS.sleep(5);
  28. } catch (InterruptedException e) {
  29. throw new RuntimeException(e);
  30. }
  31. System.out.println("监听到用户注册,准备发送短信:" + userRegister.getTelPhone());
  32. }
  33. }

这个时候,SmartApplicationListener 接口的实现是不支持异步注解。使用的话会直接报错。