Например, у меня есть следующий интерфейс
public interface Converter<I, O> {
public O convert(I input);
}
Абстрактный класс, реализующий этот интерфейс
public abstract class AbstractIntegerConverter<T> implements Converter<T, Integer> {
public Integer convert(T input) {
// convert anything to int
}
public abstract Integer defaultValue();
}
И конкретная реализация
@Component
public class StringToIntegerConverter extends AbstractIntegerConverter<String> {
@Override
public Integer defaultValue() {
// implementation
}
}
Я хочу посоветовать все методы, которые преобразуют String во что угодно. Я создал следующий аспект
@Aspect
@Component
public class ConverterAspect {
@Around("execution(* *..*.Converter+.convert(String))")
public Object adviceStringConverter(ProceedingJoinPoint joinPoint) throws Throwable {
// logic
}
}
Это не работает. Spring не создает прокси для класса StringToIntegerConverter.
Однако, если я переопределяю метод convert(String input) из абстрактного класса, он начинает работать, и Spring успешно создает прокси для StringToIntegerConverter и выполняет всю необходимую логику.
@Component
public class StringToIntegerConverter extends AbstractIntegerConverter<String> {
@Override
public Integer convert(String input) {
return super.convert(input);
}
@Override
public Integer defaultValue() {
// implementation
}
}
Почему это происходит? Есть ли способ определить pointcut, чтобы мне не нужно было переопределять метод convert(String input)?




В AbstractIntegerConverter сигнатура метода не public Integer convert(String input), а public Integer convert(T input), поэтому ваш pointcut * *..*.Converter+.convert(String) не совпадает. Вместо этого используйте Object или * и проверьте тип времени выполнения через args() вместо использования подписи времени компиляции:
@Around("execution(* *..Converter+.convert(*)) && args(input)")
public Object adviceStringConverter(ProceedingJoinPoint joinPoint, String input) throws Throwable {
System.out.println(joinPoint);
return joinPoint.proceed();
}
С моей добавленной строкой журнала вы увидите в консоли что-то вроде:
execution(Integer de.scrum_master.app.AbstractIntegerConverter.convert(Object))
Видеть? convert(Object), а не convert(String), поэтому несоответствие.
P.S.: Это был интересный вопрос, не такой скучный, как большинство AspectJ или Spring AOP. Так что спасибо за это. :-)
Обновлять:
Что касается вашего дополнительного вопроса, вот что печатает javap -s (аспект деактивирован):
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;
}
Видеть? Подпись — convert(Object), как показывает вывод журнала моего аспекта.
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;
}
И нет метода convert(String) или convert(whatever) в классе преобразователя бетона.
Ты мне веришь теперь?
Нет. Я не знаю, как, по вашему мнению, работает наследование, но не путем копирования байтового кода в унаследованные классы. Метод по-прежнему определен в абстрактном классе и, следовательно, является универсальным, т.е. фактически сигнатура метода содержит Object, а не String, как вы можете легко проверить с помощью javap -c. Так что мое решение — это не какой-то грязный обходной путь, которым вы должны быть недовольны, это правильное решение, потому что вы так закодировали свои классы. Вывод журнала моего аспекта также доказывает это: AbstractIntegerConverter.convert(Object).
Я добавил вывод javap -s (не такой подробный) в свой ответ для вашего удобства и надеюсь, что вы примете его сейчас.
Я не знаю, как именно работает наследование на низком уровне, но я точно знаю, что когда мы расширяем универсальный класс, параметры типа, которые мы указываем, каким-то образом сохраняются. Затем мы можем получить параметры универсального типа от конкретных наследников во время выполнения, как описано в Жетоны супертипа. Поэтому я подумал, что есть способ указать AspectJ использовать эту информацию.
В прошлый раз, когда я проверял, я показал вам способ сделать это, разве это не то, что делает мой код? Мне жаль, что вы не одобряете реальность файловой структуры класса JVM и то, как она используется AspectJ, но я ничего не могу сделать, чтобы изменить это для вас.
Для справки, это описание того, как использовать дженерики в AspectJ. AspectJ проверяет байт-код, а байт-код для метода, который вы хотите сопоставить, имеет общий тип параметра, поэтому применяется стирание типа. Это также объясняет, почему это работает, если вы непосредственно определяете метод в конкретном подклассе, потому что там вы даете ему определенный тип параметра.
Спасибо за ответ. Это решение, которое я сейчас использую, но я не совсем доволен им. Если
StringToIntegerConverterрасширяетAbstractIntegerConverter<String>, то у него есть методInteger convert(String input), унаследованный отAbstractIntegerConverter<String>. Не так ли? Почему тогда аспект его не видит? Я предполагаю, что это как-то связано со стиранием типов, ноStringToIntegerConverter— это конкретный не универсальный класс, поэтому такое поведение выглядит для меня очень странным.