Skip to content

Commit 9f0617a

Browse files
committed
feat(reflection): Introduce MethodMetadata and ReflectionCache for performance optimization
1 parent 6b76fcd commit 9f0617a

File tree

5 files changed

+150
-21
lines changed

5 files changed

+150
-21
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.github.codeboyzhou.mcp.declarative.reflect;
2+
3+
import com.github.codeboyzhou.mcp.declarative.common.Immutable;
4+
import java.lang.reflect.Method;
5+
import java.lang.reflect.Parameter;
6+
import java.util.Objects;
7+
8+
public final class MethodMetadata {
9+
10+
private final Immutable<Method> method;
11+
12+
private final Parameter[] parameters;
13+
14+
private final String methodSignature;
15+
16+
public MethodMetadata(Method method) {
17+
this.method = Immutable.of(method);
18+
this.parameters = method.getParameters();
19+
this.methodSignature = method.toGenericString();
20+
}
21+
22+
public static MethodMetadata of(Method method) {
23+
return new MethodMetadata(method);
24+
}
25+
26+
public Method getMethod() {
27+
return method.get();
28+
}
29+
30+
public Parameter[] getParameters() {
31+
return parameters.clone();
32+
}
33+
34+
public String getMethodSignature() {
35+
return methodSignature;
36+
}
37+
38+
@Override
39+
public boolean equals(Object obj) {
40+
if (this == obj) {
41+
return true;
42+
}
43+
if (obj == null || getClass() != obj.getClass()) {
44+
return false;
45+
}
46+
MethodMetadata that = (MethodMetadata) obj;
47+
return Objects.equals(method, that.method);
48+
}
49+
50+
@Override
51+
public int hashCode() {
52+
return Objects.hash(method);
53+
}
54+
55+
@Override
56+
public String toString() {
57+
return String.format("MethodMetadata{methodSignature=%s}", methodSignature);
58+
}
59+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.github.codeboyzhou.mcp.declarative.reflect;
2+
3+
import java.lang.reflect.Method;
4+
import java.util.concurrent.ConcurrentHashMap;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
8+
public enum ReflectionCache {
9+
INSTANCE;
10+
11+
private static final Logger log = LoggerFactory.getLogger(ReflectionCache.class);
12+
13+
private final ConcurrentHashMap<Method, MethodMetadata> methodCache = new ConcurrentHashMap<>();
14+
15+
public MethodMetadata getMethodMetadata(Method method) {
16+
return methodCache.computeIfAbsent(
17+
method,
18+
m -> {
19+
final String className = m.getDeclaringClass().getName();
20+
log.debug("Caching method metadata for method: {}.{}", className, m.getName());
21+
return MethodMetadata.of(m);
22+
});
23+
}
24+
25+
public boolean isCached(Method method) {
26+
return methodCache.containsKey(method);
27+
}
28+
}

src/main/java/com/github/codeboyzhou/mcp/declarative/server/component/McpServerPromptFactory.java

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import com.github.codeboyzhou.mcp.declarative.annotation.McpPrompt;
44
import com.github.codeboyzhou.mcp.declarative.annotation.McpPromptParam;
5+
import com.github.codeboyzhou.mcp.declarative.reflect.MethodMetadata;
6+
import com.github.codeboyzhou.mcp.declarative.reflect.ReflectionCache;
57
import com.github.codeboyzhou.mcp.declarative.util.ObjectMappers;
68
import com.github.codeboyzhou.mcp.declarative.util.Strings;
79
import com.github.codeboyzhou.mcp.declarative.util.Types;
@@ -22,23 +24,33 @@ public class McpServerPromptFactory
2224

2325
@Override
2426
public McpServerFeatures.SyncPromptSpecification create(Class<?> clazz, Method method) {
27+
// Use reflection cache for performance optimization
28+
MethodMetadata metadata = ReflectionCache.INSTANCE.getMethodMetadata(method);
29+
2530
McpPrompt promptMethod = method.getAnnotation(McpPrompt.class);
2631
final String name = Strings.defaultIfBlank(promptMethod.name(), method.getName());
2732
final String title = resolveComponentAttributeValue(promptMethod.title());
2833
final String description = resolveComponentAttributeValue(promptMethod.description());
29-
List<McpSchema.PromptArgument> promptArguments = createPromptArguments(method);
34+
35+
List<McpSchema.PromptArgument> promptArguments = createPromptArguments(metadata);
3036
McpSchema.Prompt prompt = new McpSchema.Prompt(name, title, description, promptArguments);
31-
log.debug("Registering prompt: {}", ObjectMappers.toJson(prompt));
37+
38+
log.debug(
39+
"Registering prompt: {} (Cached: {})",
40+
ObjectMappers.toJson(prompt),
41+
ReflectionCache.INSTANCE.isCached(method));
42+
3243
return new McpServerFeatures.SyncPromptSpecification(
3344
prompt,
3445
(exchange, request) -> {
3546
Object result;
3647
try {
3748
Object instance = injector.getInstance(clazz);
38-
List<Object> typedValues = asTypedParameterValues(method, request.arguments());
39-
result = method.invoke(instance, typedValues.toArray());
49+
List<Object> typedValues = asTypedParameters(metadata, request.arguments());
50+
// Use cached method for invocation
51+
result = metadata.getMethod().invoke(instance, typedValues.toArray());
4052
} catch (Exception e) {
41-
log.error("Error invoking prompt method", e);
53+
log.error("Error invoking prompt method {}", metadata.getMethodSignature(), e);
4254
result = e + ": " + e.getMessage();
4355
}
4456
McpSchema.Content content = new McpSchema.TextContent(result.toString());
@@ -48,9 +60,10 @@ public McpServerFeatures.SyncPromptSpecification create(Class<?> clazz, Method m
4860
});
4961
}
5062

51-
private List<McpSchema.PromptArgument> createPromptArguments(Method method) {
52-
Parameter[] methodParams = method.getParameters();
63+
private List<McpSchema.PromptArgument> createPromptArguments(MethodMetadata metadata) {
64+
Parameter[] methodParams = metadata.getParameters();
5365
List<McpSchema.PromptArgument> promptArguments = new ArrayList<>(methodParams.length);
66+
5467
for (Parameter param : methodParams) {
5568
if (param.isAnnotationPresent(McpPromptParam.class)) {
5669
McpPromptParam promptParam = param.getAnnotation(McpPromptParam.class);
@@ -61,12 +74,14 @@ private List<McpSchema.PromptArgument> createPromptArguments(Method method) {
6174
promptArguments.add(new McpSchema.PromptArgument(name, title, description, required));
6275
}
6376
}
77+
6478
return promptArguments;
6579
}
6680

67-
private List<Object> asTypedParameterValues(Method method, Map<String, Object> arguments) {
68-
Parameter[] methodParams = method.getParameters();
81+
private List<Object> asTypedParameters(MethodMetadata metadata, Map<String, Object> arguments) {
82+
Parameter[] methodParams = metadata.getParameters();
6983
List<Object> typedValues = new ArrayList<>(methodParams.length);
84+
7085
for (Parameter param : methodParams) {
7186
Object rawValue = null;
7287
if (param.isAnnotationPresent(McpPromptParam.class)) {
@@ -79,6 +94,7 @@ private List<Object> asTypedParameterValues(Method method, Map<String, Object> a
7994
Object typedValue = Types.convert(rawValue, targetType);
8095
typedValues.add(typedValue);
8196
}
97+
8298
return typedValues;
8399
}
84100
}

src/main/java/com/github/codeboyzhou/mcp/declarative/server/component/McpServerResourceFactory.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.github.codeboyzhou.mcp.declarative.server.component;
22

33
import com.github.codeboyzhou.mcp.declarative.annotation.McpResource;
4+
import com.github.codeboyzhou.mcp.declarative.reflect.MethodMetadata;
5+
import com.github.codeboyzhou.mcp.declarative.reflect.ReflectionCache;
46
import com.github.codeboyzhou.mcp.declarative.util.ObjectMappers;
57
import com.github.codeboyzhou.mcp.declarative.util.Strings;
68
import io.modelcontextprotocol.server.McpServerFeatures;
@@ -17,10 +19,14 @@ public class McpServerResourceFactory
1719

1820
@Override
1921
public McpServerFeatures.SyncResourceSpecification create(Class<?> clazz, Method method) {
22+
// Use reflection cache for performance optimization
23+
MethodMetadata metadata = ReflectionCache.INSTANCE.getMethodMetadata(method);
24+
2025
McpResource res = method.getAnnotation(McpResource.class);
2126
final String name = Strings.defaultIfBlank(res.name(), method.getName());
2227
final String title = resolveComponentAttributeValue(res.title());
2328
final String description = resolveComponentAttributeValue(res.description());
29+
2430
McpSchema.Resource resource =
2531
McpSchema.Resource.builder()
2632
.uri(res.uri())
@@ -30,16 +36,22 @@ public McpServerFeatures.SyncResourceSpecification create(Class<?> clazz, Method
3036
.mimeType(res.mimeType())
3137
.annotations(new McpSchema.Annotations(List.of(res.roles()), res.priority()))
3238
.build();
33-
log.debug("Registering resource: {}", ObjectMappers.toJson(resource));
39+
40+
log.debug(
41+
"Registering resource: {} (Cached: {})",
42+
ObjectMappers.toJson(resource),
43+
ReflectionCache.INSTANCE.isCached(method));
44+
3445
return new McpServerFeatures.SyncResourceSpecification(
3546
resource,
3647
(exchange, request) -> {
3748
Object result;
3849
try {
3950
Object instance = injector.getInstance(clazz);
40-
result = method.invoke(instance);
51+
// Use cached method for invocation
52+
result = metadata.getMethod().invoke(instance);
4153
} catch (Exception e) {
42-
log.error("Error invoking resource method", e);
54+
log.error("Error invoking resource method {}", metadata.getMethodSignature(), e);
4355
result = e + ": " + e.getMessage();
4456
}
4557
McpSchema.ResourceContents contents =

src/main/java/com/github/codeboyzhou/mcp/declarative/server/component/McpServerToolFactory.java

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import com.github.codeboyzhou.mcp.declarative.annotation.McpTool;
66
import com.github.codeboyzhou.mcp.declarative.annotation.McpToolParam;
77
import com.github.codeboyzhou.mcp.declarative.enums.JsonSchemaDataType;
8+
import com.github.codeboyzhou.mcp.declarative.reflect.MethodMetadata;
9+
import com.github.codeboyzhou.mcp.declarative.reflect.ReflectionCache;
810
import com.github.codeboyzhou.mcp.declarative.util.ObjectMappers;
911
import com.github.codeboyzhou.mcp.declarative.util.Strings;
1012
import com.github.codeboyzhou.mcp.declarative.util.Types;
@@ -30,19 +32,28 @@ public class McpServerToolFactory
3032

3133
@Override
3234
public McpServerFeatures.SyncToolSpecification create(Class<?> clazz, Method method) {
35+
// Use reflection cache for performance optimization
36+
MethodMetadata metadata = ReflectionCache.INSTANCE.getMethodMetadata(method);
37+
3338
McpTool toolMethod = method.getAnnotation(McpTool.class);
3439
final String name = Strings.defaultIfBlank(toolMethod.name(), method.getName());
3540
final String title = resolveComponentAttributeValue(toolMethod.title());
3641
final String description = resolveComponentAttributeValue(toolMethod.description());
37-
McpSchema.JsonSchema paramSchema = createJsonSchema(method);
42+
43+
McpSchema.JsonSchema paramSchema = createJsonSchema(metadata);
3844
McpSchema.Tool tool =
3945
McpSchema.Tool.builder()
4046
.name(name)
4147
.title(title)
4248
.description(description)
4349
.inputSchema(paramSchema)
4450
.build();
45-
log.debug("Registering tool: {}", ObjectMappers.toJson(tool));
51+
52+
log.debug(
53+
"Registering tool: {} (Cached: {})",
54+
ObjectMappers.toJson(tool),
55+
ReflectionCache.INSTANCE.isCached(method));
56+
4657
return McpServerFeatures.SyncToolSpecification.builder()
4758
.tool(tool)
4859
.callHandler(
@@ -51,10 +62,11 @@ public McpServerFeatures.SyncToolSpecification create(Class<?> clazz, Method met
5162
boolean isError = false;
5263
try {
5364
Object instance = injector.getInstance(clazz);
54-
List<Object> typedValues = asTypedParameterValues(method, request.arguments());
55-
result = method.invoke(instance, typedValues.toArray());
65+
List<Object> typedValues = asTypedParameters(metadata, request.arguments());
66+
// Use cached method for invocation
67+
result = metadata.getMethod().invoke(instance, typedValues.toArray());
5668
} catch (Exception e) {
57-
log.error("Error invoking tool method", e);
69+
log.error("Error invoking tool method {}", metadata.getMethodSignature(), e);
5870
result = e + ": " + e.getMessage();
5971
isError = true;
6072
}
@@ -64,12 +76,12 @@ public McpServerFeatures.SyncToolSpecification create(Class<?> clazz, Method met
6476
.build();
6577
}
6678

67-
private McpSchema.JsonSchema createJsonSchema(Method method) {
79+
private McpSchema.JsonSchema createJsonSchema(MethodMetadata metadata) {
6880
Map<String, Object> properties = new LinkedHashMap<>();
6981
Map<String, Object> definitions = new LinkedHashMap<>();
7082
List<String> required = new ArrayList<>();
7183

72-
Parameter[] methodParams = method.getParameters();
84+
Parameter[] methodParams = metadata.getParameters();
7385
for (Parameter param : methodParams) {
7486
if (param.isAnnotationPresent(McpToolParam.class)) {
7587
McpToolParam toolParam = param.getAnnotation(McpToolParam.class);
@@ -142,9 +154,10 @@ private Map<String, Object> createJsonSchemaDefinition(Class<?> definitionClass)
142154
return definitionJsonSchema;
143155
}
144156

145-
private List<Object> asTypedParameterValues(Method method, Map<String, Object> arguments) {
146-
Parameter[] methodParams = method.getParameters();
157+
private List<Object> asTypedParameters(MethodMetadata metadata, Map<String, Object> arguments) {
158+
Parameter[] methodParams = metadata.getParameters();
147159
List<Object> typedValues = new ArrayList<>(methodParams.length);
160+
148161
for (Parameter param : methodParams) {
149162
Object rawValue = null;
150163
if (param.isAnnotationPresent(McpToolParam.class)) {
@@ -157,6 +170,7 @@ private List<Object> asTypedParameterValues(Method method, Map<String, Object> a
157170
Object typedValue = Types.convert(rawValue, targetType);
158171
typedValues.add(typedValue);
159172
}
173+
160174
return typedValues;
161175
}
162176
}

0 commit comments

Comments
 (0)