I'm creating an annotation, like below
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Annotation {
String template();
String[] parameters() default {};
}
I want it to be a generic one, so I can use it with nested objects or simple integers.
I will use it on top of methods, like
@Annotation(
template = "Test-{0}-{1}-{2}",
parameters = {"#request.playroundUid.fundraiserId", "#request.selectionPlayroundNumber", "#request.playroundUid.playroundType"}
)
public TestResponse test(Request request)
So I created an aspect which is using the method below
public static String generateName(ProceedingJoinPoint joinPoint, Annotation annotation) {
String template = annotation.template();
Object[] args = joinPoint.getArgs();
String[] parameters = annotation.parameters();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String[] parameterNames = signature.getParameterNames();
// Create a map of parameter names -> values
Map<String, Object> paramMap = new HashMap<>();
for (int i = 0; i < parameterNames.length; i++) {
paramMap.put(parameterNames[i], args[i]);
}
// DEBUG: Print out the map to verify correct parameter storage
System.out.println("Parameter Map: " + paramMap);
// SpEL context
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariables(paramMap); // Bind method parameters (e.g., request)
ExpressionParser parser = new SpelExpressionParser();
for (int i = 0; i < parameters.length; i++) {
String parameter = parameters[i];
if (parameter.startsWith("#")) {
try {
String expression = parameter.substring(1); // Remove "#"
// DEBUG: Print out the expression being evaluated
System.out.println("Evaluating SpEL expression: " + expression);
// Evaluate the SpEL expression directly
Object evaluatedValue = parser.parseExpression(expression).getValue(context);
if (evaluatedValue == null) {
throw new RuntimeException("SpEL expression '" + parameter + "' evaluated to null. Check field access.");
}
template = template.replace("{" + i + "}", evaluatedValue.toString());
} catch (Exception e) {
throw new RuntimeException("Failed to evaluate SpEL expression: " + parameter, e);
}
} else {
template = template.replace("{" + i + "}", args[i] != null ? args[i].toString() : "null");
}
}
return template;
}
But the line below fails to parse request object.
parser.parseExpression(expression).getValue(context);
java.lang.RuntimeException: Failed to evaluate SpEL expression: #request.playroundUid.fundraiserId
at com.novamedia.nl.beehive.participationadministration.util.AnnotationUtil.generateName(AnnotationUtil.java:112)
at com.novamedia.nl.beehive.participationadministration.aspect.AspectTest.shouldGenerateCorrectNameForComplexParameters(AspectTest.java:194)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'request' cannot be found on null
at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:225)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:112)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:100)
at org.springframework.expression.spel.ast.CompoundExpression.getValueRef(CompoundExpression.java:60)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:96)
at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:114)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:273)
at com.novamedia.nl.beehive.participationadministration.util.AnnotationUtil.generateLockName(AnnotationUtil.java:104)
... 4 more
Here is my unit test
@Test
void shouldGenerateCorrectNameForComplexParameters() throws NoSuchMethodException {
// Arrange
Annotation annotation = mock(Annotation.class);
when(annotation.template()).thenReturn("CreateTicketsForPlayround-{0}-{1}-{2}");
when(annotation.parameters()).thenReturn(new String[]{"#request.playroundUid.fundraiserId", "#request.selectionPlayroundNumber", "#request.playroundUid.playroundType"});
Method mockMethod = MyTestService.class.getMethod("createTicketsForPlayround", CreateTicketsForPlayroundRequest.class);
when(methodSignature.getMethod()).thenReturn(mockMethod);
when(methodSignature.getParameterNames()).thenReturn(new String[]{"request"});
// Create a mock request with nested properties
CreateTicketsForPlayroundRequest request = CreateTicketsForPlayroundRequest.builder()
.selectionPlayroundNumber(1)
.playroundUid(new PlayroundUid(1001, 1, PlayroundType.REGULAR, 1))
.build();
when(joinPoint.getArgs()).thenReturn(new Object[]{request});
// Act
String name = generateName(joinPoint, annotation);
// Assert
assertEquals("CreateTicketsForPlayround-1001-1-REGULAR", name);
}
Briefly, my questions are; how can I parse request objects by using SpEL expressions ? what is the best practise for such case ? Should I use SpEL expressions or is there another way to handle ?
getArgsbut not for thegetSignature.