Skip to content

Commit 37f2bdb

Browse files
committed
GH-376 - 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.
1 parent 08e837f commit 37f2bdb

File tree

5 files changed

+112
-7
lines changed

5 files changed

+112
-7
lines changed

spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/ApplicationModulesEndpoint.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package org.springframework.modulith.actuator;
1717

18-
import java.util.Map;
1918
import java.util.function.Supplier;
2019

2120
import org.slf4j.Logger;
@@ -44,13 +43,39 @@ public class ApplicationModulesEndpoint {
4443
*
4544
* @param runtime must not be {@literal null}.
4645
*/
47-
public ApplicationModulesEndpoint(Supplier<ApplicationModules> runtime) {
48-
49-
Assert.notNull(runtime, "ModulesRuntime must not be null!");
46+
private ApplicationModulesEndpoint(Supplier<String> precomputed) {
5047

5148
LOGGER.debug("Activating Spring Modulith actuator.");
5249

53-
this.structure = SingletonSupplier.of(new ApplicationModulesExporter(runtime.get())::toJson);
50+
this.structure = SingletonSupplier.of(precomputed);
51+
}
52+
53+
/**
54+
* Creates a new {@link ApplicationModulesEndpoint} from the pre-computed actuator content
55+
*
56+
* @param precomputed must not be {@literal null}.
57+
* @return will never be {@literal null}.
58+
* @since 1.1
59+
*/
60+
public static ApplicationModulesEndpoint precomputed(Supplier<String> precomputed) {
61+
62+
Assert.notNull(precomputed, "Precomputed content must not be null!");
63+
64+
return new ApplicationModulesEndpoint(precomputed);
65+
}
66+
67+
/**
68+
* Creates a new {@link ApplicationModulesEndpoint} for the given lazily initialized {@link ApplicationModules}.
69+
*
70+
* @param modules must not be {@literal null}.
71+
* @return will never be {@literal null}.
72+
* @since 1.1
73+
*/
74+
public static ApplicationModulesEndpoint ofApplicationModules(Supplier<ApplicationModules> modules) {
75+
76+
Assert.notNull(modules, "ApplicationModules must not be null!");
77+
78+
return new ApplicationModulesEndpoint(() -> new ApplicationModulesExporter(modules.get()).toJson());
5479
}
5580

5681
/**

spring-modulith-actuator/src/main/java/org/springframework/modulith/actuator/autoconfigure/ApplicationModulesEndpointConfiguration.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,18 @@
1515
*/
1616
package org.springframework.modulith.actuator.autoconfigure;
1717

18+
import java.nio.charset.StandardCharsets;
19+
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
1822
import org.springframework.boot.autoconfigure.AutoConfiguration;
1923
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2024
import org.springframework.context.annotation.Bean;
25+
import org.springframework.core.io.ClassPathResource;
26+
import org.springframework.core.io.Resource;
2127
import org.springframework.modulith.actuator.ApplicationModulesEndpoint;
2228
import org.springframework.modulith.runtime.ApplicationModulesRuntime;
29+
import org.springframework.util.function.ThrowingSupplier;
2330

2431
/**
2532
* Auto-configuration for the {@link ApplicationModulesEndpoint}.
@@ -29,9 +36,24 @@
2936
@AutoConfiguration
3037
class ApplicationModulesEndpointConfiguration {
3138

39+
static final String FILE_LOCATION = "META-INF/spring-modulith/application-modules.json";
40+
41+
private static final Resource PRECOMPUTED = new ClassPathResource(FILE_LOCATION);
42+
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationModulesEndpointConfiguration.class);
43+
3244
@Bean
3345
@ConditionalOnMissingBean
3446
ApplicationModulesEndpoint applicationModulesEndpoint(ApplicationModulesRuntime runtime) {
35-
return new ApplicationModulesEndpoint(runtime);
47+
48+
if (PRECOMPUTED.exists()) {
49+
50+
ThrowingSupplier<String> fileContent = () -> PRECOMPUTED.getContentAsString(StandardCharsets.UTF_8);
51+
52+
LOGGER.debug("Using application modules description from {}", FILE_LOCATION);
53+
return ApplicationModulesEndpoint.precomputed(fileContent);
54+
55+
} else {
56+
return ApplicationModulesEndpoint.ofApplicationModules(runtime);
57+
}
3658
}
3759
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.modulith.actuator.autoconfigure;
17+
18+
import org.slf4j.Logger;
19+
import org.slf4j.LoggerFactory;
20+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
21+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
22+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
23+
import org.springframework.modulith.core.util.ApplicationModulesExporter;
24+
import org.springframework.modulith.runtime.ApplicationModulesRuntime;
25+
26+
/**
27+
* Renders the application module description JSON into a resource named
28+
* {@value ApplicationModulesEndpointConfiguration#FILE_LOCATION}.
29+
*
30+
* @author Oliver Drotbohm
31+
* @since 1.1
32+
*/
33+
class ApplicationModulesFileGeneratingProcessor implements BeanFactoryInitializationAotProcessor {
34+
35+
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationModulesFileGeneratingProcessor.class);
36+
37+
/*
38+
* (non-Javadoc)
39+
* @see org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor#processAheadOfTime(org.springframework.beans.factory.config.ConfigurableListableBeanFactory)
40+
*/
41+
@Override
42+
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
43+
44+
return (context, __) -> {
45+
46+
var runtime = beanFactory.getBean(ApplicationModulesRuntime.class);
47+
var exporter = new ApplicationModulesExporter(runtime.get());
48+
var location = ApplicationModulesEndpointConfiguration.FILE_LOCATION;
49+
50+
LOGGER.info("Generating application modules information to {}", location);
51+
52+
context.getRuntimeHints().resources().registerPattern(location);
53+
context.getGeneratedFiles().addResourceFile(location, exporter.toJson());
54+
};
55+
}
56+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
2+
org.springframework.modulith.actuator.autoconfigure.ApplicationModulesFileGeneratingProcessor

spring-modulith-actuator/src/test/java/org/springframework/modulith/actuator/ApplicationModulesEndpointIntegrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class ApplicationModulesEndpointIntegrationTests {
3535
void exposesApplicationModulesAsMap() throws Exception {
3636

3737
var modules = TestApplicationModules.of("example");
38-
var endpoint = new ApplicationModulesEndpoint(() -> modules);
38+
var endpoint = ApplicationModulesEndpoint.ofApplicationModules(() -> modules);
3939
var result = endpoint.getApplicationModules();
4040
var context = JsonPath.parse(result);
4141

0 commit comments

Comments
 (0)