ahammani
ahammani

Reputation: 93

Can I add context in @Scheduled?

This is my code :

 @Scheduled(cron = "30 3 * * * *")
    public void myCron() {
        //we don't care what we do here
    }

I want to know if it is possible to add a tracking id (or other information) on my @Scheduled automatically.

The id will be different each time @Scheduled is triggered.

I want to do this to avoid to duplicate code like :

@Scheduled(cron = "10 3 * * * *")
public void myCron() {
    MDC.put("myId", UUID.randomUUID().toString());
    //we don't care what we do here
}
@Scheduled(cron = "30 3 * * * *")
public void mySecondCron() {
    MDC.put("myId", UUID.randomUUID().toString());
    //we don't care what we do here
}

I tired to implements SchedulingConfigurer but SchedulingConfigurer#configureTasks is too late too add behavior on taks because the task (runnable) is already created

Thanks

Upvotes: 9

Views: 4524

Answers (2)

David Siegal
David Siegal

Reputation: 4578

You can also use AOP to intercept code before it enters any method annotated with @Scheduled and set the MDC. For good measure, it clears the MDC upon exiting the method.

Note the atExecutionTimeInMyNamespace pointcut, where you can optionally put in your own package namespace to limit to just use of @Scheduled in your own code (i.e. excluding its use in any 3rd party libs, however unlikely that may be).

@Aspect
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ScheduledTaskTracingAspect {

  @Pointcut("@annotation(org.springframework.scheduling.annotation.Scheduled)")
  public void methodAnnotatedWithScheduled() {}

  @Pointcut("execution(* com.mycompany..*(..))")
  public void atExecutionTimeInMyNamespace() {}

  @Around("methodAnnotatedWithScheduled() && atExecutionTimeInMyNamespace()")
  public Object connectionAdvice(ProceedingJoinPoint joinPoint) throws Throwable {

    MDC.put("myId", UUID.randomUUID().toString());

    try {
      return joinPoint.proceed();
    }
    finally {
      // Might as well clear all the MDC, not just the "myId"
      MDC.clear();
    }
  }

}

Upvotes: 1

Mikita Harbacheuski
Mikita Harbacheuski

Reputation: 2253

You can try to implement custom TaskScheduler and register it in SchedulingConfigurer.configureTasks. ConcurrentTaskScheduler can be considered as an example. Unfortunately this class isn't well-designed for inheritance, otherwise decorateTask method will be protected. So you need to override all methods to add one additional Runnable decorator with your logic. Something like this one :

@Configuration
@EnableScheduling
public class ScheduledConfig implements SchedulingConfigurer {

    public static class MyTaskScheduler extends ConcurrentTaskScheduler {

        public MyTaskScheduler() {
        }

        public MyTaskScheduler(ScheduledExecutorService scheduledExecutor) {
            super(scheduledExecutor);
        }

        public MyTaskScheduler(Executor concurrentExecutor, ScheduledExecutorService scheduledExecutor) {
            super(concurrentExecutor, scheduledExecutor);
        }

        // TODO override other methods

        @Override
        public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
            return super.schedule(decorateTask(task), trigger);
        }

        private Runnable decorateTask(Runnable task) {
            // not 100% sure about safety of this cast
            return new MyRunnable((ScheduledMethodRunnable) task);
        }

        private static class MyRunnable implements Runnable {

            private final ScheduledMethodRunnable runnable;
            private final AtomicLong counter = new AtomicLong();

            public MyRunnable(ScheduledMethodRunnable runnable) {
                this.runnable = runnable;
            }

            @Override
            public void run() {
                System.out.println(runnable.getMethod().toGenericString() + " " + counter.incrementAndGet());
                runnable.run();
            }
        }
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        TaskScheduler taskScheduler = new MyTaskScheduler(Executors.newScheduledThreadPool(10));
        taskRegistrar.setTaskScheduler(taskScheduler);
    }

    @Scheduled(cron = "0/1 * * * * *")
    public void test() {
        System.out.println("Running task in thread " + Thread.currentThread().getId());
    }
}

Upvotes: 6

Related Questions