I have the below classes (simplified) to achieve async method calls using Java and Spring toolbox. I need to add some logic which is needed to execute before and after async method call.
Callable. I can put the logic i need here if can access data.
public class ContextAwareCallable<T> implements Callable<T> {
private Callable<T> task;
private MyContext context;
public ContextAwareCallable(Callable<T> task, MyContext context) {
this.task = task;
this.context = context;
}
@Override
public T call() throws Exception {
return task.call();
}
}
This is executor, where task is called.
public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
private static final long serialVersionUID = 1L;
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(new ContextAwareCallable<T>(task, myContext));
}
This is configurer. Initializes executor. As i understand i can put a TaskDecorator here son i can do logic i need inside that. Still, i need data from method which i cant reach inside TaskDecorator.
@EnableAsync
@Configuration
public class MyAsyncPoolConfig implements AsyncConfigurer {
@Override
@Bean("DefaultAsyncTaskExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(0);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(10);
executor.setThreadNamePrefix("myPrefix");
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new MyAsyncUncaughtExceptionHandler();
}
}
Async method itself.
@Service("TESTCOMPONENT_ASYNC_SERVICE_METHOD_CALL")
@Async("TESTCOMPONENT_ASYNCTESTER_ASYNC_POOL")
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Throwable.class)
public Future<AsyncCallMethodOutput> asyncMethodCall(AsyncCallMethodInput methodInput) throws MyException {
// actual thing done here
}
This is where async method called.
AsyncServiceExampleService asyncServiceExample = SpringApplicationContext.getContext().getBean(AsyncServiceExampleService.class);
What i need is accessing to AsyncCallMethodInput
parameter or better, value of @Service
annotation inside ContextAwarePoolExecutor
, ContextAwareCallable
or a TaskDecorator
added to configurer.
This could be done by adding those into context and copying to thread but i need to to this added logic inside Executor or Callable because these are general methods and can serve different async methods. So i don't want to force method writers adding extra data to context which they shouldn't change manually.
Is there a way to achieve this?
I found a working solution. There may be better solutions but thats the only one i can find.
Spring wraps Callable<T> task
with another class, which has a property named userDeclaredMethod
. When i debugged, this method contains my asyncMethodCall
with all metadata i need. So all i need to do access this data.
After even more research i found the following method, that extracts method.
private static Object getField(Object c, String name) throws IllegalAccessException {
while (c != null && !c.getClass().getName().toLowerCase().equals("java.lang.object")) {
try {
Field field = c.getClass().getDeclaredField(name);
field.setAccessible(true);
return field.get(c);
} catch (NoSuchFieldException e) {
c = c.getClass().getSuperclass();
}
}
return null;
}
When i call this method as follows i was able to get what i needed.
Method asyncMethod = (Method) getField(task, "val$userDeclaredMethod");
As i side note, all this code is in ContextAwarePoolExecutor
class.