From d9aedb8ebf403389fa5734797841545532d8850e Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Thu, 5 Jan 2023 10:19:51 +0100 Subject: [PATCH] GH-103 - Introduce ApplicationModuleInitializer. Application modules can now declare instances of ApplicationModuleInititalizer to execute code upon an ApplicationStartedEvent. This is achieved by registering a corresponding ApplicationListener in SpringModulithRuntimeAutoConfiguration to invoke all instances of AMI sorted via the Comparator introduced in GH-102. To make sure that the invocation order follows topological order, the runtime module strongly depends on JGraphT. --- .../ApplicationModuleInitializer.java | 34 +++++++++++++ spring-modulith-runtime/pom.xml | 6 +++ ...pringModulithRuntimeAutoConfiguration.java | 51 +++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 spring-modulith-api/src/main/java/org/springframework/modulith/ApplicationModuleInitializer.java diff --git a/spring-modulith-api/src/main/java/org/springframework/modulith/ApplicationModuleInitializer.java b/spring-modulith-api/src/main/java/org/springframework/modulith/ApplicationModuleInitializer.java new file mode 100644 index 00000000..5e5e78a3 --- /dev/null +++ b/spring-modulith-api/src/main/java/org/springframework/modulith/ApplicationModuleInitializer.java @@ -0,0 +1,34 @@ +/* + * 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; + +import org.springframework.boot.context.event.ApplicationStartedEvent; + +/** + * An interface to be implemented by Spring components that are supposed to be initialized on + * {@link ApplicationStartedEvent}. They're {@link ApplicationModule} specific as they're executed in the order + * determined by the dependencies of a module. In other words, {@link ApplicationModuleInitializer} of fundamental + * application modules are executed first, followed by the ones that depend on it. + * + * @author Oliver Drotbohm + */ +public interface ApplicationModuleInitializer { + + /** + * Run business logic that's needed to initialize the {@link ApplicationModule}. + */ + void initialize(); +} diff --git a/spring-modulith-runtime/pom.xml b/spring-modulith-runtime/pom.xml index f9630e24..5365eed8 100644 --- a/spring-modulith-runtime/pom.xml +++ b/spring-modulith-runtime/pom.xml @@ -22,6 +22,12 @@ ${project.version} + + org.jgrapht + jgrapht-core + 1.4.0 + + org.springframework.boot spring-boot-autoconfigure 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 c34254b9..ad3d2ebf 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 @@ -15,20 +15,27 @@ */ package org.springframework.modulith.runtime.autoconfigure; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.Supplier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; +import org.springframework.modulith.ApplicationModuleInitializer; import org.springframework.modulith.model.ApplicationModule; import org.springframework.modulith.model.ApplicationModules; +import org.springframework.modulith.model.FormatableType; import org.springframework.modulith.runtime.ApplicationModulesRuntime; import org.springframework.modulith.runtime.ApplicationRuntime; +import org.springframework.util.ClassUtils; /** * Auto-configuration to register a {@link SpringBootApplicationRuntime} and {@link ApplicationModulesRuntime} as Spring @@ -57,6 +64,50 @@ class SpringModulithRuntimeAutoConfiguration { return new ApplicationModulesRuntime(toSupplier(modules), runtime); } + @Bean + ApplicationListener applicationModuleInitialzingListener(ApplicationModulesRuntime runtime, + List initializers) { + + return event -> { + + var modules = runtime.get(); + + initializers.stream() // + .sorted(modules.getComparator()) // + .map(it -> LOG.isDebugEnabled() ? new LoggingApplicationModuleInitializerAdapter(it, modules) : it) + .forEach(ApplicationModuleInitializer::initialize); + }; + } + + @Slf4j + @RequiredArgsConstructor + private static class LoggingApplicationModuleInitializerAdapter implements ApplicationModuleInitializer { + + private final ApplicationModuleInitializer delegate; + private final ApplicationModules modules; + + /* + * (non-Javadoc) + * @see org.springframework.modulith.ApplicationModuleInitializer#initialize() + */ + @Override + public void initialize() { + + var listenerType = ClassUtils.getUserClass(delegate); + var formattable = FormatableType.of(listenerType); + + var formattedListenerType = modules.getModuleByType(listenerType) + .map(formattable::getAbbreviatedFullName) + .orElseGet(formattable::getAbbreviatedFullName); + + LOG.debug("Initializing {}.", formattedListenerType); + + delegate.initialize(); + + LOG.debug("{} done.", formattedListenerType); + } + } + private static ApplicationModules initializeApplicationModules(Class applicationMainClass) { LOG.debug("Obtaining Spring Modulith application modules…");