Skip to content

Commit c10d63d

Browse files
dukehoopscbeams
authored andcommitted
Cache MethodParameter annotation lookup results
Prior to this change, Spring's MethodParameter#getParameterAnnotations called java.lang.Method#getParameterAnnotations on every invocation. The latter ends up contending for a monitor inside (Sun) JDK code. This is problematic when dealing with the high number of @RequestMapping invocations that can occur in a Spring MVC @controller. This commit eliminates this contention by caching values returned by java.lang.Method#getParameterAnnotations in a static ConcurrentMap. Note that only Method parameter annotations are cached, while Constructor parameter annotations are not. This is because the issue of primary concern is, as mentioned above, @RequestMapping methods. By nature, constructors are called much more infrequently, and in most cases in a single-threaded fashion. Issue: SPR-9298
1 parent 39f74b2 commit c10d63d

File tree

2 files changed

+78
-6
lines changed

2 files changed

+78
-6
lines changed

spring-core/src/main/java/org/springframework/core/MethodParameter.java

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,8 @@
2626
import java.lang.reflect.TypeVariable;
2727
import java.util.HashMap;
2828
import java.util.Map;
29+
import java.util.concurrent.ConcurrentHashMap;
30+
import java.util.concurrent.ConcurrentMap;
2931

3032
import org.springframework.util.Assert;
3133

@@ -37,11 +39,21 @@
3739
* @author Juergen Hoeller
3840
* @author Rob Harrop
3941
* @author Andy Clement
42+
* @author Nikita Tovstoles
43+
* @author Chris Beams
4044
* @since 2.0
4145
* @see GenericCollectionTypeResolver
4246
*/
4347
public class MethodParameter {
4448

49+
50+
private static final Annotation[][] EMPTY_ANNOTATION_MATRIX = new Annotation[0][0];
51+
52+
private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
53+
54+
static final ConcurrentMap<Method, Annotation[][]> methodParamAnnotationsCache =
55+
new ConcurrentHashMap<Method, Annotation[][]>();
56+
4557
private final Method method;
4658

4759
private final Constructor constructor;
@@ -280,7 +292,7 @@ public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) {
280292
public Annotation[] getParameterAnnotations() {
281293
if (this.parameterAnnotations == null) {
282294
Annotation[][] annotationArray = (this.method != null ?
283-
this.method.getParameterAnnotations() : this.constructor.getParameterAnnotations());
295+
getMethodParameterAnnotations(this.method) : this.constructor.getParameterAnnotations());
284296
if (this.parameterIndex >= 0 && this.parameterIndex < annotationArray.length) {
285297
this.parameterAnnotations = annotationArray[this.parameterIndex];
286298
}
@@ -439,6 +451,41 @@ else if (methodOrConstructor instanceof Constructor) {
439451
}
440452
}
441453

454+
/**
455+
* Return the parameter annotations for the given method, retrieving cached values
456+
* if a lookup has already been performed for this method, otherwise perform a fresh
457+
* lookup and populate the cache with the result before returning. <strong>For
458+
* internal use only.</strong>
459+
* @param method the method to introspect for parameter annotations
460+
*/
461+
static Annotation[][] getMethodParameterAnnotations(Method method) {
462+
Assert.notNull(method);
463+
464+
Annotation[][] result = methodParamAnnotationsCache.get(method);
465+
if (result == null) {
466+
result = method.getParameterAnnotations();
467+
468+
if(result.length == 0) {
469+
result = EMPTY_ANNOTATION_MATRIX;
470+
}
471+
else {
472+
for (int i = 0; i < result.length; i++) {
473+
if (result[i].length == 0) {
474+
result[i] = EMPTY_ANNOTATION_ARRAY;
475+
}
476+
}
477+
}
478+
methodParamAnnotationsCache.put(method, result);
479+
}
480+
481+
//always return deep copy to prevent caller from modifying cache state
482+
Annotation[][] resultCopy = new Annotation[result.length][0];
483+
for(int i = 0; i < result.length; i++) {
484+
resultCopy[i] = result[i].clone();
485+
}
486+
return resultCopy;
487+
}
488+
442489
@Override
443490
public boolean equals(Object obj) {
444491
if (this == obj) {
@@ -460,7 +507,6 @@ else if (this.getMember().equals(other.getMember())) {
460507
return false;
461508
}
462509

463-
464510
@Override
465511
public int hashCode() {
466512
int result = this.hash;

spring-core/src/test/java/org/springframework/core/MethodParameterTests.java

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,11 @@
1616

1717
package org.springframework.core;
1818

19+
import java.lang.annotation.Annotation;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
1924
import java.lang.reflect.Method;
2025

2126
import org.junit.Before;
@@ -25,6 +30,7 @@
2530

2631
/**
2732
* @author Arjen Poutsma
33+
* @author Nikita Tovstoles
2834
*/
2935
public class MethodParameterTests {
3036

@@ -43,7 +49,6 @@ public void setUp() throws NoSuchMethodException {
4349
}
4450

4551

46-
4752
@Test
4853
public void testEquals() throws NoSuchMethodException {
4954
assertEquals(stringParameter, stringParameter);
@@ -77,9 +82,30 @@ public void testHashCode() throws NoSuchMethodException {
7782
assertTrue(longParameter.hashCode() != methodParameter.hashCode());
7883
}
7984

85+
@Test
86+
public void testGetMethodParamaterAnnotations() {
87+
Method method = stringParameter.getMethod();
88+
Annotation[][] expectedAnnotations = method.getParameterAnnotations();
89+
assertEquals(2, expectedAnnotations.length);
90+
assertEquals(DummyAnnotation.class, expectedAnnotations[0][0].annotationType());
91+
92+
//start with empty cache
93+
MethodParameter.methodParamAnnotationsCache.clear();
94+
95+
//check correctness
96+
assertArrayEquals(expectedAnnotations, MethodParameter.getMethodParameterAnnotations(method));
97+
//check that return value's been cached
98+
assertArrayEquals(expectedAnnotations, MethodParameter.methodParamAnnotationsCache.get(method));
99+
}
100+
80101

81-
public int method(String p1, long p2) {
102+
public int method(@DummyAnnotation String p1, long p2) {
82103
return 42;
83104
}
84105

106+
@Target(ElementType.PARAMETER)
107+
@Retention(RetentionPolicy.RUNTIME)
108+
public @interface DummyAnnotation {
109+
110+
}
85111
}

0 commit comments

Comments
 (0)