Allow @ConditionalOnEnabledEndpoint to be used on any component
Closes gh-14787
This commit is contained in:
		
							parent
							
								
									21ebb94d49
								
							
						
					
					
						commit
						861587ec78
					
				|  | @ -1,5 +1,5 @@ | |||
| /* | ||||
|  * Copyright 2012-2017 the original author or authors. | ||||
|  * Copyright 2012-2018 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. | ||||
|  | @ -23,6 +23,7 @@ import java.lang.annotation.RetentionPolicy; | |||
| import java.lang.annotation.Target; | ||||
| 
 | ||||
| import org.springframework.boot.actuate.endpoint.annotation.Endpoint; | ||||
| import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; | ||||
| import org.springframework.context.annotation.Conditional; | ||||
| import org.springframework.core.env.Environment; | ||||
| 
 | ||||
|  | @ -31,6 +32,60 @@ import org.springframework.core.env.Environment; | |||
|  * according to the endpoints specific {@link Environment} property, falling back to | ||||
|  * {@code management.endpoints.enabled-by-default} or failing that | ||||
|  * {@link Endpoint#enableByDefault()}. | ||||
|  * <p> | ||||
|  * When placed on a {@code @Bean} method, the endpoint defaults to the return type of the | ||||
|  * factory method: | ||||
|  * | ||||
|  * <pre class="code"> | ||||
|  * @Configuration | ||||
|  * public class MyConfiguration { | ||||
|  * | ||||
|  *     @ConditionalOnEnableEndpoint | ||||
|  *     @Bean | ||||
|  *     public MyEndpoint myEndpoint() { | ||||
|  *         ... | ||||
|  *     } | ||||
|  * | ||||
|  * }</pre> | ||||
|  * <p> | ||||
|  * It is also possible to use the same mechanism for extensions: | ||||
|  * | ||||
|  * <pre class="code"> | ||||
|  * @Configuration | ||||
|  * public class MyConfiguration { | ||||
|  * | ||||
|  *     @ConditionalOnEnableEndpoint | ||||
|  *     @Bean | ||||
|  *     public MyEndpointWebExtension myEndpointWebExtension() { | ||||
|  *         ... | ||||
|  *     } | ||||
|  * | ||||
|  * }</pre> | ||||
|  * <p> | ||||
|  * In the sample above, {@code MyEndpointWebExtension} will be created if the endpoint is | ||||
|  * enabled as defined by the rules above. {@code MyEndpointWebExtension} must be a regular | ||||
|  * extension that refers to an endpoint, something like: | ||||
|  * | ||||
|  * <pre class="code"> | ||||
|  * @EndpointWebExtension(endpoint = MyEndpoint.class) | ||||
|  * public class MyEndpointWebExtension { | ||||
|  * | ||||
|  * }</pre> | ||||
|  * <p> | ||||
|  * Alternatively, the target endpoint can be manually specified for components that should | ||||
|  * only be created when a given endpoint is enabled: | ||||
|  * | ||||
|  * <pre class="code"> | ||||
|  * @Configuration | ||||
|  * public class MyConfiguration { | ||||
|  * | ||||
|  *     @ConditionalOnEnableEndpoint(endpoint = MyEndpoint.class) | ||||
|  *     @Bean | ||||
|  *     public MyComponent myComponent() { | ||||
|  *         ... | ||||
|  *     } | ||||
|  * | ||||
|  * }</pre> | ||||
|  * | ||||
|  * @author Stephane Nicoll | ||||
|  * @since 2.0.0 | ||||
|  | @ -42,4 +97,12 @@ import org.springframework.core.env.Environment; | |||
| @Conditional(OnEnabledEndpointCondition.class) | ||||
| public @interface ConditionalOnEnabledEndpoint { | ||||
| 
 | ||||
| 	/** | ||||
| 	 * The endpoint type that should be checked. Inferred when the return type of the | ||||
| 	 * {@code @Bean} method is either an {@link Endpoint} or an {@link EndpointExtension}. | ||||
| 	 * @return the endpoint type to check | ||||
| 	 * @since 2.0.6 | ||||
| 	 */ | ||||
| 	Class<?> endpoint() default Void.class; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| /* | ||||
|  * Copyright 2012-2017 the original author or authors. | ||||
|  * Copyright 2012-2018 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. | ||||
|  | @ -16,6 +16,7 @@ | |||
| 
 | ||||
| package org.springframework.boot.actuate.autoconfigure.endpoint.condition; | ||||
| 
 | ||||
| import java.util.Map; | ||||
| import java.util.Optional; | ||||
| 
 | ||||
| import org.springframework.boot.actuate.endpoint.annotation.Endpoint; | ||||
|  | @ -92,16 +93,23 @@ class OnEnabledEndpointCondition extends SpringBootCondition { | |||
| 				metadata instanceof MethodMetadata | ||||
| 						&& metadata.isAnnotated(Bean.class.getName()), | ||||
| 				"OnEnabledEndpointCondition may only be used on @Bean methods"); | ||||
| 		return getEndpointAttributes(context, (MethodMetadata) metadata); | ||||
| 		Class<?> endpointType = getEndpointType(context, (MethodMetadata) metadata); | ||||
| 		return getEndpointAttributes(endpointType); | ||||
| 	} | ||||
| 
 | ||||
| 	private AnnotationAttributes getEndpointAttributes(ConditionContext context, | ||||
| 			MethodMetadata metadata) { | ||||
| 	private Class<?> getEndpointType(ConditionContext context, MethodMetadata metadata) { | ||||
| 		Map<String, Object> attributes = metadata | ||||
| 				.getAnnotationAttributes(ConditionalOnEnabledEndpoint.class.getName()); | ||||
| 		if (attributes != null && attributes.containsKey("endpoint")) { | ||||
| 			Class<?> target = (Class<?>) attributes.get("endpoint"); | ||||
| 			if (target != Void.class) { | ||||
| 				return target; | ||||
| 			} | ||||
| 		} | ||||
| 		// We should be safe to load at this point since we are in the REGISTER_BEAN phase | ||||
| 		try { | ||||
| 			Class<?> returnType = ClassUtils.forName(metadata.getReturnTypeName(), | ||||
| 			return ClassUtils.forName(metadata.getReturnTypeName(), | ||||
| 					context.getClassLoader()); | ||||
| 			return getEndpointAttributes(returnType); | ||||
| 		} | ||||
| 		catch (Throwable ex) { | ||||
| 			throw new IllegalStateException("Failed to extract endpoint id for " | ||||
|  | @ -119,8 +127,8 @@ class OnEnabledEndpointCondition extends SpringBootCondition { | |||
| 		attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(type, | ||||
| 				EndpointExtension.class, false, true); | ||||
| 		Assert.state(attributes != null, | ||||
| 				"OnEnabledEndpointCondition may only be used on @Bean methods that " | ||||
| 						+ "return an @Endpoint or @EndpointExtension"); | ||||
| 				"No endpoint is specified and the return type of the @Bean method is " | ||||
| 						+ "neither an @Endpoint, nor an @EndpointExtension"); | ||||
| 		return getEndpointAttributes(attributes.getClass("endpoint")); | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -102,6 +102,44 @@ public class ConditionalOnEnabledEndpointTests { | |||
| 						.doesNotHaveBean("fooExt")); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void outcomeWithReferenceWhenNoPropertiesShouldMatch() { | ||||
| 		this.contextRunner | ||||
| 				.withUserConfiguration(FooEndpointEnabledByDefaultTrue.class, | ||||
| 						ComponentEnabledIfEndpointIsEnabledConfiguration.class) | ||||
| 				.run((context) -> assertThat(context).hasBean("fooComponent")); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void outcomeWithReferenceWhenEndpointEnabledPropertyIsTrueShouldMatch() { | ||||
| 		this.contextRunner.withPropertyValues("management.endpoint.foo.enabled=true") | ||||
| 				.withUserConfiguration(FooEndpointEnabledByDefaultTrue.class, | ||||
| 						ComponentEnabledIfEndpointIsEnabledConfiguration.class) | ||||
| 				.run((context) -> assertThat(context).hasBean("fooComponent")); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void outcomeWithReferenceWhenEndpointEnabledPropertyIsFalseShouldNotMatch() { | ||||
| 		this.contextRunner.withPropertyValues("management.endpoint.foo.enabled=false") | ||||
| 				.withUserConfiguration(FooEndpointEnabledByDefaultTrue.class, | ||||
| 						ComponentEnabledIfEndpointIsEnabledConfiguration.class) | ||||
| 				.run((context) -> assertThat(context).doesNotHaveBean("fooComponent")); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void outcomeWithNoReferenceShouldFail() { | ||||
| 		this.contextRunner | ||||
| 				.withUserConfiguration( | ||||
| 						ComponentWithNoEndpointReferenceConfiguration.class) | ||||
| 				.run((context) -> { | ||||
| 					assertThat(context).hasFailed(); | ||||
| 					assertThat(context.getStartupFailure().getCause().getMessage()) | ||||
| 							.contains( | ||||
| 									"No endpoint is specified and the return type of the @Bean method " | ||||
| 											+ "is neither an @Endpoint, nor an @EndpointExtension"); | ||||
| 				}); | ||||
| 	} | ||||
| 
 | ||||
| 	@Endpoint(id = "foo", enableByDefault = true) | ||||
| 	static class FooEndpointEnabledByDefaultTrue { | ||||
| 
 | ||||
|  | @ -187,4 +225,26 @@ public class ConditionalOnEnabledEndpointTests { | |||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	@Configuration | ||||
| 	static class ComponentEnabledIfEndpointIsEnabledConfiguration { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		@ConditionalOnEnabledEndpoint(endpoint = FooEndpointEnabledByDefaultTrue.class) | ||||
| 		public String fooComponent() { | ||||
| 			return "foo"; | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	@Configuration | ||||
| 	static class ComponentWithNoEndpointReferenceConfiguration { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		@ConditionalOnEnabledEndpoint | ||||
| 		public String fooComponent() { | ||||
| 			return "foo"; | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue