Skip to content

Commit 417e1c1

Browse files
committed
GH-1066 - Provide abstraction to access ApplicationModuleIdentifiers in order of module dependency.
Introduce ApplicationModuleIdentifiers as abstraction for an ordered collection of application module identifiers. Introduced ApplicationModuleMetadata as abstraction for the generated metadata (usually located in META-INF/spring-modulith/application-modules.json) to expose the information we currently need to downstream infrastructure components. Migrated the components introduced to execute ApplicationModuleInitializers to AMA and adapt auto-configuration accordingly.
1 parent 7db3897 commit 417e1c1

File tree

11 files changed

+376
-54
lines changed

11 files changed

+376
-54
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2025 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.core;
17+
18+
import java.util.Iterator;
19+
import java.util.List;
20+
import java.util.function.Supplier;
21+
import java.util.stream.Stream;
22+
23+
import org.springframework.util.Assert;
24+
25+
/**
26+
* {@link ApplicationModuleIdentifier}s that allow iteration in the order provided by the sources.
27+
*
28+
* @author Oliver Drotbohm
29+
* @since 1.4
30+
*/
31+
public class ApplicationModuleIdentifiers implements Iterable<ApplicationModuleIdentifier> {
32+
33+
private final Supplier<Stream<ApplicationModuleIdentifier>> source;
34+
35+
/**
36+
* Creates a new {@link ApplicationModuleIdentifiers} instance for the given source.
37+
*
38+
* @param source must not be {@literal null}.
39+
*/
40+
private ApplicationModuleIdentifiers(Supplier<Stream<ApplicationModuleIdentifier>> source) {
41+
42+
Assert.notNull(source, "Source must not be null!");
43+
44+
this.source = source;
45+
}
46+
47+
/**
48+
* Creates a new {@link ApplicationModuleIdentifiers} from the given ApplicationModules.
49+
*
50+
* @param modules must not be {@literal null}.
51+
* @return will never be {@literal null}.
52+
*/
53+
public static ApplicationModuleIdentifiers of(ApplicationModules modules) {
54+
55+
Assert.notNull(modules, "ApplicationModules must not be null!");
56+
57+
return new ApplicationModuleIdentifiers(() -> modules.stream()
58+
.map(ApplicationModule::getIdentifier));
59+
}
60+
61+
/**
62+
* Creates a new {@link ApplicationModuleIdentifiers} for the given source identifiers.
63+
*
64+
* @param identifiers will never be {@literal null}.
65+
* @return will never be {@literal null}.
66+
*/
67+
public static ApplicationModuleIdentifiers of(List<ApplicationModuleIdentifier> identifiers) {
68+
return new ApplicationModuleIdentifiers(() -> identifiers.stream());
69+
}
70+
71+
/**
72+
* Creates a new {@link Stream} of {@link ApplicationModuleIdentifier}s.
73+
*
74+
* @return will never be {@literal null}.
75+
*/
76+
public Stream<ApplicationModuleIdentifier> stream() {
77+
return source.get();
78+
}
79+
80+
/*
81+
* (non-Javadoc)
82+
* @see java.lang.Iterable#iterator()
83+
*/
84+
@Override
85+
public Iterator<ApplicationModuleIdentifier> iterator() {
86+
return stream().iterator();
87+
}
88+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright 2025 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.runtime.autoconfigure;
17+
18+
import java.io.IOException;
19+
import java.io.UncheckedIOException;
20+
import java.util.Collection;
21+
import java.util.Collections;
22+
import java.util.List;
23+
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
import org.springframework.core.io.Resource;
27+
import org.springframework.modulith.core.ApplicationModuleIdentifier;
28+
import org.springframework.util.Assert;
29+
30+
import com.jayway.jsonpath.DocumentContext;
31+
import com.jayway.jsonpath.JsonPath;
32+
33+
/**
34+
* An abstraction for the data captured in the application module metadata file (typically
35+
* {@value org.springframework.modulith.core.util.ApplicationModulesExporter#DEFAULT_LOCATION}).
36+
*
37+
* @author Oliver Drotbohm
38+
* @since 1.4
39+
* @see org.springframework.modulith.core.util.ApplicationModulesExporter#DEFAULT_LOCATION
40+
*/
41+
class ApplicationModuleMetadata {
42+
43+
private static final ApplicationModuleMetadata NONE = new ApplicationModuleMetadata();
44+
45+
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationModuleMetadata.class);
46+
47+
/**
48+
* Creates a new {@link ApplicationModuleMetadata} for the given {@link Resource}.
49+
*
50+
* @param resource must not be {@literal null}.
51+
* @return will never be {@literal null}.
52+
*/
53+
public static ApplicationModuleMetadata of(Resource resource) {
54+
55+
Assert.notNull(resource, "Resource must not be null!");
56+
57+
if (!resource.exists()) {
58+
LOGGER.debug("Did not find application module metadata in {}.", resource.getDescription());
59+
return NONE;
60+
}
61+
62+
return new ResourceBasedApplicationModuleMetadata(resource);
63+
}
64+
65+
/**
66+
* Returns whether the metadata is present at all.
67+
*/
68+
public boolean isPresent() {
69+
return false;
70+
}
71+
72+
/**
73+
* Returns all {@link ApplicationModuleIdentifier}s.
74+
*
75+
* @return will never be {@literal null}.
76+
*/
77+
public List<ApplicationModuleIdentifier> getIdentifiers() {
78+
return Collections.emptyList();
79+
}
80+
81+
/**
82+
* Returns the names of the types registered as {@link org.springframework.modulith.ApplicationModuleInitializer}.
83+
*
84+
* @return will never be {@literal null}.
85+
*/
86+
public List<String> getInitializerTypeNames() {
87+
return Collections.emptyList();
88+
}
89+
90+
private static class ResourceBasedApplicationModuleMetadata extends ApplicationModuleMetadata {
91+
92+
private final DocumentContext document;
93+
94+
public ResourceBasedApplicationModuleMetadata(Resource metadata) {
95+
96+
var description = metadata.getDescription();
97+
98+
Assert.isTrue(metadata.exists(), () -> "Resource %s does not exist!".formatted(description));
99+
100+
LOGGER.debug("Using application module metadata located in {}.", description);
101+
102+
try {
103+
this.document = JsonPath.parse(metadata.getFile());
104+
} catch (IOException e) {
105+
throw new UncheckedIOException(e);
106+
}
107+
}
108+
109+
/*
110+
* (non-Javadoc)
111+
* @see org.springframework.modulith.runtime.autoconfigure.ApplicationModuleMetadata#isPresent()
112+
*/
113+
@Override
114+
public boolean isPresent() {
115+
return true;
116+
}
117+
118+
/*
119+
* (non-Javadoc)
120+
* @see org.springframework.modulith.runtime.autoconfigure.ApplicationModuleMetadata#getIdentifiers()
121+
*/
122+
@Override
123+
public List<ApplicationModuleIdentifier> getIdentifiers() {
124+
125+
return document.<Collection<String>> read("$.keys()").stream()
126+
.map(ApplicationModuleIdentifier::of)
127+
.toList();
128+
}
129+
130+
/*
131+
* (non-Javadoc)
132+
* @see org.springframework.modulith.runtime.autoconfigure.ApplicationModuleMetadata#getInitializerTypeNames()
133+
*/
134+
@Override
135+
public List<String> getInitializerTypeNames() {
136+
return document.<List<String>> read("$..initializers[*]");
137+
}
138+
}
139+
}

spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/DefaultApplicationModuleInitializerInvoker.java

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

18-
import java.util.function.Supplier;
1918
import java.util.stream.Stream;
2019

2120
import org.springframework.modulith.ApplicationModuleInitializer;
@@ -26,12 +25,12 @@
2625
*/
2726
class DefaultApplicationModuleInitializerInvoker implements ApplicationModuleInitializerInvoker {
2827

29-
private final Supplier<ApplicationModules> modules;
28+
private final ApplicationModules modules;
3029

3130
/**
3231
* @param modules
3332
*/
34-
DefaultApplicationModuleInitializerInvoker(Supplier<ApplicationModules> modules) {
33+
DefaultApplicationModuleInitializerInvoker(ApplicationModules modules) {
3534
this.modules = modules;
3635
}
3736

@@ -43,8 +42,6 @@ class DefaultApplicationModuleInitializerInvoker implements ApplicationModuleIni
4342
@Override
4443
public void invokeInitializers(Stream<ApplicationModuleInitializer> initializers) {
4544

46-
var modules = this.modules.get();
47-
4845
initializers
4946
.sorted(modules.getComparator()) //
5047
.map(it -> LoggingApplicationModuleInitializerAdapter.of(it, modules))

spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/LoggingApplicationModuleInitializerAdapter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ private LoggingApplicationModuleInitializerAdapter(ApplicationModuleInitializer
5252
public static ApplicationModuleInitializer of(ApplicationModuleInitializer initializer,
5353
ApplicationModules modules) {
5454

55-
if (!LoggerFactory.getLogger(initializer.getClass()).isDebugEnabled()) {
55+
if (!LOGGER.isDebugEnabled()) {
5656
return initializer;
5757
}
5858

@@ -68,7 +68,7 @@ public static ApplicationModuleInitializer of(ApplicationModuleInitializer initi
6868

6969
public static ApplicationModuleInitializer of(ApplicationModuleInitializer initializer) {
7070

71-
if (!LoggerFactory.getLogger(initializer.getClass()).isDebugEnabled()) {
71+
if (!LOGGER.isDebugEnabled()) {
7272
return initializer;
7373
}
7474

spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/MissingRuntimeDependencyFailureAnalyzer.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
1919
import org.springframework.boot.diagnostics.FailureAnalysis;
20-
import org.springframework.boot.diagnostics.FailureAnalyzer;
2120

2221
/**
2322
* {@link FailureAnalyzer} for {@link MissingRuntimeDependency}.

spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/PrecomputedApplicationModuleInitializerInvoker.java

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,24 @@
1515
*/
1616
package org.springframework.modulith.runtime.autoconfigure;
1717

18-
import java.io.IOException;
19-
import java.io.UncheckedIOException;
2018
import java.util.List;
2119
import java.util.Optional;
2220
import java.util.function.Function;
2321
import java.util.stream.Collectors;
2422
import java.util.stream.Stream;
2523

26-
import org.springframework.core.io.Resource;
2724
import org.springframework.modulith.ApplicationModuleInitializer;
2825
import org.springframework.util.Assert;
2926

30-
import com.jayway.jsonpath.JsonPath;
31-
3227
class PrecomputedApplicationModuleInitializerInvoker implements ApplicationModuleInitializerInvoker {
3328

34-
private List<String> plan;
35-
36-
public PrecomputedApplicationModuleInitializerInvoker(Resource metadata) {
29+
private final List<String> initializerTypeNames;
3730

38-
Assert.isTrue(metadata.exists(), () -> "Resource %s does not exist!".formatted(metadata.getDescription()));
31+
public PrecomputedApplicationModuleInitializerInvoker(ApplicationModuleMetadata metadata) {
3932

40-
try (var stream = metadata.getInputStream()) {
33+
Assert.isTrue(metadata.isPresent(), "ApplicationModuleMetadata not present!");
4134

42-
this.plan = JsonPath.parse(stream).<List<String>> read("$..initializers[*]");
43-
44-
} catch (IOException e) {
45-
throw new UncheckedIOException(e);
46-
}
35+
this.initializerTypeNames = metadata.getInitializerTypeNames();
4736
}
4837

4938
/*
@@ -56,11 +45,17 @@ public void invokeInitializers(Stream<ApplicationModuleInitializer> initializers
5645
var map = initializers
5746
.collect(Collectors.toMap(it -> it.getClass().getName(), Function.identity()));
5847

59-
plan.stream()
60-
.map(map::get)
48+
triggerInitialization(initializerTypeNames.stream()
49+
.map(map::remove)
6150
.map(Optional::ofNullable)
62-
.flatMap(Optional::stream)
63-
.map(LoggingApplicationModuleInitializerAdapter::of)
51+
.flatMap(Optional::stream));
52+
53+
triggerInitialization(map.values().stream());
54+
}
55+
56+
private void triggerInitialization(Stream<ApplicationModuleInitializer> initializers) {
57+
58+
initializers.map(LoggingApplicationModuleInitializerAdapter::of)
6459
.forEach(ApplicationModuleInitializer::initialize);
6560
}
6661
}

0 commit comments

Comments
 (0)