18

I am having troubles invoking a method asynchronously in Spring, when the invoker is an embedded library receiving notifications from an external system. The code looks as below:

@Service
public class DefaultNotificationProcessor implements NotificationProcessor {

    private NotificationClient client;


    @Override
    public void process(Notification notification) {
        processAsync(notification);
    }

    @PostConstruct
    public void startClient() {
        client = new NotificationClient(this, clientPort);
        client.start(); 
    }

    @PreDestroy
    public void stopClient() {
        client.stop();
    }

    @Async
    private void processAsync(Notification notification) {
        // Heavy processing
    }
}

The NotificationClient internally has a thread in which it receives notifications from another system. It accepts a NotificationProcessor in its constructor which is basically the object that will do the actual processing of notifications.

In the above code, I have given the Spring bean as the processor and attempted to process the notification asynchronously by using @Async annotation. However, it appears the notification is processed in the same thread as the one used by NotificationClient. Effectively, @Async is ignored.

What am I missing here?

Ariod
  • 5,757
  • 22
  • 73
  • 103

1 Answers1

43

@Async (as well as @Transactional and other similar annotations) will not work when the method is invoked via this (on when @Async is used for private methods*), as long as you do not use real AspectJ compiletime or runtime weaving.

*the private method thing is: when the method is private, then it must been invoked via this - so this is more the consequence then the cause

So change your code:

@Service
public class DefaultNotificationProcessor implements NotificationProcessor {


    @Resource
    private DefaultNotificationProcessor selfReference;


    @Override
    public void process(Notification notification) {
        selfReference.processAsync(notification);
    }


    //the method must not been private
    //the method must been invoked via a bean reference
    @Async
    void processAsync(Notification notification) {
        // Heavy processing
    }
}

See also the answers for: Does Spring @Transactional attribute work on a private method? -- this is the same problem

Community
  • 1
  • 1
Ralph
  • 118,862
  • 56
  • 287
  • 383
  • Is it possible to autowire DefaultNotificationProcessor inside DefaultNotificationProcessor? Or maybe it's possible to do it on class annotated as a Service, cause when I was using Component annotation it didn't work. – Marcin Erbel Jan 23 '17 at 10:31
  • @MarcinErbel have a look at: http://stackoverflow.com/questions/5152686/self-injection-with-spring/5251930#5251930 --- abstract use Spring 4.3 or use `@Resource` instead of `@Autowired` – Ralph Jan 23 '17 at 14:17
  • 1
    If using `@Autowired` you also need `@Lazy` or you'll create a dependency loop. – OrangeDog Mar 02 '17 at 17:35
  • @Ralph why do we need the process method here, can't processAsync take care even if there is only one notification ? – robin Nov 08 '21 at 12:03