Browse Source

Implement toString() for synthesized annotations

Issue: SPR-13064
pull/808/head
Sam Brannen 11 years ago
parent
commit
1e50d8d5c2
  1. 2
      spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
  2. 45
      spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java
  3. 79
      spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java

2
spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java

@ -827,7 +827,6 @@ public abstract class AnnotationUtils { @@ -827,7 +827,6 @@ public abstract class AnnotationUtils {
AnnotationAttributes attrs = new AnnotationAttributes(annotationType);
for (Method method : getAttributeMethods(annotationType)) {
try {
ReflectionUtils.makeAccessible(method);
Object value = method.invoke(annotation);
Object defaultValue = method.getDefaultValue();
@ -1274,6 +1273,7 @@ public abstract class AnnotationUtils { @@ -1274,6 +1273,7 @@ public abstract class AnnotationUtils {
List<Method> methods = new ArrayList<Method>();
for (Method method : annotationType.getDeclaredMethods()) {
if ((method.getParameterTypes().length == 0) && (method.getReturnType() != void.class)) {
ReflectionUtils.makeAccessible(method);
methods.add(method);
}
}

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

@ -20,10 +20,13 @@ import java.lang.annotation.Annotation; @@ -20,10 +20,13 @@ import java.lang.annotation.Annotation;
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;
/**
* {@link InvocationHandler} for an {@link Annotation} that Spring has
@ -38,6 +41,7 @@ import org.springframework.util.ReflectionUtils; @@ -38,6 +41,7 @@ import org.springframework.util.ReflectionUtils;
*
* @author Sam Brannen
* @since 4.2
* @see Annotation
* @see AliasFor
* @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
*/
@ -62,10 +66,16 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler { @@ -62,10 +66,16 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String attributeName = method.getName();
String methodName = method.getName();
int parameterCount = method.getParameterCount();
if ("toString".equals(methodName) && (parameterCount == 0)) {
return toString(proxy);
}
Class<?> returnType = method.getReturnType();
boolean nestedAnnotation = (Annotation[].class.isAssignableFrom(returnType) || Annotation.class.isAssignableFrom(returnType));
String aliasedAttributeName = aliasMap.get(attributeName);
String aliasedAttributeName = aliasMap.get(methodName);
boolean aliasPresent = (aliasedAttributeName != null);
ReflectionUtils.makeAccessible(method);
@ -83,14 +93,14 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler { @@ -83,14 +93,14 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
}
catch (NoSuchMethodException e) {
String msg = String.format("In annotation [%s], attribute [%s] is declared as an @AliasFor [%s], "
+ "but attribute [%s] does not exist.", this.annotationType.getName(), attributeName,
+ "but attribute [%s] does not exist.", this.annotationType.getName(), methodName,
aliasedAttributeName, aliasedAttributeName);
throw new AnnotationConfigurationException(msg);
}
ReflectionUtils.makeAccessible(aliasedMethod);
Object aliasedValue = ReflectionUtils.invokeMethod(aliasedMethod, this.annotation, args);
Object defaultValue = AnnotationUtils.getDefaultValue(this.annotation, attributeName);
Object defaultValue = AnnotationUtils.getDefaultValue(this.annotation, methodName);
if (!ObjectUtils.nullSafeEquals(value, aliasedValue) && !ObjectUtils.nullSafeEquals(value, defaultValue)
&& !ObjectUtils.nullSafeEquals(aliasedValue, defaultValue)) {
@ -98,7 +108,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler { @@ -98,7 +108,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
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.",
this.annotationType.getName(), elementName, attributeName, aliasedAttributeName,
this.annotationType.getName(), elementName, methodName, aliasedAttributeName,
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue));
throw new AnnotationConfigurationException(msg);
}
@ -124,4 +134,29 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler { @@ -124,4 +134,29 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
return value;
}
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();
while (iterator.hasNext()) {
Method attributeMethod = iterator.next();
sb.append(attributeMethod.getName());
sb.append('=');
sb.append(valueToString(ReflectionUtils.invokeMethod(attributeMethod, proxy)));
sb.append(iterator.hasNext() ? ", " : "");
}
return sb.append(")").toString();
}
private String valueToString(Object value) {
if (value instanceof Object[]) {
return "[" + StringUtils.arrayToDelimitedString((Object[]) value, ", ") + "]";
}
// else
return String.valueOf(value);
}
}

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

@ -413,7 +413,7 @@ public class AnnotationUtilsTests { @@ -413,7 +413,7 @@ public class AnnotationUtilsTests {
assertEquals("value attribute: ", "/test", attributes.getString(VALUE));
assertEquals("path attribute: ", "/test", attributes.getString("path"));
method = WebController.class.getMethod("handleMappedWithPathValueAndAttributes");
method = WebController.class.getMethod("handleMappedWithDifferentPathAndValueAttributes");
webMapping = method.getAnnotation(WebMapping.class);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(containsString("attribute [value] and its alias [path]"));
@ -600,14 +600,60 @@ public class AnnotationUtilsTests { @@ -600,14 +600,60 @@ public class AnnotationUtilsTests {
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
WebMapping webMapping = method.getAnnotation(WebMapping.class);
assertNotNull(webMapping);
WebMapping synthesizedWebMapping = synthesizeAnnotation(webMapping);
assertNotSame(webMapping, synthesizedWebMapping);
assertThat(synthesizedWebMapping, instanceOf(SynthesizedAnnotation.class));
assertNotNull(synthesizedWebMapping);
assertEquals("name attribute: ", "foo", synthesizedWebMapping.name());
assertEquals("aliased path attribute: ", "/test", synthesizedWebMapping.path());
assertEquals("actual value attribute: ", "/test", synthesizedWebMapping.value());
WebMapping synthesizedWebMapping1 = synthesizeAnnotation(webMapping);
assertNotNull(synthesizedWebMapping1);
assertNotSame(webMapping, synthesizedWebMapping1);
assertThat(synthesizedWebMapping1, instanceOf(SynthesizedAnnotation.class));
assertEquals("name attribute: ", "foo", synthesizedWebMapping1.name());
assertEquals("aliased path attribute: ", "/test", synthesizedWebMapping1.path());
assertEquals("actual value attribute: ", "/test", synthesizedWebMapping1.value());
WebMapping synthesizedWebMapping2 = synthesizeAnnotation(webMapping);
assertNotNull(synthesizedWebMapping2);
assertNotSame(webMapping, synthesizedWebMapping2);
assertThat(synthesizedWebMapping2, instanceOf(SynthesizedAnnotation.class));
assertEquals("name attribute: ", "foo", synthesizedWebMapping2.name());
assertEquals("aliased path attribute: ", "/test", synthesizedWebMapping2.path());
assertEquals("actual value attribute: ", "/test", synthesizedWebMapping2.value());
}
@Test
public void toStringForSynthesizedAnnotations() 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);
assertThat(webMappingWithAliases.toString(), is(not(synthesizedWebMapping1.toString())));
// The unsynthesized annotation for handleMappedWithSamePathAndValueAttributes()
// should produce the same toString() results as synthesized annotations for
// handleMappedWithPathAttribute()
assertToStringForWebMappingWithPathAndValue(webMappingWithPathAndValue);
assertToStringForWebMappingWithPathAndValue(synthesizedWebMapping1);
assertToStringForWebMappingWithPathAndValue(synthesizedWebMapping2);
}
private void assertToStringForWebMappingWithPathAndValue(WebMapping webMapping) {
String string = webMapping.toString();
assertThat(string, startsWith("@" + WebMapping.class.getName() + "("));
assertThat(string, containsString("value=/test"));
assertThat(string, containsString("path=/test"));
assertThat(string, containsString("name=bar"));
assertThat(string, containsString("method="));
assertThat(string, either(containsString("[GET, POST]")).or(containsString("[POST, GET]")));
assertThat(string, endsWith(")"));
}
/**
@ -942,6 +988,10 @@ public class AnnotationUtilsTests { @@ -942,6 +988,10 @@ public class AnnotationUtilsTests {
void foo();
}
enum RequestMethod {
GET, POST
}
/**
* Mock of {@code org.springframework.web.bind.annotation.RequestMapping}.
*/
@ -955,6 +1005,8 @@ public class AnnotationUtilsTests { @@ -955,6 +1005,8 @@ public class AnnotationUtilsTests {
@AliasFor(attribute = "value")
String path() default "";
RequestMethod[] method() default {};
}
@Component("webController")
@ -964,12 +1016,19 @@ public class AnnotationUtilsTests { @@ -964,12 +1016,19 @@ public class AnnotationUtilsTests {
public void handleMappedWithValueAttribute() {
}
@WebMapping(path = "/test", name = "bar")
@WebMapping(path = "/test", name = "bar", method = { RequestMethod.GET, RequestMethod.POST })
public void handleMappedWithPathAttribute() {
}
/**
* mapping is logically "equal" to handleMappedWithPathAttribute().
*/
@WebMapping(value = "/test", path = "/test", name = "bar", method = { RequestMethod.GET, RequestMethod.POST })
public void handleMappedWithSamePathAndValueAttributes() {
}
@WebMapping(value = "/enigma", path = "/test", name = "baz")
public void handleMappedWithPathValueAndAttributes() {
public void handleMappedWithDifferentPathAndValueAttributes() {
}
}

Loading…
Cancel
Save