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 6bb53c23f2deb7d1d21a3be405045d40d4156c09..2afcf44e589e93b277f15f4948bb1f167301d751 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 79f8e6e5cf032eef6fdba183fc13c3ba38560a84..556b3bee03bcfa33ce856e6f8035c37d9c75b336 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}