设计模式——观察者模式
1. 场景
- 用户登录成功后,需要发送邮件或推送消息;
- 订单状态变化时,需要异步执行推送消息等。
2. 什么是观察者模式
以上场景,可以总结为如下需求:
用户登录成功、订单状态改变等可以理解为一个事件,当触发了事件时,需要做多种互不相关的操作,如发送邮件、推送消息、调用某个方法逻辑。这里假设一个需求,当订单状态发生变化时,需要发送邮件和站内信提醒用户,并写入日志表中。
那我们可以通过观察者模式来解决这个问题
定义事件
/**
* 事件
* @author Tarzan写bug
* @since 2022/08/02
*/
public class Event {
private Object source;
public Event(Object source) {
this.source = source;
}
public Object getSource() {
return source;
}
}
定义触发器
/**
* 触发器接口
* @author Tarzan写bug
* @since 2022/08/02
*/
public interface Trigger {
/**
* 添加执行者
* @param executor
*/
void addExecutor(Executor executor);
/**
* 事件触发
* @param event
*/
void touchOff(Event event);
}
订单状态改变触发器
/**
* 订单状态改变触发器
* @author Tarzan写bug
* @since 2022/08/02
*/
public class OrderStatusChangeTrigger implements Trigger {
private List<Executor> executorList = new ArrayList<>();
@Override
public void addExecutor(Executor executor) {
executorList.add(executor);
}
@Override
public void touchOff(Event event) {
for (Executor executor : executorList) {
executor.execute(event);
}
}
}
定义执行器
/**
* 执行器接口
* @author Tarzan写bug
* @since 2022/08/02
*/
public interface Executor {
/**
* 执行方法
* @param event
*/
void execute(Event event);
}
邮件执行器
/**
* 邮件执行器
* @author Tarzan写bug
* @since 2022/08/02
*/
public class EmailExecutor implements Executor {
@Override
public void execute(Event event) {
System.out.println("邮件服务执行:" + event.getSource());
}
}
日志执行器
/**
* 日志执行器
* @author Tarzan写bug
* @since 2022/08/02
*/
public class LogExecutor implements Executor {
@Override
public void execute(Event event) {
System.out.println("日志服务执行:" + event.getSource());
}
}
站内信执行器
/**
* 站内信执行器
* @author Tarzan写bug
* @since 2022/08/02
*/
public class StationLetterExecutor implements Executor {
@Override
public void execute(Event event) {
System.out.println("站内信服务执行:" + event.getSource());
}
}
主类
/**
* @author Tarzan写bug
* @since 2022/08/02
*/
public class ObserverMain {
public static void main(String[] args) {
// 创建触发器
OrderStatusChangeTrigger trigger = new OrderStatusChangeTrigger();
// 加入执行器
trigger.addExecutor(new EmailExecutor());
trigger.addExecutor(new LogExecutor());
trigger.addExecutor(new StationLetterExecutor());
// 发布订单状态变化事件
trigger.touchOff(new Event("订单状态变化了"));
}
}
类图:
总结:观察者模式主要是触发事件和处理事件进行解耦,当一个事件需要新的处理逻辑时,只需要实现执行器接口即可,符合开闭原则。而在Spring中的观察者模式除了解耦外,还能各个执行器异步执行。
3. Spring中的观察者模式
上面的需求场景用Spring中的观察者模式来实现:
订单状态改变事件:继承ApplicationEvent类
public class OrderStatusChangeEvent extends ApplicationEvent {
public OrderStatusChangeEvent(Object source) {
super(source);
}
}
邮件执行器:实现ApplicationListener接口,并将需要监听的事件类做为泛型加入;用@Component或其他注解加入到Spring容器中
@Component
public class EmailListener implements ApplicationListener<OrderStatusChangeEvent> {
@Override
public void onApplicationEvent(OrderStatusChangeEvent orderStatusChangeEvent) {
System.out.println("邮件监听事件:" + orderStatusChangeEvent.getSource());
}
}
日志订阅者
@Component
public class LogListener implements ApplicationListener<OrderStatusChangeEvent> {
@Override
public void onApplicationEvent(OrderStatusChangeEvent orderStatusChangeEvent) {
System.out.println("日志监听事件:" + orderStatusChangeEvent.getSource());
}
}
站内信订阅者
@Component
public class StationLetterListener implements ApplicationListener<OrderStatusChangeEvent> {
@Override
public void onApplicationEvent(OrderStatusChangeEvent orderStatusChangeEvent) {
System.out.println("站内信监听事件:" + orderStatusChangeEvent.getSource());
}
}
事件发布:实现ApplicationEventPublisherAware接口,获取ApplicationEventPublisher,用该类发布事件;并@Component加入到Spring容器中
@Component
public class OrderStatusChangePublisher implements ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
public void publish() {
publisher.publishEvent(new OrderStatusChangeEvent("订单状态变化"));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}
4. Spring观察者模式原理
-
可以从发布者代码入手,即
ApplicationEventPublisher.publishEvent()开始,会调用到实现类AbstractApplicationContext.publishEvent()protected void publishEvent(Object event, @Nullable ResolvableType eventType) { Assert.notNull(event, "Event must not be null"); Object 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 { this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType); } if (this.parent != null) { if (this.parent instanceof AbstractApplicationContext) { ((AbstractApplicationContext)this.parent).publishEvent(event, eventType); } else { this.parent.publishEvent(event); } } } -
重点在
this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);中,该方法会调用实现类SimpleApplicationEventMulticaster.multicastEvent()public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event); Executor executor = this.getTaskExecutor(); Iterator var5 = this.getApplicationListeners(event, type).iterator(); while(var5.hasNext()) { ApplicationListener<?> listener = (ApplicationListener)var5.next(); if (executor != null) { executor.execute(() -> { this.invokeListener(listener, event); }); } else { this.invokeListener(listener, event); } } }方法的作用:获取对应事件的执行者(事件监听者),然后在线程池中执行每一个观察者的逻辑
-
分析
getApplicationListeners()如何获取事件监听者protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) { Object source = event.getSource(); Class<?> sourceType = source != null ? source.getClass() : null; AbstractApplicationEventMulticaster.ListenerCacheKey cacheKey = new AbstractApplicationEventMulticaster.ListenerCacheKey(eventType, sourceType); AbstractApplicationEventMulticaster.ListenerRetriever retriever = (AbstractApplicationEventMulticaster.ListenerRetriever)this.retrieverCache.get(cacheKey); if (retriever != null) { return retriever.getApplicationListeners(); } else if (this.beanClassLoader == null || ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) && (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader))) { synchronized(this.retrievalMutex) { retriever = (AbstractApplicationEventMulticaster.ListenerRetriever)this.retrieverCache.get(cacheKey); if (retriever != null) { return retriever.getApplicationListeners(); } else { retriever = new AbstractApplicationEventMulticaster.ListenerRetriever(true); Collection<ApplicationListener<?>> listeners = this.retrieveApplicationListeners(eventType, sourceType, retriever); this.retrieverCache.put(cacheKey, retriever); return listeners; } } } else { return this.retrieveApplicationListeners(eventType, sourceType, (AbstractApplicationEventMulticaster.ListenerRetriever)null); } } -
retrieveApplicationListeners()private Collection<ApplicationListener<?>> retrieveApplicationListeners(ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable AbstractApplicationEventMulticaster.ListenerRetriever retriever) { List<ApplicationListener<?>> allListeners = new ArrayList(); LinkedHashSet listeners; LinkedHashSet listenerBeans; synchronized(this.retrievalMutex) { listeners = new LinkedHashSet(this.defaultRetriever.applicationListeners); listenerBeans = new LinkedHashSet(this.defaultRetriever.applicationListenerBeans); } Iterator var7 = listeners.iterator(); while(var7.hasNext()) { ApplicationListener<?> listener = (ApplicationListener)var7.next(); if (this.supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { retriever.applicationListeners.add(listener); } allListeners.add(listener); } } if (!listenerBeans.isEmpty()) { ConfigurableBeanFactory beanFactory = this.getBeanFactory(); Iterator var15 = listenerBeans.iterator(); while(var15.hasNext()) { String listenerBeanName = (String)var15.next(); try { if (this.supportsEvent(beanFactory, listenerBeanName, eventType)) { ApplicationListener<?> listener = (ApplicationListener)beanFactory.getBean(listenerBeanName, ApplicationListener.class); if (!allListeners.contains(listener) && this.supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { if (beanFactory.isSingleton(listenerBeanName)) { retriever.applicationListeners.add(listener); } else { retriever.applicationListenerBeans.add(listenerBeanName); } } allListeners.add(listener); } } else { Object listener = beanFactory.getSingleton(listenerBeanName); if (retriever != null) { retriever.applicationListeners.remove(listener); } allListeners.remove(listener); } } catch (NoSuchBeanDefinitionException var11) { } } } AnnotationAwareOrderComparator.sort(allListeners); if (retriever != null && retriever.applicationListenerBeans.isEmpty()) { retriever.applicationListeners.clear(); retriever.applicationListeners.addAll(allListeners); } return allListeners; } private boolean supportsEvent(ConfigurableBeanFactory beanFactory, String listenerBeanName, ResolvableType eventType) { Class<?> listenerType = beanFactory.getType(listenerBeanName); if (listenerType != null && !GenericApplicationListener.class.isAssignableFrom(listenerType) && !SmartApplicationListener.class.isAssignableFrom(listenerType)) { if (!this.supportsEvent(listenerType, eventType)) { return false; } else { try { BeanDefinition bd = beanFactory.getMergedBeanDefinition(listenerBeanName); ResolvableType genericEventType = bd.getResolvableType().as(ApplicationListener.class).getGeneric(new int[0]); return genericEventType == ResolvableType.NONE || genericEventType.isAssignableFrom(eventType); } catch (NoSuchBeanDefinitionException var7) { return true; } } } else { return true; } }方法作用:通过Bean容器中获取符合事件类的Bean
5. Spring Security中的观察者模式应用
由于最近在使用Spring Security,这个框架中也有使用到观察者模式
在资源服务器校验token过滤器中,OAuth2AuthenticationProcessingFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
boolean debug = logger.isDebugEnabled();
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
try {
Authentication authentication = this.tokenExtractor.extract(request);
if (authentication == null) {
if (this.stateless && this.isAuthenticated()) {
if (debug) {
logger.debug("Clearing security context.");
}
SecurityContextHolder.clearContext();
}
if (debug) {
logger.debug("No token in request, will continue chain.");
}
} else {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
if (authentication instanceof AbstractAuthenticationToken) {
AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken)authentication;
needsDetails.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
Authentication authResult = this.authenticationManager.authenticate(authentication);
if (debug) {
logger.debug("Authentication success: " + authResult);
}
this.eventPublisher.publishAuthenticationSuccess(authResult);
SecurityContextHolder.getContext().setAuthentication(authResult);
}
} catch (OAuth2Exception var9) {
SecurityContextHolder.clearContext();
if (debug) {
logger.debug("Authentication request failed: " + var9);
}
this.eventPublisher.publishAuthenticationFailure(new BadCredentialsException(var9.getMessage(), var9), new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
this.authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(var9.getMessage(), var9));
return;
}
chain.doFilter(request, response);
}
在校验成功后会调用this.eventPublisher.publishAuthenticationSuccess(),在校验失败后调用this.eventPublisher.publishAuthenticationFailure()
在接口类AuthenticationEventPublisher中有多个实现,拿实现类DefaultAuthenticationEventPublisher为例,典型地应用了Spring的观察者模式的实现,实现了ApplicationEventPublisherAware,在publishAuthenticationSuccess()中调用this.applicationEventPublisher.publishEvent
public void publishAuthenticationSuccess(Authentication authentication) {
if (this.applicationEventPublisher != null) {
this.applicationEventPublisher.publishEvent(new AuthenticationSuccessEvent(authentication));
}
}
6. 委派模式+观察者模式
回到假设的场景代码,可以发现执行器都是实现相同的一个接口,如果一个执行器没有实现该接口是不是不能对事件进行处理了?即执行器处理的方法名不一样时,这时应该怎么办呢?这时可以通过委派模式,及Java8中Consumer传参类型实现
事件
/**
* @author Tarzan写bug
* @since 2022/08/02
*/
public class Event {
private Object source;
public Event(Object source) {
this.source = source;
}
public Object getSource() {
return source;
}
}
邮件执行器
/**
* @author Tarzan写bug
* @since 2022/08/02
*/
public class EmailExecutor {
public void emailExecute(Object event) {
System.out.println("邮件服务执行:" + ((Event)event).getSource());
}
}
日志执行器
/**
* @author Tarzan写bug
* @since 2022/08/02
*/
public class LogExecutor {
public void logExecute(Object event) {
System.out.println("日志服务执行:" + ((Event)event).getSource());
}
}
站内信执行器
/**
* @author Tarzan写bug
* @since 2022/08/02
*/
public class StationLetterExecutor {
public void stationLetterExecute(Object event) {
System.out.println("站内信服务执行:" + ((Event)event).getSource());
}
}
触发器接口
/**
* @author Tarzan写bug
* @since 2022/08/02
*/
public interface Trigger {
/**
* 添加执行者
* @param executor
*/
void addExecutor(EventHandler handler);
/**
* 事件触发
* @param event
*/
void touchOff(Event event);
}
订单状态改变触发器
/**
* @author Tarzan写bug
* @since 2022/08/02
*/
public class OrderStatusChangeTrigger implements Trigger {
private List<EventHandler> eventHandlerList = new ArrayList<>();
@Override
public void addExecutor(EventHandler handler) {
eventHandlerList.add(handler);
}
@Override
public void touchOff(Event event) {
for (EventHandler handler : eventHandlerList) {
handler.doHandler(event);
}
}
}
主类
/**
* @author Tarzan写bug
* @since 2022/08/02
*/
public class ObserverMain {
public static void main(String[] args) {
// 创建触发器
OrderStatusChangeTrigger trigger = new OrderStatusChangeTrigger();
// 执行器
EmailExecutor emailExecutor = new EmailExecutor();
LogExecutor logExecutor = new LogExecutor();
StationLetterExecutor stationLetterExecutor = new StationLetterExecutor();
// 加入执行器
trigger.addExecutor(new EventHandler(emailExecutor::emailExecute));
trigger.addExecutor(new EventHandler(logExecutor::logExecute));
trigger.addExecutor(new EventHandler(stationLetterExecutor::stationLetterExecute));
// 发布订单状态变化事件
trigger.touchOff(new Event("订单状态变化了"));
}
}
可以看到每个执行器的处理方法名不一样时,通过EventHandler这个委派类来处理不同执行器的不同方法名。
谢谢阅读,就分享到这,未完待续...
欢迎同频共振的那一部分人
作者公众号:Tarzan写bug