Backend Development 14 min read

Implementing Request-Level Locking with a Custom @ApiLock Annotation in Java

This article explains how to prevent duplicate backend requests caused by rapid user clicks by creating a reusable @ApiLock annotation that leverages distributed locks, generates lock keys from request parameters, and integrates an AOP aspect to acquire and release locks around controller methods.

Top Architect
Top Architect
Top Architect
Implementing Request-Level Locking with a Custom @ApiLock Annotation in Java

When backend APIs are invoked repeatedly due to users rapidly clicking buttons, data inconsistencies can arise; a simple front‑end debounce is not always feasible, so a backend solution is needed.

The solution introduces a custom @ApiLock annotation that can be placed on any controller method. Its attributes include waitMills (lock wait timeout), expireMills (automatic lock expiration), requiredRequestAttrs and requiredHeaders (extra request data to include in the lock key), and lockKeyGenerateStrategy (a pluggable strategy for generating the lock key).

A ApiLockKeyGenerateStrategy interface is defined with a generateKey(String prefix, Map params) method. The default implementation ApiLockKeyDefaultGenerateStrategy converts the non‑null argument map to JSON and applies MD5 to produce a unique lock key.

The core of the mechanism is the ApiLockAspectPoint aspect. It defines a pointcut on all methods under ..controller.. , and an @Around advice that:

Checks whether the method is annotated with @ApiLock ; if not, it proceeds normally.

Collects all non‑null method arguments, handling HttpServletRequest specially to extract required attributes and headers.

Filters out empty keys/values using the nested RemoveNullEntryUtil utility.

Generates the lock key via the configured strategy.

Attempts to acquire a distributed lock using redisLock.tryLock . If acquisition fails, a BizException with the message "操作过于频繁" is thrown.

Executes the original method logic while the lock is held, and finally releases the lock in a finally block.

The RemoveNullEntryUtil class provides methods to recursively remove map entries with null or empty keys/values, preventing accidental global locks caused by empty parameters.

Usage is demonstrated with a sample controller method:

@ResponseBody
@GetMapping("/success")
@ApiLock(waitMills = 5000, expireMills = 5000, requiredRequestAttrs = "token", requiredHeaders = "h")
public String success(@RequestParam("id") long id, HttpServletRequest request) {
    System.out.println("接口收到" + id + request.getAttribute("token") + request.getHeader("h"));
    userService.queryById(1);
    return "success";
}

By applying @ApiLock , developers obtain a ready‑to‑use request‑level locking mechanism that works in distributed environments without needing additional front‑end changes.

backendJavaAOPSpringAnnotationDistributedLock
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.