Spring/ Spring Boot 提供了一套事件监听机制,可以实现观察者模式。涉及到的几个核心类和接口如下
1、ApplicationEvent
ApplicationEvent(应用程序事件)它是一个抽象类,相当于观察者模式中的观察目标。
public abstract class ApplicationEvent extends EventObject {
/** use serialVersionUID from Spring 1.2 for interoperability. */
private static final long serialVersionUID = 7099057708183571937L;
/** System time when the event happened. */
private final long timestamp;
/**
* Create a new {@code ApplicationEvent}.
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
/**
* Return the system time in milliseconds when the event occurred.
*/
public final long getTimestamp() {
return this.timestamp;
}
}
ApplicationEvent 主要的核心是类构造器,它可以初始化一个 source 事件关联对象,以便在事件监听器中获取并通知更新。
2、 ApplicationListener
ApplicationListener(应用程序事件监听器)是一个接口,相当于观察者模式中的观察者。
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
ApplicationListener 继承自 Java 中的 EventListener 事件监听接口,ApplicationListener 类中只有一个 onApplicationEvent 方法,当指定监听的事件被发布后就会被触发执行,可以通过 event 获取事件中的关联对象。
3、 ApplicationEventPublisher
事件发布接口,封装了事件发布功能的基础接口。
public interface ApplicationEventPublisher {
/**
* Notify all matching listeners registered with this
* application of an application event. Events may be framework events
* (such as ContextRefreshedEvent) or application-specific events.
* Such an event publication step is effectively a hand-off to the
* multicaster and does not imply synchronous/asynchronous execution
* or even immediate execution at all. Event listeners are encouraged
* to be as efficient as possible, individually using asynchronous
* execution for longer-running and potentially blocking operations.
* @param event the event to publish
* @see #publishEvent(Object)
* @see org.springframework.context.event.ContextRefreshedEvent
* @see org.springframework.context.event.ContextClosedEvent
*/
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
/**
* Notify all matching listeners registered with this
* application of an event.
* If the specified {@code event} is not an {@link ApplicationEvent},
* it is wrapped in a {@link PayloadApplicationEvent}.
*
Such an event publication step is effectively a hand-off to the
* multicaster and does not imply synchronous/asynchronous execution
* or even immediate execution at all. Event listeners are encouraged
* to be as efficient as possible, individually using asynchronous
* execution for longer-running and potentially blocking operations.
* @param event the event to publish
* @since 4.2
* @see #publishEvent(ApplicationEvent)
* @see PayloadApplicationEvent
*/
void publishEvent(Object event);
}
ApplicationEventPublisher 有一个默认接口方法和接口方法,接口方法需要由具体的子类实现。
4、 ApplicationContext
ApplicationContext 是 Spring 框架中的核心容器。ApplicationContext 接口继承了 ApplicationEventPublisher 接口,所以可以用 ApplicationContext 发布事件。
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
@Nullable
String getId();
String getApplicationName();
String getDisplayName();
long getStartupDate();
@Nullable
ApplicationContext getParent();
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}
使用的逻辑就是通过 ApplicationEventPublisher 或者 ApplicationContext 容器发布 ApplicationEvent 事件并关联事件对象,然后 ApplicationListener 监听该事件,当事件发布后,监听器就会收执行并获取到事件及关联对象。
1、新增被观察者
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class TestEvent extends ApplicationEvent {
public testEvent(Object source) {
super(source);
}
}
2、新增观察者
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
@RequiredArgsConstructor
public class TestListener implements ApplicationListener<TestEvent> {
@NonNull
private String result;
@Async
@Override
public void onApplicationEvent(TestEvent event) {
// 更新结果
updateResult(event);
}
private void updateResult(TestEvent event) {
this.result = (String) event.getSource();
System.out.printf("result更新为:", this.result );
}
}
3、新增测试配置类
@Slf4j
@Configuration
public class ObserverConfiguration {
/**
CommandLineRunner 和 ApplicationRunner。他们的执行时机为容器启动完成的时候。
这两个接口中有一个 run 方法,我们只需要实现这个方法即可
*/
@Bean
public CommandLineRunner commandLineRunner(ApplicationContext context) {
return (args) -> {
log.info("发布事件:更新result?");
context.publishEvent(new TestEvent("最新result"));
};
}
@Bean
public ReaderListener readerListener1(){
return new ReaderListener();
}
}
输出结果为:
result更新为:最新result
实际中的观察者模式应用应该应用于具体业务,比如电商支付场景,在用户支付完后可以发布一个支付事件,然后会有扣减积分,短信通知、赠送优惠券等一系列后续的事件监听器观察者,这样可以实现业务解耦。如果大家有用到消息中间件,其实也是观察者模式中发布订阅模式的概念。利用 Spring 中的事件监听机制可以轻松实现观察者模式,观察目标也不需要维护观察者列表了,相当于发布-订阅模式,它们之间是完全解耦的,需要注意的是每个观察者需要创建一个 Bean。