Skip to content

Commit 7bf609f

Browse files
committed
Implement equals() for synthesized annotations
Issue: SPR-13065
1 parent c622f4c commit 7bf609f

File tree

2 files changed

+71
-10
lines changed

2 files changed

+71
-10
lines changed

spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@
2121
import java.lang.reflect.InvocationHandler;
2222
import java.lang.reflect.Method;
2323
import java.util.Iterator;
24-
import java.util.List;
2524
import java.util.Map;
2625

2726
import org.springframework.util.ObjectUtils;
2827
import org.springframework.util.ReflectionUtils;
2928
import org.springframework.util.StringUtils;
3029

30+
import static org.springframework.core.annotation.AnnotationUtils.*;
31+
3132
/**
3233
* {@link InvocationHandler} for an {@link Annotation} that Spring has
3334
* <em>synthesized</em> (i.e., wrapped in a dynamic proxy) with additional
@@ -56,7 +57,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
5657
private final Map<String, String> aliasMap;
5758

5859

59-
public SynthesizedAnnotationInvocationHandler(AnnotatedElement annotatedElement, Annotation annotation,
60+
SynthesizedAnnotationInvocationHandler(AnnotatedElement annotatedElement, Annotation annotation,
6061
Map<String, String> aliasMap) {
6162
this.annotatedElement = annotatedElement;
6263
this.annotation = annotation;
@@ -70,7 +71,10 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
7071
Class<?>[] parameterTypes = method.getParameterTypes();
7172
int parameterCount = parameterTypes.length;
7273

73-
if ("toString".equals(methodName) && (parameterCount == 0)) {
74+
if ("equals".equals(methodName) && (parameterCount == 1) && (parameterTypes[0] == Object.class)) {
75+
return equals(proxy, args[0]);
76+
}
77+
else if ("toString".equals(methodName) && (parameterCount == 0)) {
7478
return toString(proxy);
7579
}
7680

@@ -82,7 +86,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
8286
ReflectionUtils.makeAccessible(method);
8387
Object value = ReflectionUtils.invokeMethod(method, this.annotation, args);
8488

85-
// Nothing special to do?
89+
// No custom processing necessary?
8690
if (!aliasPresent && !nestedAnnotation) {
8791
return value;
8892
}
@@ -101,11 +105,12 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
101105

102106
ReflectionUtils.makeAccessible(aliasedMethod);
103107
Object aliasedValue = ReflectionUtils.invokeMethod(aliasedMethod, this.annotation, args);
104-
Object defaultValue = AnnotationUtils.getDefaultValue(this.annotation, methodName);
108+
Object defaultValue = getDefaultValue(this.annotation, methodName);
105109

106110
if (!ObjectUtils.nullSafeEquals(value, aliasedValue) && !ObjectUtils.nullSafeEquals(value, defaultValue)
107111
&& !ObjectUtils.nullSafeEquals(aliasedValue, defaultValue)) {
108-
String elementName = (this.annotatedElement == null ? "unknown element" : this.annotatedElement.toString());
112+
String elementName = (this.annotatedElement == null ? "unknown element"
113+
: this.annotatedElement.toString());
109114
String msg = String.format(
110115
"In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] are "
111116
+ "declared with values of [%s] and [%s], but only one declaration is permitted.",
@@ -123,23 +128,41 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
123128

124129
// Synthesize nested annotations before returning them.
125130
if (value instanceof Annotation) {
126-
value = AnnotationUtils.synthesizeAnnotation((Annotation) value, this.annotatedElement);
131+
value = synthesizeAnnotation((Annotation) value, this.annotatedElement);
127132
}
128133
else if (value instanceof Annotation[]) {
129134
Annotation[] annotations = (Annotation[]) value;
130135
for (int i = 0; i < annotations.length; i++) {
131-
annotations[i] = AnnotationUtils.synthesizeAnnotation(annotations[i], this.annotatedElement);
136+
annotations[i] = synthesizeAnnotation(annotations[i], this.annotatedElement);
132137
}
133138
}
134139

135140
return value;
136141
}
137142

143+
private boolean equals(Object proxy, Object other) {
144+
if (this == other) {
145+
return true;
146+
}
147+
if (!this.annotationType.isInstance(other)) {
148+
return false;
149+
}
150+
151+
for (Method attributeMethod : getAttributeMethods(this.annotationType)) {
152+
Object thisValue = ReflectionUtils.invokeMethod(attributeMethod, proxy);
153+
Object otherValue = ReflectionUtils.invokeMethod(attributeMethod, other);
154+
if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) {
155+
return false;
156+
}
157+
}
158+
159+
return true;
160+
}
161+
138162
private String toString(Object proxy) {
139163
StringBuilder sb = new StringBuilder("@").append(annotationType.getName()).append("(");
140164

141-
List<Method> attributeMethods = AnnotationUtils.getAttributeMethods(this.annotationType);
142-
Iterator<Method> iterator = attributeMethods.iterator();
165+
Iterator<Method> iterator = getAttributeMethods(this.annotationType).iterator();
143166
while (iterator.hasNext()) {
144167
Method attributeMethod = iterator.next();
145168
sb.append(attributeMethod.getName());

spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,44 @@ private void assertToStringForWebMappingWithPathAndValue(WebMapping webMapping)
656656
assertThat(string, endsWith(")"));
657657
}
658658

659+
@Test
660+
public void equalsForSynthesizedAnnotations() throws Exception {
661+
Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute");
662+
WebMapping webMappingWithAliases = methodWithPath.getAnnotation(WebMapping.class);
663+
assertNotNull(webMappingWithAliases);
664+
665+
Method methodWithPathAndValue = WebController.class.getMethod("handleMappedWithSamePathAndValueAttributes");
666+
WebMapping webMappingWithPathAndValue = methodWithPathAndValue.getAnnotation(WebMapping.class);
667+
assertNotNull(webMappingWithPathAndValue);
668+
669+
WebMapping synthesizedWebMapping1 = synthesizeAnnotation(webMappingWithAliases);
670+
assertNotNull(synthesizedWebMapping1);
671+
WebMapping synthesizedWebMapping2 = synthesizeAnnotation(webMappingWithAliases);
672+
assertNotNull(synthesizedWebMapping2);
673+
674+
// Equality amongst standard annotations
675+
assertThat(webMappingWithAliases, is(webMappingWithAliases));
676+
assertThat(webMappingWithPathAndValue, is(webMappingWithPathAndValue));
677+
678+
// Inequality amongst standard annotations
679+
assertThat(webMappingWithAliases, is(not(webMappingWithPathAndValue)));
680+
assertThat(webMappingWithPathAndValue, is(not(webMappingWithAliases)));
681+
682+
// Equality amongst synthesized annotations
683+
assertThat(synthesizedWebMapping1, is(synthesizedWebMapping1));
684+
assertThat(synthesizedWebMapping2, is(synthesizedWebMapping2));
685+
assertThat(synthesizedWebMapping1, is(synthesizedWebMapping2));
686+
assertThat(synthesizedWebMapping2, is(synthesizedWebMapping1));
687+
688+
// Equality between standard and synthesized annotations
689+
assertThat(synthesizedWebMapping1, is(webMappingWithPathAndValue));
690+
assertThat(webMappingWithPathAndValue, is(synthesizedWebMapping1));
691+
692+
// Inequality between standard and synthesized annotations
693+
assertThat(synthesizedWebMapping1, is(not(webMappingWithAliases)));
694+
assertThat(webMappingWithAliases, is(not(synthesizedWebMapping1)));
695+
}
696+
659697
/**
660698
* Fully reflection-based test that verifies support for
661699
* {@linkplain AnnotationUtils#synthesizeAnnotation synthesizing annotations}

0 commit comments

Comments
 (0)