Прежде всего извиняюсь за неописательный заголовок, но проблема очень специфична 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). Вы можете найти образец проекта здесь .