Customize/Extend Spring's @Async support for s

2019-08-07 12:09发布

I'm using Spring's @EnableAsync feature to execute methods asynchronously. For security I'm using Apache Shiro. In the code that is executed asynchronously I need to have access to the Shiro subject that was attached to the thread that triggered the async call.

Shiro supports using an existing subject in a different thread by associating the subject with the Callable that is to be executed on the different thread (see here):

Subject.associateWith(Callable)

Unfortunately I don't have direct access to the Callable as this stuff is encapsulated by Spring. I found that I would need to extend Spring's AnnotationAsyncExecutionInterceptor to associate my subject with the created Callable (that's the easy part).

By problem is now how to make Spring use my custom AnnotationAsyncExecutionInterceptor instead of the default one. The default one is created in AsyncAnnotationAdvisor and AsyncAnnotationBeanPostProcessor. I can of course extend these classes as well, but this only shifts to problem as I need the make Spring use my extended classes again.

Is there any way to achieve what I want?

I would be fine with adding a new custom async annotation as well. But I don't think this would be much of a help.


UPDATE: Actually my finding that AnnotationAsyncExecutionInterceptor would need to be customized was wrong. By chance I stumbled across org.apache.shiro.concurrent.SubjectAwareExecutorService which does pretty exactly what I want and made me think I could simply provide a custom executor instead of customizing the interceptor. See my answer for details.

1条回答
趁早两清
2楼-- · 2019-08-07 12:11

I managed to achieve what I want - shiro subject is automatically bound and unbound to tasks that are executed by spring's async support - by providing an extended version of the ThreadPoolTaskExecutor:

public class SubjectAwareTaskExecutor extends ThreadPoolTaskExecutor {

  @Override
  public void execute(final Runnable aTask) {
    final Subject currentSubject = ThreadContext.getSubject();
    if (currentSubject != null) {
      super.execute(currentSubject.associateWith(aTask));
    } else {
      super.execute(aTask);
    }
  }

  ... // override the submit and submitListenable method accordingly
}

To make spring use this executor I had to implement an AsyncConfigurer that returns my custom executor:

@EnableAsync
public class AsyncConfiguration implements AsyncConfigurer {

  @Override
  public Executor getAsyncExecutor() {
    final SubjectAwareTaskExecutor executor = new SubjectAwareTaskExecutor();
    executor.setBeanName("async-executor");
    executor.setCorePoolSize(10);
    executor.setMaxPoolSize(10);
    executor.initialize();
    return executor;
  }

  @Override
  public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    return new SimpleAsyncUncaughtExceptionHandler();
  }
}

With this change the parent Thread's subject will automatically be available in methods that are annotated with @Async and - probably even more important - the subject will be de-attached from the thread after execution of the asynchronous method.

查看更多
登录 后发表回答