GH-107 - Improve runtime support configuration setup.

The configuration classes declaring SpringBootApplicationRuntime and ApplicationModulesRuntime beans now describe them as infrastructure beans so that they do not get reported as eagly initialized as their initialization will be triggered when creating the BeanPostProcessors our observability support is based on.

The configuration of the observability support now consistently refer to ApplicationModulesRuntime directly and to the Tracer instance via a supplier as the latter is only needed during the actual component invocation while the former is already needed to decide whether to decorate the instances in the first place. Simplify the inheritance arrangement underneath ModuleTracingSupport.

The logging adapter for ApplicationModuleInitializers now properly unwraps proxy types so that the log output uses the user classes properly.
This commit is contained in:
Oliver Drotbohm
2023-01-05 20:00:18 +01:00
parent 262efb5828
commit 5f25974f0d
8 changed files with 54 additions and 56 deletions

View File

@@ -33,7 +33,7 @@ import org.springframework.modulith.runtime.ApplicationModulesRuntime;
@RequiredArgsConstructor
public class ModuleEventListener implements ApplicationListener<ApplicationEvent> {
private final ApplicationModulesRuntime modules;
private final ApplicationModulesRuntime runtime;
private final Supplier<Tracer> tracer;
/*
@@ -51,11 +51,11 @@ public class ModuleEventListener implements ApplicationListener<ApplicationEvent
Object object = foo.getPayload();
Class<? extends Object> payloadType = object.getClass();
if (!modules.isApplicationClass(payloadType)) {
if (!runtime.isApplicationClass(payloadType)) {
return;
}
ApplicationModule moduleByType = modules.get()
ApplicationModule moduleByType = runtime.get()
.getModuleByType(payloadType.getSimpleName())
.orElse(null);

View File

@@ -21,6 +21,7 @@ import lombok.RequiredArgsConstructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Advisor;
@@ -35,24 +36,20 @@ import org.springframework.modulith.model.ApplicationModules;
import org.springframework.modulith.runtime.ApplicationModulesRuntime;
/**
* A {@link BeanPostProcessor} that decorates beans exposed by application modules with an interceptor that registers
* module entry and exit to create tracing spans for those invocations.
*
* @author Oliver Drotbohm
*/
@RequiredArgsConstructor
public class ModuleTracingBeanPostProcessor extends ModuleTracingSupport implements BeanPostProcessor {
public static final String MODULE_BAGGAGE_KEY = "org.springframework.modulith.module";
private final ApplicationModulesRuntime runtime;
private final Tracer tracer;
private final Supplier<Tracer> tracer;
private final Map<String, Advisor> advisors = new HashMap<>();
public ModuleTracingBeanPostProcessor(ApplicationModulesRuntime runtime, Tracer tracer) {
super(runtime);
this.runtime = runtime;
this.tracer = tracer;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String)
@@ -60,7 +57,7 @@ public class ModuleTracingBeanPostProcessor extends ModuleTracingSupport impleme
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> type = getBeanUserClass(bean, beanName);
Class<?> type = runtime.getUserClass(bean, beanName);
if (!runtime.isApplicationClass(type) || !type.isInstance(bean)) {
return bean;
@@ -85,7 +82,7 @@ public class ModuleTracingBeanPostProcessor extends ModuleTracingSupport impleme
return advisors.computeIfAbsent(module.getName(), __ -> {
Advice interceptor = ModuleEntryInterceptor.of(module, tracer);
Advice interceptor = ModuleEntryInterceptor.of(module, tracer.get());
MethodMatcher matcher = new ObservableTypeMethodMatcher(type);
Pointcut pointcut = new ComposablePointcut(matcher);

View File

@@ -21,24 +21,14 @@ import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.modulith.runtime.ApplicationModulesRuntime;
import org.springframework.util.Assert;
/**
* @author Oliver Drotbohm
*/
class ModuleTracingSupport implements BeanClassLoaderAware {
private final ApplicationModulesRuntime runtime;
private ClassLoader classLoader;
protected ModuleTracingSupport(ApplicationModulesRuntime runtime) {
Assert.notNull(runtime, "ModulesRuntime must not be null!");
this.runtime = runtime;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader)
@@ -48,10 +38,6 @@ class ModuleTracingSupport implements BeanClassLoaderAware {
this.classLoader = classLoader;
}
protected final Class<?> getBeanUserClass(Object bean, String beanName) {
return runtime.getUserClass(bean, beanName);
}
protected final Object addAdvisor(Object bean, Advisor advisor) {
return addAdvisor(bean, advisor, __ -> {});
}

View File

@@ -37,18 +37,11 @@ import org.springframework.modulith.runtime.ApplicationModulesRuntime;
/**
* @author Oliver Drotbohm
*/
@RequiredArgsConstructor
public class SpringDataRestModuleTracingBeanPostProcessor extends ModuleTracingSupport implements BeanPostProcessor {
private final Tracer tracer;
private final ApplicationModulesRuntime runtime;
public SpringDataRestModuleTracingBeanPostProcessor(ApplicationModulesRuntime runtime, Tracer tracer) {
super(runtime);
this.tracer = tracer;
this.runtime = runtime;
}
private final Supplier<Tracer> tracer;
/*
* (non-Javadoc)
@@ -73,7 +66,7 @@ public class SpringDataRestModuleTracingBeanPostProcessor extends ModuleTracingS
private static class DataRestControllerInterceptor implements MethodInterceptor {
private final Supplier<ApplicationModules> modules;
private final Tracer tracer;
private final Supplier<Tracer> tracer;
/*
* (non-Javadoc)
@@ -90,7 +83,7 @@ public class SpringDataRestModuleTracingBeanPostProcessor extends ModuleTracingS
ObservedModule observed = new DefaultObservedModule(module);
return ModuleEntryInterceptor.of(observed, tracer).invoke(invocation);
return ModuleEntryInterceptor.of(observed, tracer.get()).invoke(invocation);
}
private ApplicationModule getModuleFrom(Object[] arguments) {

View File

@@ -42,8 +42,8 @@ class ModuleObservabilityAutoConfiguration {
@Bean
static ModuleTracingBeanPostProcessor moduleTracingBeanPostProcessor(ApplicationModulesRuntime runtime,
Tracer tracer) {
return new ModuleTracingBeanPostProcessor(runtime, tracer);
ObjectProvider<Tracer> tracer) {
return new ModuleTracingBeanPostProcessor(runtime, () -> tracer.getObject());
}
@Bean

View File

@@ -17,6 +17,7 @@ package org.springframework.modulith.observability.autoconfigure;
import io.micrometer.tracing.Tracer;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -33,7 +34,8 @@ class SpringDataRestModuleObservabilityAutoConfiguration {
@Bean
static SpringDataRestModuleTracingBeanPostProcessor springDataRestModuleTracingBeanPostProcessor(
ApplicationModulesRuntime runtime, Tracer tracer) {
return new SpringDataRestModuleTracingBeanPostProcessor(runtime, tracer);
ApplicationModulesRuntime runtime, ObjectProvider<Tracer> tracer) {
return new SpringDataRestModuleTracingBeanPostProcessor(runtime, () -> tracer.getObject());
}
}

View File

@@ -17,6 +17,7 @@ package org.springframework.modulith.runtime.autoconfigure;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -27,6 +28,9 @@ import org.springframework.modulith.runtime.ApplicationRuntime;
import org.springframework.util.ClassUtils;
/**
* {@link ApplicationRuntime} implementation based on an {@link ApplicationContext} and a class that's annotated with
* {@link SpringBootApplication}.
*
* @author Oliver Drotbohm
*/
@RequiredArgsConstructor
@@ -35,6 +39,7 @@ class SpringBootApplicationRuntime implements ApplicationRuntime {
private static final Map<String, Boolean> APPLICATION_CLASSES = new ConcurrentHashMap<>();
private final ApplicationContext context;
private Class<?> mainApplicationClass;
/*
* (non-Javadoc)
@@ -52,9 +57,17 @@ class SpringBootApplicationRuntime implements ApplicationRuntime {
@Override
public Class<?> getMainApplicationClass() {
var mainBeanNames = context.getBeanNamesForAnnotation(SpringBootApplication.class);
if (mainApplicationClass == null) {
return context.getType(mainBeanNames[0]);
// Traverse BeanDefinitions manually to avoid factory beans to be inspected
this.mainApplicationClass = Arrays.stream(context.getBeanDefinitionNames())
.filter(it -> context.findAnnotationOnBean(it, SpringBootApplication.class, false) != null)
.map(context::getType)
.findFirst()
.orElseThrow(() -> new IllegalStateException("Couldn't find a class annotated with @SpringBootApplication!"));
}
return mainApplicationClass;
}
/*
@@ -78,15 +91,18 @@ class SpringBootApplicationRuntime implements ApplicationRuntime {
@Override
public boolean isApplicationClass(Class<?> type) {
return APPLICATION_CLASSES.computeIfAbsent(type.getName(),
it -> {
var applicationClass = getMainApplicationClass();
if (it.startsWith("org.springframework")) {
return false;
}
return APPLICATION_CLASSES.computeIfAbsent(type.getName(), it -> computeIsApplicationClass(it, applicationClass));
}
return it.startsWith(getMainApplicationClass().getPackage().getName())
|| AutoConfigurationPackages.get(context).stream().anyMatch(pkg -> it.startsWith(pkg));
});
private boolean computeIsApplicationClass(String fqn, Class<?> applicationClass) {
if (fqn.startsWith("org.springframework")) {
return false;
}
return fqn.startsWith(applicationClass.getPackage().getName())
|| AutoConfigurationPackages.get(context).stream().anyMatch(pkg -> fqn.startsWith(pkg));
}
}

View File

@@ -23,19 +23,21 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Supplier;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.config.BeanDefinition;
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.context.annotation.Role;
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
@@ -48,12 +50,14 @@ import org.springframework.util.ClassUtils;
class SpringModulithRuntimeAutoConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ConditionalOnMissingBean(ApplicationRuntime.class)
SpringBootApplicationRuntime modulithsApplicationRuntime(ApplicationContext context) {
return new SpringBootApplicationRuntime(context);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ConditionalOnMissingBean
ApplicationModulesRuntime modulesRuntime(ApplicationRuntime runtime) {
@@ -93,7 +97,7 @@ class SpringModulithRuntimeAutoConfiguration {
@Override
public void initialize() {
var listenerType = ClassUtils.getUserClass(delegate);
var listenerType = AopUtils.getTargetClass(delegate);
var formattable = FormatableType.of(listenerType);
var formattedListenerType = modules.getModuleByType(listenerType)
@@ -104,7 +108,7 @@ class SpringModulithRuntimeAutoConfiguration {
delegate.initialize();
LOG.debug("{} done.", formattedListenerType);
LOG.debug("Initializing {} done.", formattedListenerType);
}
}