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

method-locking.mddocs/

Method-Level Locking

Annotations and infrastructure for applying distributed locks directly to scheduled methods.

@SchedulerLock Annotation

The core annotation for marking methods that should be protected by distributed locks.

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SchedulerLock {
    String name() default "";           // Lock name (required in practice)
    String lockAtMostFor() default "";  // Maximum lock duration
    String lockAtLeastFor() default ""; // Minimum lock duration
}

Parameters

  • name: The lock name used to identify the lock across nodes. Can include SpEL expressions when parameters are available.
  • lockAtMostFor: Maximum time the lock should be held (fallback for dead nodes). Overrides defaultLockAtMostFor from @EnableSchedulerLock.
  • lockAtLeastFor: Minimum time the lock should be held. Useful for preventing rapid re-execution.

Usage Examples

Basic Usage

@Component
public class ScheduledTasks {
    
    @Scheduled(cron = "0 */5 * * * *")
    @SchedulerLock(name = "processData", lockAtMostFor = "4m", lockAtLeastFor = "1m")
    public void processData() {
        // This method will only run on one node at a time
        doHeavyProcessing();
    }
}

Using SpEL Expressions

@SchedulerLock(name = "processUser-#{#userId}", lockAtMostFor = "10m")
public void processUser(Long userId) {
    // Lock name will be "processUser-123" for userId=123
    processUserData(userId);
}

Using Default Durations

@SchedulerLock(name = "simpleTask")
public void simpleTask() {
    // Uses defaultLockAtMostFor and defaultLockAtLeastFor from @EnableSchedulerLock
    doSimpleWork();
}

@LockProviderToUse Annotation

Annotation for disambiguating between multiple lock providers when more than one LockProvider bean exists.

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.PACKAGE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LockProviderToUse {
    String value(); // Bean name of the LockProvider to use
}

Usage Examples

Method-Level Provider Selection

@Component
public class ScheduledTasks {
    
    @Scheduled(cron = "0 */5 * * * *")
    @SchedulerLock(name = "criticalTask", lockAtMostFor = "10m")
    @LockProviderToUse("redisPrimaryLockProvider")
    public void criticalTask() {
        // Uses the Redis lock provider for this critical task
        performCriticalOperation();
    }
    
    @Scheduled(cron = "0 0 */1 * * *")
    @SchedulerLock(name = "housekeeping", lockAtMostFor = "30m")
    @LockProviderToUse("databaseLockProvider")
    public void housekeeping() {
        // Uses the database lock provider for housekeeping
        performHousekeeping();
    }
}

Class-Level Provider Selection

@Component
@LockProviderToUse("databaseLockProvider")
public class DatabaseTasks {
    
    @Scheduled(cron = "0 0 2 * * *")
    @SchedulerLock(name = "dbCleanup", lockAtMostFor = "2h")
    public void cleanupDatabase() {
        // Inherits database lock provider from class level
        cleanupOldRecords();
    }
}

Package-Level Provider Selection

// In package-info.java
@LockProviderToUse("defaultLockProvider")
package com.example.scheduled.tasks;

import net.javacrumbs.shedlock.spring.annotation.LockProviderToUse;

Method Proxy Infrastructure

MethodProxyScheduledLockAdvisor

AOP advisor that intercepts calls to methods annotated with @SchedulerLock.

public class MethodProxyScheduledLockAdvisor extends AbstractPointcutAdvisor {
    
    public MethodProxyScheduledLockAdvisor(
        ExtendedLockConfigurationExtractor lockConfigurationExtractor, 
        LockProviderSupplier lockProviderSupplier
    );
    
    public Pointcut getPointcut();  // Matches @SchedulerLock methods
    public Advice getAdvice();      // Returns LockingInterceptor
}

Method Interception Logic

The advisor uses a MethodInterceptor that:

  1. Validates method compatibility: Throws LockingNotSupportedException for methods returning primitives (except void)
  2. Extracts lock configuration: Uses ExtendedLockConfigurationExtractor to get lock settings
  3. Obtains lock provider: Uses LockProviderSupplier to get the appropriate lock provider
  4. Executes with lock: Uses DefaultLockingTaskExecutor to execute the method with locking
  5. Handles return types: Special handling for Optional return types
// Internal interceptor logic (simplified)
public class LockingInterceptor implements MethodInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable;
}

Supported Return Types

  • void: Standard case, no special handling
  • Object types: Returns the actual result or null if lock not acquired
  • Optional: Returns Optional.empty() if lock not acquired, otherwise the method result
  • Primitives: Not supported - throws LockingNotSupportedException

Usage with Optional Return Types

@SchedulerLock(name = "optionalTask", lockAtMostFor = "5m")
public Optional<String> optionalTask() {
    // If lock is acquired: returns Optional.of(result)
    // If lock is not acquired: returns Optional.empty()
    return Optional.of("Task completed");
}

Lock Provider Selection

Single Lock Provider

When only one LockProvider bean exists, it's used automatically:

@Configuration
public class LockConfig {
    @Bean
    public LockProvider lockProvider() {
        return new JdbcTemplateLockProvider(dataSource);
    }
}

Multiple Lock Providers

When multiple LockProvider beans exist, use @LockProviderToUse to specify which one:

@Configuration  
public class LockConfig {
    
    @Bean("primaryLockProvider")
    public LockProvider primaryLockProvider() {
        return new RedisLockProvider(redisTemplate);
    }
    
    @Bean("fallbackLockProvider") 
    public LockProvider fallbackLockProvider() {
        return new JdbcTemplateLockProvider(dataSource);
    }
}

// Then use @LockProviderToUse to select
@SchedulerLock(name = "task", lockAtMostFor = "10m")
@LockProviderToUse("primaryLockProvider")
public void scheduledTask() {
    // Uses Redis lock provider
}

Lock Provider Supplier

Internal interface that supplies the appropriate lock provider for method execution:

@FunctionalInterface
interface LockProviderSupplier {
    LockProvider supply(Object target, Method method, Object[] parameterValues);
    
    static LockProviderSupplier create(ListableBeanFactory beanFactory);
}

Implementation automatically handles:

  • Single provider scenarios (direct bean lookup)
  • Multiple provider scenarios (uses BeanNameSelectingLockProviderSupplier)

Error Handling

LockingNotSupportedException

Thrown when trying to lock methods that return primitive types (except void):

class LockingNotSupportedException extends LockException {
    LockingNotSupportedException(String message);
}

Example that throws this exception:

@SchedulerLock(name = "invalidMethod", lockAtMostFor = "5m")
public int invalidMethod() {  // ❌ Primitive return type
    return 42;
}

NoUniqueBeanDefinitionException

Thrown when multiple LockProvider beans exist but no @LockProviderToUse annotation is present:

// ❌ This will fail at runtime if multiple providers exist
@SchedulerLock(name = "ambiguousTask", lockAtMostFor = "5m")
public void ambiguousTask() {
    doWork();
}

The error message includes all available provider bean names to help with debugging.

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