CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-net-javacrumbs-shedlock--shedlock-spring

Spring Framework integration for ShedLock distributed locking system providing annotation-based task scheduling locks.

Pending
Overview
Eval results
Files

task-scheduler.mddocs/

TaskScheduler Integration

Classes and utilities for integrating with Spring's TaskScheduler infrastructure, primarily used in the deprecated PROXY_SCHEDULER intercept mode.

LockableTaskScheduler

A TaskScheduler wrapper that automatically applies locking to all scheduled tasks.

public class LockableTaskScheduler implements TaskScheduler, DisposableBean {
    
    public LockableTaskScheduler(TaskScheduler taskScheduler, LockManager lockManager);
    
    // TaskScheduler methods with Trigger
    public ScheduledFuture<?> schedule(Runnable task, Trigger trigger);
    
    // TaskScheduler methods with Date
    public ScheduledFuture<?> schedule(Runnable task, Date startTime);
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period);
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay);
    
    // TaskScheduler methods with plain timing
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period);
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay);
    
    // TaskScheduler methods with Instant (Java 8+ time API)
    public ScheduledFuture<?> schedule(Runnable task, Instant startTime);
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period);
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Duration delay);
    
    // DisposableBean
    public void destroy() throws Exception;
}

Constructor Parameters

  • taskScheduler: The underlying TaskScheduler to wrap (e.g., ConcurrentTaskScheduler)
  • lockManager: The LockManager that handles lock acquisition and release

Usage Example

@Configuration
public class SchedulerConfig {
    
    @Bean
    public LockProvider lockProvider() {
        return new JdbcTemplateLockProvider(dataSource);
    }
    
    @Bean  
    public LockManager lockManager(LockProvider lockProvider) {
        return new DefaultLockManager(lockProvider);
    }
    
    @Bean
    public TaskScheduler taskScheduler(LockManager lockManager) {
        ConcurrentTaskScheduler scheduler = new ConcurrentTaskScheduler();
        return new LockableTaskScheduler(scheduler, lockManager);
    }
}

Task Wrapping Behavior

The LockableTaskScheduler automatically wraps all submitted tasks in LockableRunnable:

private Runnable wrap(Runnable task) {
    return new LockableRunnable(task, lockManager);
}

This ensures that any task scheduled through this scheduler will be protected by distributed locking.

Scheduler Proxy Infrastructure

SchedulerProxyScheduledLockAdvisor

AOP advisor that intercepts calls to TaskScheduler methods and wraps tasks with locking.

public class SchedulerProxyScheduledLockAdvisor extends AbstractPointcutAdvisor {
    
    public SchedulerProxyScheduledLockAdvisor(
        LockProviderSupplier lockProviderSupplier, 
        ExtendedLockConfigurationExtractor lockConfigurationExtractor
    );
    
    public Pointcut getPointcut();  // Matches TaskScheduler methods
    public Advice getAdvice();      // Returns scheduler interceptor
}

Pointcut Definition

The advisor targets TaskScheduler interface methods:

// Internal pointcut implementation
private static class TaskSchedulerPointcut implements Pointcut {
    public ClassFilter getClassFilter();  // Matches TaskScheduler classes
    public MethodMatcher getMethodMatcher(); // Matches schedule* methods
}

Targeted methods:

  • schedule
  • scheduleAtFixedRate
  • scheduleWithFixedDelay

Scheduler Interception Logic

The advisor intercepts scheduler calls and:

  1. Identifies task type: Handles ScheduledMethodRunnable and OutcomeTrackingRunnable
  2. Wraps in LockableRunnable: Creates a LockableRunnable wrapper with appropriate lock configuration
  3. Supplies lock provider: Uses the configured LockProvider for the task
  4. Proceeds with modified arguments: Replaces the original task with the wrapped version
// Internal interceptor (simplified)
private static class LockingInterceptor implements MethodInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable;
}

Supported Task Types

ScheduledMethodRunnable

Standard Spring scheduled method wrapper:

