-
Notifications
You must be signed in to change notification settings - Fork 0
Programmatic Templates
While annotations provide a declarative approach, sometimes you need programmatic control over lock, semaphore, and rate limit operations. Locksmith provides template classes for these scenarios.
Use the programmatic API when:
- Lock/semaphore/rate limit keys are determined at runtime in complex ways
- You need fine-grained control over acquisition and release timing
- Annotations don't fit your use case (e.g., non-method boundaries)
- You want to conditionally acquire locks or check rate limits based on runtime state
The LocksmithLockTemplate provides programmatic access to distributed locks.
Both templates are auto-configured and can be injected:
@Service
public class MyService {
private final LocksmithLockTemplate lockTemplate;
public MyService(LocksmithLockTemplate lockTemplate) {
this.lockTemplate = lockTemplate;
}
}if (lockTemplate.tryLock("my-resource")) {
try {
// Critical section - only one instance executes this
processResource();
} finally {
lockTemplate.unlock("my-resource");
}
} else {
// Lock not acquired - handle accordingly
log.info("Resource is busy");
}The callback pattern ensures the lock is always released, even if an exception occurs:
String result = lockTemplate.executeWithLock("my-resource", () -> {
// Critical section
return processResource();
});
if (result == null) {
// Lock was not acquired
log.info("Could not acquire lock");
}boolean isLocked = lockTemplate.isLocked("my-resource");Use the builder when you need custom timing, lock types, or auto-renewal:
boolean acquired = lockTemplate.forKey("my-resource")
.waitTime(Duration.ofSeconds(5)) // Wait up to 5 seconds
.leaseTime(Duration.ofMinutes(2)) // Hold for up to 2 minutes
.tryLock();// Read lock - multiple readers allowed
lockTemplate.forKey("shared-data")
.lockType(LockType.READ)
.execute(() -> readData());
// Write lock - exclusive access
lockTemplate.forKey("shared-data")
.lockType(LockType.WRITE)
.execute(() -> writeData());For long-running operations where you can't predict the duration:
String result = lockTemplate.forKey("long-running-job")
.autoRenew() // Lock will be automatically renewed while held
.execute(() -> {
// Long-running operation - lock won't expire
return processLargeDataset();
});boolean acquired = lockTemplate.forKey("my-resource")
.waitTime(Duration.ofSeconds(10))
.leaseTime(Duration.ofMinutes(5))
.lockType(LockType.WRITE)
.tryLock();
if (acquired) {
try {
// Do work
} finally {
lockTemplate.forKey("my-resource")
.lockType(LockType.WRITE)
.unlock();
}
}The LocksmithSemaphoreTemplate provides programmatic access to distributed semaphores.
@Service
public class ApiService {
private final LocksmithSemaphoreTemplate semaphoreTemplate;
public ApiService(LocksmithSemaphoreTemplate semaphoreTemplate) {
this.semaphoreTemplate = semaphoreTemplate;
}
}// Limit to 5 concurrent executions across all instances
String permitId = semaphoreTemplate.tryAcquirePermit("api-calls", 5);
if (permitId != null) {
try {
// At most 5 instances execute this simultaneously
callExternalApi();
} finally {
semaphoreTemplate.releasePermit("api-calls", permitId);
}
} else {
// No permit available
log.info("API rate limit reached");
}String result = semaphoreTemplate.executeWithPermit("api-calls", 5, () -> {
// At most 5 concurrent executions
return callExternalApi();
});
if (result == null) {
// Permit was not acquired
log.info("Rate limit reached");
}String permitId = semaphoreTemplate.forKey("api-calls", 5)
.waitTime(Duration.ofSeconds(30)) // Wait up to 30 seconds for a permit
.leaseTime(Duration.ofMinutes(1)) // Permit expires after 1 minute
.tryAcquire();String result = semaphoreTemplate.forKey("api-calls", 5)
.waitTime(Duration.ofSeconds(10))
.leaseTime(Duration.ofMinutes(2))
.execute(() -> {
return callExternalApi();
});The LocksmithRateLimitTemplate provides programmatic access to distributed rate limiters.
@Service
public class ApiService {
private final LocksmithRateLimitTemplate rateLimitTemplate;
public ApiService(LocksmithRateLimitTemplate rateLimitTemplate) {
this.rateLimitTemplate = rateLimitTemplate;
}
}// Check if rate limit allows the request (10 permits per second by default)
if (rateLimitTemplate.tryAcquire("api-endpoint")) {
// Within rate limit
processRequest();
} else {
// Rate limit exceeded
log.info("Rate limit exceeded");
}The callback pattern provides cleaner code:
String result = rateLimitTemplate.executeWithRateLimit("api-endpoint", () -> {
// Rate limited operation
return processRequest();
});
if (result == null) {
// Rate limit was exceeded
log.info("Rate limited");
}Use the builder when you need custom permits, intervals, or rate types:
boolean acquired = rateLimitTemplate.forKey("api-endpoint")
.permits(100) // 100 requests
.interval(Duration.ofMinutes(1)) // per minute
.tryAcquire();// Overall - shared across all instances (default)
rateLimitTemplate.forKey("global-api")
.permits(100)
.interval(Duration.ofMinutes(1))
.rateType(RateType.OVERALL)
.execute(() -> callApi());
// Per-client - each instance gets its own quota
rateLimitTemplate.forKey("instance-api")
.permits(100)
.interval(Duration.ofMinutes(1))
.rateType(RateType.PER_CLIENT)
.execute(() -> callApi());boolean acquired = rateLimitTemplate.forKey("api-endpoint")
.permits(100)
.interval(Duration.ofMinutes(1))
.waitTime(Duration.ofSeconds(5)) // Wait up to 5 seconds for a permit
.tryAcquire();String result = rateLimitTemplate.forKey("user-api")
.permits(100)
.interval(Duration.ofMinutes(1))
.rateType(RateType.OVERALL)
.waitTime(Duration.ofSeconds(5))
.execute(() -> {
return processUserRequest();
});All templates automatically apply the configured key prefixes. For example:
locksmith:
lock:
key-prefix: "myapp:lock:"
semaphore:
key-prefix: "myapp:sem:"
rate-limit:
key-prefix: "myapp:rl:"-
lockTemplate.tryLock("orders")→ Redis key:myapp:lock:orders -
semaphoreTemplate.tryAcquirePermit("pool", 5)→ Redis key:myapp:sem:pool -
rateLimitTemplate.tryAcquire("api")→ Redis key:myapp:rl:api
All templates are thread-safe and can be safely shared across threads. They are also compatible with virtual threads (Project Loom).
| Feature | Annotations | Templates |
|---|---|---|
| Declarative | Yes | No |
| Dynamic keys at runtime | Via SpEL | Full control |
| Fine-grained timing control | Limited | Full control |
| Non-method boundaries | No | Yes |
| Automatic resource cleanup | Yes | With callback pattern |
| Code readability | High | Medium |
Use annotations (@DistributedLock, @DistributedSemaphore, @RateLimit) for:
- Method-level locking/rate limiting with known keys
- Simple SpEL expressions for dynamic keys
- Clean, declarative code
Use templates (LocksmithLockTemplate, LocksmithSemaphoreTemplate, LocksmithRateLimitTemplate) for:
- Complex key generation logic
- Conditional locking/rate limiting based on runtime state
- Non-method boundaries (e.g., within loops, across method calls)
- Maximum control over timing and behavior
- Distributed Locks - Annotation-based locking
- Distributed Semaphores - Annotation-based semaphores
- Rate Limiting - Annotation-based rate limiting
- Lock Types - REENTRANT, READ, and WRITE locks
- Auto-Renew Lease Time - Automatic lease extension
- Configuration - Configure defaults and key prefixes
