浅析观察者模式在Spring Security中的应用

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方法会调用ApplicationEventMulticastermulticastEvent方法广播SessionEvent。
  • SimpleApplicationEventMulticaster中的multicastEvent方法会获取已注册的ApplicationListener集合,遍历此集合并调用ApplicationListeneronApplicationEvent方法
  • 在实现了ApplicationListener接口的SessionRegistryImpl类中的onApplicationEvent方法会对接收到的SessionEvent做最后的逻辑处理。

4. 细节拆分及代码分析

根据上述调用流程,我们逐步分析流程中的代码细节。

4.1 ApplicationContext和ApplicationListener的注册

ApplicationListener是在何时何处注册或者说添加到ApplicationEventMulticaster中的呢?

答案就是在Spring应用启动的时候注册进去的,也就是ApplicationContextrefresh方法中的标注 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 {
......
// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
// 1️⃣
registerListeners();
......
} catch (BeansException ex) {
......
} finally {
......
}
}
}
}

通过查看refresh方法调用链,不难发现refresh方法是在SpringApplicationrun方法中调用的,也就是在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) {
// Not allowed in some environments.
}
}
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) {
// 1️⃣
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); // 2️⃣
}

}

1️⃣ 此处代码中的HttpSessionDestroyedEvent就实现了ApplicationEvent接口 - 代表观察者模式中的事件 (ApplicationEvent) 角色

2️⃣ 并且在组装出Spring能够处理的事件后,HttpSessionEventPublisher通过获取ApplicationContext并调用publishEvent方法发布事件 - 此处又出现了事件发布 (ApplicationEventPublisher) 角色

4.3 AbstractApplicationContext

接下来我们可以看一下ApplicationContextpublishEvent方法中具体做了哪些事情。

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");

// 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 {
// 1️⃣
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);
}
}
}

......
}

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) {
// 2️⃣
executor.execute(() -> invokeListener(listener, event));
}
else {
// 1️⃣
invokeListener(listener, event);
}
}
}

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
// 1️⃣
doInvokeListener(listener, event);
}
catch (Throwable err) {
errorHandler.handleError(err);
}
}
else {
// 1️⃣
doInvokeListener(listener, event);
}
}

@SuppressWarnings({"rawtypes", "unchecked"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
// 1️⃣
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的事件处理机制是如何运作的。