// Internal handling
private LockableRunnable wrapTask(ScheduledMethodRunnable task) {
    LockProvider lockProvider = lockProviderSupplier.supply(
        task.getTarget(), task.getMethod(), new Object[] {}
    );
    LockManager lockManager = new DefaultLockManager(lockProvider, lockConfigurationExtractor);
    return new LockableRunnable(task, lockManager);
}

OutcomeTrackingRunnable

Spring's outcome tracking wrapper (requires reflection):

// Internal reflection-based handling for OutcomeTrackingRunnable
private Object wrapTask(Object firstArgument) throws NoSuchFieldException, IllegalAccessException {
    if (firstArgument.getClass().getSimpleName().equals("OutcomeTrackingRunnable")) {
        Field runnable = firstArgument.getClass().getDeclaredField("runnable");
        runnable.setAccessible(true);
        Object wrappedRunnable = runnable.get(firstArgument);
        if (wrappedRunnable instanceof ScheduledMethodRunnable task) {
            return wrapTask(task);
        }
    }
    return firstArgument;
}

Default TaskScheduler Registration

When using PROXY_SCHEDULER mode, ShedLock automatically registers a default TaskScheduler if none exists.

RegisterDefaultTaskSchedulerPostProcessor

public class RegisterDefaultTaskSchedulerPostProcessor 
    implements BeanDefinitionRegistryPostProcessor, Ordered, BeanFactoryAware {
    
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
    public int getOrder(); // Returns LOWEST_PRECEDENCE
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException;
}

Registration Logic

  1. Check for existing TaskScheduler: If any TaskScheduler beans exist, do nothing
  2. Check for ScheduledExecutorService beans:
    • If exactly one exists: Create ConcurrentTaskScheduler using it
    • If zero exist: Create plain ConcurrentTaskScheduler
    • If multiple exist: Create plain ConcurrentTaskScheduler and log warning

Registered Bean Configuration

With Existing ScheduledExecutorService

// Equivalent registration when one ScheduledExecutorService exists
@Bean(DEFAULT_TASK_SCHEDULER_BEAN_NAME)
public TaskScheduler taskScheduler(ScheduledExecutorService scheduledExecutor) {
    ConcurrentTaskScheduler scheduler = new ConcurrentTaskScheduler();
    scheduler.setScheduledExecutor(scheduledExecutor);
    return scheduler;
}

Without ScheduledExecutorService

// Equivalent registration when no ScheduledExecutorService exists
@Bean(DEFAULT_TASK_SCHEDULER_BEAN_NAME) 
public TaskScheduler taskScheduler() {
    return new ConcurrentTaskScheduler();
}

The default bean name is DEFAULT_TASK_SCHEDULER_BEAN_NAME from Spring's ScheduledAnnotationBeanPostProcessor.

Integration Modes Comparison

PROXY_METHOD (Recommended)

  • Intercepts method calls directly
  • Works with any scheduler configuration
  • Better performance (no scheduler wrapping)
  • Cleaner stack traces
  • Full support for method return values

PROXY_SCHEDULER (Deprecated)

  • Intercepts scheduler calls
  • Requires compatible task types
  • Additional reflection overhead
  • May not work with all Spring versions
  • Limited return value handling

Migration Example

Before (PROXY_SCHEDULER):

@EnableSchedulerLock(
    interceptMode = InterceptMode.PROXY_SCHEDULER,
    defaultLockAtMostFor = "10m"
)
public class SchedulerConfig {
    // TaskScheduler beans are automatically wrapped
}

After (PROXY_METHOD):

@EnableSchedulerLock(
    interceptMode = InterceptMode.PROXY_METHOD,  // or omit (default)
    defaultLockAtMostFor = "10m"
)
public class SchedulerConfig {
    // Methods are intercepted directly
}

No other changes required - the @SchedulerLock annotations work the same way.

Install with Tessl CLI

npx tessl i tessl/maven-net-javacrumbs-shedlock--shedlock-spring

docs

advanced-configuration.md

configuration.md

index.md

method-locking.md

task-scheduler.md

tile.json