Spring Framework integration for ShedLock distributed locking system providing annotation-based task scheduling locks.
—
Annotations and infrastructure for applying distributed locks directly to scheduled methods.
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
}defaultLockAtMostFor from @EnableSchedulerLock.@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();
}
}@SchedulerLock(name = "processUser-#{#userId}", lockAtMostFor = "10m")
public void processUser(Long userId) {
// Lock name will be "processUser-123" for userId=123
processUserData(userId);
}@SchedulerLock(name = "simpleTask")
public void simpleTask() {
// Uses defaultLockAtMostFor and defaultLockAtLeastFor from @EnableSchedulerLock
doSimpleWork();
}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
}@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();
}
}@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();
}
}// In package-info.java
@LockProviderToUse("defaultLockProvider")
package com.example.scheduled.tasks;
import net.javacrumbs.shedlock.spring.annotation.LockProviderToUse;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
}The advisor uses a MethodInterceptor that:
LockingNotSupportedException for methods returning primitives (except void)ExtendedLockConfigurationExtractor to get lock settingsLockProviderSupplier to get the appropriate lock providerDefaultLockingTaskExecutor to execute the method with lockingOptional return types// Internal interceptor logic (simplified)
public class LockingInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable;
}Optional.empty() if lock not acquired, otherwise the method resultLockingNotSupportedException@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");
}When only one LockProvider bean exists, it's used automatically:
@Configuration
public class LockConfig {
@Bean
public LockProvider lockProvider() {
return new JdbcTemplateLockProvider(dataSource);
}
}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
}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:
BeanNameSelectingLockProviderSupplier)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;
}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