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

advanced-configuration.mddocs/

Advanced Configuration

Extended configuration extraction and AOP infrastructure for advanced ShedLock use cases.

ExtendedLockConfigurationExtractor

Extended interface for extracting lock configuration from method calls with parameter support.

public interface ExtendedLockConfigurationExtractor extends LockConfigurationExtractor {
    
    // Extract lock configuration for method with parameters
    Optional<LockConfiguration> getLockConfiguration(
        Object object, 
        Method method, 
        Object[] parameterValues
    );
}

This interface extends the core LockConfigurationExtractor to provide Spring-specific method parameter handling.

SpringLockConfigurationExtractor

The main implementation of lock configuration extraction with Spring-specific features.

public class SpringLockConfigurationExtractor implements ExtendedLockConfigurationExtractor {
    
    // Constructor without BeanFactory (limited functionality)
    public SpringLockConfigurationExtractor(
        Duration defaultLockAtMostFor,
        Duration defaultLockAtLeastFor,
        StringValueResolver embeddedValueResolver,
        Converter<String, Duration> durationConverter
    );
    
    // Full constructor with BeanFactory support
    public SpringLockConfigurationExtractor(
        Duration defaultLockAtMostFor,
        Duration defaultLockAtLeastFor, 
        StringValueResolver embeddedValueResolver,
        Converter<String, Duration> durationConverter,
        BeanFactory beanFactory
    );
    
    // Extract configuration from Runnable (for scheduler mode)
    public Optional<LockConfiguration> getLockConfiguration(Runnable task);
    
    // Extract configuration from method call with parameters
    public Optional<LockConfiguration> getLockConfiguration(
        Object target, 
        Method method, 
        Object[] parameterValues
    );
    
    // Duration calculation methods
    Duration getLockAtMostFor(AnnotationData annotation);
    Duration getLockAtLeastFor(AnnotationData annotation);
    
    // Static annotation finding utilities
    static AnnotationData findAnnotation(Object target, Method method);
    static <A extends Annotation> A findAnnotation(Object target, Method method, Class<A> annotationType);
}

Constructor Parameters

  • defaultLockAtMostFor: Default maximum lock duration from @EnableSchedulerLock
  • defaultLockAtLeastFor: Default minimum lock duration from @EnableSchedulerLock
  • embeddedValueResolver: Spring's value resolver for property placeholders
  • durationConverter: Converter for string duration formats
  • beanFactory: Bean factory for SpEL expression evaluation (optional)

Configuration Extraction Process

  1. Find annotations: Searches for @SchedulerLock on method, then target class method
  2. Extract lock name: Processes SpEL expressions if parameters are available
  3. Calculate durations: Resolves lockAtMostFor and lockAtLeastFor with fallbacks
  4. Create configuration: Returns LockConfiguration with current timestamp

SpEL Expression Support

The extractor supports Spring Expression Language (SpEL) in lock names when method parameters are available:

@SchedulerLock(name = "processUser-#{#userId}", lockAtMostFor = "10m")
public void processUser(Long userId) {
    // Lock name becomes "processUser-123" when userId = 123
}

@SchedulerLock(name = "processOrder-#{#order.id}-#{#order.customerId}", lockAtMostFor = "5m")
public void processOrder(Order order) {
    // Lock name becomes "processOrder-456-789" 
}

Property Placeholder Support

Lock names and durations can reference application properties:

@SchedulerLock(name = "${app.scheduled.task.name}", lockAtMostFor = "${app.scheduled.task.timeout}")
public void configurableTask() {
    // Values resolved from application.properties
}

Annotation Processing

AnnotationData Record

Internal record for holding parsed annotation data:

record AnnotationData(
    String name,
    long lockAtMostFor,
    String lockAtMostForString,
    long lockAtLeastFor, 
    String lockAtLeastForString
) {}

Annotation Finding Logic

The extractor uses sophisticated annotation finding that handles Spring AOP proxies:

  1. Direct method lookup: Check the method directly for annotations
  2. Target class lookup: If method is proxied, find the same method on the target class
  3. Merged annotations: Uses Spring's AnnotatedElementUtils.getMergedAnnotation() for composed annotations
// Static utility method
static <A extends Annotation> A findAnnotation(Object target, Method method, Class<A> annotationType) {
    // First try the method directly
    A annotation = AnnotatedElementUtils.getMergedAnnotation(method, annotationType);
    if (annotation != null) {
        return annotation;
    }
    
    // Try to find annotation on proxied class
    Class<?> targetClass = AopUtils.getTargetClass(target);
    try {
        Method methodOnTarget = targetClass.getMethod(method.getName(), method.getParameterTypes());
        return AnnotatedElementUtils.getMergedAnnotation(methodOnTarget, annotationType);
    } catch (NoSuchMethodException e) {
        return null;
    }
}

Parameter Name Discovery

For SpEL expression support, the extractor uses Spring's parameter name discovery:

public static final PrioritizedParameterNameDiscoverer PARAMETER_NAME_DISCOVERER;

// Supports multiple discovery strategies:
// - KotlinReflectionParameterNameDiscoverer (if Kotlin is present)
// - SimpleParameterNameDiscoverer (custom implementation)

Custom Parameter Name Discoverer

private static class SimpleParameterNameDiscoverer implements ParameterNameDiscoverer {
    public String[] getParameterNames(Method method);
    public String[] getParameterNames(Constructor<?> ctor);
}

This custom discoverer avoids calling hasRealParameterData() which can cause issues in certain test scenarios.

Evaluation Context Creation

For SpEL expression evaluation, the extractor creates Spring evaluation contexts:

private Optional<EvaluationContext> getEvaluationContext(Method method, Object[] parameterValues) {
    if (method.getParameters().length > 0 && method.getParameters().length == parameterValues.length) {
        StandardEvaluationContext evaluationContext = new MethodBasedEvaluationContext(
            beanFactory, method, parameterValues, PARAMETER_NAME_DISCOVERER
        );
        originalEvaluationContext.applyDelegatesTo(evaluationContext);
        return Optional.of(evaluationContext);
    }
    return Optional.empty();
}

The evaluation context includes:

  • Method parameters: Available as SpEL variables (e.g., #userId)
  • Bean factory access: Can reference Spring beans in expressions
  • Property resolution: Access to application properties

Duration Resolution

Flexible Duration Input

The extractor supports multiple duration input formats with fallback logic:

private Duration getValue(
    long valueFromAnnotation,           // Numeric milliseconds (-1 if not set)
    String stringValueFromAnnotation,   // String duration ("" if not set)
    Duration defaultValue,              // Default from @EnableSchedulerLock
    String paramName                    // For error messages
) {
    // Priority 1: Numeric value (if >= 0)
    if (valueFromAnnotation >= 0) {
        return Duration.of(valueFromAnnotation, MILLIS);
    }
    
    // Priority 2: String value (if not empty)
    if (StringUtils.hasText(stringValueFromAnnotation)) {
        // Resolve properties
        if (embeddedValueResolver != null) {
            stringValueFromAnnotation = embeddedValueResolver.resolveStringValue(stringValueFromAnnotation);
        }
        // Convert string to duration
        Duration result = durationConverter.convert(stringValueFromAnnotation);
        if (result.isNegative()) {
            throw new IllegalArgumentException("Invalid " + paramName + " value - cannot set negative duration");
        }
        return result;
    }
    
    // Priority 3: Default value
    return defaultValue;
}

Error Handling

Invalid duration formats throw descriptive IllegalArgumentException:

// Examples of error messages:
"Invalid lockAtMostForString value \"invalid\" - cannot parse into long nor duration"
"Invalid lockAtLeastForString value \"-5s\" - cannot set negative duration"

Advanced Usage Examples

Complex SpEL Expressions

@Component
public class AdvancedScheduledTasks {
    
    @SchedulerLock(
        name = "user-#{#user.department}-#{#user.id}", 
        lockAtMostFor = "#{#user.department == 'VIP' ? '30m' : '10m'}"
    )
    public void processUser(User user) {
        // Lock name: "user-Engineering-123"
        // Duration: 30 minutes for VIP department, 10 minutes otherwise
    }
    
    @SchedulerLock(
        name = "batch-#{T(java.time.LocalDate).now()}-#{#batchSize}",
        lockAtMostFor = "PT#{#batchSize / 100}M"  // 1 minute per 100 items
    )
    public void processBatch(int batchSize) {
        // Lock name: "batch-2023-12-01-1000"
        // Duration: 10 minutes for 1000 items
    }
}

Property-Driven Configuration

# application.yml
app:
  locking:
    user-processing:
      name: "user-processor-${spring.application.name}"
      timeout: "PT15M"
    batch-processing:
      timeout: "PT1H"
@SchedulerLock(
    name = "${app.locking.user-processing.name}",
    lockAtMostFor = "${app.locking.user-processing.timeout}"
)
public void processUsers() {
    // Configuration externalized to properties
}

Custom Configuration Extractor

For advanced use cases, you can provide your own extractor:

@Configuration
public class CustomLockConfig {
    
    @Bean
    @Primary
    public ExtendedLockConfigurationExtractor customLockConfigurationExtractor() {
        return new CustomSpringLockConfigurationExtractor(
            Duration.ofMinutes(30),        // default lockAtMostFor
            Duration.ZERO,                 // default lockAtLeastFor
            embeddedValueResolver,
            StringToDurationConverter.INSTANCE,
            beanFactory
        );
    }
}

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