Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
package org.springframework.ai.mcp.annotation.spring.scan;

import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
Expand All @@ -33,6 +35,8 @@ public abstract class AbstractAnnotatedMethodBeanPostProcessor extends Annotated

private final AbstractMcpAnnotatedBeans registry;

private final Map<Class<?>, Set<Class<? extends Annotation>>> annotationCache = new ConcurrentHashMap<>();

public AbstractAnnotatedMethodBeanPostProcessor(AbstractMcpAnnotatedBeans registry,
Set<Class<? extends Annotation>> targetAnnotations) {
super(targetAnnotations);
Expand All @@ -44,7 +48,7 @@ public AbstractAnnotatedMethodBeanPostProcessor(AbstractMcpAnnotatedBeans regist
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> beanClass = AopUtils.getTargetClass(bean); // Handle proxied beans
Set<Class<? extends Annotation>> foundAnnotations = scan(beanClass);
Set<Class<? extends Annotation>> foundAnnotations = this.annotationCache.computeIfAbsent(beanClass, this::scan);
// Register the bean if it has any of our target annotations
if (!foundAnnotations.isEmpty()) {
this.registry.addMcpAnnotatedBean(bean, foundAnnotations);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

Expand Down Expand Up @@ -143,6 +144,30 @@ void testCorrectAnnotationsAreCaptured() {
assertTrue(capturedAnnotations.contains(TestAnnotation.class));
}

@Test
void scansUnannotatedClassOnlyOnce() {
var processor = spy(this.processor);
for (int i = 0; i < 100; i++) {
NoAnnotationBean bean = new NoAnnotationBean();
assertSame(bean, processor.postProcessAfterInitialization(bean, "prototypeBean"));
}
verify(processor).scan(NoAnnotationBean.class);
verify(this.registry, never()).addMcpAnnotatedBean(any(), any());
}

@Test
void cachesAnnotationsWhileRegisteringEachBeanInstance() {
var processor = spy(this.processor);
AnnotatedBean first = new AnnotatedBean();
AnnotatedBean second = new AnnotatedBean();
processor.postProcessAfterInitialization(first, "first");
processor.postProcessAfterInitialization(second, "second");

verify(processor).scan(AnnotatedBean.class);
verify(this.registry).addMcpAnnotatedBean(same(first), any());
verify(this.registry).addMcpAnnotatedBean(same(second), any());
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface TestAnnotation {
Expand Down