如何监听/订阅Spring事件的执行情况?

Spring自定义事件

Spring的自定义事件机制,为应用提供了便捷的解耦方式。Spring很多内置事件其实也都是自定义事件的一种实现,例如

Spring的一些内置事件
ApplicationReadyEvent
ApplicationStartedEvent
ApplicationEnvironmentPreparedEvent

Spring自定义事件是Spring Aware机制外的一大补充。

我们可以方便使用Spring 自定义事件来解耦程序,笔者就大量使用Spring自定义事件,在不同的模块间通信。由此在使用过程中衍生出了新的需求

需求起因

笔者在使用自定义事件时,通常一个事件有多个监听/订阅方。笔者是通过事件的传递机制,来控制一个初始化生命周期。在Spring中的一些实现中,也可以看到,在某个事件完成后,派发下一个事件。通过这种硬编码的方式,其实可以清晰知道一个事件是否完成。

//伪代码
//在一个事件中传递下一个事件
public void onApplicationEvent(RPCCompleteEvent event) {

       
        ApplicationContext app = event.getApplicationContext();
        AOPPreScanEvent aopPreScanEvent = new AOPPreScanEvent(event.getSource());

        app.publishEvent(aopPreScanEvent);

}

但是这是硬编码的方式,必须在一个事件内部才能这样知道他的“完结”,并进行后续的逻辑。

其次,当一个事件有多个监听/订阅时,我们需要的是所有监听/订阅处理完毕后,继续逻辑处理,并且多个监听/订阅时,我们也无法在其中一个监听/订阅传递后续的逻辑,因为这里面可能涉及顺序问题。综上,我们需要一个在外部观察自定义事件执行情况的机制。

Spring自定义事件解析

通过源码解析可知,SimpleApplicationEventMulticaster中实际调用了ApplicationListener

//SimpleApplicationEventMulticaster.java
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        
        //很容易就发现,自定义事件的执行逻辑。
        //如果在上面植入一些before和after的钩子函数,岂不是完美解决?
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
}

所以,我们只需要在这里做改动即可。

SimpleApplicationEventMulticaster的扩展

用来添加before或after等钩子函数

//定义新的事件执行器
//扩展原来的SimpleApplicationEventMulticaster 
public class SimpleApplicationEventMulticasterExtend extends SimpleApplicationEventMulticaster {

    @Autowired
    private ShinagawaSimpleApplicationEventInvokeListener shinagawaSimpleApplicationEventInvokeListener;


    @Override
    public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : ResolvableType.forInstance(event));
        Executor executor = getTaskExecutor();

        List<ApplicationListener<?>> applicationListenerList = getApplicationListeners(event, type).stream().collect(Collectors.toList());

        shinagawaSimpleApplicationEventInvokeListener.setEventInvokeStatus(event.getClass(), ShinagawaSimpleApplicationEventInvokeListener.EventInvokeStatusEnum.START);

        for (ApplicationListener<?> listener : applicationListenerList) {
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }

        if(executor==null) {
            shinagawaSimpleApplicationEventInvokeListener.setEventInvokeStatus(event.getClass(), ShinagawaSimpleApplicationEventInvokeListener.EventInvokeStatusEnum.COMPLETE);
        }else{
            //多线程版本。暂时不考虑实现。
        }

    }
}
//用来管理事件的钩子函数
//你也可以实现你的版本
public class SimpleApplicationEventInvokeListener {

    private static Map<Class,EventInvokeStatusEnum> EVENT_INVOKE_STATUS_MAP = new ConcurrentHashMap<>();

    private static Map<Class,Boolean> EVENT_LISTENER_MAP = new ConcurrentHashMap<>();

    private static final long delay = 100;

    EventInvokeStatusEnum getEventInvokeStatus(Class event){
        if(EVENT_INVOKE_STATUS_MAP.containsKey(event)){
            return EVENT_INVOKE_STATUS_MAP.get(event);
        }
        return EventInvokeStatusEnum.UNKNOWN;
    }

    void setEventInvokeStatus(Class event,EventInvokeStatusEnum eventInvokeStatusEnum){
        EVENT_INVOKE_STATUS_MAP.put(event,eventInvokeStatusEnum);
    }


    public void eventInvokeCompleteListener(Class event,Runnable runnable){

        if(!EVENT_LISTENER_MAP.containsKey(event)){

            EVENT_LISTENER_MAP.put(event,true);
            ScheduledExecutorService scheduledExecutorService = ThreadPoolHelper.getSchedulePool(1,"eventListener-"+runnable.getClass().getName());
            Runnable r = new EventCompleteCheckRunable(event,runnable,scheduledExecutorService);

            scheduledExecutorService.schedule(r,delay, TimeUnit.MILLISECONDS);

        }
    }

