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<ReadinessState> event) {
...
}
Closes gh-21898
This commit is contained in:
parent
a65ca7ac2f
commit
1604545727
|
|
@ -19,6 +19,8 @@ package org.springframework.boot.availability;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationEvent;
|
import org.springframework.context.ApplicationEvent;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.context.PayloadApplicationEvent;
|
||||||
|
import org.springframework.core.ResolvableType;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -32,9 +34,7 @@ import org.springframework.util.Assert;
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @since 2.3.0
|
* @since 2.3.0
|
||||||
*/
|
*/
|
||||||
public class AvailabilityChangeEvent<S extends AvailabilityState> extends ApplicationEvent {
|
public class AvailabilityChangeEvent<S extends AvailabilityState> extends PayloadApplicationEvent<S> {
|
||||||
|
|
||||||
private final S state;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@link AvailabilityChangeEvent} instance.
|
* Create a new {@link AvailabilityChangeEvent} instance.
|
||||||
|
|
@ -42,9 +42,7 @@ public class AvailabilityChangeEvent<S extends AvailabilityState> extends Applic
|
||||||
* @param state the availability state (never {@code null})
|
* @param state the availability state (never {@code null})
|
||||||
*/
|
*/
|
||||||
public AvailabilityChangeEvent(Object source, S state) {
|
public AvailabilityChangeEvent(Object source, S state) {
|
||||||
super(source);
|
super(source, state);
|
||||||
Assert.notNull(state, "State must not be null");
|
|
||||||
this.state = state;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,7 +50,20 @@ public class AvailabilityChangeEvent<S extends AvailabilityState> extends Applic
|
||||||
* @return the availability state
|
* @return the availability state
|
||||||
*/
|
*/
|
||||||
public S getState() {
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,10 @@ import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationEvent;
|
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.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
|
@ -39,7 +43,7 @@ class AvailabilityChangeEventTests {
|
||||||
@Test
|
@Test
|
||||||
void createWhenStateIsNullThrowsException() {
|
void createWhenStateIsNullThrowsException() {
|
||||||
assertThatIllegalArgumentException().isThrownBy(() -> new AvailabilityChangeEvent<>(this.source, null))
|
assertThatIllegalArgumentException().isThrownBy(() -> new AvailabilityChangeEvent<>(this.source, null))
|
||||||
.withMessage("State must not be null");
|
.withMessage("Payload must not be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -49,6 +53,24 @@ class AvailabilityChangeEventTests {
|
||||||
assertThat(event.getState()).isEqualTo(state);
|
assertThat(event.getState()).isEqualTo(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getResolvableType() {
|
||||||
|
LivenessState state = LivenessState.CORRECT;
|
||||||
|
AvailabilityChangeEvent<LivenessState> 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<SubClassedEnum> event = new AvailabilityChangeEvent<>(this.source, state);
|
||||||
|
ResolvableType type = event.getResolvableType();
|
||||||
|
assertThat(type.resolve()).isEqualTo(AvailabilityChangeEvent.class);
|
||||||
|
assertThat(type.resolveGeneric()).isEqualTo(SubClassedEnum.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void publishPublishesEvent() {
|
void publishPublishesEvent() {
|
||||||
ApplicationContext context = mock(ApplicationContext.class);
|
ApplicationContext context = mock(ApplicationContext.class);
|
||||||
|
|
@ -61,4 +83,45 @@ class AvailabilityChangeEventTests {
|
||||||
assertThat(event.getState()).isEqualTo(state);
|
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<LivenessState> event) {
|
||||||
|
assertThat(event.getState()).isInstanceOf(LivenessState.class).isEqualTo(LivenessState.CORRECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue