Tobia
Tobia

Reputation: 9506

Spring @Async method inside a Service

I have this service bean with a sync method calling the internal async method:

@Service
public class MyService {
    
    public worker() {
        asyncJob();
    }
    
    @Async
    void asyncJob() {
        ...
    }
    
}

The trouble is that the asyncJob is not really called in async way. I found that this doesn't work because an internal call skips the AOP proxy.

So I try to self-refer the bean:

@Service
public class MyService {
    
    MyService mySelf;

    @Autowired
    ApplicationContext cnt;

    @PostConstruct
    public void init() {
        mySelf=(MyService)cnt.getBean("myService");
    }
    
    
    public void worker() {
        mySelf.asyncJob();
    }
    
    @Async
    void asyncJob() {
        ...
    }
    
}

It fails. Again no async call.

So I tried to divide it in two beans:

@Service
public class MyService {

    @Autowired
    MyAsyncService myAsyncService;

    public void worker() {
        myAsyncService.asyncJob();
    }
}

@Service
public class MyAsyncService {

    @Async
    void asyncJob() {
        ...
    }
    
}

Fails again.


The only working way is to call it from a Controller Bean:

@Controller
public class MyController {

    @Autowired
    MyAsyncService myAsyncService;

    @RequestMapping("/test")
    public void worker() {
        myAsyncService.asyncJob();
    }

}

@Service
public class MyAsyncService {

    @Async
    public void asyncJob() {
        ...
    }
    
}

But in this case it is a service job. Why I cannot call it from a service?

Upvotes: 24

Views: 22373

Answers (3)

slax57
slax57

Reputation: 591

In my case, it was easier to remove the @Async annotation and use the taskExecutor directly to submit my task:

Before

    @Async("taskExecutor")
    private Future<U> executerEnAsync(
            final T pInput) {

        final U resultat = this.appelerBS(pInput);
        return new AsyncResult<U>(resultat);

    }

After

    @Autowired
    private AsyncTaskExecutor taskExecutor;

    private Future<U> executerEnAsync(
            final T pInput) {

        final Future<U> future = taskExecutor.submit(new Callable<U>() {

            @Override
            public U call() {
                final U resultat = appelerBS(pInput);
                return resultat;
            }
        });

        return future;

    }

Upvotes: 0

Tobia
Tobia

Reputation: 9506

I solved the third method (divide it in two beans) changing the async method's access modifier to public:

@Service
public class MyService {

    @Autowired
    MyAsyncService myAsyncService;

    public void worker() {
        myAsyncService.asyncJob();
    }
}

@Service
public class MyAsyncService {

    @Async
    public void asyncJob() { // switched to public
        ...
    }

}

Upvotes: 11

jlb
jlb

Reputation: 19970

Found a really nice way to solve this (with java8) in the case where you have a lot of various things you want to both sync and async. Instead of creating a separate XXXAsync service for each 'synchronous' service, create a generic async service wrapper:

@Service
public class AsyncService {

    @Async
    public void run(final Runnable runnable) {
        runnable.run();
    }
}

and then use it as such:

@Service
public class MyService {

    @Autowired
    private AsyncService asyncService;


    public void refreshAsync() {
        asyncService.run(this::refresh);
    }


    public void refresh() {
        // my business logic
    }


    public void refreshWithParamsAsync(String param1, Integer param2) {
        asyncService.run(() -> this.refreshWithParams(param1, param2));
    }


    public void refreshWithParams(String param1, Integer param2) {
        // my business logic with parameters
    }

}

Upvotes: 46

Related Questions