diff --git a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java
index 6bb53c23f2..2afcf44e58 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java
@@ -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
* synthesized (i.e., wrapped in a dynamic proxy) with additional
@@ -56,7 +57,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
private final Map aliasMap;
- public SynthesizedAnnotationInvocationHandler(AnnotatedElement annotatedElement, Annotation annotation,
+ SynthesizedAnnotationInvocationHandler(AnnotatedElement annotatedElement, Annotation annotation,
Map 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 attributeMethods = AnnotationUtils.getAttributeMethods(this.annotationType);
- Iterator iterator = attributeMethods.iterator();
+ Iterator iterator = getAttributeMethods(this.annotationType).iterator();
while (iterator.hasNext()) {
Method attributeMethod = iterator.next();
sb.append(attributeMethod.getName());
diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java
index 79f8e6e5cf..556b3bee03 100644
--- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java
+++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java
@@ -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}