From 160454572723f047c72171c8e25268c2abffaf5a Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 12 Jun 2020 10:29:00 -0700 Subject: [PATCH] Ensure AvailabilityChangeEvent carries generics Update `AvailabilityChangeEvent` to be a `PayloadEvent` and ensure that the `getResolvableType` method returns a generic compatible result. Prior to this commit, a ClassCastExeption would be thrown if the following event listener was declared: @EventListener void onEvent(AvailabilityChangeEvent event) { ... } Closes gh-21898 --- .../availability/AvailabilityChangeEvent.java | 25 +++++-- .../AvailabilityChangeEventTests.java | 65 ++++++++++++++++++- 2 files changed, 82 insertions(+), 8 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/availability/AvailabilityChangeEvent.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/availability/AvailabilityChangeEvent.java index ce14a5c296..9c6fa1e879 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/availability/AvailabilityChangeEvent.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/availability/AvailabilityChangeEvent.java @@ -19,6 +19,8 @@ package org.springframework.boot.availability; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.PayloadApplicationEvent; +import org.springframework.core.ResolvableType; import org.springframework.util.Assert; /** @@ -32,9 +34,7 @@ import org.springframework.util.Assert; * @author Phillip Webb * @since 2.3.0 */ -public class AvailabilityChangeEvent extends ApplicationEvent { - - private final S state; +public class AvailabilityChangeEvent extends PayloadApplicationEvent { /** * Create a new {@link AvailabilityChangeEvent} instance. @@ -42,9 +42,7 @@ public class AvailabilityChangeEvent extends Applic * @param state the availability state (never {@code null}) */ public AvailabilityChangeEvent(Object source, S state) { - super(source); - Assert.notNull(state, "State must not be null"); - this.state = state; + super(source, state); } /** @@ -52,7 +50,20 @@ public class AvailabilityChangeEvent extends Applic * @return the availability state */ public S getState() { - return this.state; + return getPayload(); + } + + @Override + public ResolvableType getResolvableType() { + return ResolvableType.forClassWithGenerics(getClass(), getStateType()); + } + + private Class getStateType() { + S state = getState(); + if (state instanceof Enum) { + return ((Enum) state).getDeclaringClass(); + } + return state.getClass(); } /** diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/availability/AvailabilityChangeEventTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/availability/AvailabilityChangeEventTests.java index e8416b7cd0..3c1bb6a5c3 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/availability/AvailabilityChangeEventTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/availability/AvailabilityChangeEventTests.java @@ -21,6 +21,10 @@ import org.mockito.ArgumentCaptor; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; +import org.springframework.core.ResolvableType; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -39,7 +43,7 @@ class AvailabilityChangeEventTests { @Test void createWhenStateIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> new AvailabilityChangeEvent<>(this.source, null)) - .withMessage("State must not be null"); + .withMessage("Payload must not be null"); } @Test @@ -49,6 +53,24 @@ class AvailabilityChangeEventTests { assertThat(event.getState()).isEqualTo(state); } + @Test + void getResolvableType() { + LivenessState state = LivenessState.CORRECT; + AvailabilityChangeEvent event = new AvailabilityChangeEvent<>(this.source, state); + ResolvableType type = event.getResolvableType(); + assertThat(type.resolve()).isEqualTo(AvailabilityChangeEvent.class); + assertThat(type.resolveGeneric()).isEqualTo(LivenessState.class); + } + + @Test + void getResolvableTypeWhenSubclassedEnum() { + SubClassedEnum state = SubClassedEnum.TWO; + AvailabilityChangeEvent event = new AvailabilityChangeEvent<>(this.source, state); + ResolvableType type = event.getResolvableType(); + assertThat(type.resolve()).isEqualTo(AvailabilityChangeEvent.class); + assertThat(type.resolveGeneric()).isEqualTo(SubClassedEnum.class); + } + @Test void publishPublishesEvent() { ApplicationContext context = mock(ApplicationContext.class); @@ -61,4 +83,45 @@ class AvailabilityChangeEventTests { assertThat(event.getState()).isEqualTo(state); } + @Test + void publishEvenToContextConsidersGenericType() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class); + AvailabilityChangeEvent.publish(context, LivenessState.CORRECT); + AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC); + } + + enum SubClassedEnum implements AvailabilityState { + + ONE { + + @Override + String getDescription() { + return "I have been overridden"; + } + + }, + + TWO { + + @Override + String getDescription() { + return "I have aslo been overridden"; + } + + }; + + abstract String getDescription(); + + } + + @Configuration + static class Config { + + @EventListener + void onLivenessAvailabilityChange(AvailabilityChangeEvent event) { + assertThat(event.getState()).isInstanceOf(LivenessState.class).isEqualTo(LivenessState.CORRECT); + } + + } + }