Spring事件发布与监听的实现方式

120 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第23天,点击查看活动详情

前言

在前面的文章中,我们研究了一下Spring事件发布与监听的原理,除了了解原理外,我们还需要学会如何更好地使用这些功能,接下来这篇文章将简单地通过示例来介绍一下我们在实际项目中如何使用Spring事件发布与监听功能;

事件三要素

在事件发布与监听中,我们需要抓住最关键的三要素:

  • 事件本身

    我们在Spring项目中可以实现自定义的事件,通过继承ApplicationEvent来定义业务相关的事件;另外的话,Spring本身也提供了一些内置事件:

    事件描述
    ContextStartedEvent容器启动的时候触发
    ContextRefreshedEvent容器初始化或者ApplicationContext被刷新的时候触发
    ContextStoppedEvent容器停止的时候触发
    ContextClosedEvent容器关闭的时候触发
    ApplicationStartingEventSpringboot启动的时候触发
    ApplicationEnvironmentPreparedEvent环境变量准备完毕的时候触发
    ApplicationPreparedEventcontext上下文创建完毕的时候触发
    ApplicationReadyEventSpringboot加载完毕的时候触发
    ApplicationFailedEventSpringboot启动异常的时候触发
  • 事件发布者

    Spring项目中,我们主要关注两个事件发布者:ApplicationEventPublisherApplicationContext

  • 事件监听者

    我们可以有多种方式实现事件的监听:

    1.通过@EventListener注解的方式标注监听方法;

    2.通过实现ApplicationListener接口的onApplicationEvent()方法来完成监听;

示例

下面我们通过一个简单的示例来看看如何实现自定义的事件发布与监听:

@Service
public class AccountServiceImpl implements AccountService {
  // 引入事件发布器
	@Autowired
	private ApplicationEventPublisher publisher;
  // 操作数据库
  @Autowired
	private AccountMapper accountMapper;
  
  @Transactional
	@Override
	public int createAccount(String accountName, String password) {
		Account accountInfo = new Account();
		accountInfo.setAccount(accountName);
		accountInfo.setEnabled(1);
		accountInfo.setCreateTime(new Date());
		accountInfo.setId(UUID.randomUUID().toString());
		accountInfo.setPassword(password);
		int result = accountMapper.insert(accountInfo);
		// 新账号创建事件发布
		publisher.publishEvent(new AccountCreateEvent(accountInfo));
		return result;
	}
}

  // 自定义事件,账号创建成功后发布该事件
	public class AccountCreateEvent extends ApplicationEvent {
		public AccountCreateEvent(Account account) {
			super(account);
		}
	}

// 事件监听器
@Configuration
public class BusinessEventListener {

  // 监听方法
	@EventListener
	public void accountCreateEventListener(AccountCreateEvent event) {
    // 打印发布的事件信息
		try {
			System.out.println(event.getSource());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

上述示例中,事件发布器是ApplicationEventPublisher,直接通过@Autowired注解引入,事件监听器通过@EventListener标注方法完成,这就完成了一次简单的事件发布与监听;

事件发布器的其他引入方式

我们除了可以直接通过@Autowired注解方式引入事件发布器,还可以通过以下方式获取事件发布器:

  • 实现ApplicationEventPublisherAware接口的setApplicationEventPublisher()方法获取事件发布器;
  • 通过@Autowired获取ApplicationContext作为事件发布器;
  • 通过实现ApplicationContextAware接口的setApplicationContext()方法获取ApplicationContext作为事件发布器

事件监听器的实现方式

  • 实现ApplicationListener接口

    @Component
    public class BusinessEventListener implements ApplicationListener<AccountCreateEvent> {
    ​
        @Override
        public void onApplicationEvent(AccountCreateEvent event) {
            try {
                System.out.println(event.getSource());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
  • @EventListener注解标注

    @Configuration
    public class BusinessEventListener {
    ​
        @EventListener
        public void accountCreateEventListener(AccountCreateEvent event) {
            try {
                System.out.println(event.getSource());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

基本上使用上述两种方式就完全能够实现目标事件的监听;

异步监听

在前面的文章中有讲述Spring事件发布与监听的原理,默认情况下监听逻辑都是同步调用的,如果我们需要异步调用的话,还需要增加其他配置:

1.通过@EnableAsync注解开启异步功能;

@EnableAsync
@SpringBootApplication
public class AwesomeSpringApplication{
  public static void main(String[] args) {
		SpringApplication.run(AwesomeSpringApplication.class, args);
	}
}

我们在启动类上面添加@EnableAsync以便开启异步功能;

2.在目标方法或类上面添加@Async注解,使得执行逻辑被单独的线程池调用;

@Configuration
public class BusinessEventListener {

	@Async
	@EventListener
	public void accountCreateEventListener(AccountServiceImpl.AccountCreateEvent event) {
		try {
			System.out.println(event.getSource());
			System.out.println("线程名称:" + Thread.currentThread().getName());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

在目标方法上面,我们添加了@Async,这样监听逻辑和事件发布的逻辑将不在同一个线程中,小伙伴们可以通过打印线程名称来确定是否配置成功;

注意:@Async注解同样可以放在类上面;

指定异步线程池

我们如果想要异步执行是在我们自己定义的线程池中,那么该如何配置呢?其实非常简单,按照下面步骤来操作即可:

1.创建自定义线程池,并交给Spring管理;

@Configuration
public class ThreadPoolConfig {
	@Bean("businessEventExecutor")
	public Executor businessEventExecutor() {
		return new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000), new ThreadFactory() {
			private AtomicInteger threadIndex = new AtomicInteger(0);

			@Override
			public Thread newThread(@NotNull Runnable r) {
				Thread t = new Thread(r, "businessEventThread" + threadIndex.getAndIncrement());
				return t;
			}
		}, new ThreadPoolExecutor.AbortPolicy());
	}
}

2.在使用@Async注解时,指定目标线程池的BeanName:

@Configuration
@Async("businessEventExecutor")
public class BusinessEventListener {

	@EventListener
	public void accountCreateEventListener(AccountServiceImpl.AccountCreateEvent event) {
		try {
			System.out.println(event.getSource());
			System.out.println("线程名称:" + Thread.currentThread().getName());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

按照以上配置,就可以在事件监听过程中指定为自定义的线程池来执行;