Search code examples
javaspringaopspring-aop

Advice inherited method from generic abstract class in concrete non-generic class


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?


Solution

  • 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?