Allow custom availability states
Create a general purpose `AvailabilityState` interface and refactor the existing `LivenessState` and `ReadinessState` to use it. A single `AvailabilityChangeEvent` is now used to carry all availability state updates. This commit also renames `ApplicationAvailabilityProvider` to `ApplicationAvailabilityBean` and extracts an `ApplicationAvailability` interface that other beans can inject. The helps to hide the event listener method, which is really internal. Finally the state enums have been renamed as follows: - `LivenessState.LIVE` -> `LivenessState.CORRECT` - `ReadinessState.READY` -> `ReadinessState.ACCEPTING_TRAFFIC` - `ReadinessState.UNREADY` -> `ReadinessState.REFUSING_TRAFFIC` See gh-20962
This commit is contained in:
parent
473d4fd73d
commit
bb79c847b2
|
|
@ -29,7 +29,7 @@ import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.availability.ApplicationAvailabilityProvider;
|
||||
import org.springframework.boot.availability.ApplicationAvailability;
|
||||
import org.springframework.boot.cloud.CloudPlatform;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
|
|
@ -50,17 +50,16 @@ public class ProbesHealthContributorAutoConfiguration {
|
|||
@Bean
|
||||
@ConditionalOnEnabledHealthIndicator("livenessProbe")
|
||||
@ConditionalOnMissingBean
|
||||
public LivenessProbeHealthIndicator livenessProbeHealthIndicator(
|
||||
ApplicationAvailabilityProvider applicationAvailabilityProvider) {
|
||||
return new LivenessProbeHealthIndicator(applicationAvailabilityProvider);
|
||||
public LivenessProbeHealthIndicator livenessProbeHealthIndicator(ApplicationAvailability applicationAvailability) {
|
||||
return new LivenessProbeHealthIndicator(applicationAvailability);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnEnabledHealthIndicator("readinessProbe")
|
||||
@ConditionalOnMissingBean
|
||||
public ReadinessProbeHealthIndicator readinessProbeHealthIndicator(
|
||||
ApplicationAvailabilityProvider applicationAvailabilityProvider) {
|
||||
return new ReadinessProbeHealthIndicator(applicationAvailabilityProvider);
|
||||
ApplicationAvailability applicationAvailability) {
|
||||
return new ReadinessProbeHealthIndicator(applicationAvailability);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import org.springframework.boot.actuate.availability.ReadinessProbeHealthIndicat
|
|||
import org.springframework.boot.actuate.health.HealthEndpointGroupsRegistryCustomizer;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration;
|
||||
import org.springframework.boot.availability.ApplicationAvailabilityProvider;
|
||||
import org.springframework.boot.availability.ApplicationAvailability;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
|
@ -40,7 +40,7 @@ class ProbesHealthContributorAutoConfigurationTests {
|
|||
|
||||
@Test
|
||||
void probesNotConfiguredIfNotKubernetes() {
|
||||
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ApplicationAvailabilityProvider.class)
|
||||
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class)
|
||||
.doesNotHaveBean(LivenessProbeHealthIndicator.class)
|
||||
.doesNotHaveBean(ReadinessProbeHealthIndicator.class)
|
||||
.doesNotHaveBean(HealthEndpointGroupsRegistryCustomizer.class));
|
||||
|
|
@ -49,7 +49,7 @@ class ProbesHealthContributorAutoConfigurationTests {
|
|||
@Test
|
||||
void probesConfiguredIfProperty() {
|
||||
this.contextRunner.withPropertyValues("management.health.probes.enabled=true")
|
||||
.run((context) -> assertThat(context).hasSingleBean(ApplicationAvailabilityProvider.class)
|
||||
.run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class)
|
||||
.hasSingleBean(LivenessProbeHealthIndicator.class)
|
||||
.hasSingleBean(ReadinessProbeHealthIndicator.class)
|
||||
.hasSingleBean(HealthEndpointGroupsRegistryCustomizer.class));
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ package org.springframework.boot.actuate.availability;
|
|||
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.boot.availability.ApplicationAvailabilityProvider;
|
||||
import org.springframework.boot.actuate.health.Status;
|
||||
import org.springframework.boot.availability.ApplicationAvailability;
|
||||
import org.springframework.boot.availability.LivenessState;
|
||||
|
||||
/**
|
||||
|
|
@ -30,20 +31,16 @@ import org.springframework.boot.availability.LivenessState;
|
|||
*/
|
||||
public class LivenessProbeHealthIndicator extends AbstractHealthIndicator {
|
||||
|
||||
private final ApplicationAvailabilityProvider applicationAvailabilityProvider;
|
||||
private final ApplicationAvailability applicationAvailability;
|
||||
|
||||
public LivenessProbeHealthIndicator(ApplicationAvailabilityProvider applicationAvailabilityProvider) {
|
||||
this.applicationAvailabilityProvider = applicationAvailabilityProvider;
|
||||
public LivenessProbeHealthIndicator(ApplicationAvailability applicationAvailability) {
|
||||
this.applicationAvailability = applicationAvailability;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doHealthCheck(Health.Builder builder) throws Exception {
|
||||
if (LivenessState.LIVE.equals(this.applicationAvailabilityProvider.getLivenessState())) {
|
||||
builder.up();
|
||||
}
|
||||
else {
|
||||
builder.down();
|
||||
}
|
||||
LivenessState state = this.applicationAvailability.getLivenessState();
|
||||
builder.status(LivenessState.CORRECT == state ? Status.UP : Status.DOWN);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ package org.springframework.boot.actuate.availability;
|
|||
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.boot.availability.ApplicationAvailabilityProvider;
|
||||
import org.springframework.boot.actuate.health.Status;
|
||||
import org.springframework.boot.availability.ApplicationAvailability;
|
||||
import org.springframework.boot.availability.ReadinessState;
|
||||
|
||||
/**
|
||||
|
|
@ -30,20 +31,16 @@ import org.springframework.boot.availability.ReadinessState;
|
|||
*/
|
||||
public class ReadinessProbeHealthIndicator extends AbstractHealthIndicator {
|
||||
|
||||
private final ApplicationAvailabilityProvider applicationAvailabilityProvider;
|
||||
private final ApplicationAvailability applicationAvailability;
|
||||
|
||||
public ReadinessProbeHealthIndicator(ApplicationAvailabilityProvider applicationAvailabilityProvider) {
|
||||
this.applicationAvailabilityProvider = applicationAvailabilityProvider;
|
||||
public ReadinessProbeHealthIndicator(ApplicationAvailability applicationAvailability) {
|
||||
this.applicationAvailability = applicationAvailability;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doHealthCheck(Health.Builder builder) throws Exception {
|
||||
if (ReadinessState.READY.equals(this.applicationAvailabilityProvider.getReadinessState())) {
|
||||
builder.up();
|
||||
}
|
||||
else {
|
||||
builder.outOfService();
|
||||
}
|
||||
ReadinessState state = this.applicationAvailability.getReadinessState();
|
||||
builder.status(ReadinessState.ACCEPTING_TRAFFIC == state ? Status.UP : Status.OUT_OF_SERVICE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ import org.junit.jupiter.api.BeforeEach;
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.health.Status;
|
||||
import org.springframework.boot.availability.ApplicationAvailabilityProvider;
|
||||
import org.springframework.boot.availability.ApplicationAvailability;
|
||||
import org.springframework.boot.availability.LivenessState;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.when;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
|
|
@ -34,25 +34,25 @@ import static org.mockito.Mockito.mock;
|
|||
*/
|
||||
class LivenessProbeHealthIndicatorTests {
|
||||
|
||||
private ApplicationAvailabilityProvider stateProvider;
|
||||
private ApplicationAvailability availability;
|
||||
|
||||
private LivenessProbeHealthIndicator healthIndicator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
this.stateProvider = mock(ApplicationAvailabilityProvider.class);
|
||||
this.healthIndicator = new LivenessProbeHealthIndicator(this.stateProvider);
|
||||
this.availability = mock(ApplicationAvailability.class);
|
||||
this.healthIndicator = new LivenessProbeHealthIndicator(this.availability);
|
||||
}
|
||||
|
||||
@Test
|
||||
void livenessIsLive() {
|
||||
when(this.stateProvider.getLivenessState()).thenReturn(LivenessState.LIVE);
|
||||
given(this.availability.getLivenessState()).willReturn(LivenessState.CORRECT);
|
||||
assertThat(this.healthIndicator.health().getStatus()).isEqualTo(Status.UP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void livenessIsBroken() {
|
||||
when(this.stateProvider.getLivenessState()).thenReturn(LivenessState.BROKEN);
|
||||
given(this.availability.getLivenessState()).willReturn(LivenessState.BROKEN);
|
||||
assertThat(this.healthIndicator.health().getStatus()).isEqualTo(Status.DOWN);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ import org.junit.jupiter.api.BeforeEach;
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.health.Status;
|
||||
import org.springframework.boot.availability.ApplicationAvailabilityProvider;
|
||||
import org.springframework.boot.availability.ApplicationAvailability;
|
||||
import org.springframework.boot.availability.ReadinessState;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.when;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
|
|
@ -34,25 +34,25 @@ import static org.mockito.Mockito.mock;
|
|||
*/
|
||||
class ReadinessProbeHealthIndicatorTests {
|
||||
|
||||
private ApplicationAvailabilityProvider stateProvider;
|
||||
private ApplicationAvailability availability;
|
||||
|
||||
private ReadinessProbeHealthIndicator healthIndicator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
this.stateProvider = mock(ApplicationAvailabilityProvider.class);
|
||||
this.healthIndicator = new ReadinessProbeHealthIndicator(this.stateProvider);
|
||||
this.availability = mock(ApplicationAvailability.class);
|
||||
this.healthIndicator = new ReadinessProbeHealthIndicator(this.availability);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readinessIsReady() {
|
||||
when(this.stateProvider.getReadinessState()).thenReturn(ReadinessState.READY);
|
||||
given(this.availability.getReadinessState()).willReturn(ReadinessState.ACCEPTING_TRAFFIC);
|
||||
assertThat(this.healthIndicator.health().getStatus()).isEqualTo(Status.UP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readinessIsUnready() {
|
||||
when(this.stateProvider.getReadinessState()).thenReturn(ReadinessState.UNREADY);
|
||||
given(this.availability.getReadinessState()).willReturn(ReadinessState.REFUSING_TRAFFIC);
|
||||
assertThat(this.healthIndicator.health().getStatus()).isEqualTo(Status.OUT_OF_SERVICE);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@
|
|||
|
||||
package org.springframework.boot.autoconfigure.availability;
|
||||
|
||||
import org.springframework.boot.availability.ApplicationAvailabilityProvider;
|
||||
import org.springframework.boot.availability.ApplicationAvailabilityBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration} for
|
||||
* {@link ApplicationAvailabilityProvider}.
|
||||
* {@link ApplicationAvailabilityBean}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 2.3.0
|
||||
|
|
@ -31,8 +31,8 @@ import org.springframework.context.annotation.Configuration;
|
|||
public class ApplicationAvailabilityAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public ApplicationAvailabilityProvider applicationAvailabilityProvider() {
|
||||
return new ApplicationAvailabilityProvider();
|
||||
public ApplicationAvailabilityBean applicationAvailability() {
|
||||
return new ApplicationAvailabilityBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration;
|
||||
import org.springframework.boot.availability.ApplicationAvailabilityProvider;
|
||||
import org.springframework.boot.availability.ApplicationAvailability;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
|
@ -37,7 +37,7 @@ class ApplicationAvailabilityAutoConfigurationTests {
|
|||
|
||||
@Test
|
||||
void providerIsPresent() {
|
||||
this.contextRunner.run(((context) -> assertThat(context).hasSingleBean(ApplicationAvailabilityProvider.class)));
|
||||
this.contextRunner.run(((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.availability;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* Provides {@link AvailabilityState availability state} information for the application.
|
||||
* <p>
|
||||
* Components can inject this class to get the current state information. To update the
|
||||
* state of the application an {@link AvailabilityChangeEvent} should be
|
||||
* {@link ApplicationContext#publishEvent published} to the application context with
|
||||
* directly or via {@link AvailabilityChangeEvent#publish}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Phillip Webb
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public interface ApplicationAvailability {
|
||||
|
||||
/**
|
||||
* Return the {@link LivenessState} of the application.
|
||||
* @return the liveness state
|
||||
*/
|
||||
default LivenessState getLivenessState() {
|
||||
return getState(LivenessState.class, LivenessState.BROKEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link ReadinessState} of the application.
|
||||
* @return the readiness state
|
||||
*/
|
||||
default ReadinessState getReadinessState() {
|
||||
return getState(ReadinessState.class, ReadinessState.REFUSING_TRAFFIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@link AvailabilityState} information for the application.
|
||||
* @param <S> the state type
|
||||
* @param stateType the state type
|
||||
* @param defaultState the default state to return if no event of the given type has
|
||||
* been published yet (must not be {@code null}.
|
||||
* @return the readiness state
|
||||
* @see #getState(Class)
|
||||
*/
|
||||
<S extends AvailabilityState> S getState(Class<S> stateType, S defaultState);
|
||||
|
||||
/**
|
||||
* Return {@link AvailabilityState} information for the application.
|
||||
* @param <S> the state type
|
||||
* @param stateType the state type
|
||||
* @return the readiness state or {@code null} if no event of the given type has been
|
||||
* published yet
|
||||
* @see #getState(Class, AvailabilityState)
|
||||
*/
|
||||
<S extends AvailabilityState> S getState(Class<S> stateType);
|
||||
|
||||
/**
|
||||
* Return the last {@link AvailabilityChangeEvent} received for a given state type.
|
||||
* @param <S> the state type
|
||||
* @param stateType the state type
|
||||
* @return the readiness state or {@code null} if no event of the given type has been
|
||||
* published yet
|
||||
*/
|
||||
<S extends AvailabilityState> AvailabilityChangeEvent<S> getLastChangeEvent(Class<S> stateType);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.availability;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Bean that provides an {@link ApplicationAvailability} implementation by listening for
|
||||
* {@link AvailabilityChangeEvent change events}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Phillip Webb
|
||||
* @since 2.3.0
|
||||
* @see ApplicationAvailability
|
||||
*/
|
||||
public class ApplicationAvailabilityBean
|
||||
implements ApplicationAvailability, ApplicationListener<AvailabilityChangeEvent<?>> {
|
||||
|
||||
private final Map<Class<? extends AvailabilityState>, AvailabilityChangeEvent<?>> events = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public <S extends AvailabilityState> S getState(Class<S> stateType, S defaultState) {
|
||||
Assert.notNull(stateType, "StateType must not be null");
|
||||
Assert.notNull(defaultState, "DefaultState must not be null");
|
||||
S state = getState(stateType);
|
||||
return (state != null) ? state : defaultState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends AvailabilityState> S getState(Class<S> stateType) {
|
||||
AvailabilityChangeEvent<S> event = getLastChangeEvent(stateType);
|
||||
return (event != null) ? event.getState() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <S extends AvailabilityState> AvailabilityChangeEvent<S> getLastChangeEvent(Class<S> stateType) {
|
||||
return (AvailabilityChangeEvent<S>) this.events.get(stateType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(AvailabilityChangeEvent<?> event) {
|
||||
Class<? extends AvailabilityState> stateType = getStateType(event.getState());
|
||||
this.events.put(stateType, event);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Class<? extends AvailabilityState> getStateType(AvailabilityState state) {
|
||||
if (state instanceof Enum) {
|
||||
return (Class<? extends AvailabilityState>) ((Enum<?>) state).getDeclaringClass();
|
||||
}
|
||||
return state.getClass();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.availability;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Holds the availability state of the application.
|
||||
* <p>
|
||||
* Other application components can get the current state information from the
|
||||
* {@code ApplicationAvailabilityProvider}, or publish application evens such as
|
||||
* {@link ReadinessStateChangedEvent} and {@link LivenessStateChangedEvent} to update the
|
||||
* state of the application.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class ApplicationAvailabilityProvider implements ApplicationListener<ApplicationEvent> {
|
||||
|
||||
private LivenessState livenessState;
|
||||
|
||||
private ReadinessState readinessState;
|
||||
|
||||
/**
|
||||
* Create a new {@link ApplicationAvailabilityProvider} instance with
|
||||
* {@link LivenessState#BROKEN} and {@link ReadinessState#UNREADY}.
|
||||
*/
|
||||
public ApplicationAvailabilityProvider() {
|
||||
this(LivenessState.BROKEN, ReadinessState.UNREADY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ApplicationAvailabilityProvider} with the given states.
|
||||
* @param livenessState the liveness state
|
||||
* @param readinessState the readiness state
|
||||
*/
|
||||
public ApplicationAvailabilityProvider(LivenessState livenessState, ReadinessState readinessState) {
|
||||
Assert.notNull(livenessState, "LivenessState must not be null");
|
||||
Assert.notNull(readinessState, "ReadinessState must not be null");
|
||||
this.livenessState = livenessState;
|
||||
this.readinessState = readinessState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link LivenessState} of the application.
|
||||
* @return the liveness state
|
||||
*/
|
||||
public LivenessState getLivenessState() {
|
||||
return this.livenessState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link ReadinessState} of the application.
|
||||
* @return the readiness state
|
||||
*/
|
||||
public ReadinessState getReadinessState() {
|
||||
return this.readinessState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
if (event instanceof LivenessStateChangedEvent) {
|
||||
LivenessStateChangedEvent livenessEvent = (LivenessStateChangedEvent) event;
|
||||
this.livenessState = livenessEvent.getLivenessState();
|
||||
}
|
||||
else if (event instanceof ReadinessStateChangedEvent) {
|
||||
ReadinessStateChangedEvent readinessEvent = (ReadinessStateChangedEvent) event;
|
||||
this.readinessState = readinessEvent.getReadinessState();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.availability;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ApplicationEvent} sent when the {@link AvailabilityState} of the application
|
||||
* changes.
|
||||
* <p>
|
||||
* Any application component can send such events to update the state of the application.
|
||||
*
|
||||
* @param <S> the availability state type
|
||||
* @author Brian Clozel
|
||||
* @author Phillip Webb
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class AvailabilityChangeEvent<S extends AvailabilityState> extends ApplicationEvent {
|
||||
|
||||
private final S state;
|
||||
|
||||
/**
|
||||
* Create a new {@link AvailabilityChangeEvent} instance.
|
||||
* @param source the source of the event
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the changed availability state.
|
||||
* @return the availability state
|
||||
*/
|
||||
public S getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that can be used to publish an {@link AvailabilityChangeEvent}
|
||||
* to the given application context.
|
||||
* @param <S> the availability state type
|
||||
* @param context the context used to publish the event
|
||||
* @param state the changed availability state
|
||||
*/
|
||||
public static <S extends AvailabilityState> void publish(ApplicationContext context, S state) {
|
||||
Assert.notNull(context, "Context must not be null");
|
||||
publish(context, context, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that can be used to publish an {@link AvailabilityChangeEvent}
|
||||
* to the given application context.
|
||||
* @param <S> the availability state type
|
||||
* @param publisher the publisher used to publish the event
|
||||
* @param source the source of the event
|
||||
* @param state the changed availability state
|
||||
*/
|
||||
public static <S extends AvailabilityState> void publish(ApplicationEventPublisher publisher, Object source,
|
||||
S state) {
|
||||
Assert.notNull(publisher, "Publisher must not be null");
|
||||
publisher.publishEvent(new AvailabilityChangeEvent<>(source, state));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.availability;
|
||||
|
||||
/**
|
||||
* Tagging interface used on {@link ApplicationAvailability} states. This interface is
|
||||
* usually implemented on an {@code enum} type.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.3.0
|
||||
* @see LivenessState
|
||||
* @see ReadinessState
|
||||
*/
|
||||
public interface AvailabilityState {
|
||||
|
||||
}
|
||||
|
|
@ -26,15 +26,15 @@ package org.springframework.boot.availability;
|
|||
* @author Brian Clozel
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public enum LivenessState {
|
||||
public enum LivenessState implements AvailabilityState {
|
||||
|
||||
/**
|
||||
* The application is running and its internal state is correct.
|
||||
*/
|
||||
LIVE,
|
||||
CORRECT,
|
||||
|
||||
/**
|
||||
* The internal state of the application is broken.
|
||||
* The application is running but its internal state is broken.
|
||||
*/
|
||||
BROKEN
|
||||
|
||||
|
|
|
|||
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.availability;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* {@link ApplicationEvent} sent when the {@link LivenessState} of the application
|
||||
* changes.
|
||||
* <p>
|
||||
* Any application component can send such events to update the state of the application.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class LivenessStateChangedEvent extends ApplicationEvent {
|
||||
|
||||
private final String cause;
|
||||
|
||||
LivenessStateChangedEvent(LivenessState state, String cause) {
|
||||
super(state);
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public LivenessState getLivenessState() {
|
||||
return (LivenessState) getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code ApplicationEvent} signaling that the {@link LivenessState} is
|
||||
* live.
|
||||
* @param cause the cause of the live internal state of the application
|
||||
* @return the application event
|
||||
*/
|
||||
public static LivenessStateChangedEvent live(String cause) {
|
||||
return new LivenessStateChangedEvent(LivenessState.LIVE, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code ApplicationEvent} signaling that the {@link LivenessState} is
|
||||
* broken.
|
||||
* @param cause the cause of the broken internal state of the application
|
||||
* @return the application event
|
||||
*/
|
||||
public static LivenessStateChangedEvent broken(String cause) {
|
||||
return new LivenessStateChangedEvent(LivenessState.BROKEN, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code ApplicationEvent} signaling that the {@link LivenessState} is
|
||||
* broken.
|
||||
* @param throwable the exception that caused the broken internal state of the
|
||||
* application
|
||||
* @return the application event
|
||||
*/
|
||||
public static LivenessStateChangedEvent broken(Throwable throwable) {
|
||||
return new LivenessStateChangedEvent(LivenessState.BROKEN, throwable.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -26,16 +26,16 @@ package org.springframework.boot.availability;
|
|||
* @author Brian Clozel
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public enum ReadinessState {
|
||||
|
||||
/**
|
||||
* The application is not willing to receive traffic.
|
||||
*/
|
||||
UNREADY,
|
||||
public enum ReadinessState implements AvailabilityState {
|
||||
|
||||
/**
|
||||
* The application is ready to receive traffic.
|
||||
*/
|
||||
READY
|
||||
ACCEPTING_TRAFFIC,
|
||||
|
||||
/**
|
||||
* The application is not willing to receive traffic.
|
||||
*/
|
||||
REFUSING_TRAFFIC
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.availability;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* {@link ApplicationEvent} sent when the {@link ReadinessState} of the application
|
||||
* changes.
|
||||
* <p>
|
||||
* Any application component can send such events to update the state of the application.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class ReadinessStateChangedEvent extends ApplicationEvent {
|
||||
|
||||
ReadinessStateChangedEvent(ReadinessState state) {
|
||||
super(state);
|
||||
}
|
||||
|
||||
public ReadinessState getReadinessState() {
|
||||
return (ReadinessState) getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code ApplicationEvent} signaling that the {@link ReadinessState} is
|
||||
* ready.
|
||||
* @return the application event
|
||||
*/
|
||||
public static ReadinessStateChangedEvent ready() {
|
||||
return new ReadinessStateChangedEvent(ReadinessState.READY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code ApplicationEvent} signaling that the {@link ReadinessState} is
|
||||
* unready.
|
||||
* @return the application event
|
||||
*/
|
||||
public static ReadinessStateChangedEvent unready() {
|
||||
return new ReadinessStateChangedEvent(ReadinessState.UNREADY);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -21,8 +21,9 @@ import org.apache.commons.logging.LogFactory;
|
|||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.SpringApplicationRunListener;
|
||||
import org.springframework.boot.availability.LivenessStateChangedEvent;
|
||||
import org.springframework.boot.availability.ReadinessStateChangedEvent;
|
||||
import org.springframework.boot.availability.AvailabilityChangeEvent;
|
||||
import org.springframework.boot.availability.LivenessState;
|
||||
import org.springframework.boot.availability.ReadinessState;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
|
@ -99,13 +100,13 @@ public class EventPublishingRunListener implements SpringApplicationRunListener,
|
|||
@Override
|
||||
public void started(ConfigurableApplicationContext context) {
|
||||
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
|
||||
context.publishEvent(LivenessStateChangedEvent.live("Application started"));
|
||||
AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void running(ConfigurableApplicationContext context) {
|
||||
context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
|
||||
context.publishEvent(ReadinessStateChangedEvent.ready());
|
||||
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ import reactor.core.publisher.Mono;
|
|||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.boot.availability.ReadinessStateChangedEvent;
|
||||
import org.springframework.boot.availability.AvailabilityChangeEvent;
|
||||
import org.springframework.boot.availability.ReadinessState;
|
||||
import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext;
|
||||
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
|
|
@ -149,7 +150,7 @@ public class ReactiveWebServerApplicationContext extends GenericReactiveWebAppli
|
|||
|
||||
@Override
|
||||
protected void doClose() {
|
||||
publishEvent(ReadinessStateChangedEvent.unready());
|
||||
AvailabilityChangeEvent.publish(this, ReadinessState.REFUSING_TRAFFIC);
|
||||
WebServer webServer = getWebServer();
|
||||
if (webServer != null) {
|
||||
webServer.shutDownGracefully();
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ import org.springframework.beans.BeansException;
|
|||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.Scope;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.boot.availability.ReadinessStateChangedEvent;
|
||||
import org.springframework.boot.availability.AvailabilityChangeEvent;
|
||||
import org.springframework.boot.availability.ReadinessState;
|
||||
import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
|
|
@ -169,7 +170,7 @@ public class ServletWebServerApplicationContext extends GenericWebApplicationCon
|
|||
|
||||
@Override
|
||||
protected void doClose() {
|
||||
publishEvent(ReadinessStateChangedEvent.unready());
|
||||
AvailabilityChangeEvent.publish(this, ReadinessState.REFUSING_TRAFFIC);
|
||||
WebServer webServer = this.webServer;
|
||||
if (webServer != null) {
|
||||
webServer.shutDownGracefully();
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mockito;
|
||||
|
|
@ -46,8 +47,10 @@ import org.springframework.beans.factory.support.BeanDefinitionOverrideException
|
|||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
|
||||
import org.springframework.boot.availability.LivenessStateChangedEvent;
|
||||
import org.springframework.boot.availability.ReadinessStateChangedEvent;
|
||||
import org.springframework.boot.availability.AvailabilityChangeEvent;
|
||||
import org.springframework.boot.availability.AvailabilityState;
|
||||
import org.springframework.boot.availability.LivenessState;
|
||||
import org.springframework.boot.availability.ReadinessState;
|
||||
import org.springframework.boot.context.event.ApplicationContextInitializedEvent;
|
||||
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
|
||||
import org.springframework.boot.context.event.ApplicationFailedEvent;
|
||||
|
|
@ -108,6 +111,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.BDDMockito.willThrow;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
|
|
@ -410,9 +414,10 @@ class SpringApplicationTests {
|
|||
inOrder.verify(listener).onApplicationEvent(isA(ApplicationPreparedEvent.class));
|
||||
inOrder.verify(listener).onApplicationEvent(isA(ContextRefreshedEvent.class));
|
||||
inOrder.verify(listener).onApplicationEvent(isA(ApplicationStartedEvent.class));
|
||||
inOrder.verify(listener).onApplicationEvent(isA(LivenessStateChangedEvent.class));
|
||||
inOrder.verify(listener).onApplicationEvent(argThat(isAvailabilityChangeEventWithState(LivenessState.CORRECT)));
|
||||
inOrder.verify(listener).onApplicationEvent(isA(ApplicationReadyEvent.class));
|
||||
inOrder.verify(listener).onApplicationEvent(isA(ReadinessStateChangedEvent.class));
|
||||
inOrder.verify(listener)
|
||||
.onApplicationEvent(argThat(isAvailabilityChangeEventWithState(ReadinessState.ACCEPTING_TRAFFIC)));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
|
|
@ -886,7 +891,7 @@ class SpringApplicationTests {
|
|||
this.context = application.run();
|
||||
assertThat(events).hasAtLeastOneElementOfType(ApplicationPreparedEvent.class);
|
||||
assertThat(events).hasAtLeastOneElementOfType(ContextRefreshedEvent.class);
|
||||
verifyTestListenerEvents();
|
||||
verifyRegisteredListenerSuccessEvents();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -899,24 +904,21 @@ class SpringApplicationTests {
|
|||
this.context = application.run();
|
||||
assertThat(events).hasAtLeastOneElementOfType(ApplicationPreparedEvent.class);
|
||||
assertThat(events).hasAtLeastOneElementOfType(ContextRefreshedEvent.class);
|
||||
verifyTestListenerEvents();
|
||||
verifyRegisteredListenerSuccessEvents();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void verifyTestListenerEvents() {
|
||||
private void verifyRegisteredListenerSuccessEvents() {
|
||||
ApplicationListener<ApplicationEvent> listener = this.context.getBean("testApplicationListener",
|
||||
ApplicationListener.class);
|
||||
verifyListenerEvents(listener, ContextRefreshedEvent.class, ApplicationStartedEvent.class,
|
||||
LivenessStateChangedEvent.class, ApplicationReadyEvent.class, ReadinessStateChangedEvent.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void verifyListenerEvents(ApplicationListener<ApplicationEvent> listener,
|
||||
Class<? extends ApplicationEvent>... eventTypes) {
|
||||
for (Class<? extends ApplicationEvent> eventType : eventTypes) {
|
||||
verify(listener).onApplicationEvent(isA(eventType));
|
||||
}
|
||||
verifyNoMoreInteractions(listener);
|
||||
InOrder inOrder = Mockito.inOrder(listener);
|
||||
inOrder.verify(listener).onApplicationEvent(isA(ContextRefreshedEvent.class));
|
||||
inOrder.verify(listener).onApplicationEvent(isA(ApplicationStartedEvent.class));
|
||||
inOrder.verify(listener).onApplicationEvent(argThat(isAvailabilityChangeEventWithState(LivenessState.CORRECT)));
|
||||
inOrder.verify(listener).onApplicationEvent(isA(ApplicationReadyEvent.class));
|
||||
inOrder.verify(listener)
|
||||
.onApplicationEvent(argThat(isAvailabilityChangeEventWithState(ReadinessState.ACCEPTING_TRAFFIC)));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
@ -926,8 +928,7 @@ class SpringApplicationTests {
|
|||
SpringApplication application = new SpringApplication(ExampleConfig.class);
|
||||
application.addListeners(listener);
|
||||
assertThatExceptionOfType(ApplicationContextException.class).isThrownBy(application::run);
|
||||
verifyListenerEvents(listener, ApplicationStartingEvent.class, ApplicationEnvironmentPreparedEvent.class,
|
||||
ApplicationContextInitializedEvent.class, ApplicationPreparedEvent.class, ApplicationFailedEvent.class);
|
||||
verifyRegisteredListenerFailedFromApplicationEvents(listener);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
@ -938,8 +939,17 @@ class SpringApplicationTests {
|
|||
application.setWebApplicationType(WebApplicationType.NONE);
|
||||
application.addListeners(listener);
|
||||
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(application::run);
|
||||
verifyListenerEvents(listener, ApplicationStartingEvent.class, ApplicationEnvironmentPreparedEvent.class,
|
||||
ApplicationContextInitializedEvent.class, ApplicationPreparedEvent.class, ApplicationFailedEvent.class);
|
||||
verifyRegisteredListenerFailedFromApplicationEvents(listener);
|
||||
}
|
||||
|
||||
private void verifyRegisteredListenerFailedFromApplicationEvents(ApplicationListener<ApplicationEvent> listener) {
|
||||
InOrder inOrder = Mockito.inOrder(listener);
|
||||
inOrder.verify(listener).onApplicationEvent(isA(ApplicationStartingEvent.class));
|
||||
inOrder.verify(listener).onApplicationEvent(isA(ApplicationEnvironmentPreparedEvent.class));
|
||||
inOrder.verify(listener).onApplicationEvent(isA(ApplicationContextInitializedEvent.class));
|
||||
inOrder.verify(listener).onApplicationEvent(isA(ApplicationPreparedEvent.class));
|
||||
inOrder.verify(listener).onApplicationEvent(isA(ApplicationFailedEvent.class));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
@ -949,7 +959,8 @@ class SpringApplicationTests {
|
|||
SpringApplication application = new SpringApplication(ExampleConfig.class);
|
||||
application.addInitializers((applicationContext) -> applicationContext.addApplicationListener(listener));
|
||||
assertThatExceptionOfType(ApplicationContextException.class).isThrownBy(application::run);
|
||||
verifyListenerEvents(listener, ApplicationFailedEvent.class);
|
||||
verify(listener).onApplicationEvent(isA(ApplicationFailedEvent.class));
|
||||
verifyNoMoreInteractions(listener);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
@ -960,7 +971,17 @@ class SpringApplicationTests {
|
|||
application.setWebApplicationType(WebApplicationType.NONE);
|
||||
application.addInitializers((applicationContext) -> applicationContext.addApplicationListener(listener));
|
||||
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(application::run);
|
||||
verifyListenerEvents(listener, ApplicationFailedEvent.class);
|
||||
verify(listener).onApplicationEvent(isA(ApplicationFailedEvent.class));
|
||||
verifyNoMoreInteractions(listener);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void verifyRegisteredListenerFailedFromContextEvents() {
|
||||
ApplicationListener<ApplicationEvent> listener = this.context.getBean("testApplicationListener",
|
||||
ApplicationListener.class);
|
||||
InOrder inOrder = Mockito.inOrder(listener);
|
||||
inOrder.verify(listener).onApplicationEvent(isA(ApplicationFailedEvent.class));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -1124,6 +1145,12 @@ class SpringApplicationTests {
|
|||
.getBean(AtomicInteger.class)).hasValue(1);
|
||||
}
|
||||
|
||||
private <S extends AvailabilityState> ArgumentMatcher<ApplicationEvent> isAvailabilityChangeEventWithState(
|
||||
S state) {
|
||||
return (argument) -> (argument instanceof AvailabilityChangeEvent<?>)
|
||||
&& ((AvailabilityChangeEvent<?>) argument).getState().equals(state);
|
||||
}
|
||||
|
||||
private Condition<ConfigurableEnvironment> matchingPropertySource(final Class<?> propertySourceClass,
|
||||
final String name) {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.availability;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ApplicationAvailabilityBean}
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ApplicationAvailabilityBeanTests {
|
||||
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
private ApplicationAvailabilityBean availability;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.context = new AnnotationConfigApplicationContext(ApplicationAvailabilityBean.class);
|
||||
this.availability = this.context.getBean(ApplicationAvailabilityBean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLivenessStateWhenNoEventHasBeenPublishedReturnsDefaultState() {
|
||||
assertThat(this.availability.getLivenessState()).isEqualTo(LivenessState.BROKEN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLivenessStateWhenEventHasBeenPublishedReturnsPublishedState() {
|
||||
AvailabilityChangeEvent.publish(this.context, LivenessState.CORRECT);
|
||||
assertThat(this.availability.getLivenessState()).isEqualTo(LivenessState.CORRECT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getReadinessStateWhenNoEventHasBeenPublishedReturnsDefaultState() {
|
||||
assertThat(this.availability.getReadinessState()).isEqualTo(ReadinessState.REFUSING_TRAFFIC);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getReadinessStateWhenEventHasBeenPublishedReturnsPublishedState() {
|
||||
AvailabilityChangeEvent.publish(this.context, ReadinessState.ACCEPTING_TRAFFIC);
|
||||
assertThat(this.availability.getReadinessState()).isEqualTo(ReadinessState.ACCEPTING_TRAFFIC);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getStateWhenNoEventHasBeenPublishedReturnsDefaultState() {
|
||||
assertThat(this.availability.getState(TestState.class)).isNull();
|
||||
assertThat(this.availability.getState(TestState.class, TestState.ONE)).isEqualTo(TestState.ONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getStateWhenEventHasBeenPublishedReturnsPublishedState() {
|
||||
AvailabilityChangeEvent.publish(this.context, TestState.TWO);
|
||||
assertThat(this.availability.getState(TestState.class)).isEqualTo(TestState.TWO);
|
||||
assertThat(this.availability.getState(TestState.class, TestState.ONE)).isEqualTo(TestState.TWO);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLastChangeEventWhenNoEventHasBeenPublishedReturnsDefaultState() {
|
||||
assertThat(this.availability.getLastChangeEvent(TestState.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLastChangeEventWhenEventHasBeenPublishedReturnsPublishedState() {
|
||||
AvailabilityChangeEvent.publish(this.context, TestState.TWO);
|
||||
assertThat(this.availability.getLastChangeEvent(TestState.class)).isNotNull();
|
||||
}
|
||||
|
||||
enum TestState implements AvailabilityState {
|
||||
|
||||
ONE {
|
||||
@Override
|
||||
public String test() {
|
||||
return "spring";
|
||||
}
|
||||
},
|
||||
|
||||
TWO {
|
||||
@Override
|
||||
public String test() {
|
||||
return "boot";
|
||||
}
|
||||
};
|
||||
|
||||
abstract String test();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.availability;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ApplicationAvailabilityProvider}
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class ApplicationAvailabilityProviderTests {
|
||||
|
||||
@Test
|
||||
void initialStateShouldBeFailures() {
|
||||
ApplicationAvailabilityProvider stateProvider = new ApplicationAvailabilityProvider();
|
||||
assertThat(stateProvider.getLivenessState()).isEqualTo(LivenessState.BROKEN);
|
||||
assertThat(stateProvider.getReadinessState()).isEqualTo(ReadinessState.UNREADY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateLivenessState() {
|
||||
ApplicationAvailabilityProvider stateProvider = new ApplicationAvailabilityProvider();
|
||||
LivenessState livenessState = LivenessState.LIVE;
|
||||
stateProvider.onApplicationEvent(new LivenessStateChangedEvent(livenessState, "Startup complete"));
|
||||
assertThat(stateProvider.getLivenessState()).isEqualTo(livenessState);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateReadiessState() {
|
||||
ApplicationAvailabilityProvider stateProvider = new ApplicationAvailabilityProvider();
|
||||
stateProvider.onApplicationEvent(ReadinessStateChangedEvent.ready());
|
||||
assertThat(stateProvider.getReadinessState()).isEqualTo(ReadinessState.READY);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.availability;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link AvailabilityChangeEvent}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AvailabilityChangeEventTests {
|
||||
|
||||
private Object source = new Object();
|
||||
|
||||
@Test
|
||||
void createWhenStateIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new AvailabilityChangeEvent<>(this.source, null))
|
||||
.withMessage("State must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getStateReturnsState() {
|
||||
LivenessState state = LivenessState.CORRECT;
|
||||
AvailabilityChangeEvent<LivenessState> event = new AvailabilityChangeEvent<>(this.source, state);
|
||||
assertThat(event.getState()).isEqualTo(state);
|
||||
}
|
||||
|
||||
@Test
|
||||
void publishPublishesEvent() {
|
||||
ApplicationContext context = mock(ApplicationContext.class);
|
||||
AvailabilityState state = LivenessState.CORRECT;
|
||||
AvailabilityChangeEvent.publish(context, state);
|
||||
ArgumentCaptor<ApplicationEvent> captor = ArgumentCaptor.forClass(ApplicationEvent.class);
|
||||
verify(context).publishEvent(captor.capture());
|
||||
AvailabilityChangeEvent<?> event = (AvailabilityChangeEvent<?>) captor.getValue();
|
||||
assertThat(event.getSource()).isEqualTo(context);
|
||||
assertThat(event.getState()).isEqualTo(state);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -26,8 +26,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.availability.LivenessStateChangedEvent;
|
||||
import org.springframework.boot.availability.ReadinessStateChangedEvent;
|
||||
import org.springframework.boot.availability.AvailabilityChangeEvent;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
|
|
@ -71,9 +70,9 @@ class EventPublishingRunListenerTests {
|
|||
checkApplicationEvents(ApplicationPreparedEvent.class);
|
||||
context.refresh();
|
||||
this.runListener.started(context);
|
||||
checkApplicationEvents(ApplicationStartedEvent.class, LivenessStateChangedEvent.class);
|
||||
checkApplicationEvents(ApplicationStartedEvent.class, AvailabilityChangeEvent.class);
|
||||
this.runListener.running(context);
|
||||
checkApplicationEvents(ApplicationReadyEvent.class, ReadinessStateChangedEvent.class);
|
||||
checkApplicationEvents(ApplicationReadyEvent.class, AvailabilityChangeEvent.class);
|
||||
}
|
||||
|
||||
void checkApplicationEvents(Class<?>... eventClasses) {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
|||
import org.springframework.beans.factory.config.Scope;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.boot.availability.ReadinessStateChangedEvent;
|
||||
import org.springframework.boot.availability.AvailabilityChangeEvent;
|
||||
import org.springframework.boot.testsupport.system.CapturedOutput;
|
||||
import org.springframework.boot.testsupport.system.OutputCaptureExtension;
|
||||
import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer;
|
||||
|
|
@ -178,7 +178,7 @@ class ServletWebServerApplicationContextTests {
|
|||
this.context.refresh();
|
||||
this.context.addApplicationListener(listener);
|
||||
this.context.close();
|
||||
assertThat(listener.receivedEvents()).hasSize(2).extracting("class").contains(ReadinessStateChangedEvent.class,
|
||||
assertThat(listener.receivedEvents()).hasSize(2).extracting("class").contains(AvailabilityChangeEvent.class,
|
||||
ContextClosedEvent.class);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue