GH-927 - Introduce ObservedModule.isEventListenerInvocation(MethodInvocation).
Also introduce ObservedModule.getIdentifier() as a replacement for the now deprecated ObservedModule.getName().
This commit is contained in:
@@ -21,13 +21,17 @@ import java.util.Arrays;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.aop.ProxyMethodInvocation;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.modulith.core.ApplicationModule;
|
||||
import org.springframework.modulith.core.ApplicationModuleIdentifier;
|
||||
import org.springframework.modulith.core.ApplicationModules;
|
||||
import org.springframework.modulith.core.ArchitecturallyEvidentType.ReferenceMethod;
|
||||
import org.springframework.modulith.core.FormattableType;
|
||||
import org.springframework.modulith.core.SpringBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.tngtech.archunit.core.domain.JavaClass;
|
||||
import com.tngtech.archunit.core.domain.JavaMethod;
|
||||
|
||||
class DefaultObservedModule implements ObservedModule {
|
||||
|
||||
@@ -51,7 +55,16 @@ class DefaultObservedModule implements ObservedModule {
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return module.getName();
|
||||
return getIdentifier().toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.modulith.observability.ObservedModule#getIdentifier()
|
||||
*/
|
||||
@Override
|
||||
public ApplicationModuleIdentifier getIdentifier() {
|
||||
return module.getIdentifier();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -69,37 +82,7 @@ class DefaultObservedModule implements ObservedModule {
|
||||
*/
|
||||
@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
|
||||
|
||||
var advised = (Advised) ((ProxyMethodInvocation) invocation).getProxy();
|
||||
var 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);
|
||||
return toString(findModuleLocalMethod(invocation), module);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -144,11 +127,61 @@ class DefaultObservedModule implements ObservedModule {
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private static String toString(Method method, ApplicationModule module) {
|
||||
return toString(method.getDeclaringClass(), method, module);
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.modulith.observability.ObservedModule#isEventListenerInvocation(org.aopalliance.intercept.MethodInvocation)
|
||||
*/
|
||||
@Override
|
||||
public boolean isEventListenerInvocation(MethodInvocation invocation) {
|
||||
|
||||
var method = findModuleLocalMethod(invocation);
|
||||
var type = module.getArchitecturallyEvidentType(method.getDeclaringClass());
|
||||
|
||||
return type.isEventListener()
|
||||
&& type.getReferenceMethods()
|
||||
.map(ReferenceMethod::getMethod)
|
||||
.map(JavaMethod::reflect)
|
||||
.anyMatch(method::equals);
|
||||
}
|
||||
|
||||
private static String toString(Class<?> type, Method method, ApplicationModule module) {
|
||||
private Method findModuleLocalMethod(MethodInvocation invocation) {
|
||||
|
||||
Method method = invocation.getMethod();
|
||||
|
||||
if (module.contains(method.getDeclaringClass())) {
|
||||
return invocation.getMethod();
|
||||
}
|
||||
|
||||
if (!ProxyMethodInvocation.class.isInstance(invocation)) {
|
||||
return invocation.getMethod();
|
||||
}
|
||||
|
||||
// For class-based proxies, use the target class
|
||||
|
||||
var advised = (Advised) ((ProxyMethodInvocation) invocation).getProxy();
|
||||
var targetClass = advised.getTargetClass();
|
||||
|
||||
if (module.contains(targetClass)) {
|
||||
|
||||
return AopUtils.getMostSpecificMethod(method, targetClass);
|
||||
}
|
||||
|
||||
// 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 AopUtils.getMostSpecificMethod(method, targetClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return invocation.getMethod();
|
||||
}
|
||||
|
||||
private static String toString(Method method, ApplicationModule module) {
|
||||
|
||||
var type = method.getDeclaringClass();
|
||||
|
||||
var typeName = module.getType(type.getName())
|
||||
.map(FormattableType::of)
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.lang.reflect.Method;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.modulith.core.ApplicationModule;
|
||||
import org.springframework.modulith.core.ApplicationModuleIdentifier;
|
||||
import org.springframework.modulith.core.ApplicationModules;
|
||||
|
||||
import com.tngtech.archunit.core.domain.JavaClass;
|
||||
@@ -29,8 +30,27 @@ import com.tngtech.archunit.core.domain.JavaClass;
|
||||
*/
|
||||
interface ObservedModule {
|
||||
|
||||
/**
|
||||
* Returns the name of the application module.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
* @deprecated since 1.3, use {@link #getIdentifier()} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Returns the {@link ApplicationModuleIdentifier} of the underlying module.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
ApplicationModuleIdentifier getIdentifier();
|
||||
|
||||
/**
|
||||
* Returns the human-readable name of the module.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
String getDisplayName();
|
||||
|
||||
/**
|
||||
@@ -60,4 +80,13 @@ interface ObservedModule {
|
||||
*/
|
||||
@Nullable
|
||||
ObservedModuleType getObservedModuleType(Class<?> type, ApplicationModules modules);
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link MethodInvocation} is the invocation of an event listener as opposed to a standard
|
||||
* method invocation on a Spring bean.
|
||||
*
|
||||
* @param invocation must not be {@literal null}.
|
||||
* @since 1.3
|
||||
*/
|
||||
boolean isEventListenerInvocation(MethodInvocation invocation);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2024 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 static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import example.sample.ObservedComponent;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.modulith.core.ApplicationModule;
|
||||
import org.springframework.modulith.core.ApplicationModules;
|
||||
import org.springframework.modulith.core.ArchitecturallyEvidentType;
|
||||
import org.springframework.modulith.test.TestApplicationModules;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultObservedModule}.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
class DefaultObservedModuleUnitTests {
|
||||
|
||||
static final ApplicationModules modules = TestApplicationModules.of("example");
|
||||
|
||||
ApplicationModule module = modules.getModuleByName("sample").orElseThrow();
|
||||
ArchitecturallyEvidentType type = module.getArchitecturallyEvidentType(ObservedComponent.class);
|
||||
ObservedModule observedModule = new DefaultObservedModule(module);
|
||||
|
||||
@Test // GH-927
|
||||
void detectsEventListenerInvocation() throws Exception {
|
||||
|
||||
assertThat(observedModule.isEventListenerInvocation(forMethod("on", Object.class))).isTrue();
|
||||
assertThat(observedModule.isEventListenerInvocation(forMethod("someMethod"))).isFalse();
|
||||
}
|
||||
|
||||
private static MethodInvocation forMethod(String name, Class<?>... parameterTypes) throws Exception {
|
||||
|
||||
var method = ObservedComponent.class.getDeclaredMethod(name, parameterTypes);
|
||||
|
||||
return new MethodInvocation() {
|
||||
|
||||
@Override
|
||||
public Object proceed() throws Throwable {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getThis() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessibleObject getStaticPart() {
|
||||
return method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getArguments() {
|
||||
return new Object[] {};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user