The problem arose when I used shedlock-spring's @SchedulerLock annotation in a bean that autowires a reference to itself. The code (roughly) looked like this:

@Service
public class Service {
  @Autowired
  private Service self;

  @Scheduled // Shedlock is not applied when executed by the scheduler
  @SchedulerLock
  public void process() {}

  public void action() {
    this.process(); // Shedlock is not applied, as expected
    self.process(); // Shedlock is properly applied, as expected
  }
}

What I discovered was that when Spring's scheduler runs this method, locking is not applied at all. Upon closed inspection it seems that the @Scheduled annotation processor, which is defined as a BeanPostProcessor, applies itself to the unproxied instance of the service. This is not the case when I remove the self reference, as the BeanPostProcessor then properly operates on a proxy and locking works as expected. Similar behavior can be seen with the @Transactional annotation.

I understand that circular references should be resolved by other means than delaying dependency injection, but I cannot quite grasp the reason behind "falling back" to applying BeanPostProcessors on the raw bean in case of an early proxy creation. Am I missing something?

Comment From: snicoll

action is calling a method of the same class. That doesn't take the proxy into account, I am afraid. There are several sections in the reference guide that covers this. For instance, https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#scheduling-task-executor

I can see that @Scheduled could be explicitly stated in that note there so we'll reuse this issue for that.

Comment From: simopahk

You can ignore the action method, it's there only to explain the use of self-injection.

When an early reference is requested to a bean containing @Scheduled methods, and it also requires proxying (e.g. due to @SchedulerLock and @Transactional annotations), the @Scheduled annotation processor receives a reference to the raw bean, not the proxy. It's more of a problem with how AbstractAutoProxyCreator handles early references, and therefore what object subsequent BeanPostProcessors work with.

Comment From: snicoll

All in all, self injection should not be done using field injection. Rather you should use @Lazy or ObjectProvider to get a lazy-resolved reference of that bean. This will make sure proxying is applied properly.

I cannot quite grasp the reason behind "falling back" to applying BeanPostProcessors on the raw bean in case of an early proxy creation.

There's not much we can do about that. It's doing best effort considering what you're asking.