1. 前言 当用户登出时,我们的服务需要对用户Session进行清理。在Spring Security中则是通过监听session的销毁事件来触发会话信息表的相关清理工作。所以需要在系统中注册该Session的监听器来完成清理工作 ,此流程就运用到了观察者模式 。
鉴于这是一个较为容易理解的观察者模式在Spring中的应用,于是萌生出写这篇博客的想法。
2. 在spring中观察者模式的四个角色 2.1 事件(ApplicationEvent) ApplicationEvent 是所有Spring事件对象的父类 。ApplicationEvent 继承自 jdk 的 EventObject, 并且通过 source 得到事件源。
附上ApplicationEvent的相关代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package org.springframework.context;public abstract class ApplicationEvent extends EventObject { private final long timestamp; public ApplicationEvent (Object source) { super (source); this .timestamp = System.currentTimeMillis(); } public ApplicationEvent (Object source, Clock clock) { super (source); this .timestamp = clock.millis(); } public final long getTimestamp () { return this .timestamp; } }
2.2 事件监听(ApplicationListener) ApplicationListener 即事件监听器 ,其继承自 jdk 的 EventListener,其中需要关注的是onApplicationEvent 方法,当监听的事件发生后该方法会被执行。
附上ApplicationListener相关代码:
1 2 3 4 5 6 7 8 9 10 11 12 package org.springframework.context;@FunctionalInterface public interface ApplicationListener <E extends ApplicationEvent > extends EventListener { void onApplicationEvent (E event) ; static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload (Consumer<T> consumer) { return event -> consumer.accept(event.getPayload()); } }
2.3 事件发布(ApplicationContext,ApplicationEventPublisher) ApplicationContext 是 Spring 中的核心容器 ,在事件监听中 ApplicationContext 可以作为事件的发布者 。因为 ApplicationContext 继承自ApplicationEventPublisher。在ApplicationEventPublisher中定义了事件发布的方法 — publishEvent(Object event) 。
附上ApplicationEventPublisher的相关代码:
1 2 3 4 5 6 7 8 9 10 11 package org.springframework.context;@FunctionalInterface public interface ApplicationEventPublisher { default void publishEvent (ApplicationEvent event) { publishEvent((Object) event); } void publishEvent (Object event) ; }
2.4 事件管理(ApplicationEventMulticaster) ApplicationEventMulticaster 用于事件监听器的注册 ,监听器的移除 和事件的广播 。监听器的注册就是通过它来实现的,它的作用是把 Applicationcontext 发布的 Event 广播给它的监听器列表,即对应multicastEvent方法 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package org.springframework.context.event;public interface ApplicationEventMulticaster { void addApplicationListener (ApplicationListener<?> listener) ; void addApplicationListenerBean (String listenerBeanName) ; void removeApplicationListener (ApplicationListener<?> listener) ; void removeApplicationListenerBean (String listenerBeanName) ; void removeApplicationListeners (Predicate<ApplicationListener<?>> predicate) ; void removeApplicationListenerBeans (Predicate<String> predicate) ; void removeAllListeners () ; void multicastEvent (ApplicationEvent event) ; void multicastEvent (ApplicationEvent event, @Nullable ResolvableType eventType) ; }
3. 观察者模式在Spring Security中的应用 温馨提示: 在IDE中找到下方流程讲解中对应的类和方法,一步步跟进调用流程进行理解,体验更佳!
3.1 调用流程分析 在Servlet中,监听session相关事件的方法是实现HttpSessionListener接口。Spring Security的HttpSessionEventPublisher就实现了HttpSessionListener接口对Servlet中的session事件进行监听,并转化为Spring事件机制处理。
整个调用流程为
在Spring应用启动 时触发ApplicationContext的refresh操作 从而调用registerListeners方法 注册ApplicationListener 。
HttpSessionEventPublisher监听到到servlet的sessionCreated 或者sessionDestroyed 事件。
将由servlet 管理的SessionEvent转化包装为Spring 能够识别的SessionEvent 。
在HttpSessionEventPublisher中获取ApplicationContext并调用publishEvent方法 发布SessionEvent。
AbstractApplicationContext中的publishEvent方法 会调用ApplicationEventMulticaster的multicastEvent方法 广播SessionEvent。
SimpleApplicationEventMulticaster中的multicastEvent方法 会获取已注册的ApplicationListener集合,遍历此集合并调用ApplicationListener的onApplicationEvent方法 。
在实现了ApplicationListener接口的SessionRegistryImpl类中的onApplicationEvent方法 会对接收到的SessionEvent做最后的逻辑处理。
4. 细节拆分及代码分析 根据上述调用流程,我们逐步分析流程中的代码细节。
4.1 ApplicationContext和ApplicationListener的注册 ApplicationListener是在何时何处注册或者说添加到ApplicationEventMulticaster中的呢?
答案就是在Spring应用启动的时候注册进去的,也就是ApplicationContext的refresh方法 中的标注 1️⃣ 处。
此处附上ApplicationContext的 refresh方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package org.springframework.context.support;public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext { @Override public void refresh () throws BeansException, IllegalStateException { synchronized (this .startupShutdownMonitor) { ...... try { ...... initApplicationEventMulticaster(); onRefresh(); registerListeners(); ...... } catch (BeansException ex) { ...... } finally { ...... } } } }
通过查看refresh方法调用链,不难发现refresh方法 是在SpringApplication的run方法 中调用的,也就是在Spring应用启动 时调用。
此处附上SpringApplication的run方法代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package org.springframework.boot;public class SpringApplication { ...... public ConfigurableApplicationContext run (String... args) { ...... refreshContext(context); afterRefresh(context, applicationArguments); ...... } private void refreshContext (ConfigurableApplicationContext context) { if (this .registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { } } refresh((ApplicationContext) context); } protected void refresh (ConfigurableApplicationContext applicationContext) { applicationContext.refresh(); } ...... }
4.2 HttpSessionEventPublisher Spring Security在HttpSessionEventPublisher类中实现servlet的HttpSessionListener接口监听HttpSession的变化事件,并转化成Spring的事件机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package org.springframework.security.web.session;public class HttpSessionEventPublisher implements HttpSessionListener , HttpSessionIdListener { private static final String LOGGER_NAME = HttpSessionEventPublisher.class.getName(); ApplicationContext getContext (ServletContext servletContext) { return SecurityWebApplicationContextUtils.findRequiredWebApplicationContext(servletContext); } @Override public void sessionCreated (HttpSessionEvent event) { extracted(event.getSession(), new HttpSessionCreatedEvent (event.getSession())); } @Override public void sessionDestroyed (HttpSessionEvent event) { extracted(event.getSession(), new HttpSessionDestroyedEvent (event.getSession())); } @Override public void sessionIdChanged (HttpSessionEvent event, String oldSessionId) { extracted(event.getSession(), new HttpSessionIdChangedEvent (event.getSession(), oldSessionId)); } private void extracted (HttpSession session, ApplicationEvent e) { Log log = LogFactory.getLog(LOGGER_NAME); log.debug(LogMessage.format("Publishing event: %s" , e)); getContext(session.getServletContext()).publishEvent(e); } }
1️⃣ 此处代码中的HttpSessionDestroyedEvent就实现了ApplicationEvent接口 - 代表观察者模式中的事件 (ApplicationEvent) 角色 。
2️⃣ 并且在组装出Spring能够处理的事件后,HttpSessionEventPublisher通过获取ApplicationContext并调用publishEvent方法 发布事件 - 此处又出现了事件发布 (ApplicationEventPublisher) 角色 。
4.3 AbstractApplicationContext 接下来我们可以看一下ApplicationContext的publishEvent方法 中具体做了哪些事情。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package org.springframework.context.support;public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext { ...... protected void publishEvent (Object event, @Nullable ResolvableType eventType) { Assert.notNull(event, "Event must not be null" ); 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); } } } ...... }
1️⃣ 在publishEvent方法 中会获取ApplicationEventMulticaster并调用multicastEvent方法 进行事件广播。
4.4 SimpleApplicationEventMulticaster ApplicationEventMulticaster接口定义了对监听者的操作,如增加监听者 、移除监听者 ,以及广播事件 的方法。- 对应观察者模式中的事件管理 (ApplicationEventMulticaster) 角色 。
Spring框架提供了ApplicationEventMulticaster 接口的默认实现SimpleApplicationEventMulticaster,如果本地容器中没有ApplicationEventMulticaster的实现就会使用这个默认实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package org.springframework.context.event;public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster { @Nullable private Executor taskExecutor; ...... @Override public void multicastEvent (ApplicationEvent event) { multicastEvent(event, resolveDefaultEventType(event)); } @Override public void multicastEvent (final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); Executor executor = getTaskExecutor(); for (ApplicationListener<?> listener : getApplicationListeners(event, type)) { 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); } } @SuppressWarnings({"rawtypes", "unchecked"}) private void doInvokeListener (ApplicationListener listener, ApplicationEvent event) { try { listener.onApplicationEvent(event); } catch (ClassCastException ex) { ...... } } ...... }
1️⃣ 在SimpleApplicationEventMulticaster中,可以看到multicastEvent方法 中遍历了事件相关的Listener集合,并依次调用Listener的onApplicationEvent方法 。
2️⃣ Spring还提供了异步发布事件的能力,taskExecutor不为null时即异步执行。
4.5 SessionRegistryImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package org.springframework.security.core.session;public class SessionRegistryImpl implements SessionRegistry , ApplicationListener<AbstractSessionEvent> { ...... @Override public void onApplicationEvent (AbstractSessionEvent event) { if (event instanceof SessionDestroyedEvent) { SessionDestroyedEvent sessionDestroyedEvent = (SessionDestroyedEvent) event; String sessionId = sessionDestroyedEvent.getId(); removeSessionInformation(sessionId); } else if (event instanceof SessionIdChangedEvent) { SessionIdChangedEvent sessionIdChangedEvent = (SessionIdChangedEvent) event; String oldSessionId = sessionIdChangedEvent.getOldSessionId(); if (this .sessionIds.containsKey(oldSessionId)) { Object principal = this .sessionIds.get(oldSessionId).getPrincipal(); removeSessionInformation(oldSessionId); registerNewSession(sessionIdChangedEvent.getNewSessionId(), principal); } } } }
在SessionRegistryImpl中实现了ApplicationListener接口, 订阅并处理AbstractSessionEvent - 对应观察者模式中的事件监听 (ApplicationListener) 角色 。
可以看到在onApplicationEvent方法 中就实现了对SessionDestroyedEvent和SessionIdChangedEvent的处理细节。
5. 总结 通过Spring Security中对Session事件监听处理的流程进行分析与理解,了解观察者模式如何在Spring中进行应用,同时也了解到Spring的事件处理机制是如何运作的。