spring AoP, pointcut expression for overloaded methods with same parameter types

3.1k views Asked by At

I've defined a class for CRUD operations on comments. The read method is overloaded.

class Comment{
    // method 1: returns all the comments by a user
    findAll(long userId, long subjectId, String param);

    // method 2: returns all the comments of all the users
    findAll(long subjectId, String param)
}

The point cut expression I've tried is

@Around("execution(* com.package..*Controller.findAll(..)) && args(userId,subjectId,..)")
public Object validateFindAll(final ProceedingJoinPoint proceedingJoinPoint, final long userId, final long subjectId) {
    // validate userId, if available
    // validate subjectId
}

Problem: As the data types for userId and subjectId are same, the point expression when applied to method 2 shifts the param values by 1 place. This means, the expression does not understand that the first parameter userId isn't passed. Instead, userId gets 'subjectId' as value and the subjectId gets the adjacent parameter 'param' as its value.

Note

  1. I am trying to avoid writing another method like findUserComments().

  2. I want to maintain consistency across the application. There are other classes with similar patterns of CRUD operations.

Question: Is it possible to define an expression applicable to both the methods with the first parameter userId being optional ?

EDIT - Solution While I was playing around with different approaches as suggested in the solutions below, I've finally removed method 2. I handle that case in method 1.

2

There are 2 answers

0
Bipul Sinha On BEST ANSWER

Problem is related to method averloading actually. Since, you are passing long userId and long subjectId AOP will always try to match those arguments. Solutions could be

1) Create another pointcut for other argument i.e. 1 for long,long and other for long, String

2) Use variable argument signature in the begining such as

 @Around("execution(* com.org..findAll(..)) && args(..,subjectId,param)")
public Object validateFindAll(final ProceedingJoinPoint joinPoint, final long userId, final long subjectId) {

 }

instead of using variable argument in the begining. Then you can use getArgs() method to figure out arguments. This is simple solution but may slowdown your processing.

3) Though as a design issue, I would suggest to encapsulate all your parameters in one single object and pass it. Instead of passing multiple parameters. It will help you in future as well.

1
kriegaex On

You cannot explicitly bind an AspectJ parameter and then expect it to match an incompatible signature. Thus, your pointcut will only match findAll(long, long, ..), i.e. "method 1" in your example. You can specify optional arguments with .., but then you cannot bind them to named parameters.

For example, it is possible to match both methods and bind long subjectId and String param via args(.., subjectId, param) because both parameters are predictably right-aligned at the end of the signature. If you want any optional (and thus unbound) parameter, you need to use thisJoinPoint.getArgs():

@Around("execution(* com.package..*Controller.findAll(..)) && args(.., subjectId, param)")
public Object validateFindAll(
    final ProceedingJoinPoint thisJoinPoint,
    final long subjectId,
    final String param
) {
    if (thisJoinPoint.getArgs().length == 3)
        System.out.println(thisJoinPoint + " -> " + thisJoinPoint.getArgs()[0] + ", " + subjectId + ", " + param);
    else
        System.out.println(thisJoinPoint + " -> " + subjectId + ", " + param);
    // (...)
}

But while getArgs() is dynamic, it probably is slower than parameter binding because it uses reflection. Maybe having two pointcuts is not so bad after all. If your advice method does complicated things before/after proceed(), you can still factor those things out into helper methods and call them from both advice.