    class EventCompleteCheckRunable implements Runnable {

        private Runnable callback = null;
        private Class event;
        private ScheduledExecutorService scheduledExecutorService = null;
        public EventCompleteCheckRunable(Class event,Runnable callback,ScheduledExecutorService scheduledExecutorService) {
            this.callback = callback;
            this.event = event;
            this.scheduledExecutorService = scheduledExecutorService;
        }

        @Override
        public void run() {
            try {

                //log.info("线程:{}",Thread.currentThread().getName());

                if (getEventInvokeStatus(event).equals(EventInvokeStatusEnum.COMPLETE)) {
                    scheduledExecutorService.shutdown();
                    EVENT_LISTENER_MAP.remove(event);
                    this.callback.run();
                    return;
                }

                //继续定时

                scheduledExecutorService.schedule(this, delay, TimeUnit.MILLISECONDS);
            }catch (Throwable e){
                log.error("事件传播失败,异常:{}", ExceptionUtils.getStackTrace(e));
            }
        }
    }

    enum EventInvokeStatusEnum{

        START,          //事件准备开始执行
        COMPLETE,       //事件执行完成
        UNKNOWN         //未知

    }
}

此时,相当于已经重新定义了Spring自定义事件的执行器,现在只需要将他添加到Spring中

植入专属SimpleApplicationEventMulticaster

根据源码可知,我们只需将扩展后的SimpleApplicationEventMulticaster命名为

APPLICATION_EVENT_MULTICASTER_BEAN_NAME

即可完成覆盖

//AbstractApplicationContext.java
protected void initApplicationEventMulticaster() {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();

        //当存在该bean时,则使用外部的自定义事件执行器
		if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
			this.applicationEventMulticaster =
					beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
			if (logger.isTraceEnabled()) {
				logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
			}
		}
		else {
            //没有则注册默认
			this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
			beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
			if (logger.isTraceEnabled()) {
				logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
						"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
			}
		}
	}

 

//注入扩展后的事件执行器
    @Bean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
    public SimpleApplicationEventMulticasterExtend getSimpleApplicationEventMulticasterExtend(){
        return new SimpleApplicationEventMulticasterExtend();
    }

至此,我们可以在外部监听/订阅一个事件结束与否的信息了。。。

可以以无侵入的方式来执行我们的逻辑。

使用示例

//钩子函数定义
simpleApplicationEventInvokeListener.eventInvokeCompleteListener(AOPPreScanEvent.class, new Runnable() {
            @Override
            public void run() {
                //AOPPreScanEvent事件执行完成后,想做的事
            }
        });

总结

这个需求产生于偶然。但是通过源码分析,发现Spring预留了如何替换SimpleApplicationEventMulticaster的扩展点,代表框架设计者想到了该场景。

代码只是样例,实际可能会有细微差别,本文仅是提供思路。请大家举一反三。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Cloud分布式程序中,如果多个实例都监听同一个Redis key过期事件,可能会导致重复消费的问题。因为Redis的keyspace notifications功能是发布-订阅模式,每个订阅者都会收到相同的事件通知。 为了避免这个问题,可以使用分布式锁来控制只有一个实例能够处理该事件。常用的分布式锁包括Redisson、ZooKeeper、Curator等。 以下是使用Redisson实现分布式锁的示例代码: 1. 添加Redisson和Spring Data Redis依赖: ```xml <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.14.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> ``` 2. 创建一个RedissonClient Bean: ```java @Bean public RedissonClient redissonClient() { Config config = new Config(); config.useSingleServer().setAddress("redis://localhost:6379"); return Redisson.create(config); } ``` 3. 创建一个RedisKeyExpirationListener类,并在其中加入分布式锁逻辑: ```java public class RedisKeyExpirationListener { private static final String LOCK_NAME = "key-expiration-lock"; @Autowired private RedissonClient redissonClient; public void onMessage(Message message, byte[] pattern) { String expiredKey = message.toString(); RLock lock = redissonClient.getLock(LOCK_NAME); try { if (lock.tryLock()) { // 这里可以写你要执行的代码 System.out.println("Key expired: " + expiredKey); } } finally { lock.unlock(); } } } ``` 4. 将RedisKeyExpirationListener注册到RedisMessageListenerContainer中: ```java @Autowired private RedisMessageListenerContainer redisMessageListenerContainer; @Autowired private MessageListenerAdapter messageListenerAdapter; @PostConstruct public void init() { // 要监听的key的pattern String pattern = "__keyevent@0__:expired"; redisMessageListenerContainer.addMessageListener(messageListenerAdapter, new PatternTopic(pattern)); } ``` 通过加入分布式锁逻辑,可以确保只有一个实例能够处理该事件,避免了重复消费的问题。需要注意的是,锁的粒度应该尽量小,避免对性能产生影响。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值