Reputation: 1
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
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
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