Skip to content

Commit

Permalink
GH-936 - Detect methods (meta-)annotated with @MessageMapping as modu…
Browse files Browse the repository at this point in the history
…le entry points.

We now consider all methods that are (meta-)annotated with Spring Messaging's @MessageMapping which is consistently used in a lot of broker annotations such as @(Rabbit|Kafka)Listener etc.
  • Loading branch information
odrotbohm committed Nov 19, 2024
1 parent a199bfc commit 4218dff
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,24 @@
import com.tngtech.archunit.lang.ArchRule;

/**
* Utility to deal with a variety of types.
*
* @author Oliver Drotbohm
*/
class Types {
public class Types {

/**
* Loads the class with the given name if present on the classpath.
*
* @param <T> the type to be loaded
* @param name the fully-qualified name of the type to be loaded, must not be {@literal null} or empty.
* @return can be {@literal null}.
*/
@Nullable
@SuppressWarnings("unchecked")
static <T> Class<T> loadIfPresent(String name) {
public static <T> Class<T> loadIfPresent(String name) {

Assert.hasText(name, "Name must not be null or empty!");

ClassLoader loader = Types.class.getClassLoader();

Expand Down
6 changes: 6 additions & 0 deletions spring-modulith-observability/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,19 @@
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import com.tngtech.archunit.core.domain.JavaClass;

/**
* Represents a type in an {@link ObservedModule}.
*
* @author Oliver Drotbohm
*/
class ObservedModuleType {

private static Collection<Class<?>> IGNORED_TYPES = List.of(Advised.class, TargetClassAware.class);
private static Predicate<Method> IS_USER_METHOD = it -> !Modifier.isPrivate(it.getModifiers())
private static final Collection<Class<?>> IGNORED_TYPES = List.of(Advised.class, TargetClassAware.class);
private static final Predicate<Method> IS_USER_METHOD = it -> !Modifier.isPrivate(it.getModifiers())
&& !(ReflectionUtils.isObjectMethod(it) || IGNORED_TYPES.contains(it.getDeclaringClass()));
private static final String MESSAGE_MAPPING_ANNOTATION = "org.springframework.messaging.handler.annotation.MessageMapping";

private final ApplicationModules modules;
private final ObservedModule module;
Expand Down Expand Up @@ -75,18 +78,19 @@ class ObservedModuleType {
/**
* Returns whether the type should be observed at all. Can be skipped for types not exposed by the module unless they
* listen to events of other modules.
*
* @return
*/
public boolean shouldBeObserved() {

if (type.getType().isMetaAnnotatedWith(Configuration.class)) {
var javaType = type.getType();

if (javaType.isMetaAnnotatedWith(Configuration.class)) {
return false;
}

return type.isController()
|| listensToOtherModulesEvents()
|| module.exposes(type.getType());
|| module.exposes(javaType)
|| hasMethodWithMessageMappingAnnotation(javaType);
}

/**
Expand Down Expand Up @@ -115,4 +119,19 @@ private boolean listensToOtherModulesEvents() {
.map(it -> !module.isObservedModule(it))
.orElse(true);
}

/**
* Returns whether the given type contains a method (meta-)annotated with {@code MessageMapping}.
*
* @param type must not be {@literal null}.
*/
private static boolean hasMethodWithMessageMappingAnnotation(JavaClass type) {

Assert.notNull(type, "Type must not be null!");

return MESSAGE_MAPPING_ANNOTATION != null
&& type.getMethods()
.stream()
.anyMatch(it -> it.isMetaAnnotatedWith(MESSAGE_MAPPING_ANNOTATION));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 example.sample;

import org.springframework.stereotype.Component;

/**
* @author Oliver Drotbohm
*/
@Component
class SampleMessageListener {

@TestListener
void listener() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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 example.sample;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import org.springframework.messaging.handler.annotation.MessageMapping;

/**
* @author Oliver Drotbohm
*/
@Retention(RUNTIME)
@Target(METHOD)
@MessageMapping
@interface TestListener {}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.springframework.modulith.core.ApplicationModule;
import org.springframework.modulith.core.ApplicationModules;
import org.springframework.modulith.core.ArchitecturallyEvidentType;
import org.springframework.modulith.core.Types;
import org.springframework.modulith.test.TestApplicationModules;
import org.springframework.util.ReflectionUtils;

Expand Down Expand Up @@ -69,4 +70,15 @@ void doesNotObserveConfigurationClasses() {

assertThat(observedType.shouldBeObserved()).isFalse();
}

@Test // GH-936
void exposesMessageListenerMethodsForObservation() {

var type = Types.loadIfPresent("example.sample.SampleMessageListener");

var architecturallyEvidentType = module.getArchitecturallyEvidentType(type);
var moduleType = new ObservedModuleType(modules, new DefaultObservedModule(module), architecturallyEvidentType);

assertThat(moduleType.shouldBeObserved()).isTrue();
}
}

0 comments on commit 4218dff

Please sign in to comment.