diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleIdentifiers.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleIdentifiers.java new file mode 100644 index 00000000..9a3a9185 --- /dev/null +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleIdentifiers.java @@ -0,0 +1,88 @@ +/* + * Copyright 2025 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.core; + +import java.util.Iterator; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.springframework.util.Assert; + +/** + * {@link ApplicationModuleIdentifier}s that allow iteration in the order provided by the sources. + * + * @author Oliver Drotbohm + * @since 1.4 + */ +public class ApplicationModuleIdentifiers implements Iterable { + + private final Supplier> source; + + /** + * Creates a new {@link ApplicationModuleIdentifiers} instance for the given source. + * + * @param source must not be {@literal null}. + */ + private ApplicationModuleIdentifiers(Supplier> source) { + + Assert.notNull(source, "Source must not be null!"); + + this.source = source; + } + + /** + * Creates a new {@link ApplicationModuleIdentifiers} from the given ApplicationModules. + * + * @param modules must not be {@literal null}. + * @return will never be {@literal null}. + */ + public static ApplicationModuleIdentifiers of(ApplicationModules modules) { + + Assert.notNull(modules, "ApplicationModules must not be null!"); + + return new ApplicationModuleIdentifiers(() -> modules.stream() + .map(ApplicationModule::getIdentifier)); + } + + /** + * Creates a new {@link ApplicationModuleIdentifiers} for the given source identifiers. + * + * @param identifiers will never be {@literal null}. + * @return will never be {@literal null}. + */ + public static ApplicationModuleIdentifiers of(List identifiers) { + return new ApplicationModuleIdentifiers(() -> identifiers.stream()); + } + + /** + * Creates a new {@link Stream} of {@link ApplicationModuleIdentifier}s. + * + * @return will never be {@literal null}. + */ + public Stream stream() { + return source.get(); + } + + /* + * (non-Javadoc) + * @see java.lang.Iterable#iterator() + */ + @Override + public Iterator iterator() { + return stream().iterator(); + } +} diff --git a/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/ApplicationModuleMetadata.java b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/ApplicationModuleMetadata.java new file mode 100644 index 00000000..b5f7c1ae --- /dev/null +++ b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/ApplicationModuleMetadata.java @@ -0,0 +1,139 @@ +/* + * Copyright 2025 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.runtime.autoconfigure; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.Resource; +import org.springframework.modulith.core.ApplicationModuleIdentifier; +import org.springframework.util.Assert; + +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; + +/** + * An abstraction for the data captured in the application module metadata file (typically + * {@value org.springframework.modulith.core.util.ApplicationModulesExporter#DEFAULT_LOCATION}). + * + * @author Oliver Drotbohm + * @since 1.4 + * @see org.springframework.modulith.core.util.ApplicationModulesExporter#DEFAULT_LOCATION + */ +class ApplicationModuleMetadata { + + private static final ApplicationModuleMetadata NONE = new ApplicationModuleMetadata(); + + private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationModuleMetadata.class); + + /** + * Creates a new {@link ApplicationModuleMetadata} for the given {@link Resource}. + * + * @param resource must not be {@literal null}. + * @return will never be {@literal null}. + */ + public static ApplicationModuleMetadata of(Resource resource) { + + Assert.notNull(resource, "Resource must not be null!"); + + if (!resource.exists()) { + LOGGER.debug("Did not find application module metadata in {}.", resource.getDescription()); + return NONE; + } + + return new ResourceBasedApplicationModuleMetadata(resource); + } + + /** + * Returns whether the metadata is present at all. + */ + public boolean isPresent() { + return false; + } + + /** + * Returns all {@link ApplicationModuleIdentifier}s. + * + * @return will never be {@literal null}. + */ + public List getIdentifiers() { + return Collections.emptyList(); + } + + /** + * Returns the names of the types registered as {@link org.springframework.modulith.ApplicationModuleInitializer}. + * + * @return will never be {@literal null}. + */ + public List getInitializerTypeNames() { + return Collections.emptyList(); + } + + private static class ResourceBasedApplicationModuleMetadata extends ApplicationModuleMetadata { + + private final DocumentContext document; + + public ResourceBasedApplicationModuleMetadata(Resource metadata) { + + var description = metadata.getDescription(); + + Assert.isTrue(metadata.exists(), () -> "Resource %s does not exist!".formatted(description)); + + LOGGER.debug("Using application module metadata located in {}.", description); + + try { + this.document = JsonPath.parse(metadata.getFile()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.modulith.runtime.autoconfigure.ApplicationModuleMetadata#isPresent() + */ + @Override + public boolean isPresent() { + return true; + } + + /* + * (non-Javadoc) + * @see org.springframework.modulith.runtime.autoconfigure.ApplicationModuleMetadata#getIdentifiers() + */ + @Override + public List getIdentifiers() { + + return document.> read("$.keys()").stream() + .map(ApplicationModuleIdentifier::of) + .toList(); + } + + /* + * (non-Javadoc) + * @see org.springframework.modulith.runtime.autoconfigure.ApplicationModuleMetadata#getInitializerTypeNames() + */ + @Override + public List getInitializerTypeNames() { + return document.> read("$..initializers[*]"); + } + } +} diff --git a/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/DefaultApplicationModuleInitializerInvoker.java b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/DefaultApplicationModuleInitializerInvoker.java index e4fbfb51..b85724ae 100644 --- a/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/DefaultApplicationModuleInitializerInvoker.java +++ b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/DefaultApplicationModuleInitializerInvoker.java @@ -15,7 +15,6 @@ */ package org.springframework.modulith.runtime.autoconfigure; -import java.util.function.Supplier; import java.util.stream.Stream; import org.springframework.modulith.ApplicationModuleInitializer; @@ -26,12 +25,12 @@ import org.springframework.modulith.core.ApplicationModules; */ class DefaultApplicationModuleInitializerInvoker implements ApplicationModuleInitializerInvoker { - private final Supplier modules; + private final ApplicationModules modules; /** * @param modules */ - DefaultApplicationModuleInitializerInvoker(Supplier modules) { + DefaultApplicationModuleInitializerInvoker(ApplicationModules modules) { this.modules = modules; } @@ -43,8 +42,6 @@ class DefaultApplicationModuleInitializerInvoker implements ApplicationModuleIni @Override public void invokeInitializers(Stream initializers) { - var modules = this.modules.get(); - initializers .sorted(modules.getComparator()) // .map(it -> LoggingApplicationModuleInitializerAdapter.of(it, modules)) diff --git a/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/LoggingApplicationModuleInitializerAdapter.java b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/LoggingApplicationModuleInitializerAdapter.java index 6bce4f5c..a00dfe71 100644 --- a/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/LoggingApplicationModuleInitializerAdapter.java +++ b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/LoggingApplicationModuleInitializerAdapter.java @@ -52,7 +52,7 @@ class LoggingApplicationModuleInitializerAdapter implements ApplicationModuleIni public static ApplicationModuleInitializer of(ApplicationModuleInitializer initializer, ApplicationModules modules) { - if (!LoggerFactory.getLogger(initializer.getClass()).isDebugEnabled()) { + if (!LOGGER.isDebugEnabled()) { return initializer; } @@ -68,7 +68,7 @@ class LoggingApplicationModuleInitializerAdapter implements ApplicationModuleIni public static ApplicationModuleInitializer of(ApplicationModuleInitializer initializer) { - if (!LoggerFactory.getLogger(initializer.getClass()).isDebugEnabled()) { + if (!LOGGER.isDebugEnabled()) { return initializer; } diff --git a/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/MissingRuntimeDependencyFailureAnalyzer.java b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/MissingRuntimeDependencyFailureAnalyzer.java index b412352b..501806f8 100644 --- a/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/MissingRuntimeDependencyFailureAnalyzer.java +++ b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/MissingRuntimeDependencyFailureAnalyzer.java @@ -17,7 +17,6 @@ package org.springframework.modulith.runtime.autoconfigure; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; -import org.springframework.boot.diagnostics.FailureAnalyzer; /** * {@link FailureAnalyzer} for {@link MissingRuntimeDependency}. diff --git a/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/PrecomputedApplicationModuleInitializerInvoker.java b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/PrecomputedApplicationModuleInitializerInvoker.java index 7f42c095..5abc2182 100644 --- a/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/PrecomputedApplicationModuleInitializerInvoker.java +++ b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/PrecomputedApplicationModuleInitializerInvoker.java @@ -15,35 +15,24 @@ */ package org.springframework.modulith.runtime.autoconfigure; -import java.io.IOException; -import java.io.UncheckedIOException; import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.springframework.core.io.Resource; import org.springframework.modulith.ApplicationModuleInitializer; import org.springframework.util.Assert; -import com.jayway.jsonpath.JsonPath; - class PrecomputedApplicationModuleInitializerInvoker implements ApplicationModuleInitializerInvoker { - private List plan; + private final List initializerTypeNames; - public PrecomputedApplicationModuleInitializerInvoker(Resource metadata) { + public PrecomputedApplicationModuleInitializerInvoker(ApplicationModuleMetadata metadata) { - Assert.isTrue(metadata.exists(), () -> "Resource %s does not exist!".formatted(metadata.getDescription())); + Assert.isTrue(metadata.isPresent(), "ApplicationModuleMetadata not present!"); - try (var stream = metadata.getInputStream()) { - - this.plan = JsonPath.parse(stream).> read("$..initializers[*]"); - - } catch (IOException e) { - throw new UncheckedIOException(e); - } + this.initializerTypeNames = metadata.getInitializerTypeNames(); } /* @@ -56,11 +45,17 @@ class PrecomputedApplicationModuleInitializerInvoker implements ApplicationModul var map = initializers .collect(Collectors.toMap(it -> it.getClass().getName(), Function.identity())); - plan.stream() - .map(map::get) + triggerInitialization(initializerTypeNames.stream() + .map(map::remove) .map(Optional::ofNullable) - .flatMap(Optional::stream) - .map(LoggingApplicationModuleInitializerAdapter::of) + .flatMap(Optional::stream)); + + triggerInitialization(map.values().stream()); + } + + private void triggerInitialization(Stream initializers) { + + initializers.map(LoggingApplicationModuleInitializerAdapter::of) .forEach(ApplicationModuleInitializer::initialize); } } diff --git a/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/SpringModulithRuntimeAutoConfiguration.java b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/SpringModulithRuntimeAutoConfiguration.java index e6c378bc..b6e1396a 100644 --- a/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/SpringModulithRuntimeAutoConfiguration.java +++ b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/SpringModulithRuntimeAutoConfiguration.java @@ -36,6 +36,7 @@ import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.modulith.ApplicationModuleInitializer; import org.springframework.modulith.core.ApplicationModule; +import org.springframework.modulith.core.ApplicationModuleIdentifiers; import org.springframework.modulith.core.ApplicationModules; import org.springframework.modulith.core.ApplicationModulesFactory; import org.springframework.modulith.core.util.ApplicationModulesExporter; @@ -83,16 +84,39 @@ class SpringModulithRuntimeAutoConfiguration { return __ -> invoker.invokeInitializers(initializers.stream()); } + /** + * {@link ApplicationModuleMetadata} obtained from the Spring Modulith metadata located at + * {@value ApplicationModulesExporter#DEFAULT_LOCATION}. + * + * @param metadata will never be {@literal null}. + * @return will never be {@literal null}. + */ + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + static ApplicationModuleMetadata applicationModuleMetadata( + @Value("classpath:" + ApplicationModulesExporter.DEFAULT_LOCATION) Resource metadata) { + return ApplicationModuleMetadata.of(metadata); + } + @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) @ConditionalOnBean(ApplicationModuleInitializer.class) - static ApplicationModuleInitializerInvoker applicationModuleInitializerInvoker( - @Value("classpath:" + ApplicationModulesExporter.DEFAULT_LOCATION) Resource metadata, + static ApplicationModuleInitializerInvoker applicationModuleInitializerInvoker(ApplicationModuleMetadata metadata, ObjectProvider runtime) { - return metadata.exists() + return metadata.isPresent() ? new PrecomputedApplicationModuleInitializerInvoker(metadata) - : new DefaultApplicationModuleInitializerInvoker(runtime.getIfAvailable()); + : new DefaultApplicationModuleInitializerInvoker(runtime.getObject().get()); + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + ApplicationModuleIdentifiers applicationModuleIdentifiers(ApplicationModuleMetadata metadata, + ObjectProvider runtime) { + + return metadata.isPresent() + ? ApplicationModuleIdentifiers.of(metadata.getIdentifiers()) + : ApplicationModuleIdentifiers.of(runtime.getObject().get()); } private static class ApplicationModulesBootstrap { diff --git a/spring-modulith-runtime/src/test/java/example/moduleA/ModuleAType.java b/spring-modulith-runtime/src/test/java/example/moduleA/ModuleAType.java index b8dcedfd..ce4ae804 100644 --- a/spring-modulith-runtime/src/test/java/example/moduleA/ModuleAType.java +++ b/spring-modulith-runtime/src/test/java/example/moduleA/ModuleAType.java @@ -15,7 +15,19 @@ */ package example.moduleA; +import java.time.LocalDateTime; + +import org.springframework.modulith.ApplicationModuleInitializer; + /** * @author Oliver Drotbohm */ -public class ModuleAType {} +public class ModuleAType implements ApplicationModuleInitializer { + + public LocalDateTime initialized; + + @Override + public void initialize() { + this.initialized = LocalDateTime.now(); + } +} diff --git a/spring-modulith-runtime/src/test/java/org/springframework/modulith/runtime/autoconfigure/PrecomputedApplicationModuleInitializerInvokerUnitTests.java b/spring-modulith-runtime/src/test/java/org/springframework/modulith/runtime/autoconfigure/PrecomputedApplicationModuleInitializerInvokerUnitTests.java index 00f32284..88e13a2b 100644 --- a/spring-modulith-runtime/src/test/java/org/springframework/modulith/runtime/autoconfigure/PrecomputedApplicationModuleInitializerInvokerUnitTests.java +++ b/spring-modulith-runtime/src/test/java/org/springframework/modulith/runtime/autoconfigure/PrecomputedApplicationModuleInitializerInvokerUnitTests.java @@ -24,6 +24,7 @@ import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.modulith.ApplicationModuleInitializer; +import org.springframework.modulith.core.util.ApplicationModulesExporter; /** * @author Oliver Drotbohm @@ -33,8 +34,10 @@ class PrecomputedApplicationModuleInitializerInvokerUnitTests { @Test void ordersInitializersBasedOnPrecomputedMetadata() throws IOException { - var resource = new ClassPathResource("application-modules.json"); - var invoker = new PrecomputedApplicationModuleInitializerInvoker(resource); + var resource = new ClassPathResource(ApplicationModulesExporter.DEFAULT_LOCATION); + var metadata = ApplicationModuleMetadata.of(resource); + + var invoker = new PrecomputedApplicationModuleInitializerInvoker(metadata); var second = new SecondApplicationModuleInitializer(); var first = new FirstApplicationModuleInitializer(); diff --git a/spring-modulith-runtime/src/test/java/org/springframework/modulith/runtime/autoconfigure/SpringModulithRuntimeAutoConfigurationIntegrationTests.java b/spring-modulith-runtime/src/test/java/org/springframework/modulith/runtime/autoconfigure/SpringModulithRuntimeAutoConfigurationIntegrationTests.java index 8cbd1414..d6131615 100644 --- a/spring-modulith-runtime/src/test/java/org/springframework/modulith/runtime/autoconfigure/SpringModulithRuntimeAutoConfigurationIntegrationTests.java +++ b/spring-modulith-runtime/src/test/java/org/springframework/modulith/runtime/autoconfigure/SpringModulithRuntimeAutoConfigurationIntegrationTests.java @@ -18,6 +18,9 @@ package org.springframework.modulith.runtime.autoconfigure; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import example.moduleA.ModuleAType; + +import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; @@ -34,7 +37,11 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.core.io.ClassPathResource; import org.springframework.modulith.ApplicationModuleInitializer; +import org.springframework.modulith.core.ApplicationModuleIdentifier; +import org.springframework.modulith.core.ApplicationModuleIdentifiers; +import org.springframework.modulith.core.util.ApplicationModulesExporter; import org.springframework.modulith.runtime.ApplicationModulesRuntime; import org.springframework.modulith.runtime.ApplicationRuntime; @@ -51,12 +58,19 @@ class SpringModulithRuntimeAutoConfigurationIntegrationTests { @SpringBootApplication static class SampleApp {} + InitializationTracker tracker = new InitializationTracker(); + + ApplicationContextRunner runner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SpringModulithRuntimeAutoConfiguration.class)) + .withBean(InitializationTracker.class, () -> tracker); + + ClassLoader withoutMetadata = new FilteredClassLoader( + new ClassPathResource(ApplicationModulesExporter.DEFAULT_LOCATION)); + @Test // GH-87 void bootstrapRegistersRuntimeInstances() { - new ApplicationContextRunner() - .withUserConfiguration(SampleApp.class) - .withConfiguration(AutoConfigurations.of(SpringModulithRuntimeAutoConfiguration.class)) + runner.withUserConfiguration(SampleApp.class) .run(context -> { assertThat(context.getBean(ApplicationRuntime.class)).isNotNull(); assertThat(context.getBean(ApplicationModulesRuntime.class)).isNotNull(); @@ -66,9 +80,7 @@ class SpringModulithRuntimeAutoConfigurationIntegrationTests { @Test // GH-160 void missingArchUnitRuntimeDependencyEscalatesOnContextStartup() { - new ApplicationContextRunner() - .withUserConfiguration(SampleApp.class) - .withConfiguration(AutoConfigurations.of(SpringModulithRuntimeAutoConfiguration.class)) + runner.withUserConfiguration(SampleApp.class) .withClassLoader(new FilteredClassLoader(ClassFileImporter.class)) .run(context -> { assertThat(context).hasFailed(); @@ -79,29 +91,72 @@ class SpringModulithRuntimeAutoConfigurationIntegrationTests { } @Test // GH-375 - void registersInitializingListenerIfInitializersPresent() { + void registersIntializerInvokerIfInitializersPresent() { - var tracker = new InitializationTracker(); + var unlisted = new ModuleAType(); + var listed = new SampleInitializer(); - var beanName = "applicationModuleInitializingListener"; - var runner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(SpringModulithRuntimeAutoConfiguration.class)) - .withBean(InitializationTracker.class, () -> tracker); + var runner = this.runner.withUserConfiguration(SampleApp.class) + .withBean(ModuleAType.class, () -> unlisted) + .withBean(SampleInitializer.class, () -> listed); - // No initializer -> no listener runner.run(context -> { - assertThat(context).doesNotHaveBean(beanName); + assertThat(context).hasSingleBean(PrecomputedApplicationModuleInitializerInvoker.class); + + context.publishEvent(mock(ApplicationStartedEvent.class)); + + // Initializer invoked + assertThat(unlisted.initialized).isNotNull(); + assertThat(listed.initialized).isNotNull(); + assertThat(listed.initialized).isBefore(unlisted.initialized); + tracker.assertBeanNotInitialized(ApplicationModulesRuntime.class); }); - // Initializer -> listener - runner.withBean(ApplicationModuleInitializer.class, () -> () -> {}) - .withBean(DummyApplication.class) + runner.withClassLoader(withoutMetadata) .run(context -> { + assertThat(context).hasSingleBean(DefaultApplicationModuleInitializerInvoker.class); + context.publishEvent(mock(ApplicationStartedEvent.class)); - assertThat(context).hasBean(beanName); + + tracker.assertBeanInitialized(ApplicationModulesRuntime.class); + }); + } + + @Test // GH-375 + void doesNotRegisterIntializerInvokerIfInitializersPresent() { + + runner.run(context -> { + + assertThat(context).doesNotHaveBean(ApplicationModuleInitializerInvoker.class); + + tracker.assertBeanNotInitialized(ApplicationModulesRuntime.class); + }); + } + + @Test // GH-1066 + void registersApplicationModuleIdentifiersWithoutInstantiatingApplicationModulesIfMetadataPresent() { + + runner.run(context -> { + + tracker.assertBeanNotInitialized(ApplicationModulesRuntime.class); + assertThat(context).hasSingleBean(ApplicationModuleIdentifiers.class); + + assertThat(context.getBean(ApplicationModuleIdentifiers.class)) + .extracting(ApplicationModuleIdentifier::toString) + .containsExactly("a", "b", "c"); + }); + + runner.withUserConfiguration(SampleApp.class) + .withClassLoader(withoutMetadata) + .run(context -> { + + tracker.assertBeanInitialized(ApplicationModulesRuntime.class); + assertThat(context).hasSingleBean(ApplicationModuleIdentifiers.class); + + assertThat(context.getBean(ApplicationModuleIdentifiers.class)).isEmpty(); }); } @@ -144,6 +199,13 @@ class SpringModulithRuntimeAutoConfigurationIntegrationTests { } } - @SpringBootApplication - static class DummyApplication {} + static class SampleInitializer implements ApplicationModuleInitializer { + + LocalDateTime initialized; + + @Override + public void initialize() { + this.initialized = LocalDateTime.now(); + } + } } diff --git a/spring-modulith-runtime/src/test/resources/application-modules.json b/spring-modulith-runtime/src/test/resources/META-INF/spring-modulith/application-modules.json similarity index 69% rename from spring-modulith-runtime/src/test/resources/application-modules.json rename to spring-modulith-runtime/src/test/resources/META-INF/spring-modulith/application-modules.json index 2d00a237..4483a27b 100644 --- a/spring-modulith-runtime/src/test/resources/application-modules.json +++ b/spring-modulith-runtime/src/test/resources/META-INF/spring-modulith/application-modules.json @@ -4,5 +4,8 @@ }, "b" : { "initializers" : [ "org.springframework.modulith.runtime.autoconfigure.PrecomputedApplicationModuleInitializerInvokerUnitTests$SecondApplicationModuleInitializer" ] + }, + "c" : { + "initializers" : [ "org.springframework.modulith.runtime.autoconfigure.SpringModulithRuntimeAutoConfigurationIntegrationTests$SampleInitializer" ] } }