Skip to content

Commit 0bbf0ae

Browse files
committed
DATACMNS-282 - Improved caching in AnnotationBasedPersistentProperty.
AnnotationBasedPersistentProperty now caches direct annotations on construction but still tries to lookup an annotation as meta-annotation if not found in cache on later requests. Extended try/catch block in AbstractMappingContext.addPersistentEntity(…) to invalidate cache on exceptions during property creation as well.
1 parent 0bbe567 commit 0bbf0ae

File tree

3 files changed

+117
-4
lines changed

3 files changed

+117
-4
lines changed

src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,11 +278,12 @@ protected E addPersistentEntity(TypeInformation<?> typeInformation) {
278278
descriptors.put(descriptor.getName(), descriptor);
279279
}
280280

281-
ReflectionUtils.doWithFields(type, new PersistentPropertyCreator(entity, descriptors),
282-
PersistentFieldFilter.INSTANCE);
283-
284281
try {
282+
283+
ReflectionUtils.doWithFields(type, new PersistentPropertyCreator(entity, descriptors),
284+
PersistentFieldFilter.INSTANCE);
285285
entity.verify();
286+
286287
} catch (MappingException e) {
287288
persistentEntities.remove(typeInformation);
288289
throw e;

src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,49 @@ public AnnotationBasedPersistentProperty(Field field, PropertyDescriptor propert
5959
PersistentEntity<?, P> owner, SimpleTypeHolder simpleTypeHolder) {
6060

6161
super(field, propertyDescriptor, owner, simpleTypeHolder);
62+
populateAnnotationCache(field);
6263
this.value = findAnnotation(Value.class);
6364
}
6465

66+
/**
67+
* Populates the annotation cache by eagerly accessing the annotations directly annotated to the accessors (if
68+
* available) and the backing field. Annotations override annotations found on field.
69+
*
70+
* @param field
71+
* @throws MappingException in case we find an ambiguous mapping on the accessor methods
72+
*/
73+
private final void populateAnnotationCache(Field field) {
74+
75+
for (Method method : Arrays.asList(getGetter(), getSetter())) {
76+
77+
if (method == null) {
78+
continue;
79+
}
80+
81+
for (Annotation annotation : method.getAnnotations()) {
82+
83+
Class<? extends Annotation> annotationType = annotation.annotationType();
84+
85+
if (annotationCache.containsKey(annotationType)) {
86+
throw new MappingException(String.format("Ambiguous mapping! Annotation %s configured "
87+
+ "multiple times on accessor methods of property %s in class %s!", annotationType, getName(), getOwner()
88+
.getType().getName()));
89+
}
90+
91+
annotationCache.put(annotationType, annotation);
92+
}
93+
}
94+
95+
for (Annotation annotation : field.getAnnotations()) {
96+
97+
Class<? extends Annotation> annotationType = annotation.annotationType();
98+
99+
if (!annotationCache.containsKey(annotationType)) {
100+
annotationCache.put(annotationType, annotation);
101+
}
102+
}
103+
}
104+
65105
/**
66106
* Inspects a potentially available {@link Value} annotation at the property and returns the {@link String} value of
67107
* it.

src/test/java/org/springframework/data/mapping/model/AbstractAnnotationBasedPropertyUnitTests.java

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,30 @@
2323
import java.lang.annotation.Retention;
2424
import java.lang.annotation.RetentionPolicy;
2525
import java.lang.annotation.Target;
26+
import java.util.Map;
27+
import java.util.concurrent.ConcurrentMap;
2628

2729
import org.junit.Before;
2830
import org.junit.Test;
2931
import org.springframework.data.annotation.Id;
3032
import org.springframework.data.mapping.context.SampleMappingContext;
3133
import org.springframework.data.mapping.context.SamplePersistentProperty;
34+
import org.springframework.data.util.ClassTypeInformation;
35+
import org.springframework.data.util.TypeInformation;
36+
import org.springframework.test.util.ReflectionTestUtils;
3237

3338
/**
3439
* @author Oliver Gierke
3540
*/
3641
public class AbstractAnnotationBasedPropertyUnitTests<P extends AnnotationBasedPersistentProperty<P>> {
3742

3843
BasicPersistentEntity<Object, SamplePersistentProperty> entity;
44+
SampleMappingContext context;
3945

4046
@Before
4147
public void setUp() {
4248

43-
SampleMappingContext context = new SampleMappingContext();
49+
context = new SampleMappingContext();
4450
entity = context.getPersistentEntity(Sample.class);
4551
}
4652

@@ -72,6 +78,47 @@ public void findsMetaAnnotation() {
7278
assertAnnotationPresent(Id.class, entity.getPersistentProperty("id"));
7379
}
7480

81+
/**
82+
* @see DATACMNS-282
83+
*/
84+
@Test
85+
public void populatesAnnotationCacheWithDirectAnnotationsOnCreation() {
86+
87+
SamplePersistentProperty property = entity.getPersistentProperty("meta");
88+
89+
// Assert direct annotations are cached on construction
90+
Map<Class<? extends Annotation>, Annotation> cache = getAnnotationCache(property);
91+
assertThat(cache.containsKey(MyAnnotationAsMeta.class), is(true));
92+
assertThat(cache.containsKey(MyAnnotation.class), is(false));
93+
94+
// Assert meta annotation is found and cached
95+
MyAnnotation annotation = property.findAnnotation(MyAnnotation.class);
96+
assertThat(annotation, is(notNullValue()));
97+
assertThat(cache.containsKey(MyAnnotation.class), is(true));
98+
}
99+
100+
/**
101+
* @see DATACMNS-282
102+
*/
103+
@Test
104+
@SuppressWarnings("unchecked")
105+
public void discoversAmbiguousMappingUsingDirectAnnotationsOnAccessors() {
106+
107+
try {
108+
context.getPersistentEntity(InvalidSample.class);
109+
fail("Expected MappingException!");
110+
} catch (MappingException o_O) {
111+
ConcurrentMap<TypeInformation<?>, ?> entities = (ConcurrentMap<TypeInformation<?>, ?>) ReflectionTestUtils
112+
.getField(context, "persistentEntities");
113+
assertThat(entities.containsKey(ClassTypeInformation.from(InvalidSample.class)), is(false));
114+
}
115+
}
116+
117+
@SuppressWarnings("unchecked")
118+
private Map<Class<? extends Annotation>, Annotation> getAnnotationCache(SamplePersistentProperty property) {
119+
return (Map<Class<? extends Annotation>, Annotation>) ReflectionTestUtils.getField(property, "annotationCache");
120+
}
121+
75122
private <A extends Annotation> A assertAnnotationPresent(Class<A> annotationType,
76123
AnnotationBasedPersistentProperty<?> property) {
77124

@@ -90,6 +137,9 @@ static class Sample {
90137
String getter;
91138
String setter;
92139

140+
@MyAnnotationAsMeta
141+
String meta;
142+
93143
@MyAnnotation("field")
94144
String override;
95145

@@ -109,12 +159,34 @@ public String getOverride() {
109159
}
110160
}
111161

162+
static class InvalidSample {
163+
164+
String meta;
165+
166+
@MyAnnotation
167+
public String getMeta() {
168+
return meta;
169+
}
170+
171+
@MyAnnotation
172+
public void setMeta(String meta) {
173+
this.meta = meta;
174+
}
175+
}
176+
112177
@Retention(RetentionPolicy.RUNTIME)
113178
@Target(value = { FIELD, METHOD, ANNOTATION_TYPE })
114179
public static @interface MyAnnotation {
115180
String value() default "";
116181
}
117182

183+
@Retention(RetentionPolicy.RUNTIME)
184+
@Target(value = { FIELD, METHOD })
185+
@MyAnnotation
186+
public static @interface MyAnnotationAsMeta {
187+
188+
}
189+
118190
@Retention(RetentionPolicy.RUNTIME)
119191
@Target(value = { FIELD, METHOD, ANNOTATION_TYPE })
120192
@Id

0 commit comments

Comments
 (0)