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:
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
|
||||
org.springframework.modulith.actuator.autoconfigure.ApplicationModulesFileGeneratingProcessor
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user