GH-1 - Update Maven artifact coordinates, package and directory structure.

Original pull request: GH-7.
This commit is contained in:
Björn Kieling
2022-07-07 12:16:07 +02:00
committed by Oliver Drotbohm
parent 6c444769d7
commit 417e215993
255 changed files with 764 additions and 759 deletions

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2022 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.observability;
/**
* Abstraction of the application runtime environment. Primarily to keep references to Spring Boot out of the core
* observability implementation.
*
* @author Oliver Drotbohm
*/
public interface ApplicationRuntime {
/**
* Returns the identifier of the application.
*
* @return
*/
String getId();
/**
* Returns the primary application class.
*
* @return
*/
Class<?> getMainApplicationClass();
/**
* Obtain the end user class for the given bean and bean name. Necessary to reveal the actual user type from
* potentially proxied instances.
*
* @param bean
* @param beanName
* @return
*/
Class<?> getUserClass(Object bean, String beanName);
/**
* Returns whether the given type is an application class, i.e. user code in one of the application packages.
*
* @param type
* @return
* @see #getApplicationPackages()
*/
boolean isApplicationClass(Class<?> type);
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright 2022 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.observability;
import lombok.RequiredArgsConstructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.modulith.model.FormatableJavaClass;
import org.springframework.modulith.model.Module;
import org.springframework.modulith.model.Modules;
import org.springframework.modulith.model.SpringBean;
import org.springframework.aop.ProxyMethodInvocation;
import org.springframework.aop.framework.Advised;
import com.tngtech.archunit.core.domain.JavaClass;
@RequiredArgsConstructor
class DefaultObservedModule implements ObservedModule {
private final Module module;
/*
* (non-Javadoc)
* @see org.springframework.modulith.observability.ObservedModule#getName()
*/
@Override
public String getName() {
return module.getName();
}
/*
* (non-Javadoc)
* @see org.springframework.modulith.observability.ObservedModule#getDisplayName()
*/
@Override
public String getDisplayName() {
return module.getDisplayName();
}
/*
* (non-Javadoc)
* @see org.springframework.modulith.observability.ObservedModule#getInvokedMethod(org.aopalliance.intercept.MethodInvocation)
*/
@Override
public String getInvokedMethod(MethodInvocation invocation) {
Method method = invocation.getMethod();
if (module.contains(method.getDeclaringClass())) {
return toString(invocation.getMethod(), module);
}
if (!ProxyMethodInvocation.class.isInstance(invocation)) {
return toString(invocation.getMethod(), module);
}
// For class-based proxies, use the target class
Advised advised = (Advised) ((ProxyMethodInvocation) invocation).getProxy();
Class<?> targetClass = advised.getTargetClass();
if (module.contains(targetClass)) {
return toString(targetClass, method, module);
}
// For JDK proxies, find original interface the method was logically declared on
for (Class<?> type : advised.getProxiedInterfaces()) {
if (module.contains(type)) {
if (Arrays.asList(type.getMethods()).contains(method)) {
return toString(type, method, module);
}
}
}
return toString(invocation.getMethod(), module);
}
/*
* (non-Javadoc)
* @see org.springframework.modulith.observability.ObservedModule#exposes(com.tngtech.archunit.core.domain.JavaClass)
*/
@Override
public boolean exposes(JavaClass type) {
return module.isExposed(type);
}
/*
* (non-Javadoc)
* @see org.springframework.modulith.observability.ObservedModule#isObservedModule(org.springframework.modulith.model.Module)
*/
@Override
public boolean isObservedModule(Module module) {
return this.module.equals(module);
}
/*
* (non-Javadoc)
* @see org.springframework.modulith.observability.ObservedModule#getInterceptionConfiguration(java.lang.Class, org.springframework.modulith.model.Modules)
*/
public ObservedModuleType getObservedModuleType(Class<?> type, Modules modules) {
return module.getSpringBeans().stream()
.filter(it -> it.getFullyQualifiedTypeName().equals(type.getName()))
.map(SpringBean::toArchitecturallyEvidentType)
.findFirst()
.map(it -> new ObservedModuleType(modules, this, it))
.filter(ObservedModuleType::shouldBeTraced)
.orElse(null);
}
private static String toString(Method method, Module module) {
return toString(method.getDeclaringClass(), method, module);
}
private static String toString(Class<?> type, Method method, Module module) {
String typeName = module.getType(type.getName())
.map(FormatableJavaClass::of)
.map(FormatableJavaClass::getAbbreviatedFullName)
.orElseGet(() -> type.getName());
return String.format("%s.%s(…)", typeName, method.getName());
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2022 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.observability;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.cloud.sleuth.BaggageInScope;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.Tracer.SpanInScope;
@Slf4j
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
class ModuleEntryInterceptor implements MethodInterceptor {
private static Map<String, ModuleEntryInterceptor> CACHE = new HashMap<>();
private final ObservedModule module;
private final Tracer tracer;
public static ModuleEntryInterceptor of(ObservedModule module, Tracer tracer) {
String name = module.getName();
return CACHE.computeIfAbsent(name, __ -> {
return new ModuleEntryInterceptor(module, tracer);
});
}
/*
* (non-Javadoc)
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String moduleName = module.getName();
Span currentSpan = tracer.currentSpan();
if (currentSpan != null) {
BaggageInScope currentBaggage = tracer.getBaggage(ModuleTracingBeanPostProcessor.MODULE_BAGGAGE_KEY);
if (currentBaggage != null && moduleName.equals(currentBaggage.get())) {
return invocation.proceed();
}
}
String invokedMethod = module.getInvokedMethod(invocation);
LOG.trace("Entering {} via {}.", module.getDisplayName(), invokedMethod);
Span span = tracer.spanBuilder()
.name(moduleName)
.tag("module.method", invokedMethod)
.tag(ModuleTracingBeanPostProcessor.MODULE_BAGGAGE_KEY, moduleName)
.start();
try (
SpanInScope ws = tracer.withSpan(span); //
BaggageInScope baggage = tracer.createBaggage(ModuleTracingBeanPostProcessor.MODULE_BAGGAGE_KEY, moduleName); //
) {
return invocation.proceed();
} finally {
LOG.trace("Leaving {}", module.getDisplayName());
span.end();
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2022 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.observability;
import lombok.RequiredArgsConstructor;
import java.util.function.Supplier;
import org.springframework.modulith.model.Module;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.PayloadApplicationEvent;
/**
* @author Oliver Drotbohm
*/
@RequiredArgsConstructor
public class ModuleEventListener implements ApplicationListener<ApplicationEvent> {
private final ModulesRuntime modules;
private final Supplier<Tracer> tracer;
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (!PayloadApplicationEvent.class.isInstance(event)) {
return;
}
PayloadApplicationEvent<?> foo = (PayloadApplicationEvent<?>) event;
Object object = foo.getPayload();
Class<? extends Object> payloadType = object.getClass();
if (!modules.isApplicationClass(payloadType)) {
return;
}
Module moduleByType = modules.get()
.getModuleByType(payloadType.getSimpleName())
.orElse(null);
if (moduleByType == null) {
return;
}
Span span = tracer.get().currentSpan();
if (span == null) {
return;
}
span.event("Published " + payloadType.getName());
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright 2022 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.observability;
import lombok.RequiredArgsConstructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.aopalliance.aop.Advice;
import org.springframework.modulith.model.Modules;
import org.springframework.aop.Advisor;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.StaticMethodMatcher;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cloud.sleuth.Tracer;
/**
* @author Oliver Drotbohm
*/
public class ModuleTracingBeanPostProcessor extends ModuleTracingSupport implements BeanPostProcessor {
public static final String MODULE_BAGGAGE_KEY = "org.springframework.modulith.module";
private final ApplicationRuntime runtime;
private final Tracer tracer;
private final Map<String, Advisor> advisors = new HashMap<>();
public ModuleTracingBeanPostProcessor(ApplicationRuntime 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)
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> type = getBeanUserClass(bean, beanName);
if (!runtime.isApplicationClass(type) || !type.isInstance(bean)) {
return bean;
}
Modules modules = getModules();
return modules.getModuleByType(type.getName())
.map(DefaultObservedModule::new)
.map(it -> {
ObservedModuleType moduleType = it.getObservedModuleType(type, modules);
return moduleType != null //
? addAdvisor(bean, getOrBuildAdvisor(it, moduleType)) //
: bean;
}).orElse(bean);
}
private Advisor getOrBuildAdvisor(ObservedModule module, ObservedModuleType type) {
return advisors.computeIfAbsent(module.getName(), __ -> {
Advice interceptor = ModuleEntryInterceptor.of(module, tracer);
MethodMatcher matcher = new ObservableTypeMethodMatcher(type);
Pointcut pointcut = new ComposablePointcut(matcher);
return new DefaultPointcutAdvisor(pointcut, interceptor);
});
}
@RequiredArgsConstructor
private static class ObservableTypeMethodMatcher extends StaticMethodMatcher {
private final ObservedModuleType type;
/*
* (non-Javadoc)
* @see org.springframework.aop.MethodMatcher#matches(java.lang.reflect.Method, java.lang.Class)
*/
@Override
public boolean matches(Method method, Class<?> targetClass) {
return type.getMethodsToIntercept().test(method);
}
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2022 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.observability;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.springframework.modulith.model.Modules;
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.util.Assert;
/**
* @author Oliver Drotbohm
*/
class ModuleTracingSupport implements BeanClassLoaderAware {
private final Supplier<Modules> modules;
private final ApplicationRuntime context;
private ClassLoader classLoader;
protected ModuleTracingSupport(ApplicationRuntime context) {
Assert.notNull(context, "ApplicationContext must not be null!");
this.modules = ModulesRuntime.of(context);
this.context = context;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader)
*/
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
protected final Modules getModules() {
try {
return modules.get();
} catch (Exception o_O) {
throw new RuntimeException(o_O);
}
}
protected final Class<?> getBeanUserClass(Object bean, String beanName) {
return context.getUserClass(bean, beanName);
}
protected final Object addAdvisor(Object bean, Advisor advisor) {
return addAdvisor(bean, advisor, __ -> {});
}
protected final Object addAdvisor(Object bean, Advisor advisor, Consumer<ProxyFactory> customizer) {
if (Advised.class.isInstance(bean)) {
((Advised) bean).addAdvisor(0, advisor);
return bean;
} else {
ProxyFactory factory = new ProxyFactory(bean);
customizer.accept(factory);
factory.addAdvisor(advisor);
return factory.getProxy(classLoader);
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2022 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.observability;
import lombok.RequiredArgsConstructor;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Supplier;
import org.springframework.modulith.model.Modules;
/**
* Bootstrap type to make sure we only bootstrap the initialization of a {@link Modules} instance per application class
* once.
*
* @author Oliver Drotbohm
*/
@RequiredArgsConstructor
public class ModulesRuntime implements Supplier<Modules> {
private static final Map<String, ModulesRuntime> MODULES = new HashMap<>();
private final Supplier<Modules> modules;
private final ApplicationRuntime runtime;
/*
* (non-Javadoc)
* @see java.util.function.Supplier#get()
*/
@Override
public Modules get() {
return modules.get();
}
boolean isApplicationClass(Class<?> type) {
return runtime.isApplicationClass(type);
}
public static ModulesRuntime of(ApplicationRuntime runtime) {
return MODULES.computeIfAbsent(runtime.getId(), it -> {
Class<?> mainClass = runtime.getMainApplicationClass();
Future<Modules> modules = Executors.newFixedThreadPool(1).submit(() -> Modules.of(mainClass));
return new ModulesRuntime(toSupplier(modules), runtime);
});
}
private static Supplier<Modules> toSupplier(Future<Modules> modules) {
return () -> {
try {
return modules.get();
} catch (Exception o_O) {
throw new RuntimeException(o_O);
// TODO: handle exception
}
};
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2022 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.observability;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.modulith.model.Module;
import org.springframework.modulith.model.Modules;
import com.tngtech.archunit.core.domain.JavaClass;
/**
* @author Oliver Drotbohm
*/
interface ObservedModule {
String getName();
String getDisplayName();
/**
* Returns the name of the actually invoked {@link Method}.
*
* @param invocation must not be {@literal null}.
* @return
*/
String getInvokedMethod(MethodInvocation invocation);
/**
* Returns whether the {@link ObservedModule} exposes the given {@link JavaClass}.
*
* @param type
* @return
*/
boolean exposes(JavaClass type);
boolean isObservedModule(Module module);
/**
* Returns the {@link ObservedModuleType} for the given type and {@link Modules}.
*
* @param type
* @param modules
* @return
*/
ObservedModuleType getObservedModuleType(Class<?> type, Modules modules);
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2022 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.observability;
import lombok.RequiredArgsConstructor;
import java.lang.reflect.Method;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.springframework.modulith.model.ArchitecturallyEvidentType;
import org.springframework.modulith.model.ArchitecturallyEvidentType.ReferenceMethod;
import org.springframework.modulith.model.Modules;
/**
* Represents a type in an {@link ObservedModule}.
*
* @author Oliver Drotbohm
*/
@RequiredArgsConstructor
public class ObservedModuleType {
private final Modules modules;
private final ObservedModule module;
private final ArchitecturallyEvidentType type;
/**
* Returns whether the type should be traced at all. Can be skipped for types not exposed by the module unless they
* listen to events of other modules.
*
* @return
*/
public boolean shouldBeTraced() {
boolean isApiType = module.exposes(type.getType());
return type.isController() || listensToOtherModulesEvents() || isApiType;
}
/**
* Returns a predicate to filter the methods to intercept. For event listeners it's the listener methods only. For
* everything else, all (public) methods will be intercepted.
*
* @return
*/
public Predicate<Method> getMethodsToIntercept() {
if (!type.isEventListener()) {
return it -> true;
}
return candidate -> type.getReferenceMethods() //
.map(ReferenceMethod::getMethod) //
.anyMatch(it -> it.reflect().equals(candidate));
}
private boolean listensToOtherModulesEvents() {
if (!type.isEventListener()) {
return false;
}
return type.getReferenceTypes()
.flatMap(it -> modules
.getModuleByType(it)
.map(Stream::of)
.orElseGet(Stream::empty))
.findFirst()
.map(it -> !module.isObservedModule(it))
.orElse(true);
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright 2022 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.observability;
import lombok.RequiredArgsConstructor;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.modulith.model.Module;
import org.springframework.modulith.model.Modules;
import org.springframework.aop.Advisor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.data.rest.webmvc.BasePathAwareController;
import org.springframework.data.rest.webmvc.RootResourceInformation;
/**
* @author Oliver Drotbohm
*/
public class SpringDataRestModuleTracingBeanPostProcessor extends ModuleTracingSupport implements BeanPostProcessor {
private final Tracer tracer;
private final ApplicationRuntime runtime;
public SpringDataRestModuleTracingBeanPostProcessor(ApplicationRuntime runtime, Tracer tracer) {
super(runtime);
this.tracer = tracer;
this.runtime = runtime;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String)
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> type = runtime.getUserClass(bean, beanName);
if (!AnnotatedElementUtils.hasAnnotation(type, BasePathAwareController.class)) {
return bean;
}
Advice interceptor = new DataRestControllerInterceptor(getModules(), tracer);
Advisor advisor = new DefaultPointcutAdvisor(interceptor);
return addAdvisor(bean, advisor, it -> it.setProxyTargetClass(true));
}
@RequiredArgsConstructor
private static class DataRestControllerInterceptor implements MethodInterceptor {
private final Modules modules;
private final Tracer tracer;
/*
* (non-Javadoc)
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Module module = getModuleFrom(invocation.getArguments());
if (module == null) {
return invocation.proceed();
}
ObservedModule observed = new DefaultObservedModule(module);
return ModuleEntryInterceptor.of(observed, tracer).invoke(invocation);
}
private Module getModuleFrom(Object[] arguments) {
for (Object argument : arguments) {
if (!RootResourceInformation.class.isInstance(arguments)) {
continue;
}
RootResourceInformation info = (RootResourceInformation) argument;
return modules.getModuleByType(info.getDomainType().getName()).orElse(null);
}
return null;
}
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2022 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.observability.autoconfigure;
import brave.TracingCustomizer;
import brave.baggage.BaggageField;
import brave.baggage.BaggagePropagationConfig;
import brave.baggage.BaggagePropagationCustomizer;
import brave.handler.MutableSpan;
import brave.handler.SpanHandler;
import brave.propagation.TraceContext;
import org.springframework.modulith.observability.ApplicationRuntime;
import org.springframework.modulith.observability.ModuleEventListener;
import org.springframework.modulith.observability.ModuleTracingBeanPostProcessor;
import org.springframework.modulith.observability.ModulesRuntime;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Oliver Drotbohm
*/
@Configuration(proxyBeanMethods = false)
class ModuleObservabilityAutoConfiguration {
@Bean
static SpringBootApplicationRuntime modulithsApplicationRuntime(ApplicationContext context) {
return new SpringBootApplicationRuntime(context);
}
@Bean
static ModuleTracingBeanPostProcessor moduleTracingBeanPostProcessor(ApplicationRuntime runtime,
Tracer tracer) {
return new ModuleTracingBeanPostProcessor(runtime, tracer);
}
@Bean
static ModuleEventListener tracingModuleEventListener(ApplicationRuntime runtime, ObjectProvider<Tracer> tracer) {
return new ModuleEventListener(ModulesRuntime.of(runtime), () -> tracer.getObject());
}
/**
* Brave-specific auto configuration.
*
* @author Oliver Drotbohm
*/
@ConditionalOnClass(TracingCustomizer.class)
static class ModulithsBraveIntegrationAutoConfiguration {
@Bean
BaggagePropagationCustomizer moduleBaggagePropagationCustomizer() {
return builder -> builder
.add(BaggagePropagationConfig.SingleBaggageField
.local(BaggageField.create(ModuleTracingBeanPostProcessor.MODULE_BAGGAGE_KEY)));
}
@Bean
SpanHandler spanHandler() {
return new SpanHandler() {
/*
* (non-Javadoc)
* @see brave.handler.SpanHandler#end(brave.propagation.TraceContext, brave.handler.MutableSpan, brave.handler.SpanHandler.Cause)
*/
@Override
public boolean end(TraceContext context, MutableSpan span, Cause cause) {
String value = span.tag(ModuleTracingBeanPostProcessor.MODULE_BAGGAGE_KEY);
if (value != null) {
span.localServiceName(value);
return true;
}
BaggageField field = BaggageField.getByName(context, ModuleTracingBeanPostProcessor.MODULE_BAGGAGE_KEY);
value = field.getValue();
if (value != null) {
span.localServiceName(value);
}
return true;
}
};
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2022 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.observability.autoconfigure;
import lombok.RequiredArgsConstructor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.modulith.observability.ApplicationRuntime;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ClassUtils;
/**
* @author Oliver Drotbohm
*/
@RequiredArgsConstructor
class SpringBootApplicationRuntime implements ApplicationRuntime {
private static final Map<String, Boolean> APPLICATION_CLASSES = new ConcurrentHashMap<>();
private final ApplicationContext context;
/*
* (non-Javadoc)
* @see org.springframework.modulith.observability.ApplicationRuntime#getId()
*/
@Override
public String getId() {
return context.getId();
}
/*
* (non-Javadoc)
* @see org.springframework.modulith.observability.ApplicationRuntime#getApplicationClass()
*/
@Override
public Class<?> getMainApplicationClass() {
String[] mainBeanNames = context.getBeanNamesForAnnotation(SpringBootApplication.class);
return context.getType(mainBeanNames[0]);
}
/*
* (non-Javadoc)
* @see org.springframework.modulith.observability.ApplicationRuntime#getBeanUserClass(java.lang.Object, java.lang.String)
*/
@Override
public Class<?> getUserClass(Object bean, String beanName) {
Class<?> beanType = context.containsBean(beanName)
? context.getType(beanName)
: bean.getClass();
return ClassUtils.getUserClass(beanType);
}
/*
* (non-Javadoc)
* @see org.springframework.modulith.observability.ApplicationRuntime#isApplicationClass(java.lang.Class)
*/
@Override
public boolean isApplicationClass(Class<?> type) {
return APPLICATION_CLASSES.computeIfAbsent(type.getName(),
it -> {
if (it.startsWith("org.springframework")) {
return false;
}
return it.startsWith(getMainApplicationClass().getPackage().getName())
|| AutoConfigurationPackages.get(context).stream().anyMatch(pkg -> it.startsWith(pkg));
});
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2022 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.observability.autoconfigure;
import org.springframework.modulith.observability.ApplicationRuntime;
import org.springframework.modulith.observability.SpringDataRestModuleTracingBeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.webmvc.RepositoryController;
/**
* @author Oliver Drotbohm
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RepositoryController.class)
class SpringDataRestModuleObservabilityAutoConfiguration {
@Bean
static SpringDataRestModuleTracingBeanPostProcessor springDataRestModuleTracingBeanPostProcessor(
ApplicationRuntime runtime, Tracer tracer) {
return new SpringDataRestModuleTracingBeanPostProcessor(runtime, tracer);
}
}

View File

@@ -0,0 +1,3 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.modulith.observability.autoconfigure.ModuleObservabilityAutoConfiguration,\
org.springframework.modulith.observability.autoconfigure.SpringDataRestModuleObservabilityAutoConfiguration

View File

@@ -0,0 +1,2 @@
org.springframework.modulith.observability.autoconfigure.ModuleObservabilityAutoConfiguration
org.springframework.modulith.observability.autoconfigure.SpringDataRestModuleObservabilityAutoConfiguration

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2022 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 example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Oliver Drotbohm
*/
@SpringBootApplication
public class ExampleApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(ExampleApplication.class, args);
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2022 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 example;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author Oliver Drotbohm
*/
@SpringBootTest
class ExampleApplicationIntegrationTests {
@Test
void bootstrapsSuccessfully() {}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2022 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.observability.autoconfigure;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.modulith.observability.ApplicationRuntime;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
/**
* Unit tests for {@link SpringBootApplicationRuntime}.
*
* @author Oliver Drotbohm
*/
@ExtendWith(MockitoExtension.class)
public class SpringBootApplicationRuntimeUnitTests {
@Mock ApplicationContext context;
@Test
void extractsUserTypeFromClassBasedProxy() {
Object proxy = new ProxyFactory(new Sample()).getProxy();
ApplicationRuntime runtime = new SpringBootApplicationRuntime(context);
assertThat(proxy.getClass()).isNotEqualTo(Sample.class);
assertThat(runtime.getUserClass(proxy, "sample")).isEqualTo(Sample.class);
}
static class Sample {}
}

View File

@@ -0,0 +1,2 @@
# As long as we're on a Spring Cloud version not officially compatible with Spring Boot 2.7
spring.cloud.compatibility-verifier.enabled=false

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %5p %40.40c:%4L - %m%n</pattern>
</encoder>
</appender>
<!-- <logger name="org.springframework.modulith" level="debug" /> -->
<root level="error">
<appender-ref ref="console" />
</root>
</configuration>