Spring事件监听模式初识

本文深入探讨了事件驱动机制在JDK与Spring框架中的实现,对比观察者模式、发布订阅模式及消息队列等概念,解析Spring事件监听器的注册与事件发布流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

原文: 从Spring中的(ApplicationEvent)事件驱动机制出发

说到事件驱动,我心里一直就有一个不解的疑问:它和我们老生长谈的一些概念比如:【观察者模式】【发布订阅模式】【消息队列MQ】【消息驱动】【EventSourcing】等等是一回事吗?

可能很多小伙伴会回答:差不多。确实,很有深意的三字回答。

那么本文将以Spring的事件驱动机制为引子,好好的聊聊这里面的关系和差异~

JDK中的事件驱动机制

在了解其它之前,有必要先了解下JDK为我们提供的事件驱动(EventListener、EventObject)、观察者模式(Observer)。

JDK不仅提供了Observable类、Observer接口支持观察者模式,而且也提供了EventObject、EventListener接口来支持事件监听模式。

这些类都属于java.util下的

一、JDK1.0提供
1.1 观察者模式(Observable和Observer) JDK1.0提供

被观察对象:观察者 = 1:n (观察者可以有N个嘛)

观察者(Observer)相当于事件监听者(监听器),被观察者(Observable)相当于事件源和事件,执行逻辑时通知observer即可触发oberver的update,同时可传被观察者和参数。简化了事件-监听模式的实现。

// 观察者,实现此接口即可
public interface Observer {
	// 当被观察的对象发生变化时候,这个方法会被调用
	//Observable o:被观察的对象
	// Object arg:传入的参数
    void update(Observable o, Object arg);
}

// 它是一个Class
public class Observable {

	// 是否变化,决定了后面是否调用update方法
    private boolean changed = false;
    // 用来存放所有`观察自己的对象`的引用,以便逐个调用update方法
    // 需要注意的是:1.8的jdk源码为Vector(线程安全的),有版本的源码是ArrayList的集合实现; 
    private Vector<Observer> obs;

    public Observable() {
        obs = new Vector<>();
    }

	public synchronized void addObserver(Observer o); //添加一个观察者 注意调用的是addElement方法,添加到末尾   所以执行时是倒序执行的
	public synchronized void deleteObserver(Observer o);
	public synchronized void deleteObservers(); //删除所有的观察者

	// 循环调用所有的观察者的update方法
	public void notifyObservers();
	public void notifyObservers(Object arg);
    public synchronized int countObservers() {
        return obs.size();
    }

	// 修改changed的值
    protected synchronized void setChanged() {
        changed = true;
    }
    protected synchronized void clearChanged() {
        changed = false;
    }
    public synchronized boolean hasChanged() {
        return changed;
    }
}

它的使用非常的便捷,看个例子就能明白

class Person extends Observable {
    public String name;

    public Person(String name) {
        this.name = name;
    }

    // 给鱼:这样所有观察的猫都会过来了
    // fishType: 鱼的名字
    public void giveFish(String fishName) {
        setChanged(); // 这个一定不能忘
        notifyObservers(fishName);
    }
}

class Cat implements Observer {
    public String name;

    public Cat(String name) {
        this.name = name;
    }

    @Override
    public void update(Observable o, Object arg) {
        String preffix = o.toString();
        if (o instanceof Person) {
            preffix = ((Person) o).name;
        }
        System.out.println(preffix + "主人放 " + arg + "~了," + name + "去吃鱼吧");
    }
}

// 测试方法如下:
    public static void main(String[] args) {
        Person person = new Person("fsx");

        // 来10只猫 观察这个人
        for (int i = 0; i < 10; i++) {
            person.addObserver(new Cat("cat" + i));
        }

        //开始放fish,这时候观察的猫就应该都过来了
        person.giveFish("草鱼");
    }

