Elegant Parameter Annotation + AOP for Shop Permission Validation in Spring
The article demonstrates how to create a custom @ValidShop annotation and an AOP aspect that intercepts Spring MVC controller methods, extracts the annotated parameter via reflection, validates the shop code through a helper service, and proceeds only when the check succeeds.
Requirement Background
To prevent a merchant from performing unauthorized operations on other stores after logging into the merchant center, the shop account (identified by shopCode) must be validated by calling a shop‑service API.
Technology and Principles Involved
The solution relies on a custom annotation combined with Spring AOP, which internally uses dynamic proxies and Java reflection. This pattern can also be applied to logging, distributed locks, flow control, transaction management, and ThreadLocal‑based implicit parameter passing.
Custom Annotation Definition
import java.lang.annotation.*;
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidShop {
String value() default "shopCode";
}AOP Interceptor Implementation
import org.apache.commons.lang.ArrayUtils;
import org.apache.logging.log4j.util.Strings;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Optional;
@Aspect
@Component
public class ShopValidator {
@Around("@within(org.springframework.web.bind.annotation.RestController)")
public Object aroundController(ProceedingJoinPoint pjp) throws Throwable {
Optional<String> shopCode = getAnnotatedValue(pjp);
if (shopCode.isPresent()) {
Result validateResult = ShopHelper.validateShopCode(shopCode.get());
if (validateResult.isFailed()) {
return validateResult;
}
}
return pjp.proceed();
}
private static Optional<String> getAnnotatedValue(ProceedingJoinPoint pjp)
throws NoSuchFieldException, IllegalAccessException {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Annotation[][] parameterAnnotations = signature.getMethod().getParameterAnnotations();
for (Annotation[] parameterAnnotation : parameterAnnotations) {
int paramIndex = ArrayUtils.indexOf(parameterAnnotations, parameterAnnotation);
for (Annotation annotation : parameterAnnotation) {
if (annotation instanceof ValidShop) {
ValidShop annotated = (ValidShop) annotation;
Object[] args = pjp.getArgs();
Object param = args[paramIndex];
if (param != null) {
if (param instanceof String) {
return Optional.of((String) param);
} else {
String fieldName = annotated.value();
Field field = param.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
Object fieldValue = field.get(param);
if (fieldValue instanceof String) {
return Optional.of((String) fieldValue);
}
}
}
return Optional.of(Strings.EMPTY);
}
}
}
return Optional.empty();
}
}Controller Method Usage
/** Method 1: receive shopCode as String */
@GetMapping("/{shopCode}")
public Result<Void> xxx(@PathVariable("shopCode") @ValidShop String shopCode) {
return Result.ok();
}
/** Method 2: receive object containing shopCode */
@PostMapping("/page")
public Result<Void> xxx(@RequestBody @ValidShop ShopQueryParam param) {
return Result.ok();
}The core idea is to obtain the method parameter annotated with @ValidShop via reflection, retrieve its value (or the value of the specified field when the parameter is an object), and invoke the business validation logic. If validation fails, the aspect returns the failure result directly; otherwise, the original controller method proceeds.
This approach achieves low‑intrusion code enhancement, decouples validation logic from business code, and can be extended to other cross‑cutting concerns such as logging, distributed locking, and flow control.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect's Journey
E‑commerce, SaaS, AI architect; DDD enthusiast; SKILL enthusiast
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
