Polishing of annotation model for object creators.

Move to @PersistenceCreator as canonical annotation to explicitly express constructors and methods to be used to create domain object instances from persistence operations. Removed @FactoryMethod as it's not needed anymore. @PersistenceConstructor is now deprecated.

Renamed EntityCreatorMetadata(Support|Discoverer) to InstanceCreatorMetadata(Support|Discoverer) to avoid further manifestation of the notion of an entity in the metamodel as it's not used to only handle entities.

Issue #2476.
This commit is contained in:
Oliver Drotbohm
2022-01-28 11:05:50 +01:00
parent c4a324e3cf
commit ac9f99fc54
25 changed files with 117 additions and 162 deletions

View File

@@ -17,9 +17,9 @@ This means we need two fundamental steps:
Spring Data automatically tries to detect a persistent entity's constructor to be used to materialize objects of that type.
The resolution algorithm works as follows:
1. If there is a single static factory method annotated with `@FactoryMethod` then it is used.
1. If there is a single static factory method annotated with `@PersistenceCreator` then it is used.
2. If there is a single constructor, it is used.
3. If there are multiple constructors and exactly one is annotated with `@PersistenceConstructor`, it is used.
3. If there are multiple constructors and exactly one is annotated with `@PersistenceCreator`, it is used.
4. If there's a no-argument constructor, it is used.
Other constructors will be ignored.
@@ -205,9 +205,9 @@ Even if the intent is that the calculation should be preferred, it's important t
<4> The `comment` property is mutable is populated by setting its field directly.
<5> The `remarks` properties are mutable and populated by setting the `comment` field directly or by invoking the setter method for
<6> The class exposes a factory method and a constructor for object creation.
The core idea here is to use factory methods instead of additional constructors to avoid the need for constructor disambiguation through `@PersistenceConstructor`.
The core idea here is to use factory methods instead of additional constructors to avoid the need for constructor disambiguation through `@PersistenceCreator`.
Instead, defaulting of properties is handled within the factory method.
If you want Spring Data to use the factory method for object instantiation, annotate it with `@FactoryMethod`.
If you want Spring Data to use the factory method for object instantiation, annotate it with `@PersistenceCreator`.
[[mapping.general-recommendations]]
== General recommendations
@@ -217,7 +217,7 @@ Also, this avoids your domain objects to be littered with setter methods that al
If you need those, prefer to make them package protected so that they can only be invoked by a limited amount of co-located types.
Constructor-only materialization is up to 30% faster than properties population.
* _Provide an all-args constructor_ -- Even if you cannot or don't want to model your entities as immutable values, there's still value in providing a constructor that takes all properties of the entity as arguments, including the mutable ones, as this allows the object mapping to skip the property population for optimal performance.
* _Use factory methods instead of overloaded constructors to avoid ``@PersistenceConstructor``_ -- With an all-argument constructor needed for optimal performance, we usually want to expose more application use case specific constructors that omit things like auto-generated identifiers etc.
* _Use factory methods instead of overloaded constructors to avoid ``@PersistenceCreator``_ -- With an all-argument constructor needed for optimal performance, we usually want to expose more application use case specific constructors that omit things like auto-generated identifiers etc.
It's an established pattern to rather use static factory methods to expose these variants of the all-args constructor.
* _Make sure you adhere to the constraints that allow the generated instantiator and property accessor classes to be used_ --
* _For identifiers to be generated, still use a final field in combination with an all-arguments persistence constructor (preferred) or a `with…` method_ --
@@ -307,14 +307,14 @@ data class Person(val id: String, val name: String)
----
====
The class above compiles to a typical class with an explicit constructor.We can customize this class by adding another constructor and annotate it with `@PersistenceConstructor` to indicate a constructor preference:
The class above compiles to a typical class with an explicit constructor.We can customize this class by adding another constructor and annotate it with `@PersistenceCreator` to indicate a constructor preference:
====
[source,kotlin]
----
data class Person(var id: String, val name: String) {
@PersistenceConstructor
@PersistenceCreator
constructor(id: String) : this(id, "unknown")
}
----

View File

@@ -1,33 +0,0 @@
/*
* Copyright 2011-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to declare a {@code static} method as factory method for class instantiation.
*
* @author Mark Paluch
* @since 3.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@EntityCreatorAnnotation
public @interface FactoryMethod {
}

View File

@@ -25,9 +25,11 @@ import java.lang.annotation.Target;
*
* @author Jon Brisbin
* @author Mark Paluch
* @author Oliver Drotbohm
* @deprecated in favor of {@link PersistenceCreator} since 3.0, to be removed in 3.1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE })
@EntityCreatorAnnotation
public @interface PersistenceConstructor {
}
@PersistenceCreator
@Deprecated
public @interface PersistenceConstructor {}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2021 the original author or authors.
* Copyright 2011-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,9 +24,9 @@ import java.lang.annotation.Target;
* Marker annotation to declare a constructor or factory method annotation as factory/preferred constructor annotation.
*
* @author Mark Paluch
* @author Oliver Drotbohm
* @since 3.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.ANNOTATION_TYPE })
public @interface EntityCreatorAnnotation {
}
@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
public @interface PersistenceCreator {}

View File

@@ -26,7 +26,7 @@ import org.springframework.util.ReflectionUtils;
* @author Mark Paluch
* @since 3.0
*/
public final class FactoryMethod<T, P extends PersistentProperty<P>> extends EntityCreatorMetadataSupport<T, P> {
public final class FactoryMethod<T, P extends PersistentProperty<P>> extends InstanceCreatorMetadataSupport<T, P> {
/**
* Creates a new {@link FactoryMethod} from the given {@link Constructor} and {@link Parameter}s.
@@ -49,5 +49,4 @@ public final class FactoryMethod<T, P extends PersistentProperty<P>> extends Ent
public Method getFactoryMethod() {
return (Method) getExecutable();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2021 the original author or authors.
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,12 +18,13 @@ package org.springframework.data.mapping;
import java.util.List;
/**
* Metadata describing a mechanism to create an entity instance.
* Metadata describing a mechanism to create instances of persistent types.
*
* @author Mark Paluch
* @author Oliver Drotbohm
* @since 3.0
*/
public interface EntityCreatorMetadata<P extends PersistentProperty<P>> {
public interface InstanceCreatorMetadata<P extends PersistentProperty<P>> {
/**
* Check whether the given {@link PersistentProperty} is being used as creator parameter.

View File

@@ -28,22 +28,23 @@ import org.springframework.util.Assert;
* persistent data to objects.
*
* @author Mark Paluch
* @author Oliver Drotbohm
* @since 3.0
*/
class EntityCreatorMetadataSupport<T, P extends PersistentProperty<P>> implements EntityCreatorMetadata<P> {
class InstanceCreatorMetadataSupport<T, P extends PersistentProperty<P>> implements InstanceCreatorMetadata<P> {
private final Executable executable;
private final List<Parameter<Object, P>> parameters;
private final Map<PersistentProperty<?>, Boolean> isPropertyParameterCache = new ConcurrentHashMap<>();
/**
* Creates a new {@link EntityCreatorMetadataSupport} from the given {@link Executable} and {@link Parameter}s.
* Creates a new {@link InstanceCreatorMetadataSupport} from the given {@link Executable} and {@link Parameter}s.
*
* @param executable must not be {@literal null}.
* @param parameters must not be {@literal null}.
*/
@SafeVarargs
public EntityCreatorMetadataSupport(Executable executable, Parameter<Object, P>... parameters) {
public InstanceCreatorMetadataSupport(Executable executable, Parameter<Object, P>... parameters) {
Assert.notNull(executable, "Executable must not be null!");
Assert.notNull(parameters, "Parameters must not be null!");
@@ -72,7 +73,7 @@ class EntityCreatorMetadataSupport<T, P extends PersistentProperty<P>> implement
/**
* Returns whether the given {@link PersistentProperty} is referenced in a creator argument of the
* {@link PersistentEntity} backing this {@link EntityCreatorMetadataSupport}.
* {@link PersistentEntity} backing this {@link InstanceCreatorMetadataSupport}.
* <p>
* Results of this call are cached and reused on the next invocation. Calling this method for a
* {@link PersistentProperty} that was not yet added to its owning {@link PersistentEntity} will capture that state

View File

@@ -49,14 +49,14 @@ public interface PersistentEntity<T, P extends PersistentProperty<P>> extends It
* indicates that the instantiation of the object of that persistent entity is done through either a customer
* {@link org.springframework.data.mapping.model.EntityInstantiator} or handled by custom conversion
* mechanisms entirely.
* @deprecated since 3.0, use {@link #getEntityCreator()}.
* @deprecated since 3.0, use {@link #getInstanceCreatorMetadata()}.
*/
@Nullable
@Deprecated
PreferredConstructor<T, P> getPersistenceConstructor();
/**
* Returns the {@link EntityCreatorMetadata} to be used to instantiate objects of this {@link PersistentEntity}.
* Returns the {@link InstanceCreatorMetadata} to be used to instantiate objects of this {@link PersistentEntity}.
*
* @return {@literal null} in case no suitable creation mechanism for automatic construction can be found. This
* usually indicates that the instantiation of the object of that persistent entity is done through either a
@@ -65,7 +65,7 @@ public interface PersistentEntity<T, P extends PersistentProperty<P>> extends It
* @since 3.0
*/
@Nullable
EntityCreatorMetadata<P> getEntityCreator();
InstanceCreatorMetadata<P> getInstanceCreatorMetadata();
/**
* Returns whether the given {@link PersistentProperty} is referred to by a constructor argument of the

View File

@@ -35,7 +35,7 @@ import org.springframework.util.ReflectionUtils;
* @author Myeonghyeon Lee
* @author Xeno Amess
*/
public final class PreferredConstructor<T, P extends PersistentProperty<P>> extends EntityCreatorMetadataSupport<T, P> {
public final class PreferredConstructor<T, P extends PersistentProperty<P>> extends InstanceCreatorMetadataSupport<T, P> {
private final List<Parameter<Object, P>> parameters;

View File

@@ -17,16 +17,7 @@ package org.springframework.data.mapping.model;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.*;
import java.util.stream.Collectors;
import org.springframework.core.annotation.AnnotatedElementUtils;
@@ -63,7 +54,7 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
private static final String TYPE_MISMATCH = "Target bean of type %s is not of type of the persistent entity (%s)!";
private final @Nullable EntityCreatorMetadata<P> creator;
private final @Nullable InstanceCreatorMetadata<P> creator;
private final TypeInformation<T> information;
private final List<P> properties;
private final List<P> persistentPropertiesCache;
@@ -109,7 +100,7 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
this.properties = new ArrayList<>();
this.persistentPropertiesCache = new ArrayList<>();
this.comparator = comparator;
this.creator = EntityCreatorMetadataDiscoverer.discover(this);
this.creator = InstanceCreatorMetadataDiscoverer.discover(this);
this.associations = comparator == null ? new HashSet<>() : new TreeSet<>(new AssociationComparator<>(comparator));
this.propertyCache = new HashMap<>(16, 1f);
@@ -129,12 +120,14 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
@Nullable
@Override
@SuppressWarnings("unchecked")
public PreferredConstructor<T, P> getPersistenceConstructor() {
return creator instanceof PreferredConstructor ? (PreferredConstructor<T, P>) creator : null;
}
@Nullable
@Override
public EntityCreatorMetadata<P> getEntityCreator() {
public InstanceCreatorMetadata<P> getInstanceCreatorMetadata() {
return creator;
}

View File

@@ -25,7 +25,6 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.asm.ClassWriter;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
@@ -33,8 +32,8 @@ import org.springframework.asm.Type;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.core.NativeDetector;
import org.springframework.data.mapping.EntityCreatorMetadata;
import org.springframework.data.mapping.FactoryMethod;
import org.springframework.data.mapping.InstanceCreatorMetadata;
import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
@@ -159,7 +158,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
*/
protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity<?, ?> entity) {
return new EntityInstantiatorAdapter(
createObjectInstantiator(entity, entity.getEntityCreator()));
createObjectInstantiator(entity, entity.getInstanceCreatorMetadata()));
}
/**
@@ -187,20 +186,20 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
return true;
}
var entityCreator = entity.getEntityCreator();
var creatorMetadata = entity.getInstanceCreatorMetadata();
if (entityCreator == null) {
if (creatorMetadata == null) {
return true;
}
if (entityCreator instanceof PreferredConstructor<?, ?> persistenceConstructor) {
if (creatorMetadata instanceof PreferredConstructor<?, ?> persistenceConstructor) {
if (Modifier.isPrivate(persistenceConstructor.getConstructor().getModifiers())) {
return true;
}
}
if (entityCreator instanceof FactoryMethod<?, ?> factoryMethod) {
if (creatorMetadata instanceof FactoryMethod<?, ?> factoryMethod) {
if (Modifier.isPrivate(factoryMethod.getFactoryMethod().getModifiers())) {
return true;
@@ -227,7 +226,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
/**
* Creates a dynamically generated {@link ObjectInstantiator} for the given {@link PersistentEntity} and
* {@link EntityCreatorMetadata}. There will always be exactly one {@link ObjectInstantiator} instance per
* {@link InstanceCreatorMetadata}. There will always be exactly one {@link ObjectInstantiator} instance per
* {@link PersistentEntity}.
*
* @param entity
@@ -235,7 +234,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
* @return
*/
ObjectInstantiator createObjectInstantiator(PersistentEntity<?, ?> entity,
@Nullable EntityCreatorMetadata<?> constructor) {
@Nullable InstanceCreatorMetadata<?> constructor) {
try {
return (ObjectInstantiator) this.generator.generateCustomInstantiatorClass(entity, constructor).newInstance();
@@ -269,7 +268,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
ParameterValueProvider<P> provider) {
var params = extractInvocationArguments(entity.getEntityCreator(), provider);
var params = extractInvocationArguments(entity.getInstanceCreatorMetadata(), provider);
try {
return (T) instantiator.newInstance(params);
@@ -287,7 +286,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
* @return
*/
static <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments(
@Nullable EntityCreatorMetadata<P> constructor, ParameterValueProvider<P> provider) {
@Nullable InstanceCreatorMetadata<P> constructor, ParameterValueProvider<P> provider) {
if (constructor == null || !constructor.hasParameters()) {
return allocateArguments(0);
@@ -336,7 +335,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
ParameterValueProvider<P> provider) {
var params = extractInvocationArguments(entity.getEntityCreator(), provider);
var params = extractInvocationArguments(entity.getInstanceCreatorMetadata(), provider);
throw new MappingInstantiationException(entity, Arrays.asList(params),
new BeanInstantiationException(typeToCreate, "Class is abstract"));
@@ -400,7 +399,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
* @return
*/
public Class<?> generateCustomInstantiatorClass(PersistentEntity<?, ?> entity,
@Nullable EntityCreatorMetadata<?> constructor) {
@Nullable InstanceCreatorMetadata<?> constructor) {
var className = generateClassName(entity);
var type = entity.getType();
@@ -441,7 +440,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
* @return
*/
public byte[] generateBytecode(String internalClassName, PersistentEntity<?, ?> entity,
@Nullable EntityCreatorMetadata<?> entityCreator) {
@Nullable InstanceCreatorMetadata<?> entityCreator) {
var cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
@@ -476,7 +475,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
* @param entityCreator
*/
private void visitCreateMethod(ClassWriter cw, PersistentEntity<?, ?> entity,
@Nullable EntityCreatorMetadata<?> entityCreator) {
@Nullable InstanceCreatorMetadata<?> entityCreator) {
var entityTypeResourcePath = Type.getInternalName(entity.getType());

View File

@@ -25,8 +25,8 @@ import java.util.List;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.data.annotation.EntityCreatorAnnotation;
import org.springframework.data.mapping.EntityCreatorMetadata;
import org.springframework.data.annotation.PersistenceCreator;
import org.springframework.data.mapping.InstanceCreatorMetadata;
import org.springframework.data.mapping.FactoryMethod;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.Parameter;
@@ -40,7 +40,7 @@ import org.springframework.lang.Nullable;
* @author Mark Paluch
* @since 3.0
*/
class EntityCreatorMetadataDiscoverer {
class InstanceCreatorMetadataDiscoverer {
private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
@@ -53,13 +53,13 @@ class EntityCreatorMetadataDiscoverer {
* @return
*/
@Nullable
public static <T, P extends PersistentProperty<P>> EntityCreatorMetadata<P> discover(PersistentEntity<T, P> entity) {
public static <T, P extends PersistentProperty<P>> InstanceCreatorMetadata<P> discover(PersistentEntity<T, P> entity) {
var declaredConstructors = entity.getType().getDeclaredConstructors();
var declaredMethods = entity.getType().getDeclaredMethods();
var hasAnnotatedFactoryMethod = findAnnotation(EntityCreatorAnnotation.class, declaredMethods);
var hasAnnotatedConstructor = findAnnotation(EntityCreatorAnnotation.class, declaredConstructors);
var hasAnnotatedFactoryMethod = findAnnotation(PersistenceCreator.class, declaredMethods);
var hasAnnotatedConstructor = findAnnotation(PersistenceCreator.class, declaredConstructors);
if (hasAnnotatedConstructor && hasAnnotatedFactoryMethod) {
throw new MappingException(
@@ -92,7 +92,7 @@ class EntityCreatorMetadataDiscoverer {
continue;
}
if (findAnnotation(EntityCreatorAnnotation.class, method)) {
if (findAnnotation(PersistenceCreator.class, method)) {
candidates.add(method);
}
}
@@ -124,11 +124,11 @@ class EntityCreatorMetadataDiscoverer {
private static void validateMethod(Method method) {
if (MergedAnnotations.from(method).isPresent(EntityCreatorAnnotation.class)) {
if (MergedAnnotations.from(method).isPresent(PersistenceCreator.class)) {
if (!Modifier.isStatic(method.getModifiers())) {
throw new MappingException(
"@Factory can only be used on static methods. Offending method: %s".formatted(method));
"@PersistenceCreator can only be used on static methods. Offending method: %s".formatted(method));
}
}
}

View File

@@ -81,7 +81,7 @@ public class InstantiationAwarePropertyAccessor<T> implements PersistentProperty
return;
}
var creator = owner.getEntityCreator();
var creator = owner.getInstanceCreatorMetadata();
if (creator == null) {
throw new IllegalStateException(String.format(NO_SETTER_OR_CONSTRUCTOR, property.getName(), owner.getType()));

View File

@@ -24,7 +24,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import org.springframework.data.mapping.EntityCreatorMetadata;
import org.springframework.data.mapping.InstanceCreatorMetadata;
import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
@@ -46,7 +46,7 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta
@Override
protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity<?, ?> entity) {
var creator = entity.getEntityCreator();
var creator = entity.getInstanceCreatorMetadata();
if (KotlinReflectionUtils.isSupportedKotlinClass(entity.getType())
&& creator instanceof PreferredConstructor<?, ?> constructor) {
@@ -80,9 +80,9 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta
DefaultingKotlinConstructorResolver(PersistentEntity<?, ?> entity) {
var hit = resolveDefaultConstructor(entity);
var creator = entity.getEntityCreator();
var creator = entity.getInstanceCreatorMetadata();
if (hit != null && creator instanceof PreferredConstructor<?, ?> persistenceConstructor) {
if ((hit != null) && creator instanceof PreferredConstructor<?, ?> persistenceConstructor) {
this.defaultConstructor = new PreferredConstructor<>(hit,
persistenceConstructor.getParameters().toArray(new Parameter[0]));
} else {
@@ -93,7 +93,7 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta
@Nullable
private static Constructor<?> resolveDefaultConstructor(PersistentEntity<?, ?> entity) {
if (!(entity.getEntityCreator()instanceof PreferredConstructor<?, ?> persistenceConstructor)) {
if (!(entity.getInstanceCreatorMetadata() instanceof PreferredConstructor<?, ?> persistenceConstructor)) {
return null;
}
@@ -112,7 +112,7 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta
var syntheticParameters = KotlinDefaultMask.getMaskCount(constructor.getParameterCount())
+ /* DefaultConstructorMarker */ 1;
if (constructor.getParameterCount() + syntheticParameters != candidate.getParameterCount()) {
if ((constructor.getParameterCount() + syntheticParameters) != candidate.getParameterCount()) {
continue;
}
@@ -191,7 +191,7 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
ParameterValueProvider<P> provider) {
var params = extractInvocationArguments(entity.getEntityCreator(), provider);
var params = extractInvocationArguments(entity.getInstanceCreatorMetadata(), provider);
try {
return (T) instantiator.newInstance(params);
@@ -201,7 +201,7 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta
}
private <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments(
@Nullable EntityCreatorMetadata<P> entityCreator, ParameterValueProvider<P> provider) {
@Nullable InstanceCreatorMetadata<P> entityCreator, ParameterValueProvider<P> provider) {
if (entityCreator == null) {
throw new IllegalArgumentException("EntityCreator must not be null!");
@@ -227,7 +227,7 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta
var parameter = parameters.get(index);
var type = parameter.getType().getType();
if (it.isOptional() && params[index] == null) {
if (it.isOptional() && (params[index] == null)) {
if (type.isPrimitive()) {
// apply primitive defaulting to prevent NPE on primitive downcast

View File

@@ -22,8 +22,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.data.mapping.EntityCreatorMetadata;
import org.springframework.data.mapping.FactoryMethod;
import org.springframework.data.mapping.InstanceCreatorMetadata;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.util.KotlinReflectionUtils;
@@ -44,7 +44,7 @@ public class MappingInstantiationException extends RuntimeException {
private static final String TEXT_TEMPLATE = "Failed to instantiate %s using constructor %s with arguments %s";
private final Class<?> entityType;
private final EntityCreatorMetadata<?> entityCreator;
private final InstanceCreatorMetadata<?> entityCreator;
private final List<Object> constructorArguments;
/**
@@ -75,7 +75,7 @@ public class MappingInstantiationException extends RuntimeException {
super(buildExceptionMessage(entity, arguments, message), cause);
this.entityType = entity.map(PersistentEntity::getType).orElse(null);
this.entityCreator = entity.map(PersistentEntity::getEntityCreator).orElse(null);
this.entityCreator = entity.map(PersistentEntity::getInstanceCreatorMetadata).orElse(null);
this.constructorArguments = arguments;
}
@@ -84,7 +84,7 @@ public class MappingInstantiationException extends RuntimeException {
return entity.map(it -> {
Optional<? extends EntityCreatorMetadata<?>> constructor = Optional.ofNullable(it.getEntityCreator());
Optional<? extends InstanceCreatorMetadata<?>> constructor = Optional.ofNullable(it.getInstanceCreatorMetadata());
List<String> toStringArgs = new ArrayList<>(arguments.size());
for (var o : arguments) {
@@ -98,7 +98,7 @@ public class MappingInstantiationException extends RuntimeException {
}).orElse(defaultMessage);
}
private static String toString(EntityCreatorMetadata<?> creator) {
private static String toString(InstanceCreatorMetadata<?> creator) {
if (creator instanceof PreferredConstructor<?, ?> c) {
return toString(c);
@@ -170,7 +170,7 @@ public class MappingInstantiationException extends RuntimeException {
* @return the entity creator
* @since 3.0
*/
public Optional<EntityCreatorMetadata<?>> getEntityCreator() {
public Optional<InstanceCreatorMetadata<?>> getEntityCreator() {
return Optional.ofNullable(entityCreator);
}

View File

@@ -47,10 +47,10 @@ public class PersistentEntityParameterValueProvider<P extends PersistentProperty
@SuppressWarnings("unchecked")
public <T> T getParameterValue(Parameter<T, P> parameter) {
var creator = entity.getEntityCreator();
var creator = entity.getInstanceCreatorMetadata();
if (creator.isParentParameter(parameter)) {
return (T) parent;
if (creator != null && creator.isParentParameter(parameter)) {
return (T) parent;
}
var name = parameter.getName();

View File

@@ -27,7 +27,7 @@ import java.util.List;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.annotation.EntityCreatorAnnotation;
import org.springframework.data.annotation.PersistenceCreator;
import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
@@ -111,7 +111,7 @@ public interface PreferredConstructorDiscoverer<T, P extends PersistentProperty<
continue;
}
if (AnnotationUtils.findAnnotation(candidate, EntityCreatorAnnotation.class) != null) {
if (AnnotationUtils.findAnnotation(candidate, PersistenceCreator.class) != null) {
return buildPreferredConstructor(candidate, type, entity);
}
@@ -145,11 +145,8 @@ public interface PreferredConstructorDiscoverer<T, P extends PersistentProperty<
return Arrays.stream(rawOwningType.getDeclaredConstructors()) //
.filter(it -> !it.isSynthetic()) // Synthetic constructors should not be considered
.filter(it -> AnnotationUtils.findAnnotation(it, EntityCreatorAnnotation.class) != null) // Explicitly
// defined
// constructor
// trumps
// all
// Explicitly defined creator trumps all
.filter(it -> AnnotationUtils.findAnnotation(it, PersistenceCreator.class) != null)
.map(it -> buildPreferredConstructor(it, type, entity)) //
.findFirst() //
.orElseGet(() -> {

View File

@@ -46,7 +46,7 @@ enum ReflectionEntityInstantiator implements EntityInstantiator {
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
ParameterValueProvider<P> provider) {
var creator = entity.getEntityCreator();
var creator = entity.getInstanceCreatorMetadata();
if (creator == null) {
return instantiateClass(entity);
@@ -59,7 +59,7 @@ enum ReflectionEntityInstantiator implements EntityInstantiator {
params[i++] = provider.getParameterValue(parameter);
}
if (creator instanceof FactoryMethod method) {
if (creator instanceof FactoryMethod<?, ?> method) {
try {
var t = (T) ReflectionUtils.invokeMethod(method.getFactoryMethod(), null, params);
@@ -80,6 +80,7 @@ enum ReflectionEntityInstantiator implements EntityInstantiator {
}
}
@SuppressWarnings("unchecked")
private <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T instantiateClass(
E entity) {

View File

@@ -32,8 +32,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.data.annotation.FactoryMethod;
import org.springframework.data.annotation.PersistenceCreator;
import org.springframework.data.classloadersupport.HidingClassLoader;
import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.PersistentEntity;
@@ -84,7 +83,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
PreferredConstructor<Foo, P> constructor = PreferredConstructorDiscoverer.discover(Foo.class);
doReturn(Foo.class).when(entity).getType();
doReturn(constructor).when(entity).getEntityCreator();
doReturn(constructor).when(entity).getInstanceCreatorMetadata();
assertThat(instance.createInstance(entity, provider)).isInstanceOf(Foo.class);
@@ -106,7 +105,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
void createsInnerClassInstanceCorrectly() {
var entity = new BasicPersistentEntity<Inner, P>(from(Inner.class));
assertThat(entity.getEntityCreator()).satisfies(constructor -> {
assertThat(entity.getInstanceCreatorMetadata()).satisfies(constructor -> {
var parameter = constructor.getParameters().iterator().next();
@@ -226,7 +225,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
doReturn(ObjCtorDefault.class).when(entity).getType();
doReturn(PreferredConstructorDiscoverer.discover(ObjCtorDefault.class))//
.when(entity).getEntityCreator();
.when(entity).getInstanceCreatorMetadata();
IntStream.range(0, 2)
.forEach(i -> assertThat(this.instance.createInstance(entity, provider)).isInstanceOf(ObjCtorDefault.class));
@@ -237,7 +236,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
doReturn(ObjCtorNoArgs.class).when(entity).getType();
doReturn(PreferredConstructorDiscoverer.discover(ObjCtorNoArgs.class))//
.when(entity).getEntityCreator();
.when(entity).getInstanceCreatorMetadata();
IntStream.range(0, 2).forEach(i -> {
@@ -256,7 +255,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
doReturn(ObjCtor1ParamString.class).when(entity).getType();
doReturn(PreferredConstructorDiscoverer.discover(ObjCtor1ParamString.class))//
.when(entity).getEntityCreator();
.when(entity).getInstanceCreatorMetadata();
doReturn("FOO").when(provider).getParameterValue(any());
IntStream.range(0, 2).forEach(i -> {
@@ -274,7 +273,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
doReturn(ObjCtor2ParamStringString.class).when(entity).getType();
doReturn(PreferredConstructorDiscoverer.discover(ObjCtor2ParamStringString.class))//
.when(entity).getEntityCreator();
.when(entity).getInstanceCreatorMetadata();
IntStream.range(0, 2).forEach(i -> {
@@ -294,7 +293,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
doReturn(ObjectCtor1ParamInt.class).when(entity).getType();
doReturn(PreferredConstructorDiscoverer.discover(ObjectCtor1ParamInt.class))//
.when(entity).getEntityCreator();
.when(entity).getInstanceCreatorMetadata();
IntStream.range(0, 2).forEach(i -> {
@@ -312,7 +311,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
doReturn(ObjectCtor1ParamInt.class).when(entity).getType();
doReturn(PreferredConstructorDiscoverer.discover(ObjectCtor1ParamInt.class))//
.when(entity).getEntityCreator();
.when(entity).getInstanceCreatorMetadata();
assertThatThrownBy(() -> this.instance.createInstance(entity, provider)) //
.hasCauseInstanceOf(IllegalArgumentException.class);
@@ -324,7 +323,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
doReturn(ObjectCtor7ParamsString5IntsString.class).when(entity).getType();
doReturn(PreferredConstructorDiscoverer.discover(ObjectCtor7ParamsString5IntsString.class))//
.when(entity).getEntityCreator();
.when(entity).getInstanceCreatorMetadata();
IntStream.range(0, 2).forEach(i -> {
@@ -444,7 +443,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
doReturn(type).when(entity).getType();
doReturn(PreferredConstructorDiscoverer.discover(type))//
.when(entity).getEntityCreator();
.when(entity).getInstanceCreatorMetadata();
}
static class Foo {
@@ -472,7 +471,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
this.name = name;
}
@FactoryMethod
@PersistenceCreator
public static WithFactoryMethod create(Long id, String name) {
return new WithFactoryMethod(id, "Hello " + name);
}

View File

@@ -18,14 +18,13 @@ package org.springframework.data.mapping.model;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.FactoryMethod;
import org.springframework.data.annotation.PersistenceCreator;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.util.ClassTypeInformation;
/**
* Unit tests for {@link EntityCreatorMetadataDiscoverer}.
* Unit tests for {@link InstanceCreatorMetadataDiscoverer}.
*
* @author Mark Paluch
*/
@@ -35,7 +34,7 @@ class EntityCreatorMetadataDiscovererUnitTests {
void shouldDiscoverAnnotatedFactoryMethod() {
var entity = new BasicPersistentEntity<>(ClassTypeInformation.from(FactoryMethodsPerson.class));
var creator = EntityCreatorMetadataDiscoverer.discover(entity);
var creator = InstanceCreatorMetadataDiscoverer.discover(entity);
assertThat(creator).isInstanceOf(org.springframework.data.mapping.FactoryMethod.class);
assertThat(((org.springframework.data.mapping.FactoryMethod<?, ?>) creator).getFactoryMethod().getParameterCount())
@@ -46,7 +45,7 @@ class EntityCreatorMetadataDiscovererUnitTests {
void shouldDiscoverAnnotatedConstructor() {
var entity = new BasicPersistentEntity<>(ClassTypeInformation.from(ConstructorPerson.class));
var creator = EntityCreatorMetadataDiscoverer.discover(entity);
var creator = InstanceCreatorMetadataDiscoverer.discover(entity);
assertThat(creator).isInstanceOf(PreferredConstructor.class);
}
@@ -55,7 +54,7 @@ class EntityCreatorMetadataDiscovererUnitTests {
void shouldDiscoverDefaultConstructor() {
var entity = new BasicPersistentEntity<>(ClassTypeInformation.from(Person.class));
var creator = EntityCreatorMetadataDiscoverer.discover(entity);
var creator = InstanceCreatorMetadataDiscoverer.discover(entity);
assertThat(creator).isInstanceOf(PreferredConstructor.class);
}
@@ -79,7 +78,7 @@ class EntityCreatorMetadataDiscovererUnitTests {
static class NonStaticFactoryMethod {
@FactoryMethod
@PersistenceCreator
public ConstructorPerson of(String firstname, String lastname) {
return new ConstructorPerson(firstname, lastname);
}
@@ -99,7 +98,7 @@ class EntityCreatorMetadataDiscovererUnitTests {
return new FactoryMethodsPerson(firstname, "unknown");
}
@FactoryMethod
@PersistenceCreator
public static FactoryMethodsPerson of(String firstname, String lastname) {
return new FactoryMethodsPerson(firstname, lastname);
}

View File

@@ -18,8 +18,7 @@ package org.springframework.data.mapping.model;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.FactoryMethod;
import org.springframework.data.annotation.PersistenceCreator;
import org.springframework.data.mapping.Parameter;
import org.springframework.data.util.ClassTypeInformation;
@@ -67,7 +66,7 @@ class FactoryMethodUnitTests {
this.lastname = lastname;
}
@FactoryMethod
@PersistenceCreator
public static FactoryPerson of(String firstname, String lastname) {
return new FactoryPerson(firstname, "Mr. " + lastname);
}

View File

@@ -23,7 +23,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
@@ -55,7 +54,7 @@ class PersistentEntityParameterValueProviderUnitTests<P extends PersistentProper
}
};
assertThat(entity.getEntityCreator()).satisfies(constructor -> {
assertThat(entity.getInstanceCreatorMetadata()).satisfies(constructor -> {
var iterator = constructor.getParameters().iterator();
ParameterValueProvider<P> provider = new PersistentEntityParameterValueProvider<>(entity, propertyValueProvider,
@@ -74,7 +73,7 @@ class PersistentEntityParameterValueProviderUnitTests<P extends PersistentProper
ParameterValueProvider<P> provider = new PersistentEntityParameterValueProvider<>(entity, propertyValueProvider,
Optional.of(property));
assertThat(entity.getEntityCreator())
assertThat(entity.getInstanceCreatorMetadata())
.satisfies(constructor -> assertThatExceptionOfType(MappingException.class)//
.isThrownBy(() -> provider.getParameterValue(constructor.getParameters().iterator().next()))//
.withMessageContaining("bar")//

View File

@@ -29,7 +29,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
@@ -69,7 +68,7 @@ class ReflectionEntityInstantiatorUnitTests<P extends PersistentProperty<P>> {
PreferredConstructor<Foo, P> constructor = PreferredConstructorDiscoverer.discover(Foo.class);
doReturn(constructor).when(entity).getEntityCreator();
doReturn(constructor).when(entity).getInstanceCreatorMetadata();
var instance = INSTANCE.createInstance(entity, provider);
@@ -92,7 +91,7 @@ class ReflectionEntityInstantiatorUnitTests<P extends PersistentProperty<P>> {
void createsInnerClassInstanceCorrectly() {
var entity = new BasicPersistentEntity<Inner, P>(from(Inner.class));
assertThat(entity.getEntityCreator()).satisfies(it -> {
assertThat(entity.getInstanceCreatorMetadata()).satisfies(it -> {
var parameter = it.getParameters().iterator().next();

View File

@@ -45,7 +45,7 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests {
)
every { provider.getParameterValue<String>(any()) }.returnsMany("Walter", "White")
every { entity.entityCreator } returns constructor
every { entity.instanceCreatorMetadata } returns constructor
every { entity.type } returns constructor.constructor.declaringClass
every { entity.typeInformation } returns mockk()
@@ -73,7 +73,7 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests {
null, "Walter", null, "Junior", null
)
every { entity.entityCreator } returns constructor
every { entity.instanceCreatorMetadata } returns constructor
every { entity.type } returns constructor.constructor.declaringClass
every { entity.typeInformation } returns mockk()
@@ -97,7 +97,7 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests {
)
every { provider.getParameterValue<Boolean>(any()) } returns null
every { entity.entityCreator } returns constructor
every { entity.instanceCreatorMetadata } returns constructor
every { entity.type } returns constructor.constructor.declaringClass
every { entity.typeInformation } returns mockk()
@@ -130,7 +130,7 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests {
every { provider.getParameterValue<Double>(any()) } returns null
every { provider.getParameterValue<Char>(any()) } returns null
every { provider.getParameterValue<Boolean>(any()) } returns null
every { entity.entityCreator } returns constructor
every { entity.instanceCreatorMetadata } returns constructor
every { entity.type } returns constructor.constructor.declaringClass
every { entity.typeInformation } returns mockk()
@@ -156,7 +156,7 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests {
)
every { provider.getParameterValue<String>(any()) } returns "Walter"
every { entity.entityCreator } returns constructor
every { entity.instanceCreatorMetadata } returns constructor
every { entity.type } returns constructor.constructor.declaringClass
every { entity.typeInformation } returns mockk()

View File

@@ -43,7 +43,7 @@ class ReflectionEntityInstantiatorDataClassUnitTests {
)
every { provider.getParameterValue<String>(any()) }.returnsMany("Walter", "White")
every { entity.entityCreator } returns constructor
every { entity.instanceCreatorMetadata } returns constructor
val instance: Contact =
ReflectionEntityInstantiator.INSTANCE.createInstance(entity, provider)
@@ -63,7 +63,7 @@ class ReflectionEntityInstantiatorDataClassUnitTests {
)
every { provider.getParameterValue<String>(any()) }.returnsMany("Walter", null)
every { entity.entityCreator } returns constructor
every { entity.instanceCreatorMetadata } returns constructor
val instance: ContactWithDefaulting =
ReflectionEntityInstantiator.INSTANCE.createInstance(entity, provider)