最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

java - SpringAOP JoinPoint Arguments Parser with SpEL Expression - Stack Overflow

programmeradmin4浏览0评论

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: .springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'request' cannot be found on null  
    at .springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:225)
    at .springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:112)
    at .springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:100)
    at .springframework.expression.spel.ast.CompoundExpression.getValueRef(CompoundExpression.java:60)
    at .springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:96)
    at .springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:114)
    at .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 ?

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: .springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'request' cannot be found on null  
    at .springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:225)
    at .springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:112)
    at .springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:100)
    at .springframework.expression.spel.ast.CompoundExpression.getValueRef(CompoundExpression.java:60)
    at .springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:96)
    at .springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:114)
    at .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 ?

Share Improve this question edited Feb 18 at 5:59 kriegaex 67.4k15 gold badges120 silver badges223 bronze badges asked Feb 17 at 8:23 GaripTipiciGaripTipici 5201 gold badge9 silver badges27 bronze badges 8
  • 2 "but the line below fails to parse request object" can you include the exact error message? – dani-vta Commented Feb 17 at 8:31
  • @dani-vta I added unit test and error message – GaripTipici Commented Feb 17 at 8:54
  • Are there actually parameter names in the map? Judging from the error there aren't. Did you mock things correctly? I see mocked behavior for the getArgs but not for the getSignature. – M. Deinum Commented Feb 17 at 9:28
  • @M.Deinum here is the map: request -> {CreateTicketsForPlayroundRequest@4689} "CreateTicketsForPlayroundRequest(selectionPlayroundNumber=1, playroundUid=PlayroundUid(fundraiserId=1001, playroundNumber=1, playroundType=REGULAR, extraPlayroundTypeId=1), ticketCreationCutoff=null, salesCutoffFree=null, beneficiariesExportFile=null, playSchemesExportFile=null, businessServices=null, session=null)" – GaripTipici Commented Feb 17 at 10:29
  • Please add the full stacktrace of the error instead of a snippet. – M. Deinum Commented Feb 17 at 10:52
 |  Show 3 more comments

1 Answer 1

Reset to default 2

There is no problem with the SpelExpressionParser but rather with the way you are using it. Or to be more precise the expression you are parsing.

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);
    }
}

This bit contains the problematic part. You check if the expression starts with a # and then remove the # with the parameter.substring(1). This does actually change the expression and will interpret request as being a property / field of a root object. As there is no root object here it will throw an error as you get.

The fix is rather simple just use the expression as is instead of removing the #.

if (parameter.startsWith("#")) {
    try {
        String expression = parameter

        // 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);
    }
}

Now that the expression starts with a # it will properly resolve to a VariableReference which will use your created Map to lookup a variable named request. The remained of the expression is then used as PropertyOrFieldReference on the returned object.

TIP: The SpelExpressionParser is thread-safe so you can use a single instance instead of recreating it each time you need it.

发布评论

评论列表(0)

  1. 暂无评论