Skip to content

Commit d921c71

Browse files
committed
GH-927 - Introduce ObservedModule.isEventListenerInvocation(MethodInvocation).
Also introduce ObservedModule.getIdentifier() as a replacement for the now deprecated ObservedModule.getName().
1 parent 169cf67 commit d921c71

File tree

3 files changed

+181
-35
lines changed

3 files changed

+181
-35
lines changed

spring-modulith-observability/src/main/java/org/springframework/modulith/observability/DefaultObservedModule.java

Lines changed: 68 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,17 @@
2121
import org.aopalliance.intercept.MethodInvocation;
2222
import org.springframework.aop.ProxyMethodInvocation;
2323
import org.springframework.aop.framework.Advised;
24+
import org.springframework.aop.support.AopUtils;
2425
import org.springframework.modulith.core.ApplicationModule;
26+
import org.springframework.modulith.core.ApplicationModuleIdentifier;
2527
import org.springframework.modulith.core.ApplicationModules;
28+
import org.springframework.modulith.core.ArchitecturallyEvidentType.ReferenceMethod;
2629
import org.springframework.modulith.core.FormattableType;
2730
import org.springframework.modulith.core.SpringBean;
2831
import org.springframework.util.Assert;
2932

3033
import com.tngtech.archunit.core.domain.JavaClass;
34+
import com.tngtech.archunit.core.domain.JavaMethod;
3135

3236
class DefaultObservedModule implements ObservedModule {
3337

@@ -51,7 +55,16 @@ class DefaultObservedModule implements ObservedModule {
5155
*/
5256
@Override
5357
public String getName() {
54-
return module.getName();
58+
return getIdentifier().toString();
59+
}
60+
61+
/*
62+
* (non-Javadoc)
63+
* @see org.springframework.modulith.observability.ObservedModule#getIdentifier()
64+
*/
65+
@Override
66+
public ApplicationModuleIdentifier getIdentifier() {
67+
return module.getIdentifier();
5568
}
5669

5770
/*
@@ -69,37 +82,7 @@ public String getDisplayName() {
6982
*/
7083
@Override
7184
public String getInvokedMethod(MethodInvocation invocation) {
72-
73-
Method method = invocation.getMethod();
74-
75-
if (module.contains(method.getDeclaringClass())) {
76-
return toString(invocation.getMethod(), module);
77-
}
78-
79-
if (!ProxyMethodInvocation.class.isInstance(invocation)) {
80-
return toString(invocation.getMethod(), module);
81-
}
82-
83-
// For class-based proxies, use the target class
84-
85-
var advised = (Advised) ((ProxyMethodInvocation) invocation).getProxy();
86-
var targetClass = advised.getTargetClass();
87-
88-
if (module.contains(targetClass)) {
89-
return toString(targetClass, method, module);
90-
}
91-
92-
// For JDK proxies, find original interface the method was logically declared on
93-
94-
for (Class<?> type : advised.getProxiedInterfaces()) {
95-
if (module.contains(type)) {
96-
if (Arrays.asList(type.getMethods()).contains(method)) {
97-
return toString(type, method, module);
98-
}
99-
}
100-
}
101-
102-
return toString(invocation.getMethod(), module);
85+
return toString(findModuleLocalMethod(invocation), module);
10386
}
10487

10588
/*
@@ -144,11 +127,61 @@ public ObservedModuleType getObservedModuleType(Class<?> type, ApplicationModule
144127
.orElse(null);
145128
}
146129

147-
private static String toString(Method method, ApplicationModule module) {
148-
return toString(method.getDeclaringClass(), method, module);
130+
/*
131+
* (non-Javadoc)
132+
* @see org.springframework.modulith.observability.ObservedModule#isEventListenerInvocation(org.aopalliance.intercept.MethodInvocation)
133+
*/
134+
@Override
135+
public boolean isEventListenerInvocation(MethodInvocation invocation) {
136+
137+
var method = findModuleLocalMethod(invocation);
138+
var type = module.getArchitecturallyEvidentType(method.getDeclaringClass());
139+
140+
return type.isEventListener()
141+
&& type.getReferenceMethods()
142+
.map(ReferenceMethod::getMethod)
143+
.map(JavaMethod::reflect)
144+
.anyMatch(method::equals);
149145
}
150146

151-
private static String toString(Class<?> type, Method method, ApplicationModule module) {
147+
private Method findModuleLocalMethod(MethodInvocation invocation) {
148+
149+
Method method = invocation.getMethod();
150+
151+
if (module.contains(method.getDeclaringClass())) {
152+
return invocation.getMethod();
153+
}
154+
155+
if (!ProxyMethodInvocation.class.isInstance(invocation)) {
156+
return invocation.getMethod();
157+
}
158+
159+
// For class-based proxies, use the target class
160+
161+
var advised = (Advised) ((ProxyMethodInvocation) invocation).getProxy();
162+
var targetClass = advised.getTargetClass();
163+
164+
if (module.contains(targetClass)) {
165+
166+
return AopUtils.getMostSpecificMethod(method, targetClass);
167+
}
168+
169+
// For JDK proxies, find original interface the method was logically declared on
170+
171+
for (Class<?> type : advised.getProxiedInterfaces()) {
172+
if (module.contains(type)) {
173+
if (Arrays.asList(type.getMethods()).contains(method)) {
174+
return AopUtils.getMostSpecificMethod(method, targetClass);
175+
}
176+
}
177+
}
178+
179+
return invocation.getMethod();
180+
}
181+
182+
private static String toString(Method method, ApplicationModule module) {
183+
184+
var type = method.getDeclaringClass();
152185

153186
var typeName = module.getType(type.getName())
154187
.map(FormattableType::of)

spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ObservedModule.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.aopalliance.intercept.MethodInvocation;
2121
import org.springframework.lang.Nullable;
2222
import org.springframework.modulith.core.ApplicationModule;
23+
import org.springframework.modulith.core.ApplicationModuleIdentifier;
2324
import org.springframework.modulith.core.ApplicationModules;
2425

2526
import com.tngtech.archunit.core.domain.JavaClass;
@@ -29,8 +30,27 @@
2930
*/
3031
interface ObservedModule {
3132

33+
/**
34+
* Returns the name of the application module.
35+
*
36+
* @return will never be {@literal null}.
37+
* @deprecated since 1.3, use {@link #getIdentifier()} instead.
38+
*/
39+
@Deprecated(forRemoval = true)
3240
String getName();
3341

42+
/**
43+
* Returns the {@link ApplicationModuleIdentifier} of the underlying module.
44+
*
45+
* @return will never be {@literal null}.
46+
*/
47+
ApplicationModuleIdentifier getIdentifier();
48+
49+
/**
50+
* Returns the human-readable name of the module.
51+
*
52+
* @return will never be {@literal null}.
53+
*/
3454
String getDisplayName();
3555

3656
/**
@@ -60,4 +80,13 @@ interface ObservedModule {
6080
*/
6181
@Nullable
6282
ObservedModuleType getObservedModuleType(Class<?> type, ApplicationModules modules);
83+
84+
/**
85+
* Returns whether the given {@link MethodInvocation} is the invocation of an event listener as opposed to a standard
86+
* method invocation on a Spring bean.
87+
*
88+
* @param invocation must not be {@literal null}.
89+
* @since 1.3
90+
*/
91+
boolean isEventListenerInvocation(MethodInvocation invocation);
6392
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.modulith.observability;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
20+
import example.sample.ObservedComponent;
21+
22+
import java.lang.reflect.AccessibleObject;
23+
import java.lang.reflect.Method;
24+
25+
import org.aopalliance.intercept.MethodInvocation;
26+
import org.junit.jupiter.api.Test;
27+
import org.springframework.modulith.core.ApplicationModule;
28+
import org.springframework.modulith.core.ApplicationModules;
29+
import org.springframework.modulith.core.ArchitecturallyEvidentType;
30+
import org.springframework.modulith.test.TestApplicationModules;
31+
32+
/**
33+
* Unit tests for {@link DefaultObservedModule}.
34+
*
35+
* @author Oliver Drotbohm
36+
*/
37+
class DefaultObservedModuleUnitTests {
38+
39+
static final ApplicationModules modules = TestApplicationModules.of("example");
40+
41+
ApplicationModule module = modules.getModuleByName("sample").orElseThrow();
42+
ArchitecturallyEvidentType type = module.getArchitecturallyEvidentType(ObservedComponent.class);
43+
ObservedModule observedModule = new DefaultObservedModule(module);
44+
45+
@Test // GH-927
46+
void detectsEventListenerInvocation() throws Exception {
47+
48+
assertThat(observedModule.isEventListenerInvocation(forMethod("on", Object.class))).isTrue();
49+
assertThat(observedModule.isEventListenerInvocation(forMethod("someMethod"))).isFalse();
50+
}
51+
52+
private static MethodInvocation forMethod(String name, Class<?>... parameterTypes) throws Exception {
53+
54+
var method = ObservedComponent.class.getDeclaredMethod(name, parameterTypes);
55+
56+
return new MethodInvocation() {
57+
58+
@Override
59+
public Object proceed() throws Throwable {
60+
return null;
61+
}
62+
63+
@Override
64+
public Object getThis() {
65+
return null;
66+
}
67+
68+
@Override
69+
public AccessibleObject getStaticPart() {
70+
return method;
71+
}
72+
73+
@Override
74+
public Object[] getArguments() {
75+
return new Object[] {};
76+
}
77+
78+
@Override
79+
public Method getMethod() {
80+
return method;
81+
}
82+
};
83+
}
84+
}

0 commit comments

Comments
 (0)