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
This commit is contained in:
parent
39f74b2374
commit
c10d63dc01
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2011 the original author or authors.
|
* Copyright 2002-2012 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -26,6 +26,8 @@ import java.lang.reflect.Type;
|
||||||
import java.lang.reflect.TypeVariable;
|
import java.lang.reflect.TypeVariable;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
@ -37,11 +39,21 @@ import org.springframework.util.Assert;
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @author Rob Harrop
|
* @author Rob Harrop
|
||||||
* @author Andy Clement
|
* @author Andy Clement
|
||||||
|
* @author Nikita Tovstoles
|
||||||
|
* @author Chris Beams
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
* @see GenericCollectionTypeResolver
|
* @see GenericCollectionTypeResolver
|
||||||
*/
|
*/
|
||||||
public class MethodParameter {
|
public class MethodParameter {
|
||||||
|
|
||||||
|
|
||||||
|
private static final Annotation[][] EMPTY_ANNOTATION_MATRIX = new Annotation[0][0];
|
||||||
|
|
||||||
|
private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
|
||||||
|
|
||||||
|
static final ConcurrentMap<Method, Annotation[][]> methodParamAnnotationsCache =
|
||||||
|
new ConcurrentHashMap<Method, Annotation[][]>();
|
||||||
|
|
||||||
private final Method method;
|
private final Method method;
|
||||||
|
|
||||||
private final Constructor constructor;
|
private final Constructor constructor;
|
||||||
|
@ -280,7 +292,7 @@ public class MethodParameter {
|
||||||
public Annotation[] getParameterAnnotations() {
|
public Annotation[] getParameterAnnotations() {
|
||||||
if (this.parameterAnnotations == null) {
|
if (this.parameterAnnotations == null) {
|
||||||
Annotation[][] annotationArray = (this.method != null ?
|
Annotation[][] annotationArray = (this.method != null ?
|
||||||
this.method.getParameterAnnotations() : this.constructor.getParameterAnnotations());
|
getMethodParameterAnnotations(this.method) : this.constructor.getParameterAnnotations());
|
||||||
if (this.parameterIndex >= 0 && this.parameterIndex < annotationArray.length) {
|
if (this.parameterIndex >= 0 && this.parameterIndex < annotationArray.length) {
|
||||||
this.parameterAnnotations = annotationArray[this.parameterIndex];
|
this.parameterAnnotations = annotationArray[this.parameterIndex];
|
||||||
}
|
}
|
||||||
|
@ -439,6 +451,41 @@ public class MethodParameter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the parameter annotations for the given method, retrieving cached values
|
||||||
|
* if a lookup has already been performed for this method, otherwise perform a fresh
|
||||||
|
* lookup and populate the cache with the result before returning. <strong>For
|
||||||
|
* internal use only.</strong>
|
||||||
|
* @param method the method to introspect for parameter annotations
|
||||||
|
*/
|
||||||
|
static Annotation[][] getMethodParameterAnnotations(Method method) {
|
||||||
|
Assert.notNull(method);
|
||||||
|
|
||||||
|
Annotation[][] result = methodParamAnnotationsCache.get(method);
|
||||||
|
if (result == null) {
|
||||||
|
result = method.getParameterAnnotations();
|
||||||
|
|
||||||
|
if(result.length == 0) {
|
||||||
|
result = EMPTY_ANNOTATION_MATRIX;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (int i = 0; i < result.length; i++) {
|
||||||
|
if (result[i].length == 0) {
|
||||||
|
result[i] = EMPTY_ANNOTATION_ARRAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
methodParamAnnotationsCache.put(method, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
//always return deep copy to prevent caller from modifying cache state
|
||||||
|
Annotation[][] resultCopy = new Annotation[result.length][0];
|
||||||
|
for(int i = 0; i < result.length; i++) {
|
||||||
|
resultCopy[i] = result[i].clone();
|
||||||
|
}
|
||||||
|
return resultCopy;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (this == obj) {
|
if (this == obj) {
|
||||||
|
@ -460,7 +507,6 @@ public class MethodParameter {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = this.hash;
|
int result = this.hash;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2011 the original author or authors.
|
* Copyright 2002-2012 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,11 @@
|
||||||
|
|
||||||
package org.springframework.core;
|
package org.springframework.core;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
@ -25,6 +30,7 @@ import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Arjen Poutsma
|
* @author Arjen Poutsma
|
||||||
|
* @author Nikita Tovstoles
|
||||||
*/
|
*/
|
||||||
public class MethodParameterTests {
|
public class MethodParameterTests {
|
||||||
|
|
||||||
|
@ -43,7 +49,6 @@ public class MethodParameterTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEquals() throws NoSuchMethodException {
|
public void testEquals() throws NoSuchMethodException {
|
||||||
assertEquals(stringParameter, stringParameter);
|
assertEquals(stringParameter, stringParameter);
|
||||||
|
@ -77,9 +82,30 @@ public class MethodParameterTests {
|
||||||
assertTrue(longParameter.hashCode() != methodParameter.hashCode());
|
assertTrue(longParameter.hashCode() != methodParameter.hashCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetMethodParamaterAnnotations() {
|
||||||
|
Method method = stringParameter.getMethod();
|
||||||
|
Annotation[][] expectedAnnotations = method.getParameterAnnotations();
|
||||||
|
assertEquals(2, expectedAnnotations.length);
|
||||||
|
assertEquals(DummyAnnotation.class, expectedAnnotations[0][0].annotationType());
|
||||||
|
|
||||||
public int method(String p1, long p2) {
|
//start with empty cache
|
||||||
|
MethodParameter.methodParamAnnotationsCache.clear();
|
||||||
|
|
||||||
|
//check correctness
|
||||||
|
assertArrayEquals(expectedAnnotations, MethodParameter.getMethodParameterAnnotations(method));
|
||||||
|
//check that return value's been cached
|
||||||
|
assertArrayEquals(expectedAnnotations, MethodParameter.methodParamAnnotationsCache.get(method));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int method(@DummyAnnotation String p1, long p2) {
|
||||||
return 42;
|
return 42;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Target(ElementType.PARAMETER)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface DummyAnnotation {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue