GH-1 - Update Maven artifact coordinates, package and directory structure.
Original pull request: GH-7.
This commit is contained in:
committed by
Oliver Drotbohm
parent
6c444769d7
commit
417e215993
@@ -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);
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.modulith.observability.autoconfigure.ModuleObservabilityAutoConfiguration,\
|
||||
org.springframework.modulith.observability.autoconfigure.SpringDataRestModuleObservabilityAutoConfiguration
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.modulith.observability.autoconfigure.ModuleObservabilityAutoConfiguration
|
||||
org.springframework.modulith.observability.autoconfigure.SpringDataRestModuleObservabilityAutoConfiguration
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
@@ -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
|
||||
16
spring-modulith-observability/src/test/resources/logback.xml
Normal file
16
spring-modulith-observability/src/test/resources/logback.xml
Normal 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>
|
||||
Reference in New Issue
Block a user