Skip to content

Commit 2c959b8

Browse files
committed
Polish health indicators
Align reactive and non-reactive web extensions and update `showDetails` so that it only applies to web exposure. See gh-11113 See gh-11192
1 parent 9e95483 commit 2c959b8

23 files changed

+280
-172
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryHealthWebEndpointManagementContextConfiguration.java

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@
2323
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
2424
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
2525
import org.springframework.boot.actuate.health.HealthEndpoint;
26-
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
27-
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
26+
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
27+
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
2828
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
2929
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
3030
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
31+
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
3132
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3233
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
34+
import org.springframework.boot.cloud.CloudPlatform;
3335
import org.springframework.context.annotation.Bean;
3436
import org.springframework.context.annotation.Configuration;
3537

@@ -39,7 +41,9 @@
3941
* @author Madhura Bhave
4042
*/
4143
@Configuration
42-
@AutoConfigureBefore({ ReactiveCloudFoundryActuatorAutoConfiguration.class, CloudFoundryActuatorAutoConfiguration.class })
44+
@ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)
45+
@AutoConfigureBefore({ ReactiveCloudFoundryActuatorAutoConfiguration.class,
46+
CloudFoundryActuatorAutoConfiguration.class })
4347
@AutoConfigureAfter(HealthEndpointAutoConfiguration.class)
4448
public class CloudFoundryHealthWebEndpointManagementContextConfiguration {
4549

@@ -50,11 +54,10 @@ static class ServletWebHealthConfiguration {
5054
@Bean
5155
@ConditionalOnMissingBean
5256
@ConditionalOnEnabledEndpoint
53-
@ConditionalOnBean(HealthEndpoint.class)
57+
@ConditionalOnBean({ HealthEndpoint.class, HealthEndpointWebExtension.class })
5458
public CloudFoundryHealthEndpointWebExtension cloudFoundryHealthEndpointWebExtension(
55-
HealthEndpoint healthEndpoint, HealthStatusHttpMapper healthStatusHttpMapper) {
56-
HealthEndpoint delegate = new HealthEndpoint(healthEndpoint.getHealthIndicator(), true);
57-
return new CloudFoundryHealthEndpointWebExtension(delegate, healthStatusHttpMapper);
59+
HealthEndpointWebExtension healthEndpointWebExtension) {
60+
return new CloudFoundryHealthEndpointWebExtension(healthEndpointWebExtension);
5861
}
5962

6063
}
@@ -66,12 +69,12 @@ static class ReactiveWebHealthConfiguration {
6669
@Bean
6770
@ConditionalOnMissingBean
6871
@ConditionalOnEnabledEndpoint
69-
@ConditionalOnBean(HealthEndpoint.class)
72+
@ConditionalOnBean({ HealthEndpoint.class,
73+
ReactiveHealthEndpointWebExtension.class })
7074
public CloudFoundryReactiveHealthEndpointWebExtension cloudFoundryReactiveHealthEndpointWebExtension(
71-
ReactiveHealthIndicator reactiveHealthIndicator,
72-
HealthStatusHttpMapper healthStatusHttpMapper) {
73-
return new CloudFoundryReactiveHealthEndpointWebExtension(reactiveHealthIndicator,
74-
healthStatusHttpMapper);
75+
ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension) {
76+
return new CloudFoundryReactiveHealthEndpointWebExtension(
77+
reactiveHealthEndpointWebExtension);
7578
}
7679

7780
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebAnnotationEndpointDiscoverer.java

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
package org.springframework.boot.actuate.autoconfigure.cloudfoundry;
1818

1919
import java.util.Collection;
20-
import java.util.Map;
2120

2221
import org.springframework.boot.actuate.endpoint.EndpointFilter;
22+
import org.springframework.boot.actuate.endpoint.EndpointInfo;
2323
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
2424
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
2525
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
@@ -30,34 +30,36 @@
3030
import org.springframework.context.ApplicationContext;
3131

3232
/**
33-
* {@link WebAnnotationEndpointDiscoverer} for Cloud Foundry that uses Cloud Foundry specific
34-
* extensions for the {@link HealthEndpoint}.
33+
* {@link WebAnnotationEndpointDiscoverer} for Cloud Foundry that uses Cloud Foundry
34+
* specific extensions for the {@link HealthEndpoint}.
3535
*
3636
* @author Madhura Bhave
3737
*/
38-
public class CloudFoundryWebAnnotationEndpointDiscoverer extends WebAnnotationEndpointDiscoverer {
39-
40-
private final ApplicationContext applicationContext;
38+
public class CloudFoundryWebAnnotationEndpointDiscoverer
39+
extends WebAnnotationEndpointDiscoverer {
4140

4241
private final Class<?> requiredExtensionType;
4342

44-
public CloudFoundryWebAnnotationEndpointDiscoverer(ApplicationContext applicationContext, ParameterMapper parameterMapper,
45-
EndpointMediaTypes endpointMediaTypes, EndpointPathResolver endpointPathResolver,
46-
Collection<? extends OperationMethodInvokerAdvisor> invokerAdvisors, Collection<? extends EndpointFilter<WebOperation>> filters, Class<?> requiredExtensionType) {
47-
super(applicationContext, parameterMapper, endpointMediaTypes, endpointPathResolver, invokerAdvisors, filters);
48-
this.applicationContext = applicationContext;
43+
public CloudFoundryWebAnnotationEndpointDiscoverer(
44+
ApplicationContext applicationContext, ParameterMapper parameterMapper,
45+
EndpointMediaTypes endpointMediaTypes,
46+
EndpointPathResolver endpointPathResolver,
47+
Collection<? extends OperationMethodInvokerAdvisor> invokerAdvisors,
48+
Collection<? extends EndpointFilter<WebOperation>> filters,
49+
Class<?> requiredExtensionType) {
50+
super(applicationContext, parameterMapper, endpointMediaTypes,
51+
endpointPathResolver, invokerAdvisors, filters);
4952
this.requiredExtensionType = requiredExtensionType;
5053
}
5154

5255
@Override
53-
protected void addExtension(Map<Class<?>, DiscoveredEndpoint> endpoints, Map<Class<?>, DiscoveredExtension> extensions, String beanName) {
54-
Class<?> extensionType = this.applicationContext.getType(beanName);
55-
Class<?> endpointType = getEndpointType(extensionType);
56-
if (HealthEndpoint.class.equals(endpointType) && !this.requiredExtensionType.equals(extensionType)) {
57-
return;
56+
protected boolean isExtensionExposed(Class<?> endpointType, Class<?> extensionType,
57+
EndpointInfo<WebOperation> endpointInfo) {
58+
if (HealthEndpoint.class.equals(endpointType)
59+
&& !this.requiredExtensionType.equals(extensionType)) {
60+
return false;
5861
}
59-
super.addExtension(endpoints, extensions, beanName);
62+
return super.isExtensionExposed(endpointType, extensionType, endpointInfo);
6063
}
6164

6265
}
63-

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtension.java

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,36 +25,28 @@
2525
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
2626
import org.springframework.boot.actuate.health.Health;
2727
import org.springframework.boot.actuate.health.HealthEndpoint;
28-
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
29-
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
28+
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
3029

3130
/**
32-
* Reactive {@link EndpointWebExtension} for the {@link HealthEndpoint}
33-
* that always exposes full health details.
31+
* Reactive {@link EndpointWebExtension} for the {@link HealthEndpoint} that always
32+
* exposes full health details.
3433
*
3534
* @author Madhura Bhave
35+
* @since 2.0.0
3636
*/
3737
@EndpointExtension(filter = CloudFoundryEndpointFilter.class, endpoint = HealthEndpoint.class)
3838
public class CloudFoundryReactiveHealthEndpointWebExtension {
3939

40-
private final ReactiveHealthIndicator delegate;
40+
private final ReactiveHealthEndpointWebExtension delegate;
4141

42-
private final HealthStatusHttpMapper statusHttpMapper;
43-
44-
public CloudFoundryReactiveHealthEndpointWebExtension(ReactiveHealthIndicator delegate,
45-
HealthStatusHttpMapper statusHttpMapper) {
42+
public CloudFoundryReactiveHealthEndpointWebExtension(
43+
ReactiveHealthEndpointWebExtension delegate) {
4644
this.delegate = delegate;
47-
this.statusHttpMapper = statusHttpMapper;
4845
}
4946

5047
@ReadOperation
5148
public Mono<WebEndpointResponse<Health>> health() {
52-
return this.delegate.health().map((health) -> {
53-
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
54-
return new WebEndpointResponse<>(health, status);
55-
});
49+
return this.delegate.health(true);
5650
}
5751

5852
}
59-
60-

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ public CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebFluxEndpointHand
7070
WebClient.Builder webClientBuilder) {
7171
CloudFoundryWebAnnotationEndpointDiscoverer endpointDiscoverer = new CloudFoundryWebAnnotationEndpointDiscoverer(
7272
this.applicationContext, parameterMapper, endpointMediaTypes,
73-
EndpointPathResolver.useEndpointId(), null, null, CloudFoundryReactiveHealthEndpointWebExtension.class);
73+
EndpointPathResolver.useEndpointId(), null, null,
74+
CloudFoundryReactiveHealthEndpointWebExtension.class);
7475
ReactiveCloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(
7576
webClientBuilder, this.applicationContext.getEnvironment());
7677
return new CloudFoundryWebFluxEndpointHandlerMapping(

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfiguration.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServl
7474
RestTemplateBuilder restTemplateBuilder) {
7575
CloudFoundryWebAnnotationEndpointDiscoverer endpointDiscoverer = new CloudFoundryWebAnnotationEndpointDiscoverer(
7676
this.applicationContext, parameterMapper, endpointMediaTypes,
77-
EndpointPathResolver.useEndpointId(), null, null, CloudFoundryHealthEndpointWebExtension.class);
77+
EndpointPathResolver.useEndpointId(), null, null,
78+
CloudFoundryHealthEndpointWebExtension.class);
7879
CloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(
7980
restTemplateBuilder, this.applicationContext.getEnvironment());
8081
return new CloudFoundryWebEndpointServletHandlerMapping(

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtension.java

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,33 +23,27 @@
2323
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
2424
import org.springframework.boot.actuate.health.Health;
2525
import org.springframework.boot.actuate.health.HealthEndpoint;
26-
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
26+
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
2727

2828
/**
29-
* {@link EndpointWebExtension} for the {@link HealthEndpoint}
30-
* that always exposes full health details.
29+
* {@link EndpointWebExtension} for the {@link HealthEndpoint} that always exposes full
30+
* health details.
3131
*
3232
* @author Madhura Bhave
33+
* @since 2.0.0
3334
*/
3435
@EndpointExtension(filter = CloudFoundryEndpointFilter.class, endpoint = HealthEndpoint.class)
3536
public class CloudFoundryHealthEndpointWebExtension {
3637

37-
private final HealthEndpoint delegate;
38+
private final HealthEndpointWebExtension delegate;
3839

39-
private final HealthStatusHttpMapper statusHttpMapper;
40-
41-
public CloudFoundryHealthEndpointWebExtension(HealthEndpoint delegate,
42-
HealthStatusHttpMapper statusHttpMapper) {
40+
public CloudFoundryHealthEndpointWebExtension(HealthEndpointWebExtension delegate) {
4341
this.delegate = delegate;
44-
this.statusHttpMapper = statusHttpMapper;
4542
}
4643

4744
@ReadOperation
4845
public WebEndpointResponse<Health> getHealth() {
49-
Health health = this.delegate.health();
50-
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
51-
return new WebEndpointResponse<>(health, status);
46+
return this.delegate.getHealth(true);
5247
}
5348

5449
}
55-

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,14 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.health;
1818

19-
import java.util.LinkedHashMap;
20-
import java.util.Map;
21-
22-
import org.springframework.beans.factory.ObjectProvider;
2319
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
24-
import org.springframework.boot.actuate.health.CompositeHealthIndicatorFactory;
25-
import org.springframework.boot.actuate.health.HealthAggregator;
2620
import org.springframework.boot.actuate.health.HealthEndpoint;
27-
import org.springframework.boot.actuate.health.HealthIndicator;
28-
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
29-
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
3021
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
3122
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3223
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3324
import org.springframework.context.ApplicationContext;
3425
import org.springframework.context.annotation.Bean;
3526
import org.springframework.context.annotation.Configuration;
36-
import org.springframework.util.ClassUtils;
3727

3828
/**
3929
* {@link EnableAutoConfiguration Auto-configuration} for {@link HealthEndpoint}.
@@ -47,46 +37,11 @@
4737
@EnableConfigurationProperties(HealthEndpointProperties.class)
4838
public class HealthEndpointAutoConfiguration {
4939

50-
private final HealthIndicator healthIndicator;
51-
52-
public HealthEndpointAutoConfiguration(ApplicationContext applicationContext,
53-
ObjectProvider<HealthAggregator> healthAggregator) {
54-
this.healthIndicator = getHealthIndicator(applicationContext,
55-
healthAggregator.getIfAvailable(OrderedHealthAggregator::new));
56-
}
57-
58-
private HealthIndicator getHealthIndicator(ApplicationContext applicationContext,
59-
HealthAggregator healthAggregator) {
60-
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
61-
indicators.putAll(applicationContext.getBeansOfType(HealthIndicator.class));
62-
if (ClassUtils.isPresent("reactor.core.publisher.Flux", null)) {
63-
new ReactiveHealthIndicators().get(applicationContext)
64-
.forEach(indicators::putIfAbsent);
65-
}
66-
CompositeHealthIndicatorFactory factory = new CompositeHealthIndicatorFactory();
67-
return factory.createHealthIndicator(healthAggregator, indicators);
68-
}
69-
7040
@Bean
7141
@ConditionalOnMissingBean
7242
@ConditionalOnEnabledEndpoint
73-
public HealthEndpoint healthEndpoint(HealthEndpointProperties properties) {
74-
return new HealthEndpoint(this.healthIndicator, properties.isShowDetails());
75-
}
76-
77-
private static class ReactiveHealthIndicators {
78-
79-
public Map<String, HealthIndicator> get(ApplicationContext applicationContext) {
80-
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
81-
applicationContext.getBeansOfType(ReactiveHealthIndicator.class)
82-
.forEach((name, indicator) -> indicators.put(name, adapt(indicator)));
83-
return indicators;
84-
}
85-
86-
private HealthIndicator adapt(ReactiveHealthIndicator indicator) {
87-
return () -> indicator.health().block();
88-
}
89-
43+
public HealthEndpoint healthEndpoint(ApplicationContext applicationContext) {
44+
return new HealthEndpoint(HealthIndicatorBeansComposite.get(applicationContext));
9045
}
9146

9247
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointProperties.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
public class HealthEndpointProperties {
2929

3030
/**
31-
* Whether to show full health details instead of just the status.
31+
* Whether to show full health details instead of just the status when exposed over a
32+
* potentially insecure connection.
3233
*/
3334
private boolean showDetails;
3435

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.health;
18+
19+
import java.util.LinkedHashMap;
20+
import java.util.Map;
21+
22+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
23+
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
24+
import org.springframework.boot.actuate.health.CompositeHealthIndicatorFactory;
25+
import org.springframework.boot.actuate.health.HealthAggregator;
26+
import org.springframework.boot.actuate.health.HealthIndicator;
27+
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
28+
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
29+
import org.springframework.context.ApplicationContext;
30+
import org.springframework.util.ClassUtils;
31+
32+
/**
33+
* Creates a {@link CompositeHealthIndicator} from beans in the
34+
* {@link ApplicationContext}.
35+
*
36+
* @author Phillip Webb
37+
*/
38+
final class HealthIndicatorBeansComposite {
39+
40+
private HealthIndicatorBeansComposite() {
41+
}
42+
43+
public static HealthIndicator get(ApplicationContext applicationContext) {
44+
HealthAggregator healthAggregator = getHealthAggregator(applicationContext);
45+
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
46+
indicators.putAll(applicationContext.getBeansOfType(HealthIndicator.class));
47+
if (ClassUtils.isPresent("reactor.core.publisher.Flux", null)) {
48+
new ReactiveHealthIndicators().get(applicationContext)
49+
.forEach(indicators::putIfAbsent);
50+
}
51+
CompositeHealthIndicatorFactory factory = new CompositeHealthIndicatorFactory();
52+
return factory.createHealthIndicator(healthAggregator, indicators);
53+
}
54+
55+
private static HealthAggregator getHealthAggregator(
56+
ApplicationContext applicationContext) {
57+
try {
58+
return applicationContext.getBean(HealthAggregator.class);
59+
}
60+
catch (NoSuchBeanDefinitionException ex) {
61+
return new OrderedHealthAggregator();
62+
}
63+
}
64+
65+
private static class ReactiveHealthIndicators {
66+
67+
public Map<String, HealthIndicator> get(ApplicationContext applicationContext) {
68+
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
69+
applicationContext.getBeansOfType(ReactiveHealthIndicator.class)
70+
.forEach((name, indicator) -> indicators.put(name, adapt(indicator)));
71+
return indicators;
72+
}
73+
74+
private HealthIndicator adapt(ReactiveHealthIndicator indicator) {
75+
return () -> indicator.health().block();
76+
}
77+
78+
}
79+
80+
}

0 commit comments

Comments
 (0)