Совет унаследовал метод от универсального абстрактного класса в конкретном неуниверсальном классе

Например, у меня есть следующий интерфейс

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

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
0
390
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В 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) в классе преобразователя бетона.

Ты мне веришь теперь?

Спасибо за ответ. Это решение, которое я сейчас использую, но я не совсем доволен им. Если StringToIntegerConverter расширяет AbstractIntegerConverter<String>, то у него есть метод Integer convert(String input), унаследованный от AbstractIntegerConverter<String>. Не так ли? Почему тогда аспект его не видит? Я предполагаю, что это как-то связано со стиранием типов, но StringToIntegerConverter — это конкретный не универсальный класс, поэтому такое поведение выглядит для меня очень странным.

Vitalii Vitrenko 17.02.2019 11:35

Нет. Я не знаю, как, по вашему мнению, работает наследование, но не путем копирования байтового кода в унаследованные классы. Метод по-прежнему определен в абстрактном классе и, следовательно, является универсальным, т.е. фактически сигнатура метода содержит Object, а не String, как вы можете легко проверить с помощью javap -c. Так что мое решение — это не какой-то грязный обходной путь, которым вы должны быть недовольны, это правильное решение, потому что вы так закодировали свои классы. Вывод журнала моего аспекта также доказывает это: AbstractIntegerConverter.convert(Object).

kriegaex 18.02.2019 02:24

Я добавил вывод javap -s (не такой подробный) в свой ответ для вашего удобства и надеюсь, что вы примете его сейчас.

kriegaex 18.02.2019 02:33

Я не знаю, как именно работает наследование на низком уровне, но я точно знаю, что когда мы расширяем универсальный класс, параметры типа, которые мы указываем, каким-то образом сохраняются. Затем мы можем получить параметры универсального типа от конкретных наследников во время выполнения, как описано в Жетоны супертипа. Поэтому я подумал, что есть способ указать AspectJ использовать эту информацию.

Vitalii Vitrenko 18.02.2019 13:02

В прошлый раз, когда я проверял, я показал вам способ сделать это, разве это не то, что делает мой код? Мне жаль, что вы не одобряете реальность файловой структуры класса JVM и то, как она используется AspectJ, но я ничего не могу сделать, чтобы изменить это для вас.

kriegaex 19.02.2019 02:54

Для справки, это описание того, как использовать дженерики в AspectJ. AspectJ проверяет байт-код, а байт-код для метода, который вы хотите сопоставить, имеет общий тип параметра, поэтому применяется стирание типа. Это также объясняет, почему это работает, если вы непосредственно определяете метод в конкретном подклассе, потому что там вы даете ему определенный тип параметра.

kriegaex 19.02.2019 03:24

Другие вопросы по теме