For example I have the following interface
public interface Converter<I, O> {
public O convert(I input);
}
Abstract class that implements this interface
public abstract class AbstractIntegerConverter<T> implements Converter<T, Integer> {
public Integer convert(T input) {
// convert anything to int
}
public abstract Integer defaultValue();
}
And concrete implementation
@Component
public class StringToIntegerConverter extends AbstractIntegerConverter<String> {
@Override
public Integer defaultValue() {
// implementation
}
}
I want to advice all methods that converts a String
to anything. I created the following aspect
@Aspect
@Component
public class ConverterAspect {
@Around("execution(* *..*.Converter+.convert(String))")
public Object adviceStringConverter(ProceedingJoinPoint joinPoint) throws Throwable {
// logic
}
}
This does not work. Spring does not create a proxy for StringToIntegerConverter
class.
However if I override convert(String input)
method from abstract class it starts working and Spring successfully creates proxy for StringToIntegerConverter
and executes all the needed logic.
@Component
public class StringToIntegerConverter extends AbstractIntegerConverter<String> {
@Override
public Integer convert(String input) {
return super.convert(input);
}
@Override
public Integer defaultValue() {
// implementation
}
}
Why does this happen? Is there any way to define the pointcut so I would not need to override the convert(String input)
method?
In AbstractIntegerConverter
the method signature is not public Integer convert(String input)
but public Integer convert(T input)
, thus your pointcut * *..*.Converter+.convert(String)
does not match. Go with Object
or *
instead and verify the runtime type via args()
instead of using the compile-time signature:
@Around("execution(* *..Converter+.convert(*)) && args(input)")
public Object adviceStringConverter(ProceedingJoinPoint joinPoint, String input) throws Throwable {
System.out.println(joinPoint);
return joinPoint.proceed();
}
With my added log line you will then see on the console something like:
execution(Integer de.scrum_master.app.AbstractIntegerConverter.convert(Object))
See? convert(Object)
, not convert(String)
, thus the mismatch.
P.S.: This was an interesting question, not as boring as most AspectJ or Spring AOP ones. So thanks for that. :-)
Update:
As for your follow-up question, here is what javap -s
prints (aspect deactivated):
javap -s AbstractIntegerConverter.class
Compiled from "AbstractIntegerConverter.java"
public abstract class de.scrum_master.app.AbstractIntegerConverter<T> implements de.scrum_master.app.Converter<T, java.lang.Integer> {
public de.scrum_master.app.AbstractIntegerConverter();
descriptor: ()V
public java.lang.Integer convert(T);
descriptor: (Ljava/lang/Object;)Ljava/lang/Integer;
public abstract java.lang.Integer defaultValue();
descriptor: ()Ljava/lang/Integer;
public java.lang.Object convert(java.lang.Object);
descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
}
See? The signature is convert(Object)
as my aspect's log output also shows.
javap -s StringToIntegerConverter.class
Compiled from "StringToIntegerConverter.java"
public class de.scrum_master.app.StringToIntegerConverter extends de.scrum_master.app.AbstractIntegerConverter<java.lang.String> {
public de.scrum_master.app.StringToIntegerConverter();
descriptor: ()V
public java.lang.Integer defaultValue();
descriptor: ()Ljava/lang/Integer;
}
And no convert(String)
or convert(whatever)
method in the concrete converter class.
Do you believe me now?