GH-378 - Avoid initializing ApplicationModules for actuators on native images.

We now register a BeanFactoryInitializationAotProcessor to generate the actuator endpoint content representing the application module structure at AOT processing time. That file is then preferred over bootstrapping an ApplicationModules instance when bootstrapping the endpoint.
This commit is contained in:
Oliver Drotbohm
2023-11-17 16:30:16 +01:00
parent 5435fd9beb
commit 4c67ba1e5f
5 changed files with 112 additions and 7 deletions

View File

@@ -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<ApplicationModules> runtime) {
Assert.notNull(runtime, "ModulesRuntime must not be null!");
private ApplicationModulesEndpoint(Supplier<String> 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<String> 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<ApplicationModules> modules) {
Assert.notNull(modules, "ApplicationModules must not be null!");
return new ApplicationModulesEndpoint(() -> new ApplicationModulesExporter(modules.get()).toJson());
}
/**

View File

@@ -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<String> fileContent = () -> PRECOMPUTED.getContentAsString(StandardCharsets.UTF_8);
LOGGER.debug("Using application modules description from {}", FILE_LOCATION);
return ApplicationModulesEndpoint.precomputed(fileContent);
} else {
return ApplicationModulesEndpoint.ofApplicationModules(runtime);
}
}
}

View File

@@ -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());
};
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
org.springframework.modulith.actuator.autoconfigure.ApplicationModulesFileGeneratingProcessor

View File

@@ -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);