diff --git a/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/ApplicationModulesEndpoint.java b/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/ApplicationModulesEndpoint.java index 4f3bdd3b..17fb0752 100644 --- a/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/ApplicationModulesEndpoint.java +++ b/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/ApplicationModulesEndpoint.java @@ -15,7 +15,6 @@ */ package org.springframework.modulith.actuator; -import java.util.Map; import java.util.function.Supplier; import org.slf4j.Logger; @@ -44,13 +43,39 @@ public class ApplicationModulesEndpoint { * * @param runtime must not be {@literal null}. */ - public ApplicationModulesEndpoint(Supplier runtime) { - - Assert.notNull(runtime, "ModulesRuntime must not be null!"); + private ApplicationModulesEndpoint(Supplier precomputed) { LOGGER.debug("Activating Spring Modulith actuator."); - this.structure = SingletonSupplier.of(new ApplicationModulesExporter(runtime.get())::toJson); + this.structure = SingletonSupplier.of(precomputed); + } + + /** + * Creates a new {@link ApplicationModulesEndpoint} from the pre-computed actuator content + * + * @param precomputed must not be {@literal null}. + * @return will never be {@literal null}. + * @since 1.0.3 + */ + public static ApplicationModulesEndpoint precomputed(Supplier precomputed) { + + Assert.notNull(precomputed, "Precomputed content must not be null!"); + + return new ApplicationModulesEndpoint(precomputed); + } + + /** + * Creates a new {@link ApplicationModulesEndpoint} for the given lazily initialized {@link ApplicationModules}. + * + * @param modules must not be {@literal null}. + * @return will never be {@literal null}. + * @since 1.0.3 + */ + public static ApplicationModulesEndpoint ofApplicationModules(Supplier modules) { + + Assert.notNull(modules, "ApplicationModules must not be null!"); + + return new ApplicationModulesEndpoint(() -> new ApplicationModulesExporter(modules.get()).toJson()); } /** diff --git a/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/autoconfigure/ApplicationModulesEndpointConfiguration.java b/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/autoconfigure/ApplicationModulesEndpointConfiguration.java index 015a0d5d..6132a870 100644 --- a/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/autoconfigure/ApplicationModulesEndpointConfiguration.java +++ b/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/autoconfigure/ApplicationModulesEndpointConfiguration.java @@ -15,11 +15,18 @@ */ package org.springframework.modulith.actuator.autoconfigure; +import java.nio.charset.StandardCharsets; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.modulith.actuator.ApplicationModulesEndpoint; import org.springframework.modulith.runtime.ApplicationModulesRuntime; +import org.springframework.util.function.ThrowingSupplier; /** * Auto-configuration for the {@link ApplicationModulesEndpoint}. @@ -29,9 +36,24 @@ import org.springframework.modulith.runtime.ApplicationModulesRuntime; @AutoConfiguration class ApplicationModulesEndpointConfiguration { + static final String FILE_LOCATION = "META-INF/spring-modulith/application-modules.json"; + + private static final Resource PRECOMPUTED = new ClassPathResource(FILE_LOCATION); + private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationModulesEndpointConfiguration.class); + @Bean @ConditionalOnMissingBean ApplicationModulesEndpoint applicationModulesEndpoint(ApplicationModulesRuntime runtime) { - return new ApplicationModulesEndpoint(runtime); + + if (PRECOMPUTED.exists()) { + + ThrowingSupplier fileContent = () -> PRECOMPUTED.getContentAsString(StandardCharsets.UTF_8); + + LOGGER.debug("Using application modules description from {}", FILE_LOCATION); + return ApplicationModulesEndpoint.precomputed(fileContent); + + } else { + return ApplicationModulesEndpoint.ofApplicationModules(runtime); + } } } diff --git a/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/autoconfigure/ApplicationModulesFileGeneratingProcessor.java b/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/autoconfigure/ApplicationModulesFileGeneratingProcessor.java new file mode 100644 index 00000000..14a8be94 --- /dev/null +++ b/spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/autoconfigure/ApplicationModulesFileGeneratingProcessor.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 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.modulith.actuator.autoconfigure; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.modulith.core.util.ApplicationModulesExporter; +import org.springframework.modulith.runtime.ApplicationModulesRuntime; + +/** + * Renders the application module description JSON into a resource named + * {@value ApplicationModulesEndpointConfiguration#FILE_LOCATION}. + * + * @author Oliver Drotbohm + * @since 1.0.3 + */ +class ApplicationModulesFileGeneratingProcessor implements BeanFactoryInitializationAotProcessor { + + private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationModulesFileGeneratingProcessor.class); + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor#processAheadOfTime(org.springframework.beans.factory.config.ConfigurableListableBeanFactory) + */ + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + + return (context, __) -> { + + var runtime = beanFactory.getBean(ApplicationModulesRuntime.class); + var exporter = new ApplicationModulesExporter(runtime.get()); + var location = ApplicationModulesEndpointConfiguration.FILE_LOCATION; + + LOGGER.info("Generating application modules information to {}", location); + + context.getRuntimeHints().resources().registerPattern(location); + context.getGeneratedFiles().addResourceFile(location, exporter.toJson()); + }; + } +} diff --git a/spring-modulith-actuator/src/main/resources/META-INF/spring/aot.factories b/spring-modulith-actuator/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000..dea082aa --- /dev/null +++ b/spring-modulith-actuator/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\ + org.springframework.modulith.actuator.autoconfigure.ApplicationModulesFileGeneratingProcessor diff --git a/spring-modulith-actuator/src/test/java/org/springframework/modulith/actuator/ApplicationModulesEndpointIntegrationTests.java b/spring-modulith-actuator/src/test/java/org/springframework/modulith/actuator/ApplicationModulesEndpointIntegrationTests.java index 063a90cd..5eb9439d 100644 --- a/spring-modulith-actuator/src/test/java/org/springframework/modulith/actuator/ApplicationModulesEndpointIntegrationTests.java +++ b/spring-modulith-actuator/src/test/java/org/springframework/modulith/actuator/ApplicationModulesEndpointIntegrationTests.java @@ -35,7 +35,7 @@ class ApplicationModulesEndpointIntegrationTests { void exposesApplicationModulesAsMap() throws Exception { var modules = TestApplicationModules.of("example"); - var endpoint = new ApplicationModulesEndpoint(() -> modules); + var endpoint = ApplicationModulesEndpoint.ofApplicationModules(() -> modules); var result = endpoint.getApplicationModules(); var context = JsonPath.parse(result);