Spring Events--观察者模式在spring中的应用
Spring Events--观察者模式在spring中的应用
参考:
1. 观察者模式
- 当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
1.1 介绍
-
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
-
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
-
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
-
关键代码:在抽象类里有一个 ArrayList 存放观察者们。
使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
- 注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
1.2 观察者模式简单实践
- Observer
/**
* @title
* @description
* @author xingzhou
* @updateTime 2022/4/5 7:59 上午
* @throws
*/
interface Observer {
/**
* 当主题状态改变时,会将一个String类型字符传入该方法的参数,
* 每个观察者都需要实现该方法
*/
public void update(String info);
}
- Subject
/**
* @author xingzhou
* @version 1.0.0
* @ClassName Subject.java
* @Description 主题接口
* @createTime 2022年04月05日 07:42:00
*/
interface Subject {
/**
* 添加观察者
* @param obj
*/
void addObserver(Observer obj);
/**
* 移除观察者
* @param obj
*/
void deleteObserver(Observer obj);
/**
* 通知观察者
*/
void notifyObserver();
}
- StudentObserver
/**
* @author xingzhou
* @version 1.0.0
* @ClassName StudentObserver.java
* @Description TODO
* @createTime 2022年04月05日 07:49:00
*/
public class StudentObserver implements Observer {
/**
* 保存一个Subject的引用,以后如果可以想取消订阅,有了这个引用会比较方便
*/
private TeacherSubject t;
/**
* 学生的姓名,用来标识不同的学生对象
*/
private String name;
/**
* 构造器用来注册观察者
*/
public StudentObserver(String name, TeacherSubject t) {
this.name = name;
this.t = t;
//每新建一个学生对象,默认添加到观察者的行列
t.addObserver(this);
}
@Override
public void update(String info) {
System.out.println(name + "得到作业:" + info);
}
}
- TeacherSubject
/**
* @author xingzhou
* @version 1.0.0
* @ClassName TeacherSubject.java
* @Description TODO
* @createTime 2022年04月05日 07:46:00
*/
public class TeacherSubject implements Subject {
private List<Observer> observers=new ArrayList<Observer>();
//记录状态的字符串
private String info;
@Override
public void addObserver(Observer obj) {
observers.add(obj);
}
@Override
public void deleteObserver(Observer obj) {
int i = observers.indexOf(obj);
if(i>=0){
observers.remove(obj);
}
}
@Override
public void notifyObserver() {
for(int i=0;i<observers.size();i++){
Observer o=(Observer)observers.get(i);
o.update(info);
}
}
//布置作业的方法,在方法最后,需要调用notifyObserver()方法,通知所有观察者更新状态
public void setHomework(String info){
this.info=info;
System.out.println("今天的作业是"+info);
this.notifyObserver();
}
}
- TestObservable
/**
* @author xingzhou
* @version 1.0.0
* @ClassName TestObservable.java
* @Description TODO
* @createTime 2022年04月05日 07:52:00
*/
public class TestObservable {
public static void main(String[] args) {
TeacherSubject teacher=new TeacherSubject();
StudentObserver zhangSan=new StudentObserver("张三", teacher);
StudentObserver LiSi=new StudentObserver("李四", teacher);
StudentObserver WangWu=new StudentObserver("王五", teacher);
teacher.setHomework("第二页第六题");
teacher.setHomework("第三页第七题");
teacher.setHomework("第五页第八题");
}
}
- 输出结果
2. spring监听机制——观察者模式的应用
spring中的事件驱动模型也叫作发布订阅模式,是观察者模式的一个典型的应用。
2.1 使用方法
spring监听模式需要三个组件:
-
- 事件,需要继承ApplicationEvent,即观察者模式中的"主题",可以看做一个普通的bean类,用于保存在事件监听器的业务逻辑中需要的一些字段;
-
- 事件监听器,需要实现ApplicationListener
,即观察者模式中的"观察者",在主题发生变化时收到通知,并作出相应的更新,加泛型表示只监听某种类型的事件;
- 事件监听器,需要实现ApplicationListener
-
- 事件发布器,需要实现ApplicationEventPublisherAware,获取spring底层组件ApplicationEventPublisher,并调用其方法发布事件,即"通知"观察者。
其中,事件监听器和事件发布器需要在springIOC容器中注册。
2.2 简单实践
- Event
/**
* @author xingzhou
* @version 1.0.0
* @ClassName Event.java
* @Description TODO
* @createTime 2022年04月05日 11:00:00
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Event {
private Long id;
private String name;
private Integer age;
}
- BusinessEvent
/**
* @author xingzhou
* @version 1.0.0
* @ClassName BusinessEvent.java
* @Description spring监听机制中的"事件"
* @createTime 2022年04月05日 08:21:00
*/
public class BusinessEvent extends ApplicationEvent {
/**
* 该类型事件携带的信息
*/
private Event eventData;
public BusinessEvent( Object source) {
super(source);
}
/**
*
* @param source 最初触发该事件的对象
* @param eventData 该类型事件携带的信息
*/
public BusinessEvent(Object source, Event eventData) {
super(source);
this.eventData = eventData;
}
public Event getEventData(){
return eventData;
}
}
- BusinessPublisher
/**
* @author xingzhou
* @version 1.0.0
* @ClassName BusinessPublisher.java
* @Description spring事件监听机制中的"事件发布器"
* @createTime 2022年04月05日 10:49:00
*/
@Component
public class BusinessPublisher implements ApplicationEventPublisherAware {
/**
* spring提供的事件发布组件
*/
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
/**
* 发布事件
*/
public void publishEvent(BusinessEvent businessEvent) {
System.out.println("发布事件:" + businessEvent.getEventData().getId());
this.applicationEventPublisher.publishEvent(businessEvent);
}
}
- BusinessListener
/**
* @author xingzhou
* @version 1.0.0
* @ClassName BusinessListener.java
* @Description TODO
* @createTime 2022年04月05日 10:48:00
*/
@Component
public class BusinessListener implements ApplicationListener<BusinessEvent> {
/**
* 监听到事件后做的处理
* @param event
*/
@Override
public void onApplicationEvent(BusinessEvent event) {
System.out.println("监听到事件:" + event.getEventData().getId());
}
}
- AsyncConfig
/**
* @author xingzhou
* @version 1.0.0
* @ClassName AsyncConfig.java
* @Description TODO
* @createTime 2022年04月04日 23:32:00
*/
@Configuration
@EnableAsync
@Slf4j
public class AsyncConfig implements AsyncConfigurer {
/**
* 自定义异步线程池,若不重写会使用默认的线程池
*/
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
// 核心线程数
taskExecutor.setCorePoolSize(2);
// 最大线程数
taskExecutor.setMaxPoolSize(10);
// 队列大小
taskExecutor.setQueueCapacity(15);
// 线程名的前缀
taskExecutor.setThreadNamePrefix("async-thread-");
taskExecutor.initialize();
return taskExecutor;
}
/**
* 捕捉IllegalArgumentException异常
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new MyAsyncExceptionHandler();
}
class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
log.info("TASK Exception message - " + throwable.getMessage());
log.info("Method name - " + method.getName());
for (Object param : objects) {
log.info("Parameter value - " + param);
}
}
}
}
- SyncListener
/**
* @author xingzhou
* @version 1.0.0
* @ClassName SyncListener.java
* @Description TODO
* @createTime 2022年03月31日 15:23:00
*/
@Slf4j
@Component
@Order(1)
public class SyncListener {
@Async
@EventListener(BusinessEvent.class)
public void createSuccess(BusinessEvent businessEvent) {
Event eventData = businessEvent.getEventData();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("f data" + eventData.getName());
log.info("receive event data --> {}", eventData.getName());
}
}
2.3 源码分析
在观察者模式中,主题发生改变时,会"通知"观察者作出相应的操作,实现方式是获取观察者列表,然后遍历、分别执行一遍其更新方法。那么,在spring事件监听中,事件发生变化时,是如何"通知"到观察者的呢?如上面的demo所述,我们是通过spring的组件ApplicationEventListener接口执行publishEvent方法发布事件的,而这个抽象方法在spring中只有一个实现,就是AbstractrApplicationContext,这是一个容器类。我们来跟进一下这个容器类对于发布事件的实现方法源码:
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
// Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
}
}
// Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else { //获取事件广播器、然后广播事件
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
粗体部分语句:首先获取事件广播器、然后广播事件。
所以问题分为两部分:如何获取事件广播器、怎样广播事件。
- 获取事件广播器
直接跟进上述语句——getApplicationEventMulticaster(),似乎找不到答案,因为这个方法是直接返回了AbstractApplicationContext类的属性。问题转化为:AbstractApplicationContext类中的事件广播器属性是什么时候被赋值的?这就要从容器创建说起了。springIOC容器创建有一个重要步骤——刷新容器refresh(),就是在AbstractApplicationContext中定义的,这个refresh()中包含了容器创建、初始化的诸多操作,其中两个步骤与事件监听有关,看一下源码
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
第一个步骤是initApplicationEventMulticaster,即初始化事件广播器,继续跟进源码会发现,是先从BeanFactory中获取,如果不存在,就新建一个。第二个步骤是registerListeners,即注册监听器,从容器中获取所有ApplicationEventListener类型的组件,添加进事件广播器。
- 广播事件
广播事件的方法是写在事件广播器的实现类——SimpleApplicationEventMulticater中的。
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); //遍历监听器,分别执行invokeListener
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
从源码中可以看出,SimpleApplicationEventMulticater从容器中获取所有的监听器列表,遍历列表,对每个监听器分别执行invokeListener方法,继续跟进invokeListener方法,它会调用一个doInvokeListener,在这个doInvokeListner中:
@SuppressWarnings({"unchecked", "rawtypes"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try { //调用监听器实现类的onApplicationEvent方法
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
// -> let's suppress the exception and just log a debug message.
Log logger = LogFactory.getLog(getClass());
if (logger.isDebugEnabled()) {
logger.debug("Non-matching event type for listener: " + listener, ex);
}
}
else {
throw ex;
}
}
}
终于看到我们熟悉的:onApplicationEvent方法,这就是暴露在外层、供我们使用的事件监听方法;
也就是在这里,实现了观察者模式中的——"通知"观察者进行更新的操作。