// 输出
fsx主人放 草鱼~,cat9去吃鱼吧
fsx主人放 草鱼~,cat8去吃鱼吧
fsx主人放 草鱼~,cat7去吃鱼吧
fsx主人放 草鱼~,cat6去吃鱼吧
fsx主人放 草鱼~,cat5去吃鱼吧
fsx主人放 草鱼~,cat4去吃鱼吧
fsx主人放 草鱼~,cat3去吃鱼吧
fsx主人放 草鱼~,cat2去吃鱼吧
fsx主人放 草鱼~,cat1去吃鱼吧
fsx主人放 草鱼~,cat0去吃鱼吧

JDK的观察者模式使用起来确实非常的方便,我们只需要面对两个对象即可。内部观察者队列啥的都交给Observable去处理了。 并且,它是线程安全的

1.2 发布订阅模式(EventListener和EventObject) JDK1.1提供

事件机制一般包括三个部分:EventObject,EventListener和Source。
EventObject:事件状态对象的基类,它封装了事件源对象以及和事件相关的信息。所有java的事件类都需要继承该类
EventListener:是一个标记接口,就是说该接口内是没有任何方法的。所有事件监听器都需要实现该接口。事件监听器注册在事件源上,当事件源的属性或状态改变的时候,调用相应监听器内的回调方法(自己写)。
Source:一个普通的POJO。事件最初发生的地方,他里面必须含有监听它的监听器们

class MyEvent extends EventObject {
    public MyEvent(Object source) {
        super(source);
    }
}

// 状态改变事件
class StatusChangedListener implements EventListener {
    public void handleEvent(MyEvent event) {
        System.out.println(event.getSource() + " 的状态改变啦~");
    }

}

// 状态没变化事件
class StateSameListener implements EventListener {
    public void handleEvent(MyEvent event) {
        System.out.println(event.getSource() + " 的状态没有任何变化~");
    }
}

class MySource {
    private int status;
    List<EventListener> eventListeners = new ArrayList<>();

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public void addListener(EventListener listener) {
        eventListeners.add(listener);
    }

    // 调用所有的合适的监听器
    public void notifyListeners(int oldStatus, int newStatus) {
        eventListeners.forEach(l -> {
            if (oldStatus == newStatus) {
                // doSamething
            } else {
                // doSamething
            }
        });
    }

}

// 测试方法
    public static void main(String[] args) {
        MySource mySource = new MySource();
        mySource.addListener(new StatusChangedListener());
        mySource.addListener(new StateSameListener());

        int oldStatus = mySource.getStatus();
        mySource.setStatus(1);
        int newStatus = mySource.getStatus();

        // 触发所有的监听者们
        mySource.notifyListeners(oldStatus, newStatus);
    }

对弈上面的观察者模式,监听模式使用起来确实非常的繁琐,且还线程安全问题还得自己考虑解决。我个人觉得JDK的源生的事件、监听模式非常难用(不太建议使用,它最大的败笔在于EventListener接口没有定义一个抽象方法,不知道是作何考虑的,应该是为了更加抽象吧)。因此接下来,大行其道的Spring事件机制就很好的解决使用上的问题~~~它也是今天的主菜
————————————————

二、Spring对事件监听模式的支持
2.1 内置监听器接口
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    
	void onApplicationEvent(E event);
}

他有2个重要子接口如下
在这里插入图片描述
SmartApplicationListener

public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
    //当前监听器支持事件类型
	boolean supportsEventType(Class<? extends ApplicationEvent> eventType);
	default boolean supportsSourceType(@Nullable Class<?> sourceType) {return true;}
	default int getOrder() { return LOWEST_PRECEDENCE;}

}

GenericApplicationListener

public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {

	boolean supportsEventType(ResolvableType eventType);
	default boolean supportsSourceType(@Nullable Class<?> sourceType) { return true;}
	default int getOrder() { return LOWEST_PRECEDENCE;}
}
2.2 内置事件对象
public abstract class ApplicationEvent extends EventObject {
	private final long timestamp;
	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}
	public final long getTimestamp() {
		return this.timestamp;
	}

}
2.3 内置监听器管理器

java没有内置的事件管理器

public interface ApplicationEventMulticaster {
	//添加一个侦听器来通知所有事件
	void addApplicationListener(ApplicationListener<?> listener);
	//添加一个侦听器bean来通知所有事件
	void addApplicationListenerBean(String listenerBeanName);
	//从通知列表中删除侦听器
	void removeApplicationListener(ApplicationListener<?> listener);
	//从通知列表中删除侦听器Bean
	void removeApplicationListenerBean(String listenerBeanName);
    //删除所有侦听器
	void removeAllListeners();
	//将给定的应用程序事件多路广播给匹配的侦听器
	void multicastEvent(ApplicationEvent event);
    //将给定的应用程序事件多路广播给匹配的侦听器,其支持泛型
	void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}

这个接口只有一个具体的实现SimpleApplicationEventMulticaster

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

	// 若set了一个执行器,那所有的监听器都将会异步执行
	@Nullable
	private Executor taskExecutor;
	// 监听者执行失败的回调~~~~~~(比如做回滚等等)
	@Nullable
	private ErrorHandler errorHandler;

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
	
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		// 这里面有个细节:如果有执行器executor ,那就会扔给线程池异步去执行
		// 默认情况下是没有的(Spring默认情况下同步执行这些监听器的)  我们可以调用set方法配置一个执行器(建议)
		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			Executor executor = getTaskExecutor();
			// 放在线程池里执行,相当于异步执行。绝大多数情况下,这里都是null
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			} else {
				invokeListener(listener, event);
			}
		}
	 }


	protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
			ErrorHandler errorHandler = getErrorHandler();
			if (errorHandler != null) {
				try {
					doInvokeListener(listener, event);
				}
				catch (Throwable err) {
					errorHandler.handleError(err);
				}
			}
			else {
				doInvokeListener(listener, event);
			}
		}


	private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
			// 如果是实现了ApplicationListener接口,则直接调用其中的onApplicationEvent()方法;
			//如果是用@EventListener注释,则调用ApplicationListenerMethodAdapter中的onApplicationEvent()方法
			listener.onApplicationEvent(event);
		}
	}
}
2.4 事件发布管理器
@FunctionalInterface
public interface ApplicationEventPublisher {

	//事件类型是ApplicationEvent
	default void publishEvent(ApplicationEvent event) {
		publishEvent((Object) event);
	}

	//发布事件,其底层调用了ApplicationEventMulticaster的multicastEvent方法
	void publishEvent(Object event);
}

Spring中容器就是一个事件发布管理器

三、Spring事件监听器过程研究
3.1 监听器管理器初始化

其过程发生在容器刷新方法refresh中的initApplicationEventMulticaster()模块,其模块在BeanPostProcessors实例后,Bean实例化前

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
    @Nullable
	private ApplicationEventMulticaster applicationEventMulticaster;

   protected void initApplicationEventMulticaster() {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		//APPLICATION_EVENT_MULTICASTER_BEAN_NAME = applicationEventMulticaster
		//如果容器中包含这个监听器管理器,就使用容器中的
		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 + "]");
			}
		}
		//如果容器中不包含这个监听器管理器,使用默认管理器SimpleApplicationEventMulticaster
		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() + "]");
			}
		}
	}

如果容器中包含这个监听器管理器,就使用容器中的,否则使用默认管理器SimpleApplicationEventMulticaster

2、监听器的注册

其过程发生在容器刷新方法refresh中的registerListeners()模块,其模块紧跟着initApplicationEventMulticaster

public abstract class AbstractApplicationContext extends DefaultResourceLoader
		implements ConfigurableApplicationContext {

    protected void registerListeners() {
		//获取日期中所有监听器,注册到监听器管理器中
		for (ApplicationListener<?> listener : getApplicationListeners()) {
			getApplicationEventMulticaster().addApplicationListener(listener);
		}

		// 从容器中获取所有实现了ApplicationListener接口的bd的bdName
		String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
		for (String listenerBeanName : listenerBeanNames) {
			getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
		}

		// 这里先发布早期的监听器
		Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
		this.earlyApplicationEvents = null;
		if (earlyEventsToProcess != null) {
			for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
				getApplicationEventMulticaster().multicastEvent(earlyEvent);
			}
		}
	}
3、事件到发布
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {

	private ApplicationEventMulticaster applicationEventMulticaster;

	public void publishEvent(Object event) {
		publishEvent(event, null);
	}

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

		// 如有必要,将事件装饰为ApplicationEvent
		ApplicationEvent applicationEvent;
		if (event instanceof ApplicationEvent) {
			applicationEvent = (ApplicationEvent) event;
		}
		else {
			applicationEvent = new PayloadApplicationEvent<>(this, event);
			if (eventType == null) {
				eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
			}
		}

		// 如果可能的话,现在就进行多播-或者在初始化多播主机后进行惰性的多播
		if (this.earlyApplicationEvents != null) {
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else {
	 	   //触发对应事件的监听器,这个监听器是当前容器的
			getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
		}

		// 如果当前容器有父容器,那么触发当前事件,父容器中的监听器
		if (this.parent != null) {
			if (this.parent instanceof AbstractApplicationContext) {
				((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
			}
			else {
				this.parent.publishEvent(event);
			}
		}
	}
四、Spring监听器应用
4.1 非注解方式Spring中监听器案例
  • 定单事件
public class OrderEvent extends ApplicationEvent {

    public OrderEvent(Object source) {
        super(source);
    }

}
  • 定单事件监听器
@Component
public class OrderEventListener implements SmartApplicationListener {

    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return OrderEvent.class == eventType;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("一个定单产生了");
    }
}
  • 发布事件
@Controller
@RequestMapping("person")
public class PersonController implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @ResponseBody
    @GetMapping("publishOrderEvent")
    public void publishOrderEvent() {
        Order order = new Order();//订单对象
        applicationContext.publishEvent(new OrderEvent(order));
    }
}
4.2 @EventListener注解方式Spring中监听器
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EventListener {
	@AliasFor("classes")
	Class<?>[] value() default {};
	@AliasFor("value")
	Class<?>[] classes() default {};
	String condition() default "";
}

@EventListener中classes属性和所标注的方法入参是其生成监听器所支持的事件类型

事件

public class OrderEvent extends ApplicationEvent {

    public OrderEvent(Object source) {
        super(source);
    }

}

事件监听器

@Component
public class OrderEventListener  {

    @EventListener
    public void listener(OrderEvent event) {
        System.out.println("i do OrderEventListener" );
    }
}

事件发布

@Controller
@RequestMapping("person")
public class PersonController implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @ResponseBody
    @GetMapping("publishOrderEvent")
    public String publishOrderEvent() {
        applicationContext.publishEvent(new OrderEvent("我发布了事件!!!"));
        System.out.println(" publishOrderEvent ");
        return "发送事件了!";
    }
}

不乏有小伙伴在启动的时候看到过这样的异常:

