1-简介
Spring Application Event是Spring框架自带的进程内事件发布、订阅机制,可用于:
- 监听
Spring生命周期事件,以扩展Spring应用; - 自定义组件解耦&依赖倒置;
- 实现Domain-Driven-Development(DDD),即领域(事件)驱动开发;
2-如何使用Application Event
2.1-订阅事件的两种方式
2.1.1-实现ApplicationListener<EventType>接口
代码示例:
public class MyClass implements ApplicationListener<CustomerApplicationEvent> {
@Override
public void onApplicationEvent(CustomerApplicationEvent event) {
//...
}
}
优点:
- 应用全生命周期有效;
缺点:
- 事件必须继承自
ApplicationEvent抽象类; - 监听器必须实现
ApplicationEventListener<EventType>接口; - 一个监听器只能监听一类事件或所有事件,不能有选择的监听部分事件;
2.1.2-使用@EventListener注解
代码示例:
public class AnnotationListenerService {
@EventListener(ContextClosedEvent.class)
public void handleContextClosedEvent() {
log.info("ContextClosedEvent without method parameter");
}
@EventListener
public void handleAllEvent(CustomerApplicationEvent event) {
log.info("CustomerApplicationEvent: {}", event);
}
@EventListener(condition = "#event.eventType.name() == 'Add'")
public void handleAddEvent(CustomerApplicationEvent event) {
log.info("CustomerApplicationEvent Add: {}", event);
}
@EventListener(condition = "#event.eventType.name() == 'Delete'")
public void handleDeleteEvent(CustomerApplicationEvent event) {
log.info("CustomerApplicationEvent Delete: {}", event);
}
}
优点:
- 事件和监听器无须继承/实现其它类或接口,侵入性更低;
- 支持监听多种事件;
- 支持按条件筛选事件;
- 只须增加一个注解,即可实现异步监听;
缺点:
- 无法监听启动阶段/Bean初始化阶段的事件;
2.2-发布事件
public class EventPublishService {
// 1-注入`ApplicationEventPublisher`
private final ApplicationEventPublisher applicationEventPublisher;
public EventPublishService(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
// 2.1-初始化阶段即发布事件
applicationEventPublisher.publishEvent(new CustomerApplicationEvent(this, EventType.Add, "Init"));
}
public void publish(String message) {
// 2.2-发布继承自ApplicationEvent的事件
applicationEventPublisher.publishEvent(new CustomerApplicationEvent(this, EventType.Update, message));
// 2.3-发布任意事件
applicationEventPublisher.publishEvent(new CustomerEvent(EventType.Update, message));
}
}
3-应用案例
3.1-优雅停机
订阅ContextClosedEvent事件,并在订阅代码中关闭待资源并等待其得到释放:
public class NameListService {
// ...
// 延迟执行线程池
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
@EventListener(ContextClosedEvent.class)
public void onApplicationClosed() {
// 服务停止时,关闭并等待线程池结束
scheduler.awaitTermination(10, TimeUnit.SECONDS);
}
// ...
}
3.2-组件解耦/适配
以下示例展示了如何扩展Spring Caffeine以实现分布式缓存:
第二步:通过CaffeineCache包装器,发布缓存更新事件
public class CaffeineCacheWithEventDecorator<K, V> implements Cache<K, V>, CacheLocalOperations<K> {
private final String name;
private final Cache<@NonNull K, V> cache;
private final ApplicationEventPublisher eventPublisher;
public CaffeineCacheWithEventDecorator(String name, Cache<@NonNull K, V> cache, ApplicationEventPublisher eventPublisher) {
this.name = name;
this.cache = cache;
this.eventPublisher = eventPublisher;
}
/// 重点1: 重载以下方法、委托给被包装缓存,并发布事件
@Override
public void invalidate(K key) {
cache.invalidate(key);
eventPublisher.publishEvent(CacheChangedEvent.builder().type(CacheChangedEvent.ChangeType.Evict).cacheName(name).keys(List.of(key)).build());
}
@Override
public void invalidateAll(@NonNull Iterable<? extends K> keys) {
cache.invalidateAll(keys);
List<K> keyList = new ArrayList<>();
keys.forEach(keyList::add);
eventPublisher.publishEvent(CacheChangedEvent.builder().type(CacheChangedEvent.ChangeType.Evict).cacheName(name).keys(keyList).build());
}
@Override
public void invalidateAll() {
cache.invalidateAll();
eventPublisher.publishEvent(CacheChangedEvent.builder().type(CacheChangedEvent.ChangeType.Evict).cacheName(name).build());
}
@Override
public void cleanUp() {
cache.cleanUp();
eventPublisher.publishEvent(CacheChangedEvent.builder().type(CacheChangedEvent.ChangeType.Evict).cacheName(name).build());
}
/// 重点2: 以下是不发通知本地缓存操作方法,用于收到通知后刷新本地缓存、以避免事件无限循环
public void localEvict(Collection<? extends K> keys) {
cache.invalidateAll(keys);
}
public void localEvictAll() {
cache.invalidateAll();
}
// 其它方法直接委托给cache
// ...
}
异步缓存也需要写个包装器,只需重载返回同步缓存的方法即可:
public class CaffeineAsyncCacheWithEventDecorator<K, V> implements AsyncCache<@NotNull K, V> {
private final String name;
private final AsyncCache<@NotNull K, V> cache;
private final ApplicationEventPublisher eventPublisher;
public CaffeineAsyncCacheWithEventDecorator(String name, AsyncCache<@NotNull K, V> cache, ApplicationEventPublisher eventPublisher) {
this.name = name;
this.cache = cache;
this.eventPublisher = eventPublisher;
}
@Override
public @NotNull CaffeineCacheWithEventDecorator<@NotNull K, V> synchronous() {
Cache<@NotNull K, V> c = cache.synchronous();
if (c instanceof CaffeineCacheWithEventDecorator) {
return (CaffeineCacheWithEventDecorator<K, V>) c;
} else {
return new CaffeineCacheWithEventDecorator<>(name, c, eventPublisher);
}
}
// 其它方法直接委托给cache
// ...
}
第二步:使用CacheManagerCustomizer自定义缓存的初始化过程,以便注册包装器
@Slf4j
public class CaffeineCacheManagerCustomizer implements CacheManagerCustomizer<CaffeineCacheManager> {
public static final String PROPERTIES_PREFIX = "spring.cache.caffeine";
private final CaffeineCacheProperties properties;
private final ApplicationEventPublisher eventPublisher;
public CaffeineCacheManagerCustomizer(CaffeineCacheProperties properties, ApplicationEventPublisher eventPublisher) {
this.properties = properties;
this.eventPublisher = eventPublisher;
}
@Override
public void customize(CaffeineCacheManager cacheManager) {
if (properties.caches == null) {
return;
}
for (CaffeineCacheDefine define : properties.caches) {
Caffeine<@NotNull Object, @NotNull Object> caffeine = Caffeine.from(define.spec);
if (properties.isAsyncCacheMode()) {
final AsyncCache<Object, Object> cache = caffeine.buildAsync();
final CaffeineAsyncCacheWithEventDecorator<Object, Object> decorator = new CaffeineAsyncCacheWithEventDecorator<>(define.name, cache, eventPublisher);
cacheManager.registerCustomCache(define.name, decorator);
} else {
final Cache<Object, Object> cache = caffeine.build();
final CaffeineCacheWithEventDecorator<Object, Object> decorator = new CaffeineCacheWithEventDecorator<>(define.name, cache, eventPublisher);
cacheManager.registerCustomCache(define.name, decorator);
}
log.info("已配置分布式缓存:{}", define.name);
}
}
@Data
@ConfigurationProperties(PROPERTIES_PREFIX)
public static class CaffeineCacheProperties {
private boolean asyncCacheMode = false;
private boolean allowNullValues = true;
private List<CaffeineCacheDefine> caches;
}
@Data
public static class CaffeineCacheDefine {
private String name;
private String spec;
}
}
第三步:使用Ignite之类网格框架,实现分布式缓存同步
@Slf4j
public class IgniteSyncCaffeineCacheManagerCustomizer implements CacheManagerCustomizer<CaffeineCacheManager> {
public static final String IGNITE_MESSAGE_TOPIC = "event.spring.cache.changed";
private final Ignite ignite;
private final JsonMapper jsonMapper;
private final ApplicationEventPublisher applicationEventPublisher;
private CaffeineCacheManager cacheManager;
public IgniteSyncCaffeineCacheManagerCustomizer(Ignite ignite, JsonMapper jsonMapper, ApplicationEventPublisher applicationEventPublisher) {
this.ignite = ignite;
this.jsonMapper = jsonMapper;
this.applicationEventPublisher = applicationEventPublisher;
//注册ignite的消息
ignite.message().localListen(IGNITE_MESSAGE_TOPIC, this::onIgniteChangeMessage);
}
/// 优雅停机:停机时移除事件监听
@EventListener(ContextClosedEvent.class)
public void onApplicationClose(@SuppressWarnings("unused") ContextClosedEvent event) {
ignite.message().stopLocalListen(IGNITE_MESSAGE_TOPIC, this::onIgniteChangeMessage);
}
/// 本地缓存变更事件转换为分布式消息发送
@Async
@EventListener
public void onCacheChangedEvent(CacheChangedEvent event) {
log.trace("Receive CacheChangedEvent:{}", event);
try {
// 转为Ignite消息发送
String message = cacheMessageToString(event);
if (message != null) {
ignite.message().send(IGNITE_MESSAGE_TOPIC, message);
}
} catch (Exception e) {
log.error("Send Ignite Message failed, topic={}", IGNITE_MESSAGE_TOPIC, e);
}
}
/// 收到分布式消息时,更新本地缓存
private boolean onIgniteChangeMessage(UUID nodeId, Object message) {
//跳过非字符串消息
if (!(message instanceof String strMsg)) {
log.trace("Ignore non string message:{}", message);
return true;
}
log.trace("Receive Ignite message from node=[{}],cache changed:[{}]", nodeId, strMsg);
// 解析缓存消息
CacheChangedEvent objMsg = parseCacheMessage(strMsg);
//跳过无效消息
if (objMsg == null || objMsg.getCacheName() == null || objMsg.getType() == null) {
return true;
}
//跳过自己发送的通知
if (!nodeId.equals(ignite.cluster().localNode().id())) {
log.trace("Ignore self send Ignite message from node[{}]", nodeId);
// 更新本地缓存
updateLocalCache(objMsg);
}
// 发送分布式缓存更新系统事件
sendDistributedCacheChangedEvent(objMsg);
return true;
}
/// 通过CacheManagerCustomizer获取到缓存管理器实例
@Override
public void customize(CaffeineCacheManager cacheManager) {
this.cacheManager = cacheManager;
}
// 具体消息序列化/反序列化、发送代码,不是重点
// ...
}