Browse Source

Efficient type plus annotation comparisons during converter retrieval

Issue: SPR-14926
Issue: SPR-12926
(cherry picked from commit f6b8b84)
pull/1257/head
Juergen Hoeller 9 years ago
parent
commit
cbc512f3be
  1. 9
      spring-context/src/main/java/org/springframework/format/support/FormattingConversionService.java
  2. 92
      spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
  3. 31
      spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java

9
spring-context/src/main/java/org/springframework/format/support/FormattingConversionService.java

@ -231,6 +231,7 @@ public class FormattingConversionService extends GenericConversionService
public AnnotationPrinterConverter(Class<? extends Annotation> annotationType, public AnnotationPrinterConverter(Class<? extends Annotation> annotationType,
AnnotationFormatterFactory<?> annotationFormatterFactory, Class<?> fieldType) { AnnotationFormatterFactory<?> annotationFormatterFactory, Class<?> fieldType) {
this.annotationType = annotationType; this.annotationType = annotationType;
this.annotationFormatterFactory = annotationFormatterFactory; this.annotationFormatterFactory = annotationFormatterFactory;
this.fieldType = fieldType; this.fieldType = fieldType;
@ -284,6 +285,7 @@ public class FormattingConversionService extends GenericConversionService
public AnnotationParserConverter(Class<? extends Annotation> annotationType, public AnnotationParserConverter(Class<? extends Annotation> annotationType,
AnnotationFormatterFactory<?> annotationFormatterFactory, Class<?> fieldType) { AnnotationFormatterFactory<?> annotationFormatterFactory, Class<?> fieldType) {
this.annotationType = annotationType; this.annotationType = annotationType;
this.annotationFormatterFactory = annotationFormatterFactory; this.annotationFormatterFactory = annotationFormatterFactory;
this.fieldType = fieldType; this.fieldType = fieldType;
@ -350,16 +352,13 @@ public class FormattingConversionService extends GenericConversionService
if (this == other) { if (this == other) {
return true; return true;
} }
if (!(other instanceof AnnotationConverterKey)) {
return false;
}
AnnotationConverterKey otherKey = (AnnotationConverterKey) other; AnnotationConverterKey otherKey = (AnnotationConverterKey) other;
return (this.annotation.equals(otherKey.annotation) && this.fieldType.equals(otherKey.fieldType)); return (this.fieldType == otherKey.fieldType && this.annotation.equals(otherKey.annotation));
} }
@Override @Override
public int hashCode() { public int hashCode() {
return (this.annotation.hashCode() + 29 * this.fieldType.hashCode()); return (this.fieldType.hashCode() * 29 + this.annotation.hashCode());
} }
} }

92
spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java

@ -72,7 +72,7 @@ public class TypeDescriptor implements Serializable {
private final ResolvableType resolvableType; private final ResolvableType resolvableType;
private final AnnotatedElement annotatedElement; private final AnnotatedElementAdapter annotatedElement;
/** /**
@ -82,7 +82,6 @@ public class TypeDescriptor implements Serializable {
* @param methodParameter the method parameter * @param methodParameter the method parameter
*/ */
public TypeDescriptor(MethodParameter methodParameter) { public TypeDescriptor(MethodParameter methodParameter) {
Assert.notNull(methodParameter, "MethodParameter must not be null");
this.resolvableType = ResolvableType.forMethodParameter(methodParameter); this.resolvableType = ResolvableType.forMethodParameter(methodParameter);
this.type = this.resolvableType.resolve(methodParameter.getParameterType()); this.type = this.resolvableType.resolve(methodParameter.getParameterType());
this.annotatedElement = new AnnotatedElementAdapter(methodParameter.getParameterIndex() == -1 ? this.annotatedElement = new AnnotatedElementAdapter(methodParameter.getParameterIndex() == -1 ?
@ -95,7 +94,6 @@ public class TypeDescriptor implements Serializable {
* @param field the field * @param field the field
*/ */
public TypeDescriptor(Field field) { public TypeDescriptor(Field field) {
Assert.notNull(field, "Field must not be null");
this.resolvableType = ResolvableType.forField(field); this.resolvableType = ResolvableType.forField(field);
this.type = this.resolvableType.resolve(field.getType()); this.type = this.resolvableType.resolve(field.getType());
this.annotatedElement = new AnnotatedElementAdapter(field.getAnnotations()); this.annotatedElement = new AnnotatedElementAdapter(field.getAnnotations());
@ -121,6 +119,7 @@ public class TypeDescriptor implements Serializable {
* @param resolvableType the resolvable type * @param resolvableType the resolvable type
* @param type the backing type (or {@code null} if it should get resolved) * @param type the backing type (or {@code null} if it should get resolved)
* @param annotations the type annotations * @param annotations the type annotations
* @since 4.0
*/ */
protected TypeDescriptor(ResolvableType resolvableType, Class<?> type, Annotation[] annotations) { protected TypeDescriptor(ResolvableType resolvableType, Class<?> type, Annotation[] annotations) {
this.resolvableType = resolvableType; this.resolvableType = resolvableType;
@ -211,7 +210,7 @@ public class TypeDescriptor implements Serializable {
} }
/** /**
* Returns the name of this type: the fully qualified class name. * Return the name of this type: the fully qualified class name.
*/ */
public String getName() { public String getName() {
return ClassUtils.getQualifiedName(getType()); return ClassUtils.getQualifiedName(getType());
@ -225,7 +224,7 @@ public class TypeDescriptor implements Serializable {
} }
/** /**
* The annotations associated with this type descriptor, if any. * Return the annotations associated with this type descriptor, if any.
* @return the annotations, or an empty array if none * @return the annotations, or an empty array if none
*/ */
public Annotation[] getAnnotations() { public Annotation[] getAnnotations() {
@ -240,6 +239,11 @@ public class TypeDescriptor implements Serializable {
* @return <tt>true</tt> if the annotation is present * @return <tt>true</tt> if the annotation is present
*/ */
public boolean hasAnnotation(Class<? extends Annotation> annotationType) { public boolean hasAnnotation(Class<? extends Annotation> annotationType) {
if (this.annotatedElement.isEmpty()) {
// Shortcut: AnnotatedElementUtils would have to expect AnnotatedElement.getAnnotations()
// to return a copy of the array, whereas we can do it more efficiently here.
return false;
}
return AnnotatedElementUtils.isAnnotated(this.annotatedElement, annotationType); return AnnotatedElementUtils.isAnnotated(this.annotatedElement, annotationType);
} }
@ -251,6 +255,11 @@ public class TypeDescriptor implements Serializable {
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T extends Annotation> T getAnnotation(Class<T> annotationType) { public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
if (this.annotatedElement.isEmpty()) {
// Shortcut: AnnotatedElementUtils would have to expect AnnotatedElement.getAnnotations()
// to return a copy of the array, whereas we can do it more efficiently here.
return null;
}
return AnnotatedElementUtils.getMergedAnnotation(this.annotatedElement, annotationType); return AnnotatedElementUtils.getMergedAnnotation(this.annotatedElement, annotationType);
} }
@ -434,37 +443,51 @@ public class TypeDescriptor implements Serializable {
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object other) {
if (this == obj) { if (this == other) {
return true; return true;
} }
if (!(obj instanceof TypeDescriptor)) { if (!(other instanceof TypeDescriptor)) {
return false; return false;
} }
TypeDescriptor other = (TypeDescriptor) obj; TypeDescriptor otherDesc = (TypeDescriptor) other;
if (!ObjectUtils.nullSafeEquals(this.type, other.type)) { if (getType() != otherDesc.getType()) {
return false; return false;
} }
if (getAnnotations().length != other.getAnnotations().length) { if (!annotationsMatch(otherDesc)) {
return false; return false;
} }
for (Annotation ann : getAnnotations()) {
if (!ann.equals(other.getAnnotation(ann.annotationType()))) {
return false;
}
}
if (isCollection() || isArray()) { if (isCollection() || isArray()) {
return ObjectUtils.nullSafeEquals(getElementTypeDescriptor(), other.getElementTypeDescriptor()); return ObjectUtils.nullSafeEquals(getElementTypeDescriptor(), otherDesc.getElementTypeDescriptor());
} }
else if (isMap()) { else if (isMap()) {
return ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), other.getMapKeyTypeDescriptor()) && return (ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), otherDesc.getMapKeyTypeDescriptor()) &&
ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), other.getMapValueTypeDescriptor()); ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), otherDesc.getMapValueTypeDescriptor()));
} }
else { else {
return true; return true;
} }
} }
private boolean annotationsMatch(TypeDescriptor otherDesc) {
Annotation[] anns = getAnnotations();
Annotation[] otherAnns = otherDesc.getAnnotations();
if (anns == otherAnns) {
return true;
}
if (anns.length != otherAnns.length) {
return false;
}
if (anns.length > 0) {
for (int i = 0; i < anns.length; i++) {
if (anns[i] != otherAnns[i]) {
return false;
}
}
}
return true;
}
@Override @Override
public int hashCode() { public int hashCode() {
return getType().hashCode(); return getType().hashCode();
@ -480,6 +503,20 @@ public class TypeDescriptor implements Serializable {
return builder.toString(); return builder.toString();
} }
/**
* Create a new type descriptor for an object.
* <p>Use this factory method to introspect a source object before asking the
* conversion system to convert it to some another type.
* <p>If the provided object is {@code null}, returns {@code null}, else calls
* {@link #valueOf(Class)} to build a TypeDescriptor from the object's class.
* @param source the source object
* @return the type descriptor
*/
public static TypeDescriptor forObject(Object source) {
return (source != null ? valueOf(source.getClass()) : null);
}
/** /**
* Create a new type descriptor from the given type. * Create a new type descriptor from the given type.
* <p>Use this to instruct the conversion system to convert an object to a * <p>Use this to instruct the conversion system to convert an object to a
@ -640,19 +677,6 @@ public class TypeDescriptor implements Serializable {
return nested(new TypeDescriptor(property), nestingLevel); return nested(new TypeDescriptor(property), nestingLevel);
} }
/**
* Create a new type descriptor for an object.
* <p>Use this factory method to introspect a source object before asking the
* conversion system to convert it to some another type.
* <p>If the provided object is {@code null}, returns {@code null}, else calls
* {@link #valueOf(Class)} to build a TypeDescriptor from the object's class.
* @param source the source object
* @return the type descriptor
*/
public static TypeDescriptor forObject(Object source) {
return (source != null ? valueOf(source.getClass()) : null);
}
private static TypeDescriptor nested(TypeDescriptor typeDescriptor, int nestingLevel) { private static TypeDescriptor nested(TypeDescriptor typeDescriptor, int nestingLevel) {
ResolvableType nested = typeDescriptor.resolvableType; ResolvableType nested = typeDescriptor.resolvableType;
for (int i = 0; i < nestingLevel; i++) { for (int i = 0; i < nestingLevel; i++) {
@ -723,6 +747,10 @@ public class TypeDescriptor implements Serializable {
return getAnnotations(); return getAnnotations();
} }
public boolean isEmpty() {
return ObjectUtils.isEmpty(this.annotations);
}
@Override @Override
public boolean equals(Object other) { public boolean equals(Object other) {
return (this == other || (other instanceof AnnotatedElementAdapter && return (this == other || (other instanceof AnnotatedElementAdapter &&

31
spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2015 the original author or authors. * Copyright 2002-2016 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.
@ -533,7 +533,7 @@ public class TypeDescriptorTests {
public void elementTypePreserveContext() throws Exception { public void elementTypePreserveContext() throws Exception {
TypeDescriptor desc = new TypeDescriptor(getClass().getField("listPreserveContext")); TypeDescriptor desc = new TypeDescriptor(getClass().getField("listPreserveContext"));
assertEquals(Integer.class, desc.getElementTypeDescriptor().getElementTypeDescriptor().getType()); assertEquals(Integer.class, desc.getElementTypeDescriptor().getElementTypeDescriptor().getType());
List<Integer> value = new ArrayList<Integer>(3); List<Integer> value = new ArrayList<>(3);
desc = desc.elementTypeDescriptor(value); desc = desc.elementTypeDescriptor(value);
assertEquals(Integer.class, desc.getElementTypeDescriptor().getType()); assertEquals(Integer.class, desc.getElementTypeDescriptor().getType());
assertNotNull(desc.getAnnotation(FieldAnnotation.class)); assertNotNull(desc.getAnnotation(FieldAnnotation.class));
@ -551,7 +551,7 @@ public class TypeDescriptorTests {
public void mapKeyTypePreserveContext() throws Exception { public void mapKeyTypePreserveContext() throws Exception {
TypeDescriptor desc = new TypeDescriptor(getClass().getField("mapPreserveContext")); TypeDescriptor desc = new TypeDescriptor(getClass().getField("mapPreserveContext"));
assertEquals(Integer.class, desc.getMapKeyTypeDescriptor().getElementTypeDescriptor().getType()); assertEquals(Integer.class, desc.getMapKeyTypeDescriptor().getElementTypeDescriptor().getType());
List<Integer> value = new ArrayList<Integer>(3); List<Integer> value = new ArrayList<>(3);
desc = desc.getMapKeyTypeDescriptor(value); desc = desc.getMapKeyTypeDescriptor(value);
assertEquals(Integer.class, desc.getElementTypeDescriptor().getType()); assertEquals(Integer.class, desc.getElementTypeDescriptor().getType());
assertNotNull(desc.getAnnotation(FieldAnnotation.class)); assertNotNull(desc.getAnnotation(FieldAnnotation.class));
@ -569,7 +569,7 @@ public class TypeDescriptorTests {
public void mapValueTypePreserveContext() throws Exception { public void mapValueTypePreserveContext() throws Exception {
TypeDescriptor desc = new TypeDescriptor(getClass().getField("mapPreserveContext")); TypeDescriptor desc = new TypeDescriptor(getClass().getField("mapPreserveContext"));
assertEquals(Integer.class, desc.getMapValueTypeDescriptor().getElementTypeDescriptor().getType()); assertEquals(Integer.class, desc.getMapValueTypeDescriptor().getElementTypeDescriptor().getType());
List<Integer> value = new ArrayList<Integer>(3); List<Integer> value = new ArrayList<>(3);
desc = desc.getMapValueTypeDescriptor(value); desc = desc.getMapValueTypeDescriptor(value);
assertEquals(Integer.class, desc.getElementTypeDescriptor().getType()); assertEquals(Integer.class, desc.getElementTypeDescriptor().getType());
assertNotNull(desc.getAnnotation(FieldAnnotation.class)); assertNotNull(desc.getAnnotation(FieldAnnotation.class));
@ -598,15 +598,16 @@ public class TypeDescriptorTests {
TypeDescriptor t12 = new TypeDescriptor(getClass().getField("mapField")); TypeDescriptor t12 = new TypeDescriptor(getClass().getField("mapField"));
assertEquals(t11, t12); assertEquals(t11, t12);
TypeDescriptor t13 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0)); MethodParameter testAnnotatedMethod = new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0);
TypeDescriptor t14 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0)); TypeDescriptor t13 = new TypeDescriptor(testAnnotatedMethod);
TypeDescriptor t14 = new TypeDescriptor(testAnnotatedMethod);
assertEquals(t13, t14); assertEquals(t13, t14);
TypeDescriptor t15 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0)); TypeDescriptor t15 = new TypeDescriptor(testAnnotatedMethod);
TypeDescriptor t16 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethodDifferentAnnotationValue", String.class), 0)); TypeDescriptor t16 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethodDifferentAnnotationValue", String.class), 0));
assertNotEquals(t15, t16); assertNotEquals(t15, t16);
TypeDescriptor t17 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0)); TypeDescriptor t17 = new TypeDescriptor(testAnnotatedMethod);
TypeDescriptor t18 = new TypeDescriptor(new MethodParameter(getClass().getMethod("test5", String.class), 0)); TypeDescriptor t18 = new TypeDescriptor(new MethodParameter(getClass().getMethod("test5", String.class), 0));
assertNotEquals(t17, t18); assertNotEquals(t17, t18);
} }
@ -841,19 +842,19 @@ public class TypeDescriptorTests {
public List<String> listOfString; public List<String> listOfString;
public List<List<String>> listOfListOfString = new ArrayList<List<String>>(); public List<List<String>> listOfListOfString = new ArrayList<>();
public List<List> listOfListOfUnknown = new ArrayList<List>(); public List<List> listOfListOfUnknown = new ArrayList<>();
public int[] intArray; public int[] intArray;
public List<String>[] arrayOfListOfString; public List<String>[] arrayOfListOfString;
public List<Integer> listField = new ArrayList<Integer>(); public List<Integer> listField = new ArrayList<>();
public Map<String, Integer> mapField = new HashMap<String, Integer>(); public Map<String, Integer> mapField = new HashMap<>();
public Map<String, List<Integer>> nestedMapField = new HashMap<String, List<Integer>>(); public Map<String, List<Integer>> nestedMapField = new HashMap<>();
public Map<List<Integer>, List<Long>> fieldMap; public Map<List<Integer>, List<Long>> fieldMap;
@ -879,9 +880,9 @@ public class TypeDescriptorTests {
public Map<CharSequence, Number> isAssignableMapKeyValueTypes; public Map<CharSequence, Number> isAssignableMapKeyValueTypes;
public MultiValueMap<String, Integer> multiValueMap = new LinkedMultiValueMap<String, Integer>(); public MultiValueMap<String, Integer> multiValueMap = new LinkedMultiValueMap<>();
public PassDownGeneric<Integer> passDownGeneric = new PassDownGeneric<Integer>(); public PassDownGeneric<Integer> passDownGeneric = new PassDownGeneric<>();
// Classes designed for test introspection // Classes designed for test introspection

Loading…
Cancel
Save