Handling repeatable Java annotations

๐Ÿ“˜ @PolicyScope Annotation System

This document explains how to define, use, and enforce repeatable @PolicyScope annotations in a Spring Boot application using Aspect-Oriented Programming (AOP).


๐Ÿงฉ Purpose

@PolicyScope is a repeatable annotation used to declare one or more logical access scopes on a method. These scopes can be inspected at runtime to enforce access control policies or log activity.


โœ… Annotation Definitions

import java.lang.annotation.*;

@Repeatable(PolicyScopes.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface PolicyScope {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface PolicyScopes {
    PolicyScope[] value();
}

๐Ÿ” @PolicyScope is marked @Repeatable using the container annotation @PolicyScopes.


๐Ÿงช Example Usage

public class UserService {
	@PolicyScope("user:read")
	@PolicyScope("audit:log")
	public void fetchUsers() {
		System.out.println("Fetching users...");
	}

	@PolicyScopes({
		@PolicyScope("user:write"),
		@PolicyScope("audit:log")
	})

	public void modifyUsers() {
		System.out.println("Modifying users...");
	}
}

You may use either:

  • Multiple @PolicyScope annotations
  • A single @PolicyScopes container
  • Or both (though redundant)

๐Ÿง  Enforcing with Spring AOP

The following aspect intercepts all methods annotated with @PolicyScope or @PolicyScopes and enforces the scopes:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class PolicyScopeAspect {

    @Around("execution(* *(..)) && (@annotation(PolicyScope) || @annotation(PolicyScopes))")
    public Object enforcePolicyScopes(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        PolicyScope[] scopes = method.getAnnotationsByType(PolicyScope.class);

        checkPolicy(scopes); // pass all annotations directly

        return joinPoint.proceed();
    }

    private void checkPolicy(PolicyScope[] scopes) {
        for (PolicyScope scope : scopes) {
            System.out.println("โœ… Enforcing policy: " + scope.value());
            // Replace this with actual policy enforcement logic
        }
    }
}

๐Ÿ“ค Example Output

Given this method:

@PolicyScopes({
    @PolicyScope("admin:access"),
    @PolicyScope("audit:log")
})
public void adminPanel() { ... }

Or this method:
@PolicyScope("admin:access")
@PolicyScope("audit:log")
public void adminPanel() { ... }

Console output:

โœ… Enforcing policy: admin:access
โœ… Enforcing policy: audit:log

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.