diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscoverer.java index a01fe72a92..ccce390624 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. @@ -19,6 +19,10 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry; import java.util.Collection; import java.util.List; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryWebEndpointDiscoverer.CloudFoundryWebEndpointDiscovererRuntimeHints; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; @@ -29,6 +33,7 @@ import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExten import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer; import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.core.annotation.MergedAnnotations; /** @@ -38,6 +43,7 @@ import org.springframework.core.annotation.MergedAnnotations; * @author Madhura Bhave * @since 2.0.0 */ +@ImportRuntimeHints(CloudFoundryWebEndpointDiscovererRuntimeHints.class) public class CloudFoundryWebEndpointDiscoverer extends WebEndpointDiscoverer { /** @@ -75,4 +81,14 @@ public class CloudFoundryWebEndpointDiscoverer extends WebEndpointDiscoverer { return MergedAnnotations.from(extensionBeanType).isPresent(EndpointCloudFoundryExtension.class); } + static class CloudFoundryWebEndpointDiscovererRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.reflection().registerType(CloudFoundryEndpointFilter.class, + (hint) -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java index 0df6400d0a..303f31ab58 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -20,13 +20,17 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive.CloudFoundryWebFluxEndpointHandlerMapping.CloudFoundryWebFluxEndpointHandlerMappingRuntimeHints; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; @@ -35,9 +39,12 @@ import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.Link; import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.reactive.AbstractWebFluxEndpointHandlerMapping; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.context.aot.BindingReflectionHintsRegistrar; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.ReflectionUtils; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping; import org.springframework.web.server.ServerWebExchange; @@ -50,6 +57,7 @@ import org.springframework.web.server.ServerWebExchange; * @author Phillip Webb * @author Brian Clozel */ +@ImportRuntimeHints(CloudFoundryWebFluxEndpointHandlerMappingRuntimeHints.class) class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointHandlerMapping { private final CloudFoundrySecurityInterceptor securityInterceptor; @@ -145,4 +153,17 @@ class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointH } + static class CloudFoundryWebFluxEndpointHandlerMappingRuntimeHints implements RuntimeHintsRegistrar { + + private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.reflection().registerMethod(Objects.requireNonNull( + ReflectionUtils.findMethod(CloudFoundryLinksHandler.class, "links", ServerWebExchange.class))); + this.bindingRegistrar.registerReflectionHints(hints.reflection(), Link.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryWebEndpointServletHandlerMapping.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryWebEndpointServletHandlerMapping.java index fd8fe0e74c..f3761e777b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryWebEndpointServletHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryWebEndpointServletHandlerMapping.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import jakarta.servlet.http.HttpServletRequest; @@ -27,8 +28,11 @@ import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryWebEndpointServletHandlerMapping.CloudFoundryWebEndpointServletHandlerMappingRuntimeHints; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; @@ -37,8 +41,11 @@ import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.Link; import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.context.aot.BindingReflectionHintsRegistrar; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.util.ReflectionUtils; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; @@ -51,6 +58,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi * @author Phillip Webb * @author Brian Clozel */ +@ImportRuntimeHints(CloudFoundryWebEndpointServletHandlerMappingRuntimeHints.class) class CloudFoundryWebEndpointServletHandlerMapping extends AbstractWebMvcEndpointHandlerMapping { private static final Log logger = LogFactory.getLog(CloudFoundryWebEndpointServletHandlerMapping.class); @@ -147,4 +155,18 @@ class CloudFoundryWebEndpointServletHandlerMapping extends AbstractWebMvcEndpoin } + static class CloudFoundryWebEndpointServletHandlerMappingRuntimeHints implements RuntimeHintsRegistrar { + + private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.reflection() + .registerMethod(Objects.requireNonNull(ReflectionUtils.findMethod(CloudFoundryLinksHandler.class, + "links", HttpServletRequest.class, HttpServletResponse.class))); + this.bindingRegistrar.registerReflectionHints(hints.reflection(), Link.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ActuatorAnnotationsRuntimeHints.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ActuatorAnnotationsRuntimeHints.java new file mode 100644 index 0000000000..0249192934 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ActuatorAnnotationsRuntimeHints.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2022 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.actuate.autoconfigure.endpoint; + +import java.util.stream.Stream; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.support.RuntimeHintsUtils; +import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; +import org.springframework.core.annotation.SynthesizedAnnotation; + +/** + * {@link RuntimeHintsRegistrar} for actuator support. + * + * @author Moritz Halbritter + */ +class ActuatorAnnotationsRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + Stream.of(Endpoint.class, ReadOperation.class, WriteOperation.class, DeleteOperation.class, + EndpointExtension.class) + .forEach((annotationType) -> RuntimeHintsUtils.registerAnnotation(hints, annotationType)); + // TODO: See https://github.com/spring-projects/spring-framework/issues/28767 + Stream.of(Endpoint.class, EndpointExtension.class).forEach( + (annotationType) -> hints.proxies().registerJdkProxy(annotationType, SynthesizedAnnotation.class)); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java index a7bcd2f262..097db6229b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java @@ -30,6 +30,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; @@ -45,6 +46,7 @@ import org.springframework.core.env.Environment; * @since 2.0.0 */ @AutoConfiguration +@ImportRuntimeHints(ActuatorAnnotationsRuntimeHints.class) public class EndpointAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfiguration.java index d5e00f03e5..939e74de19 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfiguration.java @@ -17,9 +17,12 @@ package org.springframework.boot.actuate.autoconfigure.management; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; +import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure; import org.springframework.boot.actuate.management.ThreadDumpEndpoint; +import org.springframework.boot.actuate.management.ThreadDumpEndpointWebExtension; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; @@ -39,4 +42,12 @@ public class ThreadDumpEndpointAutoConfiguration { return new ThreadDumpEndpoint(); } + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(ThreadDumpEndpoint.class) + @ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY }) + public ThreadDumpEndpointWebExtension threadDumpWebExtension(ThreadDumpEndpoint threadDumpEndpoint) { + return new ThreadDumpEndpointWebExtension(threadDumpEndpoint); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java index cf3a9494ce..9fe8956788 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java @@ -23,6 +23,10 @@ import java.util.function.Function; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryWebEndpointDiscoverer.CloudFoundryWebEndpointDiscovererRuntimeHints; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.InvocationContext; import org.springframework.boot.actuate.endpoint.SecurityContext; @@ -50,6 +54,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link CloudFoundryWebEndpointDiscoverer}. * * @author Madhura Bhave + * @author Moritz Halbritter */ class CloudFoundryWebEndpointDiscovererTests { @@ -69,6 +74,14 @@ class CloudFoundryWebEndpointDiscovererTests { }); } + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new CloudFoundryWebEndpointDiscovererRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(CloudFoundryEndpointFilter.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(runtimeHints); + } + private WebOperation findMainReadOperation(ExposableWebEndpoint endpoint) { for (WebOperation operation : endpoint.getOperations()) { if (operation.getRequestPredicate().getPath().equals("health")) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMappingTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMappingTests.java new file mode 100644 index 0000000000..ae612e9740 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMappingTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2022 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.actuate.autoconfigure.cloudfoundry.reactive; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive.CloudFoundryWebFluxEndpointHandlerMapping.CloudFoundryLinksHandler; +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive.CloudFoundryWebFluxEndpointHandlerMapping.CloudFoundryWebFluxEndpointHandlerMappingRuntimeHints; +import org.springframework.boot.actuate.endpoint.web.Link; + +/** + * Tests for {@link CloudFoundryWebFluxEndpointHandlerMapping}. + * + * @author Moritz Halbritter + */ +class CloudFoundryWebFluxEndpointHandlerMappingTests { + + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new CloudFoundryWebFluxEndpointHandlerMappingRuntimeHints().registerHints(runtimeHints, + getClass().getClassLoader()); + Assertions.assertThat(RuntimeHintsPredicates.reflection().onMethod(CloudFoundryLinksHandler.class, "links")) + .accepts(runtimeHints); + Assertions.assertThat(RuntimeHintsPredicates.reflection().onType(Link.class)).accepts(runtimeHints); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryWebEndpointServletHandlerMappingTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryWebEndpointServletHandlerMappingTests.java new file mode 100644 index 0000000000..100504cd45 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryWebEndpointServletHandlerMappingTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2022 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.actuate.autoconfigure.cloudfoundry.servlet; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryWebEndpointServletHandlerMapping.CloudFoundryLinksHandler; +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryWebEndpointServletHandlerMapping.CloudFoundryWebEndpointServletHandlerMappingRuntimeHints; +import org.springframework.boot.actuate.endpoint.web.Link; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link CloudFoundryWebEndpointServletHandlerMapping}. + * + * @author Moritz Halbritter + */ +class CloudFoundryWebEndpointServletHandlerMappingTests { + + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new CloudFoundryWebEndpointServletHandlerMappingRuntimeHints().registerHints(runtimeHints, + getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onMethod(CloudFoundryLinksHandler.class, "links")) + .accepts(runtimeHints); + assertThat(RuntimeHintsPredicates.reflection().onType(Link.class)).accepts(runtimeHints); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ActuatorAnnotationsRuntimeHintsTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ActuatorAnnotationsRuntimeHintsTests.java new file mode 100644 index 0000000000..ec5377a617 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ActuatorAnnotationsRuntimeHintsTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2022 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.actuate.autoconfigure.endpoint; + +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; +import org.springframework.core.annotation.SynthesizedAnnotation; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ActuatorAnnotationsRuntimeHints}. + * + * @author Moritz Halbritter + */ +class ActuatorAnnotationsRuntimeHintsTests { + + private final ActuatorAnnotationsRuntimeHints registrar = new ActuatorAnnotationsRuntimeHints(); + + private RuntimeHints runtimeHints; + + @BeforeEach + void setUp() { + this.runtimeHints = new RuntimeHints(); + this.registrar.registerHints(this.runtimeHints, getClass().getClassLoader()); + } + + @Test + void shouldRegisterReflectionHints() { + Set> annotations = Set.of(Endpoint.class, ReadOperation.class, WriteOperation.class, + DeleteOperation.class, EndpointExtension.class); + for (Class annotation : annotations) { + assertThat(RuntimeHintsPredicates.reflection().onType(annotation) + .withAnyMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(this.runtimeHints); + } + } + + @Test + void shouldRegisterProxyHints() { + Set> synthesizedAnnotations = Set.of(Endpoint.class, EndpointExtension.class); + for (Class synthesizedAnnotation : synthesizedAnnotations) { + assertThat( + RuntimeHintsPredicates.proxies().forInterfaces(synthesizedAnnotation, SynthesizedAnnotation.class)) + .accepts(this.runtimeHints); + } + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfigurationTests.java index ac7fb7be20..5d8d895a6b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -19,6 +19,7 @@ package org.springframework.boot.actuate.autoconfigure.management; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.management.ThreadDumpEndpoint; +import org.springframework.boot.actuate.management.ThreadDumpEndpointWebExtension; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -28,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link ThreadDumpEndpointAutoConfiguration}. * * @author Phillip Webb + * @author Moritz Halbritter */ class ThreadDumpEndpointAutoConfigurationTests { @@ -36,20 +38,27 @@ class ThreadDumpEndpointAutoConfigurationTests { @Test void runShouldHaveEndpointBean() { - this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=threaddump") - .run((context) -> assertThat(context).hasSingleBean(ThreadDumpEndpoint.class)); + this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=threaddump").run((context) -> { + assertThat(context).hasSingleBean(ThreadDumpEndpoint.class); + assertThat(context).hasSingleBean(ThreadDumpEndpointWebExtension.class); + }); } @Test void runWhenNotExposedShouldNotHaveEndpointBean() { - this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ThreadDumpEndpoint.class)); + this.contextRunner.run((context) -> { + assertThat(context).doesNotHaveBean(ThreadDumpEndpoint.class); + assertThat(context).doesNotHaveBean(ThreadDumpEndpointWebExtension.class); + }); } @Test void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() { this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=*") - .withPropertyValues("management.endpoint.threaddump.enabled:false") - .run((context) -> assertThat(context).doesNotHaveBean(ThreadDumpEndpoint.class)); + .withPropertyValues("management.endpoint.threaddump.enabled:false").run((context) -> { + assertThat(context).doesNotHaveBean(ThreadDumpEndpoint.class); + assertThat(context).doesNotHaveBean(ThreadDumpEndpointWebExtension.class); + }); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/DeleteOperation.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/DeleteOperation.java index a052504589..3b9df3d745 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/DeleteOperation.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/DeleteOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -22,6 +22,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.aot.hint.annotation.Reflective; import org.springframework.boot.actuate.endpoint.Producible; /** @@ -34,6 +35,7 @@ import org.springframework.boot.actuate.endpoint.Producible; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented +@Reflective(OperationReflectiveProcessor.class) public @interface DeleteOperation { /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/Endpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/Endpoint.java index de1848073e..8f8c793e42 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/Endpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/Endpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -22,6 +22,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.aot.hint.annotation.Reflective; import org.springframework.boot.actuate.endpoint.EndpointId; /** @@ -51,6 +52,7 @@ import org.springframework.boot.actuate.endpoint.EndpointId; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented +@Reflective public @interface Endpoint { /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointExtension.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointExtension.java index 94fb64d3d7..84564372f3 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointExtension.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -22,6 +22,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.aot.hint.annotation.Reflective; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.core.annotation.AliasFor; @@ -51,6 +52,7 @@ import org.springframework.core.annotation.AliasFor; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented +@Reflective public @interface EndpointExtension { /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/OperationReflectiveProcessor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/OperationReflectiveProcessor.java new file mode 100644 index 0000000000..2e934e40d1 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/OperationReflectiveProcessor.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2022 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.actuate.endpoint.annotation; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +import org.springframework.aot.hint.ReflectionHints; +import org.springframework.aot.hint.annotation.ReflectiveProcessor; +import org.springframework.aot.hint.annotation.SimpleReflectiveProcessor; +import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; +import org.springframework.context.aot.BindingReflectionHintsRegistrar; +import org.springframework.core.ResolvableType; +import org.springframework.core.io.Resource; + +/** + * {@link ReflectiveProcessor} that registers the annotated operation method and its + * return type for reflection. + * + * @author Moritz Halbritter + * @author Stephane Nicoll + */ +class OperationReflectiveProcessor extends SimpleReflectiveProcessor { + + private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + + @Override + protected void registerMethodHint(ReflectionHints hints, Method method) { + super.registerMethodHint(hints, method); + Type returnType = extractReturnType(method); + if (returnType != null) { + registerReflectionHints(hints, returnType); + } + } + + private Type extractReturnType(Method method) { + ResolvableType returnType = ResolvableType.forMethodReturnType(method); + if (WebEndpointResponse.class.isAssignableFrom(method.getReturnType())) { + return returnType.as(WebEndpointResponse.class).getGeneric(0).getType(); + } + return returnType.getType(); + } + + private void registerReflectionHints(ReflectionHints hints, Type type) { + if (type.equals(Resource.class)) { + return; + } + this.bindingRegistrar.registerReflectionHints(hints, type); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/ReadOperation.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/ReadOperation.java index 1997393171..ead39bc309 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/ReadOperation.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/ReadOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -22,6 +22,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.aot.hint.annotation.Reflective; import org.springframework.boot.actuate.endpoint.Producible; /** @@ -33,6 +34,7 @@ import org.springframework.boot.actuate.endpoint.Producible; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented +@Reflective(OperationReflectiveProcessor.class) public @interface ReadOperation { /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/WriteOperation.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/WriteOperation.java index df39b75fec..b9a63c4f69 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/WriteOperation.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/WriteOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -22,6 +22,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.aot.hint.annotation.Reflective; import org.springframework.boot.actuate.endpoint.Producible; /** @@ -33,6 +34,7 @@ import org.springframework.boot.actuate.endpoint.Producible; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented +@Reflective(OperationReflectiveProcessor.class) public @interface WriteOperation { /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscoverer.java index f9106e3fe1..7dde444a5c 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -18,6 +18,9 @@ package org.springframework.boot.actuate.endpoint.jmx.annotation; import java.util.Collection; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; @@ -28,7 +31,9 @@ import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint; import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointsSupplier; import org.springframework.boot.actuate.endpoint.jmx.JmxOperation; +import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpointDiscoverer.JmxEndpointDiscovererRuntimeHints; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ImportRuntimeHints; /** * {@link EndpointDiscoverer} for {@link ExposableJmxEndpoint JMX endpoints}. @@ -36,6 +41,7 @@ import org.springframework.context.ApplicationContext; * @author Phillip Webb * @since 2.0.0 */ +@ImportRuntimeHints(JmxEndpointDiscovererRuntimeHints.class) public class JmxEndpointDiscoverer extends EndpointDiscoverer implements JmxEndpointsSupplier { @@ -69,4 +75,14 @@ public class JmxEndpointDiscoverer extends EndpointDiscoverer "MBean call '" + operation.getName() + "'"); } + static class JmxEndpointDiscovererRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.reflection().registerType(JmxEndpointFilter.class, + (hint) -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscoverer.java index 1b991c71ff..43b6181207 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. @@ -20,6 +20,9 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.Operation; @@ -28,7 +31,9 @@ import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.boot.actuate.endpoint.web.PathMapper; +import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointDiscoverer.ControllerEndpointDiscovererRuntimeHints; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; @@ -39,6 +44,7 @@ import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; * @author Phillip Webb * @since 2.0.0 */ +@ImportRuntimeHints(ControllerEndpointDiscovererRuntimeHints.class) public class ControllerEndpointDiscoverer extends EndpointDiscoverer implements ControllerEndpointsSupplier { @@ -80,4 +86,14 @@ public class ControllerEndpointDiscoverer extends EndpointDiscoverer hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscoverer.java index 0349c4a3d1..366a55210a 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. @@ -20,6 +20,9 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.Operation; @@ -29,7 +32,9 @@ import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; import org.springframework.boot.actuate.endpoint.web.PathMapper; +import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointDiscoverer.ServletEndpointDiscovererRuntimeHints; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; @@ -39,6 +44,7 @@ import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; * @author Phillip Webb * @since 2.0.0 */ +@ImportRuntimeHints(ServletEndpointDiscovererRuntimeHints.class) public class ServletEndpointDiscoverer extends EndpointDiscoverer implements ServletEndpointsSupplier { @@ -79,4 +85,14 @@ public class ServletEndpointDiscoverer extends EndpointDiscoverer hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscoverer.java index 42d4d631b4..3c3004e762 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -19,6 +19,9 @@ package org.springframework.boot.actuate.endpoint.web.annotation; import java.util.Collection; import java.util.List; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; @@ -32,7 +35,9 @@ import org.springframework.boot.actuate.endpoint.web.PathMapper; import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier; import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate; +import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer.WebEndpointDiscovererRuntimeHints; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ImportRuntimeHints; /** * {@link EndpointDiscoverer} for {@link ExposableWebEndpoint web endpoints}. @@ -40,6 +45,7 @@ import org.springframework.context.ApplicationContext; * @author Phillip Webb * @since 2.0.0 */ +@ImportRuntimeHints(WebEndpointDiscovererRuntimeHints.class) public class WebEndpointDiscoverer extends EndpointDiscoverer implements WebEndpointsSupplier { @@ -87,4 +93,14 @@ public class WebEndpointDiscoverer extends EndpointDiscoverer "web request predicate " + operation.getRequestPredicate()); } + static class WebEndpointDiscovererRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.reflection().registerType(WebEndpointFilter.class, + (hint) -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java index 1c0e1ab180..93c24f72d6 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import java.util.function.Supplier; import org.reactivestreams.Publisher; @@ -30,6 +31,8 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException; import org.springframework.boot.actuate.endpoint.InvocationContext; import org.springframework.boot.actuate.endpoint.OperationArgumentResolver; @@ -44,7 +47,9 @@ import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate; import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; +import org.springframework.boot.actuate.endpoint.web.reactive.AbstractWebFluxEndpointHandlerMapping.AbstractWebFluxEndpointHandlerMappingRuntimeHints; import org.springframework.boot.web.context.WebServerApplicationContext; +import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -80,6 +85,7 @@ import org.springframework.web.util.pattern.PathPattern; * @author Brian Clozel * @since 2.0.0 */ +@ImportRuntimeHints(AbstractWebFluxEndpointHandlerMappingRuntimeHints.class) public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappingInfoHandlerMapping { private final EndpointMapping endpointMapping; @@ -484,4 +490,17 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi } + static class AbstractWebFluxEndpointHandlerMappingRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.reflection() + .registerMethod(Objects.requireNonNull(ReflectionUtils.findMethod(WriteOperationHandler.class, + "handle", ServerWebExchange.class, Map.class))) + .registerMethod(Objects.requireNonNull( + ReflectionUtils.findMethod(ReadOperationHandler.class, "handle", ServerWebExchange.class))); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointHandlerMapping.java index 2b0a7ae83f..dff2b56b91 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -19,13 +19,20 @@ package org.springframework.boot.actuate.endpoint.web.reactive; import java.util.Collection; import java.util.Collections; import java.util.Map; +import java.util.Objects; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.Link; +import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping.WebFluxEndpointHandlerMappingRuntimeHints; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.context.aot.BindingReflectionHintsRegistrar; +import org.springframework.util.ReflectionUtils; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.reactive.HandlerMapping; @@ -41,6 +48,7 @@ import org.springframework.web.util.UriComponentsBuilder; * @author Brian Clozel * @since 2.0.0 */ +@ImportRuntimeHints(WebFluxEndpointHandlerMappingRuntimeHints.class) public class WebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointHandlerMapping implements InitializingBean { private final EndpointLinksResolver linksResolver; @@ -89,4 +97,17 @@ public class WebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointHandle } + static class WebFluxEndpointHandlerMappingRuntimeHints implements RuntimeHintsRegistrar { + + private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.reflection().registerMethod(Objects.requireNonNull( + ReflectionUtils.findMethod(WebFluxLinksHandler.class, "links", ServerWebExchange.class))); + this.bindingRegistrar.registerReflectionHints(hints.reflection(), Link.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java index 3bc469d87c..bd90fa2377 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java @@ -26,12 +26,15 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Function; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import reactor.core.publisher.Flux; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException; import org.springframework.boot.actuate.endpoint.InvocationContext; @@ -46,7 +49,9 @@ import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate; import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; +import org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.AbstractWebMvcEndpointHandlerMappingRuntimeHints; import org.springframework.boot.web.context.WebServerApplicationContext; +import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -80,6 +85,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi * @author Brian Clozel * @since 2.0.0 */ +@ImportRuntimeHints(AbstractWebMvcEndpointHandlerMappingRuntimeHints.class) public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappingInfoHandlerMapping implements InitializingBean { @@ -476,4 +482,14 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin } + static class AbstractWebMvcEndpointHandlerMappingRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.reflection().registerMethod(Objects.requireNonNull( + ReflectionUtils.findMethod(OperationHandler.class, "handle", HttpServletRequest.class, Map.class))); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/WebMvcEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/WebMvcEndpointHandlerMapping.java index a6f3cfb96d..41b8cfae4e 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/WebMvcEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/WebMvcEndpointHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -19,15 +19,22 @@ package org.springframework.boot.actuate.endpoint.web.servlet; import java.util.Collection; import java.util.Collections; import java.util.Map; +import java.util.Objects; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.Link; +import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping.WebMvcEndpointHandlerMappingRuntimeHints; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.context.aot.BindingReflectionHintsRegistrar; +import org.springframework.util.ReflectionUtils; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.servlet.HandlerMapping; @@ -40,6 +47,7 @@ import org.springframework.web.servlet.HandlerMapping; * @author Phillip Webb * @since 2.0.0 */ +@ImportRuntimeHints(WebMvcEndpointHandlerMappingRuntimeHints.class) public class WebMvcEndpointHandlerMapping extends AbstractWebMvcEndpointHandlerMapping { private final EndpointLinksResolver linksResolver; @@ -86,4 +94,18 @@ public class WebMvcEndpointHandlerMapping extends AbstractWebMvcEndpointHandlerM } + static class WebMvcEndpointHandlerMappingRuntimeHints implements RuntimeHintsRegistrar { + + private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.reflection() + .registerMethod(Objects.requireNonNull(ReflectionUtils.findMethod(WebMvcLinksHandler.class, "links", + HttpServletRequest.class, HttpServletResponse.class))); + this.bindingRegistrar.registerReflectionHints(hints.reflection(), Link.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java index e019e3e9bf..13f940a5e1 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java @@ -21,6 +21,8 @@ import java.util.Arrays; import java.util.Map; import java.util.Set; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.actuate.endpoint.ApiVersion; import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; @@ -29,6 +31,9 @@ import org.springframework.boot.actuate.endpoint.annotation.Selector.Match; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; +import org.springframework.boot.actuate.health.HealthEndpointWebExtension.HealthEndpointWebExtensionRuntimeHints; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.context.aot.BindingReflectionHintsRegistrar; /** * {@link EndpointWebExtension @EndpointWebExtension} for the {@link HealthEndpoint}. @@ -44,6 +49,7 @@ import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExten * @since 2.0.0 */ @EndpointWebExtension(endpoint = HealthEndpoint.class) +@ImportRuntimeHints(HealthEndpointWebExtensionRuntimeHints.class) public class HealthEndpointWebExtension extends HealthEndpointSupport { private static final String[] NO_PATH = {}; @@ -110,4 +116,16 @@ public class HealthEndpointWebExtension extends HealthEndpointSupport { public BuildInfoContributor(BuildProperties properties) { @@ -56,4 +62,15 @@ public class BuildInfoContributor extends InfoPropertiesInfoContributor { public GitInfoContributor(GitProperties properties) { @@ -68,4 +74,15 @@ public class GitInfoContributor extends InfoPropertiesInfoContributor T getFormattedThreadDump(Function formatter) { - return formatter.apply(ManagementFactory.getThreadMXBean().dumpAllThreads(true, true)); + ThreadDumper threadDumper = createThreadDumper(); + return formatter.apply(threadDumper.dumpAllThreads()); + } + + private ThreadDumper createThreadDumper() { + if (NativeDetector.inNativeImage()) { + throw new ThreadDumperUnavailableException("Running in native image"); + } + return new ThreadMXBeanThreadDumper(); + } + + private interface ThreadDumper { + + ThreadInfo[] dumpAllThreads(); + + } + + private static class ThreadMXBeanThreadDumper implements ThreadDumper { + + @Override + public ThreadInfo[] dumpAllThreads() { + return ManagementFactory.getThreadMXBean().dumpAllThreads(true, true); + } + + } + + /** + * Exception to be thrown if the {@link ThreadDumper} cannot be created. + */ + static class ThreadDumperUnavailableException extends RuntimeException { + + ThreadDumperUnavailableException(String message) { + super(message); + } + } /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/management/ThreadDumpEndpointWebExtension.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/management/ThreadDumpEndpointWebExtension.java new file mode 100644 index 0000000000..76a1f38a30 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/management/ThreadDumpEndpointWebExtension.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2022 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.actuate.management; + +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; +import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; +import org.springframework.boot.actuate.management.ThreadDumpEndpoint.ThreadDumpDescriptor; +import org.springframework.boot.actuate.management.ThreadDumpEndpoint.ThreadDumperUnavailableException; + +/** + * {@link EndpointWebExtension @EndpointWebExtension} for the {@link ThreadDumpEndpoint}. + * + * @author Moritz Halbritter + * @since 3.0.0 + */ +@EndpointWebExtension(endpoint = ThreadDumpEndpoint.class) +public class ThreadDumpEndpointWebExtension { + + private final ThreadDumpEndpoint delegate; + + public ThreadDumpEndpointWebExtension(ThreadDumpEndpoint delegate) { + this.delegate = delegate; + } + + @ReadOperation + public WebEndpointResponse threadDump() { + try { + return new WebEndpointResponse<>(this.delegate.threadDump()); + } + catch (ThreadDumperUnavailableException ex) { + return new WebEndpointResponse<>(WebEndpointResponse.STATUS_SERVICE_UNAVAILABLE); + } + } + + @ReadOperation(produces = "text/plain;charset=UTF-8") + public WebEndpointResponse textThreadDump() { + try { + return new WebEndpointResponse<>(this.delegate.textThreadDump()); + } + catch (ThreadDumperUnavailableException ex) { + return new WebEndpointResponse<>(WebEndpointResponse.STATUS_SERVICE_UNAVAILABLE); + } + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/HazelcastCacheMeterBinderProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/HazelcastCacheMeterBinderProvider.java index 92894ec0f4..7f38ad7fb0 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/HazelcastCacheMeterBinderProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/HazelcastCacheMeterBinderProvider.java @@ -17,12 +17,17 @@ package org.springframework.boot.actuate.metrics.cache; import java.lang.reflect.Method; +import java.util.Objects; import com.hazelcast.spring.cache.HazelcastCache; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.binder.MeterBinder; import io.micrometer.core.instrument.binder.cache.HazelcastCacheMetrics; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.boot.actuate.metrics.cache.HazelcastCacheMeterBinderProvider.HazelcastCacheMeterBinderProviderRuntimeHints; +import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.util.ReflectionUtils; /** @@ -31,6 +36,7 @@ import org.springframework.util.ReflectionUtils; * @author Stephane Nicoll * @since 2.0.0 */ +@ImportRuntimeHints(HazelcastCacheMeterBinderProviderRuntimeHints.class) public class HazelcastCacheMeterBinderProvider implements CacheMeterBinderProvider { @Override @@ -56,4 +62,22 @@ public class HazelcastCacheMeterBinderProvider implements CacheMeterBinderProvid } } + static class HazelcastCacheMeterBinderProviderRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + try { + hints.reflection() + .registerMethod(Objects + .requireNonNull(ReflectionUtils.findMethod(HazelcastCache.class, "getNativeCache"))) + .registerConstructor(HazelcastCacheMetrics.class.getConstructor(Object.class, Iterable.class)); + } + catch (NoSuchMethodException ex) { + throw new IllegalStateException(ex); + } + + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/quartz/QuartzEndpointWebExtension.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/quartz/QuartzEndpointWebExtension.java index ab52b8707d..e3468512e2 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/quartz/QuartzEndpointWebExtension.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/quartz/QuartzEndpointWebExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -18,11 +18,19 @@ package org.springframework.boot.actuate.quartz; import org.quartz.SchedulerException; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzGroups; +import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobDetails; +import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobGroupSummary; +import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzTriggerGroupSummary; +import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension.QuartzEndpointWebExtensionRuntimeHints; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.context.aot.BindingReflectionHintsRegistrar; /** * {@link EndpointWebExtension @EndpointWebExtension} for the {@link QuartzEndpoint}. @@ -31,6 +39,7 @@ import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzGroups; * @since 2.5.0 */ @EndpointWebExtension(endpoint = QuartzEndpoint.class) +@ImportRuntimeHints(QuartzEndpointWebExtensionRuntimeHints.class) public class QuartzEndpointWebExtension { private final QuartzEndpoint delegate; @@ -84,4 +93,16 @@ public class QuartzEndpointWebExtension { } + static class QuartzEndpointWebExtensionRuntimeHints implements RuntimeHintsRegistrar { + + private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + this.bindingRegistrar.registerReflectionHints(hints.reflection(), QuartzGroups.class, + QuartzJobDetails.class, QuartzJobGroupSummary.class, QuartzTriggerGroupSummary.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpoint.java index 18e87b1684..00f79598a5 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpoint.java @@ -26,8 +26,13 @@ import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.ScheduledTasksEndpointRuntimeHints; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.context.aot.BindingReflectionHintsRegistrar; import org.springframework.scheduling.Trigger; import org.springframework.scheduling.config.CronTask; import org.springframework.scheduling.config.FixedDelayTask; @@ -49,6 +54,7 @@ import org.springframework.scheduling.support.ScheduledMethodRunnable; * @since 2.0.0 */ @Endpoint(id = "scheduledtasks") +@ImportRuntimeHints(ScheduledTasksEndpointRuntimeHints.class) public class ScheduledTasksEndpoint { private final Collection scheduledTaskHolders; @@ -295,4 +301,16 @@ public class ScheduledTasksEndpoint { } + static class ScheduledTasksEndpointRuntimeHints implements RuntimeHintsRegistrar { + + private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + this.bindingRegistrar.registerReflectionHints(hints.reflection(), FixedRateTaskDescription.class, + FixedDelayTaskDescription.class, CronTaskDescription.class, CustomTriggerTaskDescription.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/startup/StartupEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/startup/StartupEndpoint.java index 51b0412a65..e0c03227aa 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/startup/StartupEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/startup/StartupEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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,12 +16,18 @@ package org.springframework.boot.actuate.startup; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; import org.springframework.boot.SpringBootVersion; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; +import org.springframework.boot.actuate.startup.StartupEndpoint.StartupEndpointRuntimeHints; import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup; import org.springframework.boot.context.metrics.buffering.StartupTimeline; +import org.springframework.context.annotation.ImportRuntimeHints; /** * {@link Endpoint @Endpoint} to expose the timeline of the @@ -33,6 +39,7 @@ import org.springframework.boot.context.metrics.buffering.StartupTimeline; * @since 2.4.0 */ @Endpoint(id = "startup") +@ImportRuntimeHints(StartupEndpointRuntimeHints.class) public class StartupEndpoint { private final BufferingApplicationStartup applicationStartup; @@ -83,4 +90,26 @@ public class StartupEndpoint { } + static class StartupEndpointRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.reflection().registerType( + TypeReference + .of("org.springframework.boot.context.metrics.buffering.BufferedStartupStep$DefaultTag"), + (hint) -> hint + .onReachableType(TypeReference + .of("org.springframework.boot.context.metrics.buffering.BufferedStartupStep")) + .withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)); + hints.reflection().registerType( + TypeReference + .of("org.springframework.core.metrics.jfr.FlightRecorderStartupStep$FlightRecorderTag"), + (hint) -> hint + .onReachableType( + TypeReference.of("org.springframework.core.metrics.jfr.FlightRecorderStartupStep")) + .withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/reactive/DispatcherHandlersMappingDescriptionProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/reactive/DispatcherHandlersMappingDescriptionProvider.java index 49ef361185..67d6ad941a 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/reactive/DispatcherHandlersMappingDescriptionProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/reactive/DispatcherHandlersMappingDescriptionProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. @@ -28,9 +28,14 @@ import java.util.stream.Stream; import reactor.core.publisher.Mono; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.actuate.web.mappings.HandlerMethodDescription; import org.springframework.boot.actuate.web.mappings.MappingDescriptionProvider; +import org.springframework.boot.actuate.web.mappings.reactive.DispatcherHandlersMappingDescriptionProvider.DispatcherHandlersMappingDescriptionProviderRuntimeHints; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.context.aot.BindingReflectionHintsRegistrar; import org.springframework.core.io.Resource; import org.springframework.web.method.HandlerMethod; import org.springframework.web.reactive.DispatcherHandler; @@ -53,6 +58,7 @@ import org.springframework.web.util.pattern.PathPattern; * @author Andy Wilkinson * @since 2.0.0 */ +@ImportRuntimeHints(DispatcherHandlersMappingDescriptionProviderRuntimeHints.class) public class DispatcherHandlersMappingDescriptionProvider implements MappingDescriptionProvider { private static final List> descriptionProviders = Arrays @@ -194,4 +200,16 @@ public class DispatcherHandlersMappingDescriptionProvider implements MappingDesc } + static class DispatcherHandlersMappingDescriptionProviderRuntimeHints implements RuntimeHintsRegistrar { + + private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + this.bindingRegistrar.registerReflectionHints(hints.reflection(), + DispatcherHandlerMappingDescription.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java index 49b7e2ba3e..18592a7a9e 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java @@ -28,10 +28,15 @@ import java.util.stream.Stream; import jakarta.servlet.Servlet; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.actuate.web.mappings.HandlerMethodDescription; import org.springframework.boot.actuate.web.mappings.MappingDescriptionProvider; +import org.springframework.boot.actuate.web.mappings.servlet.DispatcherServletsMappingDescriptionProvider.DispatcherServletsMappingDescriptionProviderRuntimeHints; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.context.aot.BindingReflectionHintsRegistrar; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.DispatcherServlet; @@ -49,6 +54,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi * @author Stephane Nicoll * @since 2.0.0 */ +@ImportRuntimeHints(DispatcherServletsMappingDescriptionProviderRuntimeHints.class) public class DispatcherServletsMappingDescriptionProvider implements MappingDescriptionProvider { private static final List> descriptionProviders; @@ -195,4 +201,16 @@ public class DispatcherServletsMappingDescriptionProvider implements MappingDesc } + static class DispatcherServletsMappingDescriptionProviderRuntimeHints implements RuntimeHintsRegistrar { + + private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + this.bindingRegistrar.registerReflectionHints(hints.reflection(), + DispatcherServletMappingDescription.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/FiltersMappingDescriptionProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/FiltersMappingDescriptionProvider.java index c091d2dcfa..a03ad253c8 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/FiltersMappingDescriptionProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/FiltersMappingDescriptionProvider.java @@ -23,8 +23,13 @@ import java.util.stream.Collectors; import jakarta.servlet.Filter; import jakarta.servlet.ServletContext; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.actuate.web.mappings.MappingDescriptionProvider; +import org.springframework.boot.actuate.web.mappings.servlet.FiltersMappingDescriptionProvider.FiltersMappingDescriptionProviderRuntimeHints; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.context.aot.BindingReflectionHintsRegistrar; import org.springframework.web.context.WebApplicationContext; /** @@ -34,6 +39,7 @@ import org.springframework.web.context.WebApplicationContext; * @author Andy Wilkinson * @since 2.0.0 */ +@ImportRuntimeHints(FiltersMappingDescriptionProviderRuntimeHints.class) public class FiltersMappingDescriptionProvider implements MappingDescriptionProvider { @Override @@ -50,4 +56,16 @@ public class FiltersMappingDescriptionProvider implements MappingDescriptionProv return "servletFilters"; } + static class FiltersMappingDescriptionProviderRuntimeHints implements RuntimeHintsRegistrar { + + private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + this.bindingRegistrar.registerReflectionHints(hints.reflection(), + FilterRegistrationMappingDescription.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/ServletsMappingDescriptionProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/ServletsMappingDescriptionProvider.java index 8c36e61811..d9fa2a7e42 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/ServletsMappingDescriptionProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/ServletsMappingDescriptionProvider.java @@ -23,8 +23,13 @@ import java.util.stream.Collectors; import jakarta.servlet.Servlet; import jakarta.servlet.ServletContext; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.actuate.web.mappings.MappingDescriptionProvider; +import org.springframework.boot.actuate.web.mappings.servlet.ServletsMappingDescriptionProvider.ServletsMappingDescriptionProviderRuntimeHints; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.context.aot.BindingReflectionHintsRegistrar; import org.springframework.web.context.WebApplicationContext; /** @@ -34,6 +39,7 @@ import org.springframework.web.context.WebApplicationContext; * @author Andy Wilkinson * @since 2.0.0 */ +@ImportRuntimeHints(ServletsMappingDescriptionProviderRuntimeHints.class) public class ServletsMappingDescriptionProvider implements MappingDescriptionProvider { @Override @@ -50,4 +56,16 @@ public class ServletsMappingDescriptionProvider implements MappingDescriptionPro return "servlets"; } + static class ServletsMappingDescriptionProviderRuntimeHints implements RuntimeHintsRegistrar { + + private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + this.bindingRegistrar.registerReflectionHints(hints.reflection(), + ServletRegistrationMappingDescription.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/OperationReflectiveProcessorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/OperationReflectiveProcessorTests.java new file mode 100644 index 0000000000..8d0618f30b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/OperationReflectiveProcessorTests.java @@ -0,0 +1,141 @@ +/* + * Copyright 2012-2022 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.actuate.endpoint.annotation; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; +import org.springframework.core.io.Resource; +import org.springframework.util.ReflectionUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OperationReflectiveProcessor}. + * + * @author Moritz Halbritter + * @author Stephane Nicoll + */ +class OperationReflectiveProcessorTests { + + private final OperationReflectiveProcessor processor = new OperationReflectiveProcessor(); + + private final RuntimeHints runtimeHints = new RuntimeHints(); + + @Test + void shouldRegisterMethodAsInvokable() { + Method method = ReflectionUtils.findMethod(Methods.class, "string"); + runProcessor(method); + assertThat(RuntimeHintsPredicates.reflection().onMethod(method)).accepts(this.runtimeHints); + } + + @Test + void shouldRegisterReturnType() { + Method method = ReflectionUtils.findMethod(Methods.class, "dto"); + runProcessor(method); + assertHintsForDto(); + } + + @Test + void shouldRegisterMapValueFromReturnType() { + Method method = ReflectionUtils.findMethod(Methods.class, "dtos"); + runProcessor(method); + assertHintsForDto(); + } + + @Test + void shouldRegisterWebEndpointResponseReturnType() { + Method method = ReflectionUtils.findMethod(Methods.class, "webEndpointResponse"); + runProcessor(method); + assertHintsForDto(); + assertThat(RuntimeHintsPredicates.reflection().onType(WebEndpointResponse.class)).rejects(this.runtimeHints); + } + + @Test + void shouldNotRegisterResourceReturnType() { + Method method = ReflectionUtils.findMethod(Methods.class, "resource"); + runProcessor(method); + assertThat(RuntimeHintsPredicates.reflection().onType(Resource.class)).rejects(this.runtimeHints); + } + + private void assertHintsForDto() { + assertThat(RuntimeHintsPredicates.reflection().onType(Dto.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS)) + .accepts(this.runtimeHints); + assertThat(RuntimeHintsPredicates.reflection().onType(NestedDto.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS)) + .accepts(this.runtimeHints); + } + + private void runProcessor(Method method) { + this.processor.registerReflectionHints(this.runtimeHints.reflection(), method); + } + + @SuppressWarnings("unused") + private static class Methods { + + private String string() { + return null; + } + + private Map> dtos() { + return null; + } + + private Dto dto() { + return null; + } + + private WebEndpointResponse webEndpointResponse() { + return null; + } + + private Resource resource() { + return null; + } + + } + + @SuppressWarnings("unused") + public static class Dto { + + private final NestedDto nestedDto = new NestedDto(); + + public NestedDto getNestedDto() { + return this.nestedDto; + } + + } + + public static class NestedDto { + + private final String string = "some-string"; + + public String getString() { + return this.string; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscovererTests.java index 3fc4eaa9a3..cc004288aa 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscovererTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. @@ -26,6 +26,9 @@ import java.util.function.Function; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; @@ -37,6 +40,7 @@ import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationI import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint; import org.springframework.boot.actuate.endpoint.jmx.JmxOperation; import org.springframework.boot.actuate.endpoint.jmx.JmxOperationParameter; +import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpointDiscoverer.JmxEndpointDiscovererRuntimeHints; import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; @@ -55,6 +59,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException; * * @author Stephane Nicoll * @author Phillip Webb + * @author Moritz Halbritter */ class JmxEndpointDiscovererTests { @@ -201,6 +206,14 @@ class JmxEndpointDiscovererTests { "Endpoint bean 'nonJmxEndpoint' cannot support the extension bean 'nonJmxJmxEndpointExtension'")); } + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new JmxEndpointDiscovererRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(JmxEndpointFilter.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(runtimeHints); + } + private Object getInvoker(JmxOperation operation) { return ReflectionTestUtils.getField(operation, "invoker"); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscovererTests.java index 94021c018c..4ca1a8eae8 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscovererTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. @@ -24,10 +24,14 @@ import java.util.stream.Collectors; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointDiscoverer.ControllerEndpointDiscovererRuntimeHints; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; @@ -45,6 +49,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException; * * @author Phillip Webb * @author Stephane Nicoll + * @author Moritz Halbritter */ class ControllerEndpointDiscovererTests { @@ -126,6 +131,15 @@ class ControllerEndpointDiscovererTests { .withMessageContaining("ControllerEndpoints must not declare operations"))); } + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new ControllerEndpointDiscovererRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(ControllerEndpointFilter.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(runtimeHints); + + } + private ContextConsumer assertDiscoverer( Consumer consumer) { return (context) -> { diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscovererTests.java index cf8593b9a7..d5ca292891 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscovererTests.java @@ -30,12 +30,16 @@ import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.web.EndpointServlet; import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; +import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointDiscoverer.ServletEndpointDiscovererRuntimeHints; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; @@ -53,6 +57,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException; * * @author Phillip Webb * @author Stephane Nicoll + * @author Moritz Halbritter */ class ServletEndpointDiscovererTests { @@ -129,6 +134,14 @@ class ServletEndpointDiscovererTests { .isThrownBy(discoverer::getEndpoints).withMessageContaining("must not supply null"))); } + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new ServletEndpointDiscovererRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(ServletEndpointFilter.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(runtimeHints); + } + private ContextConsumer assertDiscoverer( Consumer consumer) { return (context) -> { diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscovererTests.java index 552c7a09cb..526eee134a 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscovererTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. @@ -31,6 +31,9 @@ import java.util.stream.Stream; import org.assertj.core.api.Condition; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; @@ -47,6 +50,7 @@ import org.springframework.boot.actuate.endpoint.web.PathMapper; import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod; import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate; +import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer.WebEndpointDiscovererRuntimeHints; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -65,6 +69,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException; * @author Andy Wilkinson * @author Stephane Nicoll * @author Phillip Webb + * @author Moritz Halbritter */ class WebEndpointDiscovererTests { @@ -216,6 +221,15 @@ class WebEndpointDiscovererTests { }); } + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new WebEndpointDiscovererRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(WebEndpointFilter.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(runtimeHints); + + } + private void load(Class configuration, Consumer consumer) { load((id) -> null, EndpointId::toString, configuration, consumer); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMappingTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMappingTests.java new file mode 100644 index 0000000000..e234dbcfc6 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMappingTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2022 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.actuate.endpoint.web.reactive; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeReference; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.endpoint.web.reactive.AbstractWebFluxEndpointHandlerMapping.AbstractWebFluxEndpointHandlerMappingRuntimeHints; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AbstractWebFluxEndpointHandlerMapping}. + * + * @author Moritz Halbritter + */ +class AbstractWebFluxEndpointHandlerMappingTests { + + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new AbstractWebFluxEndpointHandlerMappingRuntimeHints().registerHints(runtimeHints, + getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(TypeReference.of( + "org.springframework.boot.actuate.endpoint.web.reactive.AbstractWebFluxEndpointHandlerMapping.WriteOperationHandler"))) + .accepts(runtimeHints); + assertThat(RuntimeHintsPredicates.reflection().onType(TypeReference.of( + "org.springframework.boot.actuate.endpoint.web.reactive.AbstractWebFluxEndpointHandlerMapping.ReadOperationHandler"))) + .accepts(runtimeHints); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointHandlerMappingTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointHandlerMappingTests.java new file mode 100644 index 0000000000..4d02996eaa --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointHandlerMappingTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2022 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.actuate.endpoint.web.reactive; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.endpoint.web.Link; +import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping.WebFluxEndpointHandlerMappingRuntimeHints; +import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping.WebFluxLinksHandler; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link WebFluxEndpointHandlerMapping}. + * + * @author Moritz Halbritter + */ +class WebFluxEndpointHandlerMappingTests { + + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new WebFluxEndpointHandlerMappingRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onMethod(WebFluxLinksHandler.class, "links")) + .accepts(runtimeHints); + assertThat(RuntimeHintsPredicates.reflection().onType(Link.class)).accepts(runtimeHints); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMappingTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMappingTests.java new file mode 100644 index 0000000000..cd5a152d83 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMappingTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2022 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.actuate.endpoint.web.servlet; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeReference; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.AbstractWebMvcEndpointHandlerMappingRuntimeHints; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AbstractWebMvcEndpointHandlerMapping}. + * + * @author Moritz Halbritter + */ +class AbstractWebMvcEndpointHandlerMappingTests { + + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new AbstractWebMvcEndpointHandlerMappingRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(TypeReference.of( + "org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler"))) + .accepts(runtimeHints); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/WebMvcEndpointHandlerMappingTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/WebMvcEndpointHandlerMappingTests.java new file mode 100644 index 0000000000..fcfc2d3018 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/WebMvcEndpointHandlerMappingTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2022 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.actuate.endpoint.web.servlet; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.endpoint.web.Link; +import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping.WebMvcEndpointHandlerMappingRuntimeHints; +import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping.WebMvcLinksHandler; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link WebMvcEndpointHandlerMapping}. + * + * @author Moritz Halbritter + */ +class WebMvcEndpointHandlerMappingTests { + + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new WebMvcEndpointHandlerMappingRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onMethod(WebMvcLinksHandler.class, "links")) + .accepts(runtimeHints); + assertThat(RuntimeHintsPredicates.reflection().onType(Link.class)).accepts(runtimeHints); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebExtensionTests.java index 56f44169ce..eaa93b7662 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebExtensionTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebExtensionTests.java @@ -19,14 +19,19 @@ package org.springframework.boot.actuate.health; import java.time.Duration; import java.util.Collections; import java.util.Map; +import java.util.Set; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.boot.actuate.endpoint.ApiVersion; import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult; +import org.springframework.boot.actuate.health.HealthEndpointWebExtension.HealthEndpointWebExtensionRuntimeHints; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -36,6 +41,7 @@ import static org.mockito.Mockito.mock; * * @author Phillip Webb * @author Scott Frederick + * @author Moritz Halbritter */ class HealthEndpointWebExtensionTests extends HealthEndpointSupportTests { @@ -81,6 +87,18 @@ class HealthEndpointWebExtensionTests extends assertThat(response.getStatus()).isEqualTo(200); } + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new HealthEndpointWebExtensionRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + Set> bindingTypes = Set.of(Health.class, SystemHealth.class, CompositeHealth.class); + for (Class bindingType : bindingTypes) { + assertThat(RuntimeHintsPredicates.reflection().onType(bindingType) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS)) + .accepts(runtimeHints); + } + } + @Override protected HealthEndpointWebExtension create(HealthContributorRegistry registry, HealthEndpointGroups groups, Duration slowIndicatorLoggingThreshold) { diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/BuildInfoContributorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/BuildInfoContributorTests.java new file mode 100644 index 0000000000..5c3a7e195e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/BuildInfoContributorTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2022 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.actuate.info; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.info.BuildInfoContributor.BuildInfoContributorRuntimeHints; +import org.springframework.boot.info.BuildProperties; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link BuildInfoContributor}. + * + * @author Moritz Halbritter + */ +class BuildInfoContributorTests { + + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new BuildInfoContributorRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(BuildProperties.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS)) + .accepts(runtimeHints); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/GitInfoContributorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/GitInfoContributorTests.java index c253472384..c29fc0a674 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/GitInfoContributorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/GitInfoContributorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -22,6 +22,10 @@ import java.util.Properties; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.info.GitInfoContributor.GitInfoContributorRuntimeHints; import org.springframework.boot.actuate.info.InfoPropertiesInfoContributor.Mode; import org.springframework.boot.info.GitProperties; @@ -31,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link GitInfoContributor}. * * @author Stephane Nicoll + * @author Moritz Halbritter */ class GitInfoContributorTests { @@ -79,4 +84,14 @@ class GitInfoContributorTests { assertThat(id.get("abbrev")).isEqualTo("1b3cec3"); } + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new GitInfoContributorRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(GitProperties.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS)) + .accepts(runtimeHints); + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/JavaInfoContributorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/JavaInfoContributorTests.java index f431ccd90e..ec4747d998 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/JavaInfoContributorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/JavaInfoContributorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -18,6 +18,10 @@ package org.springframework.boot.actuate.info; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.info.JavaInfoContributor.JavaInfoContributorRuntimeHints; import org.springframework.boot.info.JavaInfo; import static org.assertj.core.api.Assertions.assertThat; @@ -26,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link JavaInfoContributor} * * @author Jonatan Ivanov + * @author Moritz Halbritter */ class JavaInfoContributorTests { @@ -38,4 +43,13 @@ class JavaInfoContributorTests { assertThat(info.getDetails().get("java")).isInstanceOf(JavaInfo.class); } + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new JavaInfoContributorRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(JavaInfo.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS)) + .accepts(runtimeHints); + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/OsInfoContributorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/OsInfoContributorTests.java index 333a683c46..ea85185dc6 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/OsInfoContributorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/OsInfoContributorTests.java @@ -18,6 +18,10 @@ package org.springframework.boot.actuate.info; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.info.OsInfoContributor.OsInfoContributorRuntimeHints; import org.springframework.boot.info.OsInfo; import static org.assertj.core.api.Assertions.assertThat; @@ -26,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link OsInfoContributor} * * @author Jonatan Ivanov + * @author Moritz Halbritter */ class OsInfoContributorTests { @@ -38,4 +43,13 @@ class OsInfoContributorTests { assertThat(info.getDetails().get("os")).isInstanceOf(OsInfo.class); } + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new OsInfoContributorRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(OsInfo.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS)) + .accepts(runtimeHints); + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/integration/IntegrationGraphEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/integration/IntegrationGraphEndpointTests.java index c832b2d054..2a8a63f50c 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/integration/IntegrationGraphEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/integration/IntegrationGraphEndpointTests.java @@ -16,10 +16,31 @@ package org.springframework.boot.actuate.integration; +import java.util.Set; + import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.integration.IntegrationGraphEndpoint.IntegrationGraphEndpointRuntimeHints; +import org.springframework.integration.graph.CompositeMessageHandlerNode; +import org.springframework.integration.graph.DiscardingMessageHandlerNode; +import org.springframework.integration.graph.EndpointNode; +import org.springframework.integration.graph.ErrorCapableCompositeMessageHandlerNode; +import org.springframework.integration.graph.ErrorCapableDiscardingMessageHandlerNode; +import org.springframework.integration.graph.ErrorCapableEndpointNode; +import org.springframework.integration.graph.ErrorCapableMessageHandlerNode; +import org.springframework.integration.graph.ErrorCapableRoutingNode; import org.springframework.integration.graph.Graph; import org.springframework.integration.graph.IntegrationGraphServer; +import org.springframework.integration.graph.MessageChannelNode; +import org.springframework.integration.graph.MessageGatewayNode; +import org.springframework.integration.graph.MessageHandlerNode; +import org.springframework.integration.graph.MessageProducerNode; +import org.springframework.integration.graph.MessageSourceNode; +import org.springframework.integration.graph.PollableChannelNode; +import org.springframework.integration.graph.RoutingMessageHandlerNode; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -30,6 +51,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link IntegrationGraphEndpoint}. * * @author Tim Ysewyn + * @author Moritz Halbritter */ class IntegrationGraphEndpointTests { @@ -52,4 +74,21 @@ class IntegrationGraphEndpointTests { then(this.server).should().rebuild(); } + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new IntegrationGraphEndpointRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + Set> bindingTypes = Set.of(Graph.class, EndpointNode.class, CompositeMessageHandlerNode.class, + DiscardingMessageHandlerNode.class, ErrorCapableCompositeMessageHandlerNode.class, + ErrorCapableDiscardingMessageHandlerNode.class, ErrorCapableEndpointNode.class, + ErrorCapableMessageHandlerNode.class, ErrorCapableRoutingNode.class, MessageGatewayNode.class, + MessageProducerNode.class, PollableChannelNode.class, MessageChannelNode.class, + MessageHandlerNode.class, MessageSourceNode.class, RoutingMessageHandlerNode.class); + for (Class bindingType : bindingTypes) { + assertThat(RuntimeHintsPredicates.reflection().onType(bindingType) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS)) + .accepts(runtimeHints); + } + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/management/ThreadDumpEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/management/ThreadDumpEndpointWebExtensionTests.java new file mode 100644 index 0000000000..a565b04919 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/management/ThreadDumpEndpointWebExtensionTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2022 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.actuate.management; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; +import org.springframework.boot.actuate.management.ThreadDumpEndpoint.ThreadDumpDescriptor; +import org.springframework.boot.actuate.management.ThreadDumpEndpoint.ThreadDumperUnavailableException; +import org.springframework.http.HttpStatus; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ThreadDumpEndpointWebExtension}. + * + * @author Moritz Halbritter + */ +class ThreadDumpEndpointWebExtensionTests { + + private ThreadDumpEndpointWebExtension extension; + + private ThreadDumpEndpoint delegateMock; + + @BeforeEach + void setUp() { + this.delegateMock = Mockito.mock(ThreadDumpEndpoint.class); + this.extension = new ThreadDumpEndpointWebExtension(this.delegateMock); + } + + @Test + void shouldHandleThreadDumperUnavailable() { + Mockito.when(this.delegateMock.threadDump()) + .thenThrow(new ThreadDumperUnavailableException("No thread dumper available")); + WebEndpointResponse response = this.extension.threadDump(); + assertThat(response.getStatus()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE.value()); + } + + @Test + void shouldHandleThreadDumperUnavailableText() { + Mockito.when(this.delegateMock.textThreadDump()) + .thenThrow(new ThreadDumperUnavailableException("No thread dumper available")); + WebEndpointResponse response = this.extension.textThreadDump(); + assertThat(response.getStatus()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE.value()); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/HazelcastCacheMeterBinderProviderTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/HazelcastCacheMeterBinderProviderTests.java index 737e8b855b..4e3400dacc 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/HazelcastCacheMeterBinderProviderTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/HazelcastCacheMeterBinderProviderTests.java @@ -24,6 +24,10 @@ import io.micrometer.core.instrument.binder.MeterBinder; import io.micrometer.core.instrument.binder.cache.HazelcastCacheMetrics; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.metrics.cache.HazelcastCacheMeterBinderProvider.HazelcastCacheMeterBinderProviderRuntimeHints; + import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -32,6 +36,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link HazelcastCacheMeterBinderProvider}. * * @author Stephane Nicoll + * @author Moritz Halbritter */ class HazelcastCacheMeterBinderProviderTests { @@ -46,4 +51,13 @@ class HazelcastCacheMeterBinderProviderTests { assertThat(meterBinder).isInstanceOf(HazelcastCacheMetrics.class); } + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new HazelcastCacheMeterBinderProviderRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onMethod(HazelcastCache.class, "getNativeCache")) + .accepts(runtimeHints); + assertThat(RuntimeHintsPredicates.reflection().onType(HazelcastCacheMetrics.class)).accepts(runtimeHints); + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/quartz/QuartzEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/quartz/QuartzEndpointWebExtensionTests.java new file mode 100644 index 0000000000..22ba2721bc --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/quartz/QuartzEndpointWebExtensionTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2022 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.actuate.quartz; + +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzGroups; +import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobDetails; +import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJobGroupSummary; +import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzTriggerGroupSummary; +import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension.QuartzEndpointWebExtensionRuntimeHints; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link QuartzEndpointWebExtension}. + * + * @author Moritz Halbritter + */ +class QuartzEndpointWebExtensionTests { + + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new QuartzEndpointWebExtensionRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + Set> bindingTypes = Set.of(QuartzGroups.class, QuartzJobDetails.class, QuartzJobGroupSummary.class, + QuartzTriggerGroupSummary.class); + for (Class bindingType : bindingTypes) { + assertThat(RuntimeHintsPredicates.reflection().onType(bindingType) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS)) + .accepts(runtimeHints); + } + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpointTests.java index 8d22b6f9c6..eaed65550d 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpointTests.java @@ -19,14 +19,19 @@ package org.springframework.boot.actuate.scheduling; import java.time.Duration; import java.time.Instant; import java.util.Collection; +import java.util.Set; import java.util.function.Consumer; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.CronTaskDescription; import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.CustomTriggerTaskDescription; import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.FixedDelayTaskDescription; import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.FixedRateTaskDescription; +import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.ScheduledTasksEndpointRuntimeHints; import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.ScheduledTasksReport; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -46,6 +51,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link ScheduledTasksEndpoint}. * * @author Andy Wilkinson + * @author Moritz Halbritter */ class ScheduledTasksEndpointTests { @@ -149,6 +155,19 @@ class ScheduledTasksEndpointTests { }); } + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new ScheduledTasksEndpointRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + Set> bindingTypes = Set.of(FixedRateTaskDescription.class, FixedDelayTaskDescription.class, + CronTaskDescription.class, CustomTriggerTaskDescription.class); + for (Class bindingType : bindingTypes) { + assertThat(RuntimeHintsPredicates.reflection().onType(bindingType) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS)) + .accepts(runtimeHints); + } + } + private void run(Class configuration, Consumer consumer) { this.contextRunner.withUserConfiguration(configuration) .run((context) -> consumer.accept(context.getBean(ScheduledTasksEndpoint.class).scheduledTasks())); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/startup/StartupEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/startup/StartupEndpointTests.java index 6e9ae9e5b2..206d6151bd 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/startup/StartupEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/startup/StartupEndpointTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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,11 +16,17 @@ package org.springframework.boot.actuate.startup; +import java.util.Set; import java.util.function.Consumer; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeReference; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.boot.SpringBootVersion; +import org.springframework.boot.actuate.startup.StartupEndpoint.StartupEndpointRuntimeHints; import org.springframework.boot.actuate.startup.StartupEndpoint.StartupResponse; import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -35,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Brian Clozel * @author Chris Bono + * @author Moritz Halbritter */ class StartupEndpointTests { @@ -69,6 +76,19 @@ class StartupEndpointTests { }); } + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new StartupEndpointRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + Set bindingTypes = Set.of( + TypeReference.of("org.springframework.boot.context.metrics.buffering.BufferedStartupStep$DefaultTag"), + TypeReference.of("org.springframework.core.metrics.jfr.FlightRecorderStartupStep$FlightRecorderTag")); + for (TypeReference bindingType : bindingTypes) { + assertThat(RuntimeHintsPredicates.reflection().onType(bindingType) + .withMemberCategories(MemberCategory.INVOKE_PUBLIC_METHODS)).accepts(runtimeHints); + } + } + private void testStartupEndpoint(ApplicationStartup applicationStartup, Consumer startupEndpoint) { ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withInitializer((context) -> context.setApplicationStartup(applicationStartup)) diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/reactive/DispatcherHandlersMappingDescriptionProviderTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/reactive/DispatcherHandlersMappingDescriptionProviderTests.java new file mode 100644 index 0000000000..6d5dcac318 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/reactive/DispatcherHandlersMappingDescriptionProviderTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2022 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.actuate.web.mappings.reactive; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.web.mappings.reactive.DispatcherHandlersMappingDescriptionProvider.DispatcherHandlersMappingDescriptionProviderRuntimeHints; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DispatcherHandlersMappingDescriptionProvider}. + * + * @author Moritz Halbritter + */ +class DispatcherHandlersMappingDescriptionProviderTests { + + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new DispatcherHandlersMappingDescriptionProviderRuntimeHints().registerHints(runtimeHints, + getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(DispatcherHandlerMappingDescription.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS)) + .accepts(runtimeHints); + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProviderTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProviderTests.java new file mode 100644 index 0000000000..1cb0f46126 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProviderTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2022 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.actuate.web.mappings.servlet; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.web.mappings.servlet.DispatcherServletsMappingDescriptionProvider.DispatcherServletsMappingDescriptionProviderRuntimeHints; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DispatcherServletsMappingDescriptionProvider}. + * + * @author Moritz Halbritter + */ +class DispatcherServletsMappingDescriptionProviderTests { + + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new DispatcherServletsMappingDescriptionProviderRuntimeHints().registerHints(runtimeHints, + getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(DispatcherServletMappingDescription.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS)) + .accepts(runtimeHints); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/servlet/FiltersMappingDescriptionProviderTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/servlet/FiltersMappingDescriptionProviderTests.java new file mode 100644 index 0000000000..62fb4e8846 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/servlet/FiltersMappingDescriptionProviderTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2022 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.actuate.web.mappings.servlet; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.web.mappings.servlet.FiltersMappingDescriptionProvider.FiltersMappingDescriptionProviderRuntimeHints; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link FiltersMappingDescriptionProvider}. + * + * @author Moritz Halbritter + */ +class FiltersMappingDescriptionProviderTests { + + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new FiltersMappingDescriptionProviderRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(FilterRegistrationMappingDescription.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS)) + .accepts(runtimeHints); + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/servlet/ServletsMappingDescriptionProviderTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/servlet/ServletsMappingDescriptionProviderTests.java new file mode 100644 index 0000000000..912f8151b1 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/servlet/ServletsMappingDescriptionProviderTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2022 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.actuate.web.mappings.servlet; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.boot.actuate.web.mappings.servlet.ServletsMappingDescriptionProvider.ServletsMappingDescriptionProviderRuntimeHints; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ServletsMappingDescriptionProvider}. + * + * @author Moritz Halbritter + */ +class ServletsMappingDescriptionProviderTests { + + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new ServletsMappingDescriptionProviderRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(ServletRegistrationMappingDescription.class) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS)) + .accepts(runtimeHints); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/BuildProperties.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/BuildProperties.java index 76ec3485aa..efbc13065a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/BuildProperties.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/BuildProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -21,12 +21,18 @@ import java.time.Instant; import java.time.format.DateTimeFormatter; import java.util.Properties; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.boot.info.BuildProperties.BuildPropertiesRuntimeHints; +import org.springframework.context.annotation.ImportRuntimeHints; + /** * Provide build-related information such as group and artifact. * * @author Stephane Nicoll * @since 1.4.0 */ +@ImportRuntimeHints(BuildPropertiesRuntimeHints.class) public class BuildProperties extends InfoProperties { /** @@ -100,4 +106,13 @@ public class BuildProperties extends InfoProperties { } } + static class BuildPropertiesRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.resources().registerPattern("META-INF/build-info.properties"); + } + + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/GitProperties.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/GitProperties.java index d07f6cf88d..ba5aff3c9c 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/GitProperties.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/GitProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -21,12 +21,18 @@ import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.Properties; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.boot.info.GitProperties.GitPropertiesRuntimeHints; +import org.springframework.context.annotation.ImportRuntimeHints; + /** * Provide git-related information such as commit id and time. * * @author Stephane Nicoll * @since 1.4.0 */ +@ImportRuntimeHints(GitPropertiesRuntimeHints.class) public class GitProperties extends InfoProperties { public GitProperties(Properties entries) { @@ -126,4 +132,13 @@ public class GitProperties extends InfoProperties { } } + static class GitPropertiesRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.resources().registerPattern("git.properties"); + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/info/BuildPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/info/BuildPropertiesTests.java index 809d0e8eba..276539a8b9 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/info/BuildPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/info/BuildPropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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,12 +23,16 @@ import java.util.Properties; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; + import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link BuildProperties}. * * @author Stephane Nicoll + * @author Moritz Halbritter */ class BuildPropertiesTests { @@ -53,6 +57,14 @@ class BuildPropertiesTests { assertThat(properties.getTime()).isNull(); } + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new BuildProperties.BuildPropertiesRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.resource().forResource("META-INF/build-info.properties")) + .accepts(runtimeHints); + } + private static Properties createProperties(String group, String artifact, String version, String buildTime) { Properties properties = new Properties(); properties.put("group", group); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/info/GitPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/info/GitPropertiesTests.java index b00b8fb269..0a6d583d8e 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/info/GitPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/info/GitPropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -20,12 +20,16 @@ import java.util.Properties; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; + import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link GitProperties}. * * @author Stephane Nicoll + * @author Moritz Halbritter */ class GitPropertiesTests { @@ -94,6 +98,13 @@ class GitPropertiesTests { assertThat(properties.getShortCommitId()).isEqualTo("abcdefg"); } + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new GitProperties.GitPropertiesRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.resource().forResource("git.properties")).accepts(runtimeHints); + } + private static Properties createProperties(String branch, String commitId, String commitIdAbbrev, String commitTime) { Properties properties = new Properties();