0
# Method-Level Locking
1
2
Annotations and infrastructure for applying distributed locks directly to scheduled methods.
3
4
## @SchedulerLock Annotation
5
6
The core annotation for marking methods that should be protected by distributed locks.
7
8
```java { .api }
9
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
10
@Retention(RetentionPolicy.RUNTIME)
11
public @interface SchedulerLock {
12
String name() default ""; // Lock name (required in practice)
13
String lockAtMostFor() default ""; // Maximum lock duration
14
String lockAtLeastFor() default ""; // Minimum lock duration
15
}
16
```
17
18
### Parameters
19
20
- **name**: The lock name used to identify the lock across nodes. Can include SpEL expressions when parameters are available.
21
- **lockAtMostFor**: Maximum time the lock should be held (fallback for dead nodes). Overrides `defaultLockAtMostFor` from `@EnableSchedulerLock`.
22
- **lockAtLeastFor**: Minimum time the lock should be held. Useful for preventing rapid re-execution.
23
24
### Usage Examples
25
26
#### Basic Usage
27
```java
28
@Component
29
public class ScheduledTasks {
30
31
@Scheduled(cron = "0 */5 * * * *")
32
@SchedulerLock(name = "processData", lockAtMostFor = "4m", lockAtLeastFor = "1m")
33
public void processData() {
34
// This method will only run on one node at a time
35
doHeavyProcessing();
36
}
37
}
38
```
39
40
#### Using SpEL Expressions
41
```java
42
@SchedulerLock(name = "processUser-#{#userId}", lockAtMostFor = "10m")
43
public void processUser(Long userId) {
44
// Lock name will be "processUser-123" for userId=123
45
processUserData(userId);
46
}
47
```
48
49
#### Using Default Durations
50
```java
51
@SchedulerLock(name = "simpleTask")
52
public void simpleTask() {
53
// Uses defaultLockAtMostFor and defaultLockAtLeastFor from @EnableSchedulerLock
54
doSimpleWork();
55
}
56
```
57
58
## @LockProviderToUse Annotation
59
60
Annotation for disambiguating between multiple lock providers when more than one `LockProvider` bean exists.
61
62
```java { .api }
63
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.PACKAGE})
64
@Retention(RetentionPolicy.RUNTIME)
65
public @interface LockProviderToUse {
66
String value(); // Bean name of the LockProvider to use
67
}
68
```
69
70
### Usage Examples
71
72
#### Method-Level Provider Selection
73
```java
74
@Component
75
public class ScheduledTasks {
76
77
@Scheduled(cron = "0 */5 * * * *")
78
@SchedulerLock(name = "criticalTask", lockAtMostFor = "10m")
79
@LockProviderToUse("redisPrimaryLockProvider")
80
public void criticalTask() {
81
// Uses the Redis lock provider for this critical task
82
performCriticalOperation();
83
}
84
85
@Scheduled(cron = "0 0 */1 * * *")
86
@SchedulerLock(name = "housekeeping", lockAtMostFor = "30m")
87
@LockProviderToUse("databaseLockProvider")
88
public void housekeeping() {
89
// Uses the database lock provider for housekeeping
90
performHousekeeping();
91
}
92
}
93
```
94
95
#### Class-Level Provider Selection
96
```java
97
@Component
98
@LockProviderToUse("databaseLockProvider")
99
public class DatabaseTasks {
100
101
@Scheduled(cron = "0 0 2 * * *")
102
@SchedulerLock(name = "dbCleanup", lockAtMostFor = "2h")
103
public void cleanupDatabase() {
104
// Inherits database lock provider from class level
105
cleanupOldRecords();
106
}
107
}
108
```
109
110
#### Package-Level Provider Selection
111
```java
112
// In package-info.java
113
@LockProviderToUse("defaultLockProvider")
114
package com.example.scheduled.tasks;
115
116
import net.javacrumbs.shedlock.spring.annotation.LockProviderToUse;
117
```
118
119
## Method Proxy Infrastructure
120
121
### MethodProxyScheduledLockAdvisor
122
123
AOP advisor that intercepts calls to methods annotated with `@SchedulerLock`.
124
125
```java { .api }
126
public class MethodProxyScheduledLockAdvisor extends AbstractPointcutAdvisor {
127
128
public MethodProxyScheduledLockAdvisor(
129
ExtendedLockConfigurationExtractor lockConfigurationExtractor,
130
LockProviderSupplier lockProviderSupplier
131
);
132
133
public Pointcut getPointcut(); // Matches @SchedulerLock methods
134
public Advice getAdvice(); // Returns LockingInterceptor
135
}
136
```
137
138
### Method Interception Logic
139
140
The advisor uses a `MethodInterceptor` that:
141
142
1. **Validates method compatibility**: Throws `LockingNotSupportedException` for methods returning primitives (except void)
143
2. **Extracts lock configuration**: Uses `ExtendedLockConfigurationExtractor` to get lock settings
144
3. **Obtains lock provider**: Uses `LockProviderSupplier` to get the appropriate lock provider
145
4. **Executes with lock**: Uses `DefaultLockingTaskExecutor` to execute the method with locking
146
5. **Handles return types**: Special handling for `Optional` return types
147
148
```java { .api }
149
// Internal interceptor logic (simplified)
150
public class LockingInterceptor implements MethodInterceptor {
151
public Object invoke(MethodInvocation invocation) throws Throwable;
152
}
153
```
154
155
### Supported Return Types
156
157
- **void**: Standard case, no special handling
158
- **Object types**: Returns the actual result or null if lock not acquired
159
- **Optional**: Returns `Optional.empty()` if lock not acquired, otherwise the method result
160
- **Primitives**: **Not supported** - throws `LockingNotSupportedException`
161
162
### Usage with Optional Return Types
163
164
```java
165
@SchedulerLock(name = "optionalTask", lockAtMostFor = "5m")
166
public Optional<String> optionalTask() {
167
// If lock is acquired: returns Optional.of(result)
168
// If lock is not acquired: returns Optional.empty()
169
return Optional.of("Task completed");
170
}
171
```
172
173
## Lock Provider Selection
174
175
### Single Lock Provider
176
When only one `LockProvider` bean exists, it's used automatically:
177
178
```java
179
@Configuration
180
public class LockConfig {
181
@Bean
182
public LockProvider lockProvider() {
183
return new JdbcTemplateLockProvider(dataSource);
184
}
185
}
186
```
187
188
### Multiple Lock Providers
189
When multiple `LockProvider` beans exist, use `@LockProviderToUse` to specify which one:
190
191
```java
192
@Configuration
193
public class LockConfig {
194
195
@Bean("primaryLockProvider")
196
public LockProvider primaryLockProvider() {
197
return new RedisLockProvider(redisTemplate);
198
}
199
200
@Bean("fallbackLockProvider")
201
public LockProvider fallbackLockProvider() {
202
return new JdbcTemplateLockProvider(dataSource);
203
}
204
}
205
206
// Then use @LockProviderToUse to select
207
@SchedulerLock(name = "task", lockAtMostFor = "10m")
208
@LockProviderToUse("primaryLockProvider")
209
public void scheduledTask() {
210
// Uses Redis lock provider
211
}
212
```
213
214
### Lock Provider Supplier
215
216
Internal interface that supplies the appropriate lock provider for method execution:
217
218
```java { .api }
219
@FunctionalInterface
220
interface LockProviderSupplier {
221
LockProvider supply(Object target, Method method, Object[] parameterValues);
222
223
static LockProviderSupplier create(ListableBeanFactory beanFactory);
224
}
225
```
226
227
Implementation automatically handles:
228
- Single provider scenarios (direct bean lookup)
229
- Multiple provider scenarios (uses `BeanNameSelectingLockProviderSupplier`)
230
231
## Error Handling
232
233
### LockingNotSupportedException
234
235
Thrown when trying to lock methods that return primitive types (except void):
236
237
```java { .api }
238
class LockingNotSupportedException extends LockException {
239
LockingNotSupportedException(String message);
240
}
241
```
242
243
Example that throws this exception:
244
```java
245
@SchedulerLock(name = "invalidMethod", lockAtMostFor = "5m")
246
public int invalidMethod() { // ❌ Primitive return type
247
return 42;
248
}
249
```
250
251
### NoUniqueBeanDefinitionException
252
253
Thrown when multiple `LockProvider` beans exist but no `@LockProviderToUse` annotation is present:
254
255
```java
256
// ❌ This will fail at runtime if multiple providers exist
257
@SchedulerLock(name = "ambiguousTask", lockAtMostFor = "5m")
258
public void ambiguousTask() {
259
doWork();
260
}
261
```
262
263
The error message includes all available provider bean names to help with debugging.