Replies: 1 comment
-
I still hesitate to create a pull request as there are no reactions to this idea. Maybe some feedback from the core committers ? It should be easy to do, e.g. this would do the trick public class ApplicationModuleFilter implements BeanDefinitionRegistryPostProcessor {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final ApplicationModuleFilterProperties properties;
public ApplicationModuleFilter(ApplicationModuleFilterProperties properties) {
this.properties = properties;
}
private Class<?> getSpringBootApplicationClass(BeanDefinitionRegistry registry) {
var springBootApplicationClassName = properties.getSpringBootApplicationClass();
if (springBootApplicationClassName != null) {
try {
return Class.forName(springBootApplicationClassName);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(
String.format("Class %s not found", springBootApplicationClassName), e);
}
}
var springBootApplicationClass = getSpringBootApplicationClassFromRegisteredBeans(registry);
if (springBootApplicationClass == null) {
throw new IllegalStateException(
String.format(
"Could not resolve Spring Boot application. Please specify the fully qualified class name of your Spring Boot application class through property %s",
ApplicationModuleFilterProperties.SPRING_BOOT_APPLICATION_CLASS_PROPERTY_NAME));
} else {
return springBootApplicationClass;
}
}
private Class<?> getSpringBootApplicationClassFromRegisteredBeans(
BeanDefinitionRegistry registry) {
var springBootApplicationClasses = new ArrayList<Class<?>>();
for (String beanName : registry.getBeanDefinitionNames()) {
var beanDefinition = registry.getBeanDefinition(beanName);
if (beanDefinition.getBeanClassName() != null) {
try {
var clazz = Class.forName(beanDefinition.getBeanClassName());
if (clazz.isAnnotationPresent(
org.springframework.boot.autoconfigure.SpringBootApplication.class)) {
springBootApplicationClasses.add(clazz);
}
} catch (ClassNotFoundException e) {
throw new IllegalStateException(
String.format("Class %s not found", beanDefinition.getBeanClassName()), e);
}
}
}
if (springBootApplicationClasses.isEmpty()) {
return null;
} else if (springBootApplicationClasses.size() == 1) {
return springBootApplicationClasses.get(0);
} else {
logger.warn(
"Found multiple Spring Boot application classes: {}",
springBootApplicationClasses.stream()
.map(Class::getName)
.collect(Collectors.joining(", ")));
return null;
}
}
private Set<String> resolveFullyQualifiedClassNamesOfSpringBeansToFilter(
BeanDefinitionRegistry registry) {
var moduleIdentifiersToLoad = new HashSet<String>();
var activeModuleIdentifiers = properties.getActiveApplicationModules();
if (activeModuleIdentifiers != null && !activeModuleIdentifiers.isBlank()) {
Arrays.stream(activeModuleIdentifiers.split(","))
.forEach(moduleIdentifier -> moduleIdentifiersToLoad.add(moduleIdentifier.trim()));
} else {
logger.info(
"No modules to load specified in {}. Loading all modules by default.",
ApplicationModuleFilterProperties.ACTIVE_APPLICATION_MODULES_PROPERTY_NAME);
return new HashSet<>();
}
ApplicationModules modules =
ApplicationModules.of(getSpringBootApplicationClass(registry), (location) -> true);
var applicationModulesToLoad = new TreeSet<ApplicationModule>();
for (String moduleIdentifier : moduleIdentifiersToLoad) {
var module = modules.getModuleByName(moduleIdentifier);
if (module.isEmpty()) {
var availableModuleIdentifiers =
modules.stream()
.map(ApplicationModule::getIdentifier)
.map(ApplicationModuleIdentifier::toString)
.toList();
throw new IllegalStateException(
String.format(
"Module %s not found. Available module identifiers: %s",
moduleIdentifier, availableModuleIdentifiers));
}
applicationModulesToLoad.add(module.get());
module.get().getDependencies(modules, DependencyDepth.ALL).stream()
.map(ApplicationModuleDependency::getTargetModule)
.forEach(m -> applicationModulesToLoad.add(m));
}
logger.info(
"Loading application modules: {}",
applicationModulesToLoad.stream()
.map(ApplicationModule::getIdentifier)
.map(ApplicationModuleIdentifier::toString)
.toList());
return modules.stream()
.filter(m -> !applicationModulesToLoad.contains(m))
.flatMap(m -> m.getSpringBeans().stream())
.map(SpringBean::getFullyQualifiedTypeName)
.collect(Collectors.toSet());
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
var beanTypesToFilter = resolveFullyQualifiedClassNamesOfSpringBeansToFilter(registry);
var removedBeanDefinitionNames = new HashSet<String>();
for (String beanDefinitionName : registry.getBeanDefinitionNames()) {
var beanDefinition = registry.getBeanDefinition(beanDefinitionName);
if (beanTypesToFilter.contains(beanDefinition.getBeanClassName())) {
logger.debug(
"Filtering bean {} as it is not part of the application modules to load",
beanDefinitionName);
removedBeanDefinitionNames.add(beanDefinitionName);
registry.removeBeanDefinition(beanDefinitionName);
}
}
// remove all beans that are created by the removed beans
for (var beanDefinitionName : registry.getBeanDefinitionNames()) {
var beanDefinition = registry.getBeanDefinition(beanDefinitionName);
if (removedBeanDefinitionNames.contains(beanDefinition.getFactoryBeanName())) {
logger.debug(
"Filtering bean {} as it is not part of the application modules to load (created by {})",
beanDefinitionName,
beanDefinition.getFactoryBeanName());
registry.removeBeanDefinition(beanDefinitionName);
}
}
}
}``` |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
It would be beneficial to keep source code for many application modules in once place, but then deploy the built container multiple times with only selected application modules being loaded in a each deployed instance.
This allows to develop in kind of a monolithic, but well structured way and at the same time deploy in a more microservice oriented fashion, which allows to reap e.g. the benefits of easily scaling some parts of the overall application.
As far as I got from the docs, this is not supported out of the box by Spring Modulith. I've already setup a small prototype that uses Spring Modulith's application modules API to adapt the application context upon startup. It basically just removes all application module provided bean definitions from the application context that are not required based on a list of application modules that should be active in a given instance.
First of all I ask myself the question if this is the proper way to go ? Additionally, I ask myself the question if this isn't a useful feature which should be added to Spring Modulith ?
Beta Was this translation helpful? Give feedback.
All reactions