C.B.
C.B.

Reputation: 1

Two Pointcuts for two methods with the same name but different arguments

Hei.

I'm writing an @Aspect for the logging of my persistence layer.

First some code which may show the error to an experienced developer ;)

/** Interface of the class to observe. */
public interface PersistenceService {

    public Serializable save(Serializable serializable);

    public List<Serializable> save(List<Serializable> list)
}

/** Actual class to observe. */
@Service
public class PersistenceService {

    @Autowired
    private SomeJpaRepository rep;

    public Serializable save(Serializable serializable) {
        return rep.save(serializable);
    }

    public List<Serializable> save(List<Serializable> list) {
        return rep.save(list);
    }
}

And here the Aspect:

/** The Aspect. */
@Aspect
@Component
public class PersistenceService {

    /** A org.slf4j.Logger (using logback). */
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /** Pointcut to define the classes to observe. */
    @Pointcut("within(de.mypckg.myproject.persistence.*.*)")
    public void inPersistanceLayer() {}

    /** Pointcut for the first save-method. */
    @Pointcut("execution(public * save(..)) && args(serializable)")
    public void saveOperation(Serializable serializable) {}

    /** Pointcut for the first save-method. */
    @Pointcut("execution(public * save(..)) && args(list)")
    public void saveOperation(List<Serializable> list) {}

    /** Method for the first save-method. */
    @Around("inPersistanceLayer() && saveOperation(serializable)")
    public List<Serializable> logSave(ProceedingJoinPoint joinPoint, Serializable serializable) throws Throwable {

        // log some stuff
        Object saved = joinPoint.proceed();
        // log somemore stuff
    }

    /** Method for the second save-method. */
    @Around("inPersistanceLayer() && saveOperation(list)")
    public List<Serializable> logSave(ProceedingJoinPoint joinPoint, List<Serializable> list) throws Throwable {

        // log some stuff
        Object saved = joinPoint.proceed();
        // log somemore stuff
    }
}

If I only have one of the Pointcuts (and the method for it) it works, but if I add the second, I get the following exception:

java.lang.IllegalArgumentException: warning no match for this type name: list [Xlint:invalidAbsoluteTypeName]

I changed the order of the pointcuts, it's always the second in line. Any ideas on how to solve this?

Update
Once I had posted the question I had an idea. I changed the Pointcuts like this:

/** The Aspect. */
@Aspect
@Component
public class PersistenceService {

    /** A org.slf4j.Logger (using logback). */
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /** Pointcut to define the classes to observe. */
    @Pointcut("within(de.mypckg.myproject.persistence.*.*)")
    public void inPersistanceLayer() {}

    /** Pointcut for the save-method. */
    @Pointcut("execution(public * save(..))")
    public void saveOperation() {}

    /** Pointcut for the serializable argument. */
    @Pointcut("args(serializable)")
    public void serializableArgument(Serializable serializable) {}

    /** Pointcut for the list argument. */
    @Pointcut("args(list)")
    public void listArgument(List<Serializable> list) {}

    /** Method for the first save-method. */
    @Around("inPersistanceLayer() && saveOperation() && serializableArgument(serializable)")
    public Object logSave(ProceedingJoinPoint joinPoint, Serializable serializable) throws Throwable {

        // log some stuff
        Object saved = joinPoint.proceed();
        // log somemore stuff
        return saved;
    }

    /** Method for the second save-method. */
    @Around("inPersistanceLayer() && saveOperation(list) && listArgument(list)")
    public Object logSave(ProceedingJoinPoint joinPoint, List<Serializable> list) throws Throwable {

        // log some stuff
        Object saved = joinPoint.proceed();
        // log somemore stuff
        return saved;
    }
}

Now the exception is gone, but there is still a small issue (which is much easier to solve I guess): Since ArrayList implements Serializable both pointcuts are executed, at least in my test case where I use an ArrayList.
I will look into that and post what I find, but help is appreciated as well ;)

Update 2

Corrected a copy paste error stated by kriegaex. Thanks!

The return type of the methods logSave(..) is Object.

Update 3

I changed the code to just using one pointcut and one method and check with instanceof like kriegaex proposed.

/** The Aspect. */
@Aspect
@Component
public class PersistenceService {

