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.InvocationHandler;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
import org.springframework.util.ReflectionUtils;
|
import org.springframework.util.ReflectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link InvocationHandler} for an {@link Annotation} that Spring has
|
* {@link InvocationHandler} for an {@link Annotation} that Spring has
|
||||||
* <em>synthesized</em> (i.e., wrapped in a dynamic proxy) with additional
|
* <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;
|
private final Map<String, String> aliasMap;
|
||||||
|
|
||||||
|
|
||||||
public SynthesizedAnnotationInvocationHandler(AnnotatedElement annotatedElement, Annotation annotation,
|
SynthesizedAnnotationInvocationHandler(AnnotatedElement annotatedElement, Annotation annotation,
|
||||||
Map<String, String> aliasMap) {
|
Map<String, String> aliasMap) {
|
||||||
this.annotatedElement = annotatedElement;
|
this.annotatedElement = annotatedElement;
|
||||||
this.annotation = annotation;
|
this.annotation = annotation;
|
||||||
|
@ -70,7 +71,10 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
||||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||||
int parameterCount = parameterTypes.length;
|
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);
|
return toString(proxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +86,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
||||||
ReflectionUtils.makeAccessible(method);
|
ReflectionUtils.makeAccessible(method);
|
||||||
Object value = ReflectionUtils.invokeMethod(method, this.annotation, args);
|
Object value = ReflectionUtils.invokeMethod(method, this.annotation, args);
|
||||||
|
|
||||||
// Nothing special to do?
|
// No custom processing necessary?
|
||||||
if (!aliasPresent && !nestedAnnotation) {
|
if (!aliasPresent && !nestedAnnotation) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -101,11 +105,12 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
||||||
|
|
||||||
ReflectionUtils.makeAccessible(aliasedMethod);
|
ReflectionUtils.makeAccessible(aliasedMethod);
|
||||||
Object aliasedValue = ReflectionUtils.invokeMethod(aliasedMethod, this.annotation, args);
|
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)
|
if (!ObjectUtils.nullSafeEquals(value, aliasedValue) && !ObjectUtils.nullSafeEquals(value, defaultValue)
|
||||||
&& !ObjectUtils.nullSafeEquals(aliasedValue, 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(
|
String msg = String.format(
|
||||||
"In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] are "
|
"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.",
|
+ "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.
|
// Synthesize nested annotations before returning them.
|
||||||
if (value instanceof Annotation) {
|
if (value instanceof Annotation) {
|
||||||
value = AnnotationUtils.synthesizeAnnotation((Annotation) value, this.annotatedElement);
|
value = synthesizeAnnotation((Annotation) value, this.annotatedElement);
|
||||||
}
|
}
|
||||||
else if (value instanceof Annotation[]) {
|
else if (value instanceof Annotation[]) {
|
||||||
Annotation[] annotations = (Annotation[]) value;
|
Annotation[] annotations = (Annotation[]) value;
|
||||||
for (int i = 0; i < annotations.length; i++) {
|
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;
|
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) {
|
private String toString(Object proxy) {
|
||||||
StringBuilder sb = new StringBuilder("@").append(annotationType.getName()).append("(");
|
StringBuilder sb = new StringBuilder("@").append(annotationType.getName()).append("(");
|
||||||
|
|
||||||
List<Method> attributeMethods = AnnotationUtils.getAttributeMethods(this.annotationType);
|
Iterator<Method> iterator = getAttributeMethods(this.annotationType).iterator();
|
||||||
Iterator<Method> iterator = attributeMethods.iterator();
|
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
Method attributeMethod = iterator.next();
|
Method attributeMethod = iterator.next();
|
||||||
sb.append(attributeMethod.getName());
|
sb.append(attributeMethod.getName());
|
||||||
|
|
|
@ -656,6 +656,44 @@ public class AnnotationUtilsTests {
|
||||||
assertThat(string, endsWith(")"));
|
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
|
* Fully reflection-based test that verifies support for
|
||||||
* {@linkplain AnnotationUtils#synthesizeAnnotation synthesizing annotations}
|
* {@linkplain AnnotationUtils#synthesizeAnnotation synthesizing annotations}
|
||||||
|
|
Loading…
Reference in New Issue