0
# Advanced Configuration
1
2
Extended configuration extraction and AOP infrastructure for advanced ShedLock use cases.
3
4
## ExtendedLockConfigurationExtractor
5
6
Extended interface for extracting lock configuration from method calls with parameter support.
7
8
```java { .api }
9
public interface ExtendedLockConfigurationExtractor extends LockConfigurationExtractor {
10
11
// Extract lock configuration for method with parameters
12
Optional<LockConfiguration> getLockConfiguration(
13
Object object,
14
Method method,
15
Object[] parameterValues
16
);
17
}
18
```
19
20
This interface extends the core `LockConfigurationExtractor` to provide Spring-specific method parameter handling.
21
22
## SpringLockConfigurationExtractor
23
24
The main implementation of lock configuration extraction with Spring-specific features.
25
26
```java { .api }
27
public class SpringLockConfigurationExtractor implements ExtendedLockConfigurationExtractor {
28
29
// Constructor without BeanFactory (limited functionality)
30
public SpringLockConfigurationExtractor(
31
Duration defaultLockAtMostFor,
32
Duration defaultLockAtLeastFor,
33
StringValueResolver embeddedValueResolver,
34
Converter<String, Duration> durationConverter
35
);
36
37
// Full constructor with BeanFactory support
38
public SpringLockConfigurationExtractor(
39
Duration defaultLockAtMostFor,
40
Duration defaultLockAtLeastFor,
41
StringValueResolver embeddedValueResolver,
42
Converter<String, Duration> durationConverter,
43
BeanFactory beanFactory
44
);
45
46
// Extract configuration from Runnable (for scheduler mode)
47
public Optional<LockConfiguration> getLockConfiguration(Runnable task);
48
49
// Extract configuration from method call with parameters
50
public Optional<LockConfiguration> getLockConfiguration(
51
Object target,
52
Method method,
53
Object[] parameterValues
54
);
55
56
// Duration calculation methods
57
Duration getLockAtMostFor(AnnotationData annotation);
58
Duration getLockAtLeastFor(AnnotationData annotation);
59
60
// Static annotation finding utilities
61
static AnnotationData findAnnotation(Object target, Method method);
62
static <A extends Annotation> A findAnnotation(Object target, Method method, Class<A> annotationType);
63
}
64
```
65
66
### Constructor Parameters
67
68
- **defaultLockAtMostFor**: Default maximum lock duration from `@EnableSchedulerLock`
69
- **defaultLockAtLeastFor**: Default minimum lock duration from `@EnableSchedulerLock`
70
- **embeddedValueResolver**: Spring's value resolver for property placeholders
71
- **durationConverter**: Converter for string duration formats
72
- **beanFactory**: Bean factory for SpEL expression evaluation (optional)
73
74
### Configuration Extraction Process
75
76
1. **Find annotations**: Searches for `@SchedulerLock` on method, then target class method
77
2. **Extract lock name**: Processes SpEL expressions if parameters are available
78
3. **Calculate durations**: Resolves lockAtMostFor and lockAtLeastFor with fallbacks
79
4. **Create configuration**: Returns `LockConfiguration` with current timestamp
80
81
### SpEL Expression Support
82
83
The extractor supports Spring Expression Language (SpEL) in lock names when method parameters are available:
84
85
```java
86
@SchedulerLock(name = "processUser-#{#userId}", lockAtMostFor = "10m")
87
public void processUser(Long userId) {
88
// Lock name becomes "processUser-123" when userId = 123
89
}
90
91
@SchedulerLock(name = "processOrder-#{#order.id}-#{#order.customerId}", lockAtMostFor = "5m")
92
public void processOrder(Order order) {
93
// Lock name becomes "processOrder-456-789"
94
}
95
```
96
97
### Property Placeholder Support
98
99
Lock names and durations can reference application properties:
100
101
```java
102
@SchedulerLock(name = "${app.scheduled.task.name}", lockAtMostFor = "${app.scheduled.task.timeout}")
103
public void configurableTask() {
104
// Values resolved from application.properties
105
}
106
```
107
108
## Annotation Processing
109
110
### AnnotationData Record
111
112
Internal record for holding parsed annotation data:
113
114
```java { .api }
115
record AnnotationData(
116
String name,
117
long lockAtMostFor,
118
String lockAtMostForString,
119
long lockAtLeastFor,
120
String lockAtLeastForString
121
) {}
122
```
123
124
### Annotation Finding Logic
125
126
The extractor uses sophisticated annotation finding that handles Spring AOP proxies:
127
128
1. **Direct method lookup**: Check the method directly for annotations
129
2. **Target class lookup**: If method is proxied, find the same method on the target class
130
3. **Merged annotations**: Uses Spring's `AnnotatedElementUtils.getMergedAnnotation()` for composed annotations
131
132
```java
133
// Static utility method
134
static <A extends Annotation> A findAnnotation(Object target, Method method, Class<A> annotationType) {
135
// First try the method directly
136
A annotation = AnnotatedElementUtils.getMergedAnnotation(method, annotationType);
137
if (annotation != null) {
138
return annotation;
139
}
140
141
// Try to find annotation on proxied class
142
Class<?> targetClass = AopUtils.getTargetClass(target);
143
try {
144
Method methodOnTarget = targetClass.getMethod(method.getName(), method.getParameterTypes());
145
return AnnotatedElementUtils.getMergedAnnotation(methodOnTarget, annotationType);
146
} catch (NoSuchMethodException e) {
147
return null;
148
}
149
}
150
```
151
152
## Parameter Name Discovery
153
154
For SpEL expression support, the extractor uses Spring's parameter name discovery:
155
156
```java { .api }
157
public static final PrioritizedParameterNameDiscoverer PARAMETER_NAME_DISCOVERER;
158
159
// Supports multiple discovery strategies:
160
// - KotlinReflectionParameterNameDiscoverer (if Kotlin is present)
161
// - SimpleParameterNameDiscoverer (custom implementation)
162
```
163
164
### Custom Parameter Name Discoverer
165
166
```java { .api }
167
private static class SimpleParameterNameDiscoverer implements ParameterNameDiscoverer {
168
public String[] getParameterNames(Method method);
169
public String[] getParameterNames(Constructor<?> ctor);
170
}
171
```
172
173
This custom discoverer avoids calling `hasRealParameterData()` which can cause issues in certain test scenarios.
174
175
## Evaluation Context Creation
176
177
For SpEL expression evaluation, the extractor creates Spring evaluation contexts:
178
179
```java
180
private Optional<EvaluationContext> getEvaluationContext(Method method, Object[] parameterValues) {
181
if (method.getParameters().length > 0 && method.getParameters().length == parameterValues.length) {
182
StandardEvaluationContext evaluationContext = new MethodBasedEvaluationContext(
183
beanFactory, method, parameterValues, PARAMETER_NAME_DISCOVERER
184
);
185
originalEvaluationContext.applyDelegatesTo(evaluationContext);
186
return Optional.of(evaluationContext);
187
}
188
return Optional.empty();
189
}
190
```
191
192
The evaluation context includes:
193
- **Method parameters**: Available as SpEL variables (e.g., `#userId`)
194
- **Bean factory access**: Can reference Spring beans in expressions
195
- **Property resolution**: Access to application properties
196
197
## Duration Resolution
198
199
### Flexible Duration Input
200
201
The extractor supports multiple duration input formats with fallback logic:
202
203
```java
204
private Duration getValue(
205
long valueFromAnnotation, // Numeric milliseconds (-1 if not set)
206
String stringValueFromAnnotation, // String duration ("" if not set)
207
Duration defaultValue, // Default from @EnableSchedulerLock
208
String paramName // For error messages
209
) {
210
// Priority 1: Numeric value (if >= 0)
211
if (valueFromAnnotation >= 0) {
212
return Duration.of(valueFromAnnotation, MILLIS);
213
}
214
215
// Priority 2: String value (if not empty)
216
if (StringUtils.hasText(stringValueFromAnnotation)) {
217
// Resolve properties
218
if (embeddedValueResolver != null) {
219
stringValueFromAnnotation = embeddedValueResolver.resolveStringValue(stringValueFromAnnotation);
220
}
221
// Convert string to duration
222
Duration result = durationConverter.convert(stringValueFromAnnotation);
223
if (result.isNegative()) {
224
throw new IllegalArgumentException("Invalid " + paramName + " value - cannot set negative duration");
225
}
226
return result;
227
}
228
229
// Priority 3: Default value
230
return defaultValue;
231
}
232
```
233
234
### Error Handling
235
236
Invalid duration formats throw descriptive `IllegalArgumentException`:
237
238
```java
239
// Examples of error messages:
240
"Invalid lockAtMostForString value \"invalid\" - cannot parse into long nor duration"
241
"Invalid lockAtLeastForString value \"-5s\" - cannot set negative duration"
242
```
243
244
## Advanced Usage Examples
245
246
### Complex SpEL Expressions
247
248
```java
249
@Component
250
public class AdvancedScheduledTasks {
251
252
@SchedulerLock(
253
name = "user-#{#user.department}-#{#user.id}",
254
lockAtMostFor = "#{#user.department == 'VIP' ? '30m' : '10m'}"
255
)
256
public void processUser(User user) {
257
// Lock name: "user-Engineering-123"
258
// Duration: 30 minutes for VIP department, 10 minutes otherwise
259
}
260
261
@SchedulerLock(
262
name = "batch-#{T(java.time.LocalDate).now()}-#{#batchSize}",
263
lockAtMostFor = "PT#{#batchSize / 100}M" // 1 minute per 100 items
264
)
265
public void processBatch(int batchSize) {
266
// Lock name: "batch-2023-12-01-1000"
267
// Duration: 10 minutes for 1000 items
268
}
269
}
270
```
271
272
### Property-Driven Configuration
273
274
```yaml
275
# application.yml
276
app:
277
locking:
278
user-processing:
279
name: "user-processor-${spring.application.name}"
280
timeout: "PT15M"
281
batch-processing:
282
timeout: "PT1H"
283
```
284
285
```java
286
@SchedulerLock(
287
name = "${app.locking.user-processing.name}",
288
lockAtMostFor = "${app.locking.user-processing.timeout}"
289
)
290
public void processUsers() {
291
// Configuration externalized to properties
292
}
293
```
294
295
### Custom Configuration Extractor
296
297
For advanced use cases, you can provide your own extractor:
298
299
```java
300
@Configuration
301
public class CustomLockConfig {
302
303
@Bean
304
@Primary
305
public ExtendedLockConfigurationExtractor customLockConfigurationExtractor() {
306
return new CustomSpringLockConfigurationExtractor(
307
Duration.ofMinutes(30), // default lockAtMostFor
308
Duration.ZERO, // default lockAtLeastFor
309
embeddedValueResolver,
310
StringToDurationConverter.INSTANCE,
311
beanFactory
312
);
313
}
314
}
315
```