Implementing Custom Annotations
1. Creating Custom Annotations (@interface)
Example: Custom @Auditable meta-annotation
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Auditable {
String action();
Severity severity() default Severity.INFO;
}
2. Using Meta-Annotations
| Meta-annotation | Use |
|---|---|
@Target |
Where annotation can be applied (TYPE, METHOD, FIELD, PARAMETER) |
@Retention |
SOURCE / CLASS / RUNTIME |
@Documented |
Include in Javadoc |
@Inherited |
Subclasses inherit |
@Repeatable |
Apply multiple times |
3. Implementing Annotation Processors
| Type | Purpose |
|---|---|
| Compile-time (APT) | Code generation (Lombok, MapStruct) |
| Runtime (reflection) | Configure / behavior at runtime |
| Spring AOP | Common runtime path for behavioral annotations |
4. Using AOP for Annotation Processing (@Aspect)
Example: AOP audit advice with execution time
@Aspect
@Component
public class AuditAspect {
@Around("@annotation(audit)")
public Object audit(ProceedingJoinPoint pjp, Auditable audit) throws Throwable {
long start = System.nanoTime();
try { return pjp.proceed(); }
finally {
long ms = (System.nanoTime() - start) / 1_000_000;
log.info("AUDIT action={} ms={}", audit.action(), ms);
}
}
}
5. Creating Validation Annotations
Example: @StrongPassword validation annotation
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = StrongPasswordValidator.class)
public @interface StrongPassword {
String message() default "Password must contain upper, lower, digit, symbol";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
6. Creating Security Annotations
Example: Composed @AdminOrOwner security annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('ADMIN') or @secService.isOwner(#id, authentication)")
public @interface AdminOrOwner {}
@AdminOrOwner
public Order get(Long id) { /* … */ }
7. Creating Logging Annotations
Example: Log execution time with AOP aspect
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime { String name() default ""; }
@Aspect @Component
public class LogExecutionTimeAspect {
@Around("@annotation(t)")
public Object around(ProceedingJoinPoint pjp, LogExecutionTime t) throws Throwable {
long start = System.nanoTime();
try { return pjp.proceed(); }
finally { log.info("{} took {} ms", t.name().isBlank() ? pjp.getSignature() : t.name(),
(System.nanoTime() - start)/1_000_000); }
}
}
8. Using SpEL in Custom Annotations
Example: SpEL key evaluation in rate limit aspect
public @interface RateLimit { String key(); int limit(); }
@Around("@annotation(rl)")
public Object enforce(ProceedingJoinPoint pjp, RateLimit rl) throws Throwable {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext ctx = new StandardEvaluationContext();
// … bind args by name with ParameterNameDiscoverer
String key = parser.parseExpression(rl.key()).getValue(ctx, String.class);
// throttle by key …
return pjp.proceed();
}
9. Combining Multiple Annotations (@AliasFor)
Example: @AliasFor composed request mapping annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(method = RequestMethod.POST, consumes = "application/json")
public @interface PostJson {
@AliasFor(annotation = RequestMapping.class, attribute = "value")
String[] value() default {};
}
10. Testing Custom Annotations
| Approach | Use |
|---|---|
| Reflection | Assert annotation present + values |
| @SpringBootTest | Verify aspect intercepts |
| AOP proxy check | AopUtils.isAopProxy(bean) |