    /** A org.slf4j.Logger (using logback). */
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /** Pointcut to define the classes to observe. */
    @Pointcut("within(de.mypckg.myproject.persistence.*.*)")
    public void inPersistanceLayer() {}

    /** Pointcut for the save-method. */
    @Pointcut("execution(public * save(*)) && args(serializable)")
    public void saveOperation(Serializable serializable) {}

    /** Method for the first save-method. */
    @Around("inPersistanceLayer() && saveOperation() && serializableArgument(serializable)")
    public Serializable logSave(ProceedingJoinPoint joinPoint, Serializable serializable) throws Throwable {

        // log some stuff
        Serializable saved = (Serializable) joinPoint.proceed();

        if (saved instanceof List<?>) {
            List<?> savedList = (List<?>) saved;
            // log somemore stuff with a List
        } else {
            // log somemore stuff
        }
        return saved;
    }
}

I still wonder why it didn't work the other way.

Upvotes: 0

Views: 6333

Answers (2)

matsev
matsev

Reputation: 33789

There is a difference between if you write your pointcut as

args(java.io.Serializable)

or

execution(* *(java.io.Serializable))

The former matches if the argument is Serializable at runtime, the latter matches only for method signatures that declares a single parameter of type Serializable. In your example, you have used .. as parameter for execution which means that any number of method parameters will be matched.

Take a look at the pointcut examples in the Spring reference docs. In particular, I think that you will find the args discussion interesting.


Edit:

Note that you cannot use the execution variant for binding, but you can easily use && to combine the two like you have tried, e.g.

@Pointcut("execution(public * save(java.io.Serializable)) && args(serializable)")
public void saveOperation(Serializable serializable) {}

and

@Pointcut("execution(public * save(java.util.List)) && args(list)")
public void saveOperation(List<Serializable> list) {}

Upvotes: 0

kriegaex
kriegaex

Reputation: 67457

Here are two options for you:

Application class

package de.scrum_master.aspectj.sample;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

public class TestApp {
    public static void main(String[] args) {
        save(new HashSet<String>());
        List<Serializable> arg = new ArrayList<Serializable>();
        save(arg);
    }
    static Serializable save(Serializable arg) { return arg; }
    static List<Serializable> save(List<Serializable> arg) { return arg; }
}

Aspect

package de.scrum_master.aspectj.sample;

import java.io.Serializable;
import java.util.List;

public aspect TestAspect {
    pointcut saveOperation(Object arg) : execution(* save(*)) && args(arg);
    pointcut serializableArgument(Serializable serializable) : execution(* save(Serializable)) && args(serializable);
    pointcut listArgument(List<Serializable> list) : execution(* save(List<Serializable>)) && args(list);

    Object around(Object arg) : saveOperation(arg) {
        if (arg instanceof List)
            System.out.println("Global advice   [List]:         " + thisJoinPointStaticPart.getSignature());
        else
            System.out.println("Global advice   [Serializable]: " + thisJoinPointStaticPart.getSignature());
        return proceed(arg);
    }

    List<Serializable> around(List<Serializable> list) : listArgument(list) {
        System.out.println("Specific advice [List]:         " + thisJoinPointStaticPart.getSignature());
        return proceed(list);
    }

    Serializable around(Serializable serializable) : serializableArgument(serializable) {
        System.out.println("Specific advice [Serializable]: " + thisJoinPointStaticPart.getSignature());
        return proceed(serializable);
    }
}

As you can see, the first advice with return type Object and using the simple pointcut saveOperation(Object arg) is the generic one-stop shopping solution. The other two advice are argument type specific, each using a separate pointcut. If you weave and run the application class it yields the following output:

Global advice   [Serializable]: Serializable de.scrum_master.aspectj.sample.TestApp.save(Serializable)
Specific advice [Serializable]: Serializable de.scrum_master.aspectj.sample.TestApp.save(Serializable)
Global advice   [List]:         List de.scrum_master.aspectj.sample.TestApp.save(List)
Specific advice [List]:         List de.scrum_master.aspectj.sample.TestApp.save(List)

The advice using pointcut serializableArgument(Serializable serializable) only fires once, just as you like.

Upvotes: 1

Related Questions