Caused by: java.lang.IllegalStateException: Need to invoke method 'applicationContextEvent' declared on target class 'HelloServiceImpl', but not found in any interface(s) of the exposed proxy type. Either pull the method up to an interface or switch to CGLIB proxies by enforcing proxy-target-class mode in your configuration.
at org.springframework.core.MethodIntrospector.selectInvocableMethod(MethodIntrospector.java:132)
at org.springframework.aop.support.AopUtils.selectInvocableMethod(AopUtils.java:134)
at org.springframework.context.event.EventListenerMethodProcessor.processBean(EventListenerMethodProcessor.java:177)
at org.springframework.context.event.EventListenerMethodProcessor.afterSingletonsInstantiated(EventListenerMethodProcessor.java:133

那是因为:你把@EventListener写在XXXImpl实现类里面了,形如这样:

@Slf4j
@Service
public class HelloServiceImpl implements HelloService {
	...
    private ApplicationContext applicationContext;
    @EventListener(classes = ContextRefreshedEvent.class)
    public void applicationContextEvent(ContextRefreshedEvent event) {
        applicationContext = event.getApplicationContext();
    }
    ...
}

根本原因:Spring在解析标注有此注解的方法的时候是这么解析的:

public class EventListenerMethodProcessor implements SmartInitializingSingleton, ApplicationContextAware, BeanFactoryPostProcessor {
	...
	private void processBean(final String beanName, final Class<?> targetType) {
		...
			Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
		...
	}
	...
}

这里的context.getType(beanName)就是问题的关键。因为Spring默认给我们使用的是JDK Proxy代理(此处只考虑被代理的情况,我相信没有人的应用不使用代理的吧),所以此处getType拿到的默认就是个Proxy,显然它是它是找不到我们对应方法的(因为方法在impl的实现类里,接口里可以木有)
其实Spring的异常信息里已经说得很清楚了错误原因,再一次感叹Spring的错误消息的完善性,真的非常非常赞,特别有助于我们定位问题和解决问题

另外有一个小细节:标注有@EventListener注解(包括@TransactionalEventListener)的方法的访问权限最低是protected的
另外可以在监听方法上标注@Order来控制执行顺序哦,一般人我不告诉他~

知道了原因,从来都不缺解决方案:

强制使用CGLIB动态代理机制
监听器(@EventListener)单独写在一个@Compnent里。当然你可以使用内部类没关系,如下也是ok的,若需要高内聚小姑的话可以这么写:

@Slf4j
@Service
public class HelloServiceImpl implements HelloService {
	...
	// 这里用private是木有关系的  需要注意的是若你使用内部类,建议务必是static的  否则可能报错如下:
	// Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'helloServiceImpl' is expected to be of type 'com.fsx.service.HelloServiceImpl' but was actually of type 'com.sun.proxy.$Proxy35'
	// 因为static的类初始化不依赖于外部类,而非static得依赖外部类(所以若不是CGLIB代理  一样出问题)
    @Component
    private static class MyListener {
	    private ApplicationContext applicationContext;
        @EventListener(classes = ContextRefreshedEvent.class)
        public void applicationContextEvent(ContextRefreshedEvent event) {
            applicationContext = event.getApplicationContext();
        }
    }
	...
}

各大模式大比拼

观察者模式:它是设计模式里的一个术语。是一个非常经典的行为型设计模式。。猫叫了,主人醒了,老鼠跑了,这一经典的例子,是事件驱动模型在设计层面的体现。

发布订阅模式:很多人认为等同于观察者模式。但我的理解是两者唯一区别,是发布订阅模式需要有一个调度中心,而观察者模式不需要(观察者的列表可以直接由被观察者维护)。 但它俩混用没问题,一般都不会在表达上有歧义

消息队列MQ:中间件级别的消息队列(ActiveMQ,RabbitMQ),可以认为是发布订阅模式的一个具体体现

事件驱动->发布订阅->MQ,从抽象到具体。 因此MQ算是一个落地的产品了

EventSourcing:这个要关联到领域驱动设计。DDD对事件驱动也是非常地青睐,领域对象的状态完全是由事件驱动来控制。比如有著名的CQRS架构~~~

CQRS架构和微服务的关系:微服务的目的是为了从业务角度拆分(职责分离)当前业务领域的不同业务模块到不同的服务,每个微服务之间的数据完全独立,它们之间的交互可以通过SOA RPC调用(耦合比较高),也可以通过EDA 消息驱动(耦合比较低,比如我们常用的分布式产品:MQ)。

这类模式的优缺点

有点:
支持简单的广播通信,自动通知所有已经订阅过的对象
目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用(保持职责单一,解耦)
观察者模式分离了观察者和被观察者二者的责任,这样让类之间各自维护自己的功能,专注于自己的功能,会提高系统的可维护性和可重用性。

缺点:
如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间
如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值