Search code examples
javaspringasynchronous-method-call

Accessing async method parameters or method's annotations from task


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?


Solution

  • 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.