Spring Data Rest, переопределяющий методы @RepositoryEventHandler, вызывает ClassCastException - PullRequest
0 голосов
/ 02 мая 2020

Прежде всего извиняюсь за неописательный заголовок, но проблема очень специфична c для дизайна моего приложения.

Я пытаюсь обновить приложение Spring Boot с версии 1 (1.5. 4) до версии 2 (2.2.6 в настоящее время является самой последней).

В проекте используется среда Spring Data Rest, и в ней имеется набор @RepositoryEventHandler классов. Некоторые из обработчиков следуют некоторой иерархии, то есть существует абстрактный обработчик событий, который расширяется указанными c реализациями. Давайте назовем абстрактный обработчик (который, кстати, не аннотирован @RepositoryEventHandler, но содержит аннотированные методы-обработчики, такие как @HandleBeforeCreate) AbstractEventHandler, который имеет 2 реализации, EntityOneEventHandler и EntityTwoEventHandler.

Entity классы имеют общий интерфейс, давайте для простоты назовем его ParentEntity.

Итак, короче говоря, мы имеем ситуацию, подобную следующей (которая, конечно, упрощена, но достаточна для симуляции проблемы)

public interface ParentEntity {
}

public class EntityOne implements ParentEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
}

public class EntityTwo implements ParentEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
}
public abstract class AbstractEventHandler<E extends ParentEntity> {
    @HandleBeforeCreate
    public void handle(E entity) {
        log.info("inside entity event handler");
    }
}

@RepositoryEventHandler
public class EntityOneEventHandler extends AbstractEventHandler<EntityOne> {
}

@RepositoryEventHandler
public class EntityTwoEventHandler extends AbstractEventHandler<EntityTwo> {
    @Override
    @HandleBeforeCreate
    public void handle(EntityTwo entity) {
        super.handle(entity);
        // ... other custom logic here
    }
}

В Spring Boot 1.5.4 вышеуказанный дизайн работает без каких-либо проблем (фактически он вызывает двойной вызов обработчика события для EntityTwo, но пока это не имеет значения). В Spring Boot 2.xx вышеупомянутая конструкция выдает исключение, когда должно быть выполнено действие сохранения EntityOne.

Ошибка:

java .lang.ClassCastException : com.example.resteventhandler.EntityOne не может быть приведен к com.example.resteventhandler.EntityTwo

Причина ошибки заключается в следующем: AnnotatedEventHandlerInvoker из org.springframework.data.rest.core.event регистрирует следующие методы

Spring Boot 2.xx

AnnotatedEventHandlerInvoker.EventHandlerMethod(
  targetType=class com.example.resteventhandler.EntityOne, 
  method=public void com.example.resteventhandler.AbstractEventHandler.handle(com.example.resteventhandler.ParentEntity), 
  handler=com.example.resteventhandler.EntityOneEventHandler@6ad198c9)

AnnotatedEventHandlerInvoker.EventHandlerMethod(
  targetType=class com.example.resteventhandler.EntityTwo, 
  method=public void com.example.resteventhandler.EntityTwoEventHandler.handle(com.example.resteventhandler.EntityTwo), 
  handler=com.example.resteventhandler.EntityTwoEventHandler@4c114687)

AnnotatedEventHandlerInvoker.EventHandlerMethod(
  targetType=interface com.example.resteventhandler.ParentEntity, 
  method=public void com.example.resteventhandler.EntityTwoEventHandler.handle(com.example.resteventhandler.ParentEntity), 
  handler=com.example.resteventhandler.EntityTwoEventHandler@4c114687)

Spring Boot 1.5.4

AnnotatedEventHandlerInvoker.EventHandlerMethod(
  targetType=class com.example.resteventhandler.EntityOne, 
  method=public void com.example.resteventhandler.AbstractEventHandler.handle(com.example.resteventhandler.ParentEntity), 
  handler=com.example.resteventhandler.EntityOneEventHandler@4ff0706c)

AnnotatedEventHandlerInvoker.EventHandlerMethod(
  targetType=class com.example.resteventhandler.EntityTwo, 
  method=public void com.example.resteventhandler.EntityTwoEventHandler.handle(com.example.resteventhandler.EntityTwo), h
  andler=com.example.resteventhandler.EntityTwoEventHandler@6968bb65)

AnnotatedEventHandlerInvoker.EventHandlerMethod(
  targetType=class com.example.resteventhandler.EntityTwo, 
  method=public void com.example.resteventhandler.AbstractEventHandler.handle(com.example.resteventhandler.ParentEntity), 
  handler=com.example.resteventhandler.EntityTwoEventHandler@6968bb65)

Обратите внимание на targetType в последнем случае. Тот факт, что в версиях 2.xx целевой тип является родительским интерфейсом, приводит к выполнению следующего условия для метода AnnotatedEventHandlerInvoker.onApplicationEvent() и, в конечном итоге, к возникновению вышеупомянутой ошибки:

if (!ClassUtils.isAssignable(handlerMethod.targetType, src.getClass())) {
   continue;
}

Я думаю, что регистрация обработчика методы имеют место в методе AnnotatedEventHandlerInvoker.inspect(), который в конечном итоге использует ReflectionUtils (да, спасибо), но я заблудился, копая глубже.

Итак, некоторые из моих вопросов

  • почему изменение поведения между различными версиями?
  • Поведение Spring Boot 2.xx вызвано ошибкой в ​​ReflectionUtils
  • Поведение Spring Boot 1.5.4 вызвано ошибкой на ReflectionUtils Побочным эффектом было то, что плохой дизайн моего приложения работал «как положено», и теперь исправлена ​​ошибка, из-за которой плохой дизайн выявлялся?
  • Могу ли я что-то сделать, чтобы преодолеть проблему, не меняя дизайн на данный момент?

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

Для вашего удобства я создал пример проекта с ситуацией, описанной здесь. Все желающие могут переключаться между ветвями (с указанием разных версий Spring Boot) и запускать единый модульный тест, чтобы убедиться, что он проходит (для версии 1.5.4) с ошибками (для версий 2.xx). Вы можете найти образец проекта здесь .

...