Implement equals() for synthesized annotations
Issue: SPR-13065
This commit is contained in:
parent
c622f4c487
commit
7bf609f111
|
@ -21,13 +21,14 @@ import java.lang.reflect.AnnotatedElement;
|
|||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||
|
||||
/**
|
||||
* {@link InvocationHandler} for an {@link Annotation} that Spring has
|
||||
* <em>synthesized</em> (i.e., wrapped in a dynamic proxy) with additional
|
||||
|
@ -56,7 +57,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
|||
private final Map<String, String> aliasMap;
|
||||
|
||||
|
||||
public SynthesizedAnnotationInvocationHandler(AnnotatedElement annotatedElement, Annotation annotation,
|
||||
SynthesizedAnnotationInvocationHandler(AnnotatedElement annotatedElement, Annotation annotation,
|
||||
Map<String, String> aliasMap) {
|
||||
this.annotatedElement = annotatedElement;
|
||||
this.annotation = annotation;
|
||||
|
@ -70,7 +71,10 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
|||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
int parameterCount = parameterTypes.length;
|
||||
|
||||
if ("toString".equals(methodName) && (parameterCount == 0)) {
|
||||
if ("equals".equals(methodName) && (parameterCount == 1) && (parameterTypes[0] == Object.class)) {
|
||||
return equals(proxy, args[0]);
|
||||
}
|
||||
else if ("toString".equals(methodName) && (parameterCount == 0)) {
|
||||
return toString(proxy);
|
||||
}
|
||||
|
||||
|
@ -82,7 +86,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
|||
ReflectionUtils.makeAccessible(method);
|
||||
Object value = ReflectionUtils.invokeMethod(method, this.annotation, args);
|
||||
|
||||
// Nothing special to do?
|
||||
// No custom processing necessary?
|
||||
if (!aliasPresent && !nestedAnnotation) {
|
||||
return value;
|
||||
}
|
||||
|
@ -101,11 +105,12 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
|||
|
||||
ReflectionUtils.makeAccessible(aliasedMethod);
|
||||
Object aliasedValue = ReflectionUtils.invokeMethod(aliasedMethod, this.annotation, args);
|
||||
Object defaultValue = AnnotationUtils.getDefaultValue(this.annotation, methodName);
|
||||
Object defaultValue = getDefaultValue(this.annotation, methodName);
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(value, aliasedValue) && !ObjectUtils.nullSafeEquals(value, defaultValue)
|
||||
&& !ObjectUtils.nullSafeEquals(aliasedValue, defaultValue)) {
|
||||
String elementName = (this.annotatedElement == null ? "unknown element" : this.annotatedElement.toString());
|
||||
String elementName = (this.annotatedElement == null ? "unknown element"
|
||||
: this.annotatedElement.toString());
|
||||
String msg = String.format(
|
||||
"In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] are "
|
||||
+ "declared with values of [%s] and [%s], but only one declaration is permitted.",
|
||||
|
@ -123,23 +128,41 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
|||
|
||||
// Synthesize nested annotations before returning them.
|
||||
if (value instanceof Annotation) {
|
||||
value = AnnotationUtils.synthesizeAnnotation((Annotation) value, this.annotatedElement);
|
||||
value = synthesizeAnnotation((Annotation) value, this.annotatedElement);
|
||||
}
|
||||
else if (value instanceof Annotation[]) {
|
||||
Annotation[] annotations = (Annotation[]) value;
|
||||
for (int i = 0; i < annotations.length; i++) {
|
||||
annotations[i] = AnnotationUtils.synthesizeAnnotation(annotations[i], this.annotatedElement);
|
||||
annotations[i] = synthesizeAnnotation(annotations[i], this.annotatedElement);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private boolean equals(Object proxy, Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!this.annotationType.isInstance(other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Method attributeMethod : getAttributeMethods(this.annotationType)) {
|
||||
Object thisValue = ReflectionUtils.invokeMethod(attributeMethod, proxy);
|
||||
Object otherValue = ReflectionUtils.invokeMethod(attributeMethod, other);
|
||||
if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private String toString(Object proxy) {
|
||||
StringBuilder sb = new StringBuilder("@").append(annotationType.getName()).append("(");
|
||||
|
||||
List<Method> attributeMethods = AnnotationUtils.getAttributeMethods(this.annotationType);
|
||||
Iterator<Method> iterator = attributeMethods.iterator();
|
||||
Iterator<Method> iterator = getAttributeMethods(this.annotationType).iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Method attributeMethod = iterator.next();
|
||||
sb.append(attributeMethod.getName());
|
||||
|
|
|
@ -656,6 +656,44 @@ public class AnnotationUtilsTests {
|
|||
assertThat(string, endsWith(")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsForSynthesizedAnnotations() throws Exception {
|
||||
Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute");
|
||||
WebMapping webMappingWithAliases = methodWithPath.getAnnotation(WebMapping.class);
|
||||
assertNotNull(webMappingWithAliases);
|
||||
|
||||
Method methodWithPathAndValue = WebController.class.getMethod("handleMappedWithSamePathAndValueAttributes");
|
||||
WebMapping webMappingWithPathAndValue = methodWithPathAndValue.getAnnotation(WebMapping.class);
|
||||
assertNotNull(webMappingWithPathAndValue);
|
||||
|
||||
WebMapping synthesizedWebMapping1 = synthesizeAnnotation(webMappingWithAliases);
|
||||
assertNotNull(synthesizedWebMapping1);
|
||||
WebMapping synthesizedWebMapping2 = synthesizeAnnotation(webMappingWithAliases);
|
||||
assertNotNull(synthesizedWebMapping2);
|
||||
|
||||
// Equality amongst standard annotations
|
||||
assertThat(webMappingWithAliases, is(webMappingWithAliases));
|
||||
assertThat(webMappingWithPathAndValue, is(webMappingWithPathAndValue));
|
||||
|
||||
// Inequality amongst standard annotations
|
||||
assertThat(webMappingWithAliases, is(not(webMappingWithPathAndValue)));
|
||||
assertThat(webMappingWithPathAndValue, is(not(webMappingWithAliases)));
|
||||
|
||||
// Equality amongst synthesized annotations
|
||||
assertThat(synthesizedWebMapping1, is(synthesizedWebMapping1));
|
||||
assertThat(synthesizedWebMapping2, is(synthesizedWebMapping2));
|
||||
assertThat(synthesizedWebMapping1, is(synthesizedWebMapping2));
|
||||
assertThat(synthesizedWebMapping2, is(synthesizedWebMapping1));
|
||||
|
||||
// Equality between standard and synthesized annotations
|
||||
assertThat(synthesizedWebMapping1, is(webMappingWithPathAndValue));
|
||||
assertThat(webMappingWithPathAndValue, is(synthesizedWebMapping1));
|
||||
|
||||
// Inequality between standard and synthesized annotations
|
||||
assertThat(synthesizedWebMapping1, is(not(webMappingWithAliases)));
|
||||
assertThat(webMappingWithAliases, is(not(synthesizedWebMapping1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully reflection-based test that verifies support for
|
||||
* {@linkplain AnnotationUtils#synthesizeAnnotation synthesizing annotations}
|
||||
|
|
Loading…
Reference in New Issue