WebAsyncManagerIntegrationFilter
介绍
字面意思这是一个Web异步管理集成过滤器,翻译成中文依然很抽象。我们从ThreadLocal讲起,ThreadLocal可以理解为一个与当前线程绑定的Map, key是当前线程,value是要存储的object。当我们在同一个线程中,可以通过get()方法获取到value。如果在A线程set(object),然后在B线程中调用get(),由于线程已切换,key是A,拿B自然取不出key为A的value。对于一个SSO系统,我们常常把当前登录的用户相关信息放到ThreadLocal中,避免每次使用的时候都去调RPC接口或者DB接口去查询。Spring Security的SecurityContextHolder就是通过ThreadLocal实现的。
WebAsyncManager,Spring文档有这么一句话:The central class for managing asynchronous request processing, mainly intended as an SPI and not typically used directly by application classes. 它主要用于异步请求,笔者发现这个东西在spring mvc请求分发处理中用到很多,时间有限,笔者也没有深入的了解,我们的主要关注点是WebAsyncManagerIntegrationFilter。
代码分析
步骤1
WebAsyncManagerIntegrationFilter#doFilterInternal()关键代码如下:
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
if (securityProcessingInterceptor == null) {
asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,
new SecurityContextCallableProcessingInterceptor());
}
filterChain.doFilter(request, response);
}
步骤2
SecurityContextCallableProcessingInterceptor,这个拦截器的关键作用,就是执行前后存储和清空SecurityContext,以便在WebAsyncTask异步调用中获取到SecurityContext,核心代码如下:
@Override
public <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) {
if (securityContext == null) {
setSecurityContext(SecurityContextHolder.getContext());
}
}
@Override
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
SecurityContextHolder.setContext(securityContext);
}
@Override
public <T> void postProcess(NativeWebRequest request, Callable<T> task,
Object concurrentResult) {
SecurityContextHolder.clearContext();
}
private void setSecurityContext(SecurityContext securityContext) {
this.securityContext = securityContext;
}
步骤3
笔者做了一个实践,分别一个Runnable和WebAsyncTask中尝试从SecurityContextHolder获取当前登录用户,AuthHolder只是对SecurityContextHolder进行封装,本质上还是调用的SecurityContextHolder.get()获取线程上下文信息,代码如下:
@ApiOperation("[WebAsyncTask]异步获取上下文用户信息")
@GetMapping("/web_async_task")
public WebAsyncTask getContextByWebAsyncTask() {
log.info("1:main线程开始[{}]", Thread.currentThread());
WebAsyncTask<Oauth2User> task = new WebAsyncTask<>(300L, () -> {
log.info("1:WebAsyncTask异步线程[{}], 开始执行异步任务", Thread.currentThread().getName());
Oauth2User user = AuthHolder.user();
log.info("2:WebAsyncTask异步线程[{}], 任务执行完成, 结果:{}",
Thread.currentThread().getName(), JSON.toJSONString(user));
return user;
});
log.info("2:main线程结束[{}]", Thread.currentThread());
return task;
}
@ApiOperation("[Runnable]异步获取上下文用户信息")
@GetMapping("/runnable")
public R<Oauth2User> getContextByRunnable() throws Exception {
log.info("1:main线程开始[{}]", Thread.currentThread());
final List<Oauth2User> list = new ArrayList<>();
CountDownLatch latch = new CountDownLatch(1);
Thread async = new Thread(() -> {
try {
log.info("1:Runnable异步线程[{}], 开始执行异步任务", Thread.currentThread().getName());
Oauth2User user = AuthHolder.user();
list.add(user);
log.info("2:Runnable异步线程[{}], 任务执行完成, 结果:{}",
Thread.currentThread().getName(), JSON.toJSONString(user));
} finally {
latch.countDown();
}
});
async.start();
latch.await();
log.info("2:main线程结束[{}]", Thread.currentThread());
return R.success(list.isEmpty() ? null : list.get(0));
}
执行结果如下:
可以看出,WebAsyncTask从SecurityContextHolder获取到了身份认证信息,但Runnable却不可以。WebAsyncTask之所以能够在异步线程中从SecurityContextHolder中获取上下文信息,与WebAsyncManagerIntegrationFilter的作用密不可分。