Skip to content

Commit 59ecd49

Browse files
committed
Optimize toString() for synthesized annotations
This commit moves the toString() implementation for synthesized annotations from TypeMappedAnnotation to SynthesizedMergedAnnotationInvocationHandler in order to take advantage of the synthesized annotation attribute value cache introduced in 72b1abd. Closes gh-24970
1 parent db45b80 commit 59ecd49

File tree

2 files changed

+43
-47
lines changed

2 files changed

+43
-47
lines changed

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

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
5858
@Nullable
5959
private volatile Integer hashCode;
6060

61+
@Nullable
62+
private volatile String string;
63+
6164

6265
private SynthesizedMergedAnnotationInvocationHandler(MergedAnnotation<A> annotation, Class<A> type) {
6366
Assert.notNull(annotation, "MergedAnnotation must not be null");
@@ -78,7 +81,7 @@ public Object invoke(Object proxy, Method method, Object[] args) {
7881
return annotationHashCode();
7982
}
8083
if (ReflectionUtils.isToStringMethod(method)) {
81-
return this.annotation.toString();
84+
return annotationToString();
8285
}
8386
if (isAnnotationTypeMethod(method)) {
8487
return this.type;
@@ -171,6 +174,44 @@ private int getValueHashCode(Object value) {
171174
return value.hashCode();
172175
}
173176

177+
private String annotationToString() {
178+
String string = this.string;
179+
if (string == null) {
180+
StringBuilder builder = new StringBuilder("@").append(this.type.getName()).append("(");
181+
for (int i = 0; i < this.attributes.size(); i++) {
182+
Method attribute = this.attributes.get(i);
183+
if (i > 0) {
184+
builder.append(", ");
185+
}
186+
builder.append(attribute.getName());
187+
builder.append("=");
188+
builder.append(toString(getAttributeValue(attribute)));
189+
}
190+
builder.append(")");
191+
string = builder.toString();
192+
this.string = string;
193+
}
194+
return string;
195+
}
196+
197+
private String toString(Object value) {
198+
if (value instanceof Class) {
199+
return ((Class<?>) value).getName();
200+
}
201+
if (value.getClass().isArray()) {
202+
StringBuilder builder = new StringBuilder("[");
203+
for (int i = 0; i < Array.getLength(value); i++) {
204+
if (i > 0) {
205+
builder.append(", ");
206+
}
207+
builder.append(toString(Array.get(value, i)));
208+
}
209+
builder.append("]");
210+
return builder.toString();
211+
}
212+
return String.valueOf(value);
213+
}
214+
174215
private Object getAttributeValue(Method method) {
175216
Object value = this.valueCache.computeIfAbsent(method.getName(), attributeName -> {
176217
Class<?> type = ClassUtils.resolvePrimitiveIfNecessary(method.getReturnType());
@@ -188,7 +229,7 @@ private Object getAttributeValue(Method method) {
188229
}
189230

190231
/**
191-
* Clone the provided array, ensuring that original component type is retained.
232+
* Clone the provided array, ensuring that the original component type is retained.
192233
* @param array the array to clone
193234
*/
194235
private Object cloneArray(Object array) {

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

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,6 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
110110

111111
private final int[] resolvedMirrors;
112112

113-
@Nullable
114-
private String string;
115-
116113

117114
private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable ClassLoader classLoader,
118115
@Nullable Object source, @Nullable Object rootAttributes, ValueExtractor valueExtractor,
@@ -346,48 +343,6 @@ private boolean isSynthesizable() {
346343
return this.mapping.isSynthesizable();
347344
}
348345

349-
@Override
350-
public String toString() {
351-
String string = this.string;
352-
if (string == null) {
353-
StringBuilder builder = new StringBuilder();
354-
builder.append("@");
355-
builder.append(getType().getName());
356-
builder.append("(");
357-
for (int i = 0; i < this.mapping.getAttributes().size(); i++) {
358-
Method attribute = this.mapping.getAttributes().get(i);
359-
builder.append(i == 0 ? "" : ", ");
360-
builder.append(attribute.getName());
361-
builder.append("=");
362-
builder.append(toString(getValue(i, Object.class)));
363-
}
364-
builder.append(")");
365-
string = builder.toString();
366-
this.string = string;
367-
}
368-
return string;
369-
}
370-
371-
private Object toString(@Nullable Object value) {
372-
if (value == null) {
373-
return "";
374-
}
375-
if (value instanceof Class) {
376-
return ((Class<?>) value).getName();
377-
}
378-
if (value.getClass().isArray()) {
379-
StringBuilder builder = new StringBuilder();
380-
builder.append("[");
381-
for (int i = 0; i < Array.getLength(value); i++) {
382-
builder.append(i == 0 ? "" : ", ");
383-
builder.append(toString(Array.get(value, i)));
384-
}
385-
builder.append("]");
386-
return builder.toString();
387-
}
388-
return String.valueOf(value);
389-
}
390-
391346
@Override
392347
@Nullable
393348
protected <T> T getAttributeValue(String attributeName, Class<T> type) {

0 commit comments

Comments
 (0)