DATACMNS-1639 - Introduce InstantiationAwarePropertyAccessor.
We now ship and use a PropertyAccessor implementation by default that will fall back to use the persistence constructor and creating a new instance to "set" a property. This allows completely immutable types, ommitting previously needed wither methods, e.g. to populate automatically generated identifier values. This is implemented by using the EntityInstantiator available and a ParameterValueProvider that returns the value of the properties of the current instance but replacing the one for the value to be set. This requires moving EntityInstantiators, the EntityInstantiator SPI as well as all existing implementations (reflective, class generating and the Kotlin one) into the mapping.model package. Legacy instances stay around in deprecated form to avoid breaking existing clients (other store modules as well as user implementations of EntityInstantiator). The newly introduced implementations stay package protected as their sole users are now colocated in the same package. Access from the legacy types is handled via InternalEntityInstantiatorFactory, which is introduced in deprecated from for removal alongside the actual deprecations.
This commit is contained in:
@@ -15,29 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.convert;
|
||||
|
||||
import static org.springframework.asm.Opcodes.*;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.asm.ClassWriter;
|
||||
import org.springframework.asm.MethodVisitor;
|
||||
import org.springframework.asm.Opcodes;
|
||||
import org.springframework.asm.Type;
|
||||
import org.springframework.cglib.core.ReflectUtils;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PreferredConstructor;
|
||||
import org.springframework.data.mapping.PreferredConstructor.Parameter;
|
||||
import org.springframework.data.mapping.model.MappingInstantiationException;
|
||||
import org.springframework.data.mapping.model.InternalEntityInstantiatorFactory;
|
||||
import org.springframework.data.mapping.model.ParameterValueProvider;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* An {@link EntityInstantiator} that can generate byte code to speed-up dynamic object instantiation. Uses the
|
||||
@@ -51,479 +33,28 @@ import org.springframework.util.ClassUtils;
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 1.11
|
||||
* @deprecated since 2.3, use {@link org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class ClassGeneratingEntityInstantiator implements EntityInstantiator {
|
||||
|
||||
private static final Object[] EMPTY_ARGS = new Object[0];
|
||||
|
||||
private final ObjectInstantiatorClassGenerator generator;
|
||||
|
||||
private volatile Map<TypeInformation<?>, EntityInstantiator> entityInstantiators = new HashMap<>(32);
|
||||
private final org.springframework.data.mapping.model.EntityInstantiator delegate = InternalEntityInstantiatorFactory
|
||||
.getClassGeneratingEntityInstantiator();
|
||||
|
||||
/**
|
||||
* Creates a new {@link ClassGeneratingEntityInstantiator}.
|
||||
*/
|
||||
public ClassGeneratingEntityInstantiator() {
|
||||
this.generator = new ObjectInstantiatorClassGenerator();
|
||||
super();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider)
|
||||
* @see org.springframework.data.mapping.model.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider)
|
||||
*/
|
||||
@Override
|
||||
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
|
||||
ParameterValueProvider<P> provider) {
|
||||
|
||||
EntityInstantiator instantiator = this.entityInstantiators.get(entity.getTypeInformation());
|
||||
|
||||
if (instantiator == null) {
|
||||
instantiator = potentiallyCreateAndRegisterEntityInstantiator(entity);
|
||||
}
|
||||
|
||||
return instantiator.createInstance(entity, provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
private synchronized EntityInstantiator potentiallyCreateAndRegisterEntityInstantiator(
|
||||
PersistentEntity<?, ?> entity) {
|
||||
|
||||
Map<TypeInformation<?>, EntityInstantiator> map = this.entityInstantiators;
|
||||
EntityInstantiator instantiator = map.get(entity.getTypeInformation());
|
||||
|
||||
if (instantiator != null) {
|
||||
return instantiator;
|
||||
}
|
||||
|
||||
instantiator = createEntityInstantiator(entity);
|
||||
|
||||
map = new HashMap<>(map);
|
||||
map.put(entity.getTypeInformation(), instantiator);
|
||||
|
||||
this.entityInstantiators = map;
|
||||
|
||||
return instantiator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
private EntityInstantiator createEntityInstantiator(PersistentEntity<?, ?> entity) {
|
||||
|
||||
if (shouldUseReflectionEntityInstantiator(entity)) {
|
||||
return ReflectionEntityInstantiator.INSTANCE;
|
||||
}
|
||||
|
||||
try {
|
||||
return doCreateEntityInstantiator(entity);
|
||||
} catch (Throwable ex) {
|
||||
return ReflectionEntityInstantiator.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity<?, ?> entity) {
|
||||
return new EntityInstantiatorAdapter(createObjectInstantiator(entity, entity.getPersistenceConstructor()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
boolean shouldUseReflectionEntityInstantiator(PersistentEntity<?, ?> entity) {
|
||||
|
||||
Class<?> type = entity.getType();
|
||||
|
||||
if (type.isInterface() //
|
||||
|| type.isArray() //
|
||||
|| Modifier.isPrivate(type.getModifiers()) //
|
||||
|| (type.isMemberClass() && !Modifier.isStatic(type.getModifiers())) //
|
||||
|| ClassUtils.isCglibProxyClass(type)) { //
|
||||
return true;
|
||||
}
|
||||
|
||||
PreferredConstructor<?, ?> persistenceConstructor = entity.getPersistenceConstructor();
|
||||
if (persistenceConstructor == null || Modifier.isPrivate(persistenceConstructor.getConstructor().getModifiers())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ClassUtils.isPresent(ObjectInstantiator.class.getName(), type.getClassLoader())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates an object array for instance creation.
|
||||
*
|
||||
* @param argumentCount
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
static Object[] allocateArguments(int argumentCount) {
|
||||
return argumentCount == 0 ? EMPTY_ARGS : new Object[argumentCount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dynamically generated {@link ObjectInstantiator} for the given {@link PersistentEntity} and
|
||||
* {@link PreferredConstructor}. There will always be exactly one {@link ObjectInstantiator} instance per
|
||||
* {@link PersistentEntity}.
|
||||
*
|
||||
* @param entity
|
||||
* @param constructor
|
||||
* @return
|
||||
*/
|
||||
ObjectInstantiator createObjectInstantiator(PersistentEntity<?, ?> entity,
|
||||
@Nullable PreferredConstructor<?, ?> constructor) {
|
||||
|
||||
try {
|
||||
return (ObjectInstantiator) this.generator.generateCustomInstantiatorClass(entity, constructor).newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter to forward an invocation of the {@link EntityInstantiator} API to an {@link ObjectInstantiator}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
private static class EntityInstantiatorAdapter implements EntityInstantiator {
|
||||
|
||||
private final ObjectInstantiator instantiator;
|
||||
|
||||
/**
|
||||
* Creates a new {@link EntityInstantiatorAdapter} for the given {@link ObjectInstantiator}.
|
||||
*
|
||||
* @param instantiator must not be {@literal null}.
|
||||
*/
|
||||
public EntityInstantiatorAdapter(ObjectInstantiator instantiator) {
|
||||
this.instantiator = instantiator;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
|
||||
ParameterValueProvider<P> provider) {
|
||||
|
||||
Object[] params = extractInvocationArguments(entity.getPersistenceConstructor(), provider);
|
||||
|
||||
try {
|
||||
return (T) instantiator.newInstance(params);
|
||||
} catch (Exception e) {
|
||||
throw new MappingInstantiationException(entity, Arrays.asList(params), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the arguments required to invoke the given constructor from the given {@link ParameterValueProvider}.
|
||||
*
|
||||
* @param constructor can be {@literal null}.
|
||||
* @param provider can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments(
|
||||
@Nullable PreferredConstructor<? extends T, P> constructor, ParameterValueProvider<P> provider) {
|
||||
|
||||
if (constructor == null || !constructor.hasParameters()) {
|
||||
return allocateArguments(0);
|
||||
}
|
||||
|
||||
Object[] params = allocateArguments(constructor.getConstructor().getParameterCount());
|
||||
|
||||
int index = 0;
|
||||
for (Parameter<?, P> parameter : constructor.getParameters()) {
|
||||
params[index++] = provider.getParameterValue(parameter);
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Needs to be public as otherwise the implementation class generated does not see the interface from the classloader.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface ObjectInstantiator {
|
||||
Object newInstance(Object... args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new {@link ObjectInstantiator} class for the given custom class.
|
||||
* <p>
|
||||
* This code generator will generate a custom factory class implementing the {@link ObjectInstantiator} interface for
|
||||
* every publicly accessed constructor variant.
|
||||
* <p>
|
||||
* Given a class {@code ObjCtor1ParamString} like:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* @code
|
||||
* public class ObjCtor1ParamString extends ObjCtorNoArgs {
|
||||
*
|
||||
* public final String param1;
|
||||
*
|
||||
* public ObjCtor1ParamString(String param1) {
|
||||
* this.param1 = param1;
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* The following factory class {@code ObjCtor1ParamString_Instantiator_asdf} is generated:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* @code
|
||||
* public class ObjCtor1ParamString_Instantiator_asdf implements ObjectInstantiator {
|
||||
*
|
||||
* public Object newInstance(Object... args) {
|
||||
* return new ObjCtor1ParamString((String) args[0]);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
static class ObjectInstantiatorClassGenerator {
|
||||
|
||||
private static final String INIT = "<init>";
|
||||
private static final String TAG = "_Instantiator_";
|
||||
private static final String JAVA_LANG_OBJECT = "java/lang/Object";
|
||||
private static final String CREATE_METHOD_NAME = "newInstance";
|
||||
|
||||
private static final String[] IMPLEMENTED_INTERFACES = new String[] {
|
||||
Type.getInternalName(ObjectInstantiator.class) };
|
||||
|
||||
|
||||
/**
|
||||
* Generate a new class for the given {@link PersistentEntity}.
|
||||
*
|
||||
* @param entity
|
||||
* @param constructor
|
||||
* @return
|
||||
*/
|
||||
public Class<?> generateCustomInstantiatorClass(PersistentEntity<?, ?> entity,
|
||||
@Nullable PreferredConstructor<?, ?> constructor) {
|
||||
|
||||
String className = generateClassName(entity);
|
||||
byte[] bytecode = generateBytecode(className, entity, constructor);
|
||||
|
||||
Class<?> type = entity.getType();
|
||||
|
||||
try {
|
||||
return ReflectUtils.defineClass(className, bytecode, type.getClassLoader(), type.getProtectionDomain(), type);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
private String generateClassName(PersistentEntity<?, ?> entity) {
|
||||
return entity.getType().getName() + TAG + Integer.toString(entity.hashCode(), 36);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new class for the given {@link PersistentEntity}.
|
||||
*
|
||||
* @param internalClassName
|
||||
* @param entity
|
||||
* @param constructor
|
||||
* @return
|
||||
*/
|
||||
public byte[] generateBytecode(String internalClassName, PersistentEntity<?, ?> entity,
|
||||
@Nullable PreferredConstructor<?, ?> constructor) {
|
||||
|
||||
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
|
||||
cw.visit(Opcodes.V1_6, ACC_PUBLIC + ACC_SUPER, internalClassName.replace('.', '/'), null, JAVA_LANG_OBJECT,
|
||||
IMPLEMENTED_INTERFACES);
|
||||
|
||||
visitDefaultConstructor(cw);
|
||||
|
||||
visitCreateMethod(cw, entity, constructor);
|
||||
|
||||
cw.visitEnd();
|
||||
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
private void visitDefaultConstructor(ClassWriter cw) {
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, INIT, "()V", null, null);
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, JAVA_LANG_OBJECT, INIT, "()V", false);
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(0, 0); // (0, 0) = computed via ClassWriter.COMPUTE_MAXS
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the bytecode definition for the create method for the given {@link PersistentEntity}.
|
||||
*
|
||||
* @param cw
|
||||
* @param entity
|
||||
* @param constructor
|
||||
*/
|
||||
private void visitCreateMethod(ClassWriter cw, PersistentEntity<?, ?> entity,
|
||||
@Nullable PreferredConstructor<?, ?> constructor) {
|
||||
|
||||
String entityTypeResourcePath = Type.getInternalName(entity.getType());
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, CREATE_METHOD_NAME,
|
||||
"([Ljava/lang/Object;)Ljava/lang/Object;", null, null);
|
||||
mv.visitCode();
|
||||
mv.visitTypeInsn(NEW, entityTypeResourcePath);
|
||||
mv.visitInsn(DUP);
|
||||
|
||||
if (constructor != null) {
|
||||
|
||||
Constructor<?> ctor = constructor.getConstructor();
|
||||
Class<?>[] parameterTypes = ctor.getParameterTypes();
|
||||
List<? extends Parameter<Object, ?>> parameters = constructor.getParameters();
|
||||
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
|
||||
visitArrayIndex(mv, i);
|
||||
|
||||
mv.visitInsn(AALOAD);
|
||||
|
||||
if (parameterTypes[i].isPrimitive()) {
|
||||
|
||||
mv.visitInsn(DUP);
|
||||
String parameterName = parameters.size() > i ? parameters.get(i).getName() : null;
|
||||
|
||||
insertAssertNotNull(mv, parameterName == null ? String.format("at index %d", i) : parameterName);
|
||||
insertUnboxInsns(mv, Type.getType(parameterTypes[i]).toString().charAt(0), "");
|
||||
} else {
|
||||
mv.visitTypeInsn(CHECKCAST, Type.getInternalName(parameterTypes[i]));
|
||||
}
|
||||
}
|
||||
|
||||
mv.visitMethodInsn(INVOKESPECIAL, entityTypeResourcePath, INIT, Type.getConstructorDescriptor(ctor), false);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitMaxs(0, 0); // (0, 0) = computed via ClassWriter.COMPUTE_MAXS
|
||||
mv.visitEnd();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an appropriate value on the stack for the given index value {@code idx}.
|
||||
*
|
||||
* @param mv
|
||||
* @param idx
|
||||
*/
|
||||
private static void visitArrayIndex(MethodVisitor mv, int idx) {
|
||||
|
||||
if (idx >= 0 && idx < 6) {
|
||||
mv.visitInsn(ICONST_0 + idx);
|
||||
return;
|
||||
}
|
||||
|
||||
mv.visitLdcInsn(idx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert not-{@literal null} assertion for a parameter.
|
||||
*
|
||||
* @param mv the method visitor into which instructions should be inserted
|
||||
* @param parameterName name of the parameter to create the appropriate assertion message.
|
||||
*/
|
||||
private static void insertAssertNotNull(MethodVisitor mv, String parameterName) {
|
||||
|
||||
// Assert.notNull(property)
|
||||
mv.visitLdcInsn(String.format("Parameter %s must not be null!", parameterName));
|
||||
mv.visitMethodInsn(INVOKESTATIC, "org/springframework/util/Assert", "notNull",
|
||||
String.format("(%s%s)V", String.format("L%s;", JAVA_LANG_OBJECT), "Ljava/lang/String;"), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert any necessary cast and value call to convert from a boxed type to a primitive value.
|
||||
* <p>
|
||||
* Taken from Spring Expression 4.1.2 {@code org.springframework.expression.spel.CodeFlow#insertUnboxInsns}.
|
||||
*
|
||||
* @param mv the method visitor into which instructions should be inserted
|
||||
* @param ch the primitive type desired as output
|
||||
* @param stackDescriptor the descriptor of the type on top of the stack
|
||||
*/
|
||||
private static void insertUnboxInsns(MethodVisitor mv, char ch, String stackDescriptor) {
|
||||
|
||||
switch (ch) {
|
||||
case 'Z':
|
||||
if (!stackDescriptor.equals("Ljava/lang/Boolean")) {
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
|
||||
}
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
|
||||
break;
|
||||
case 'B':
|
||||
if (!stackDescriptor.equals("Ljava/lang/Byte")) {
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
|
||||
}
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
|
||||
break;
|
||||
case 'C':
|
||||
if (!stackDescriptor.equals("Ljava/lang/Character")) {
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
|
||||
}
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
|
||||
break;
|
||||
case 'D':
|
||||
if (!stackDescriptor.equals("Ljava/lang/Double")) {
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
|
||||
}
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
|
||||
break;
|
||||
case 'F':
|
||||
if (!stackDescriptor.equals("Ljava/lang/Float")) {
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
|
||||
}
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
|
||||
break;
|
||||
case 'I':
|
||||
if (!stackDescriptor.equals("Ljava/lang/Integer")) {
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
|
||||
}
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
|
||||
break;
|
||||
case 'J':
|
||||
if (!stackDescriptor.equals("Ljava/lang/Long")) {
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
|
||||
}
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
|
||||
break;
|
||||
case 'S':
|
||||
if (!stackDescriptor.equals("Ljava/lang/Short")) {
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
|
||||
}
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'");
|
||||
}
|
||||
}
|
||||
return delegate.createInstance(entity, provider);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,23 +16,12 @@
|
||||
package org.springframework.data.convert;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.model.ParameterValueProvider;
|
||||
|
||||
/**
|
||||
* SPI to abstract strategies to create instances for {@link PersistentEntity}s.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.3, use {@link org.springframework.data.mapping.model.EntityInstantiator} instead.
|
||||
*/
|
||||
public interface EntityInstantiator {
|
||||
|
||||
/**
|
||||
* Creates a new instance of the given entity using the given source to pull data from.
|
||||
*
|
||||
* @param entity will not be {@literal null}.
|
||||
* @param provider will not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
<T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
|
||||
ParameterValueProvider<P> provider);
|
||||
}
|
||||
@Deprecated
|
||||
public interface EntityInstantiator extends org.springframework.data.mapping.model.EntityInstantiator {}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2020 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.convert;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.model.ParameterValueProvider;
|
||||
|
||||
/**
|
||||
* Adapter to bridge calls from the new APIs back to the legacy ones.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class EntityInstantiatorAdapter implements EntityInstantiator {
|
||||
|
||||
private final org.springframework.data.mapping.model.EntityInstantiator delegate;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.model.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider)
|
||||
*/
|
||||
@Override
|
||||
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
|
||||
ParameterValueProvider<P> provider) {
|
||||
return delegate.createInstance(entity, provider);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.data.mapping.model.InternalEntityInstantiatorFactory;
|
||||
|
||||
/**
|
||||
* Simple value object allowing access to {@link EntityInstantiator} instances for a given type falling back to a
|
||||
@@ -29,17 +29,16 @@ import org.springframework.util.Assert;
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @deprecated since 2.3, use {@link org.springframework.data.mapping.model.EntityInstantiators} instead.
|
||||
*/
|
||||
public class EntityInstantiators {
|
||||
|
||||
private final EntityInstantiator fallback;
|
||||
private final Map<Class<?>, EntityInstantiator> customInstantiators;
|
||||
@Deprecated
|
||||
public class EntityInstantiators extends org.springframework.data.mapping.model.EntityInstantiators {
|
||||
|
||||
/**
|
||||
* Creates a new {@link EntityInstantiators} using the default fallback instantiator and no custom ones.
|
||||
*/
|
||||
public EntityInstantiators() {
|
||||
this(Collections.emptyMap());
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,8 +46,8 @@ public class EntityInstantiators {
|
||||
*
|
||||
* @param fallback must not be {@literal null}.
|
||||
*/
|
||||
public EntityInstantiators(EntityInstantiator fallback) {
|
||||
this(fallback, Collections.emptyMap());
|
||||
public EntityInstantiators(org.springframework.data.mapping.model.EntityInstantiator fallback) {
|
||||
super(fallback, Collections.emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,8 +55,9 @@ public class EntityInstantiators {
|
||||
*
|
||||
* @param customInstantiators must not be {@literal null}.
|
||||
*/
|
||||
public EntityInstantiators(Map<Class<?>, EntityInstantiator> customInstantiators) {
|
||||
this(new KotlinClassGeneratingEntityInstantiator(), customInstantiators);
|
||||
public EntityInstantiators(
|
||||
Map<Class<?>, org.springframework.data.mapping.model.EntityInstantiator> customInstantiators) {
|
||||
super(InternalEntityInstantiatorFactory.getKotlinClassGeneratingEntityInstantiator(), customInstantiators);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,32 +67,17 @@ public class EntityInstantiators {
|
||||
* @param defaultInstantiator must not be {@literal null}.
|
||||
* @param customInstantiators must not be {@literal null}.
|
||||
*/
|
||||
public EntityInstantiators(EntityInstantiator defaultInstantiator,
|
||||
Map<Class<?>, EntityInstantiator> customInstantiators) {
|
||||
|
||||
Assert.notNull(defaultInstantiator, "DefaultInstantiator must not be null!");
|
||||
Assert.notNull(customInstantiators, "CustomInstantiators must not be null!");
|
||||
|
||||
this.fallback = defaultInstantiator;
|
||||
this.customInstantiators = customInstantiators;
|
||||
public EntityInstantiators(org.springframework.data.mapping.model.EntityInstantiator defaultInstantiator,
|
||||
Map<Class<?>, org.springframework.data.mapping.model.EntityInstantiator> customInstantiators) {
|
||||
super(defaultInstantiator, customInstantiators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link EntityInstantiator} to be used to create the given {@link PersistentEntity}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.model.EntityInstantiators#getInstantiatorFor(org.springframework.data.mapping.PersistentEntity)
|
||||
*/
|
||||
@Override
|
||||
public EntityInstantiator getInstantiatorFor(PersistentEntity<?, ?> entity) {
|
||||
|
||||
Assert.notNull(entity, "Entity must not be null!");
|
||||
Class<?> type = entity.getType();
|
||||
|
||||
if (!customInstantiators.containsKey(type)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
EntityInstantiator instantiator = customInstantiators.get(entity.getType());
|
||||
return instantiator == null ? fallback : instantiator;
|
||||
return new EntityInstantiatorAdapter(super.getInstantiatorFor(entity));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,24 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.convert;
|
||||
|
||||
import kotlin.reflect.KFunction;
|
||||
import kotlin.reflect.KParameter;
|
||||
import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PreferredConstructor;
|
||||
import org.springframework.data.mapping.PreferredConstructor.Parameter;
|
||||
import org.springframework.data.mapping.model.KotlinDefaultMask;
|
||||
import org.springframework.data.mapping.model.MappingInstantiationException;
|
||||
import org.springframework.data.mapping.model.InternalEntityInstantiatorFactory;
|
||||
import org.springframework.data.mapping.model.ParameterValueProvider;
|
||||
import org.springframework.data.util.ReflectionUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Kotlin-specific extension to {@link ClassGeneratingEntityInstantiator} that adapts Kotlin constructors with
|
||||
@@ -41,221 +27,22 @@ import org.springframework.lang.Nullable;
|
||||
* @author Mark Paluch
|
||||
* @author Oliver Gierke
|
||||
* @since 2.0
|
||||
* @deprecated since 2.3, use {@link org.springframework.data.mapping.model.KotlinClassGeneratingEntityInstantiator}
|
||||
* instead.
|
||||
*/
|
||||
public class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInstantiator {
|
||||
@Deprecated
|
||||
public class KotlinClassGeneratingEntityInstantiator implements EntityInstantiator {
|
||||
|
||||
private final org.springframework.data.mapping.model.EntityInstantiator delegate = InternalEntityInstantiatorFactory
|
||||
.getKotlinClassGeneratingEntityInstantiator();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.ClassGeneratingEntityInstantiator#doCreateEntityInstantiator(org.springframework.data.mapping.PersistentEntity)
|
||||
* @see org.springframework.data.mapping.model.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider)
|
||||
*/
|
||||
@Override
|
||||
protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity<?, ?> entity) {
|
||||
|
||||
PreferredConstructor<?, ?> constructor = entity.getPersistenceConstructor();
|
||||
|
||||
if (ReflectionUtils.isSupportedKotlinClass(entity.getType()) && constructor != null) {
|
||||
|
||||
PreferredConstructor<?, ?> defaultConstructor = new DefaultingKotlinConstructorResolver(entity)
|
||||
.getDefaultConstructor();
|
||||
|
||||
if (defaultConstructor != null) {
|
||||
|
||||
ObjectInstantiator instantiator = createObjectInstantiator(entity, defaultConstructor);
|
||||
|
||||
return new DefaultingKotlinClassInstantiatorAdapter(instantiator, constructor);
|
||||
}
|
||||
}
|
||||
|
||||
return super.doCreateEntityInstantiator(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a {@link PreferredConstructor} to a synthetic Kotlin constructor accepting the same user-space parameters
|
||||
* suffixed by Kotlin-specifics required for defaulting and the {@code kotlin.jvm.internal.DefaultConstructorMarker}.
|
||||
*
|
||||
* @since 2.0
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
static class DefaultingKotlinConstructorResolver {
|
||||
|
||||
private final @Nullable PreferredConstructor<?, ?> defaultConstructor;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
DefaultingKotlinConstructorResolver(PersistentEntity<?, ?> entity) {
|
||||
|
||||
Constructor<?> hit = resolveDefaultConstructor(entity);
|
||||
PreferredConstructor<?, ?> persistenceConstructor = entity.getPersistenceConstructor();
|
||||
|
||||
if (hit != null && persistenceConstructor != null) {
|
||||
this.defaultConstructor = new PreferredConstructor<>(hit,
|
||||
persistenceConstructor.getParameters().toArray(new Parameter[0]));
|
||||
} else {
|
||||
this.defaultConstructor = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Constructor<?> resolveDefaultConstructor(PersistentEntity<?, ?> entity) {
|
||||
|
||||
PreferredConstructor<?, ?> persistenceConstructor = entity.getPersistenceConstructor();
|
||||
|
||||
if (persistenceConstructor == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Constructor<?> hit = null;
|
||||
Constructor<?> constructor = persistenceConstructor.getConstructor();
|
||||
|
||||
for (Constructor<?> candidate : entity.getType().getDeclaredConstructors()) {
|
||||
|
||||
// use only synthetic constructors
|
||||
if (!candidate.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// candidates must contain at least two additional parameters (int, DefaultConstructorMarker).
|
||||
// Number of defaulting masks derives from the original constructor arg count
|
||||
int syntheticParameters = KotlinDefaultMask.getMaskCount(constructor.getParameterCount())
|
||||
+ /* DefaultConstructorMarker */ 1;
|
||||
|
||||
if (constructor.getParameterCount() + syntheticParameters != candidate.getParameterCount()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
java.lang.reflect.Parameter[] constructorParameters = constructor.getParameters();
|
||||
java.lang.reflect.Parameter[] candidateParameters = candidate.getParameters();
|
||||
|
||||
if (!candidateParameters[candidateParameters.length - 1].getType().getName()
|
||||
.equals("kotlin.jvm.internal.DefaultConstructorMarker")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parametersMatch(constructorParameters, candidateParameters)) {
|
||||
hit = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return hit;
|
||||
}
|
||||
|
||||
private static boolean parametersMatch(java.lang.reflect.Parameter[] constructorParameters,
|
||||
java.lang.reflect.Parameter[] candidateParameters) {
|
||||
|
||||
return IntStream.range(0, constructorParameters.length)
|
||||
.allMatch(i -> constructorParameters[i].getType().equals(candidateParameters[i].getType()));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
PreferredConstructor<?, ?> getDefaultConstructor() {
|
||||
return defaultConstructor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity instantiator for Kotlin constructors that apply parameter defaulting. Kotlin constructors that apply
|
||||
* argument defaulting are marked with {@link kotlin.jvm.internal.DefaultConstructorMarker} and accept additional
|
||||
* parameters besides the regular (user-space) parameters. Additional parameters are:
|
||||
* <ul>
|
||||
* <li>defaulting bitmask ({@code int}), a bit mask slot for each 32 parameters</li>
|
||||
* <li>{@code DefaultConstructorMarker} (usually null)</li>
|
||||
* </ul>
|
||||
* <strong>Defaulting bitmask</strong>
|
||||
* <p/>
|
||||
* The defaulting bitmask is a 32 bit integer representing which positional argument should be defaulted. Defaulted
|
||||
* arguments are passed as {@literal null} and require the appropriate positional bit set ( {@code 1 << 2} for the 2.
|
||||
* argument)). Since the bitmask represents only 32 bit states, it requires additional masks (slots) if more than 32
|
||||
* arguments are represented.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
static class DefaultingKotlinClassInstantiatorAdapter implements EntityInstantiator {
|
||||
|
||||
private final ObjectInstantiator instantiator;
|
||||
private final KFunction<?> constructor;
|
||||
private final List<KParameter> kParameters;
|
||||
private final Constructor<?> synthetic;
|
||||
|
||||
DefaultingKotlinClassInstantiatorAdapter(ObjectInstantiator instantiator, PreferredConstructor<?, ?> constructor) {
|
||||
|
||||
KFunction<?> kotlinConstructor = ReflectJvmMapping.getKotlinFunction(constructor.getConstructor());
|
||||
|
||||
if (kotlinConstructor == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"No corresponding Kotlin constructor found for " + constructor.getConstructor());
|
||||
}
|
||||
|
||||
this.instantiator = instantiator;
|
||||
this.constructor = kotlinConstructor;
|
||||
this.kParameters = kotlinConstructor.getParameters();
|
||||
this.synthetic = constructor.getConstructor();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
|
||||
ParameterValueProvider<P> provider) {
|
||||
|
||||
Object[] params = extractInvocationArguments(entity.getPersistenceConstructor(), provider);
|
||||
|
||||
try {
|
||||
return (T) instantiator.newInstance(params);
|
||||
} catch (Exception e) {
|
||||
throw new MappingInstantiationException(entity, Arrays.asList(params), e);
|
||||
}
|
||||
}
|
||||
|
||||
private <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments(
|
||||
@Nullable PreferredConstructor<? extends T, P> preferredConstructor, ParameterValueProvider<P> provider) {
|
||||
|
||||
if (preferredConstructor == null) {
|
||||
throw new IllegalArgumentException("PreferredConstructor must not be null!");
|
||||
}
|
||||
|
||||
Object[] params = allocateArguments(
|
||||
synthetic.getParameterCount() + KotlinDefaultMask.getMaskCount(synthetic.getParameterCount()) + /* DefaultConstructorMarker */1);
|
||||
int userParameterCount = kParameters.size();
|
||||
|
||||
List<Parameter<Object, P>> parameters = preferredConstructor.getParameters();
|
||||
|
||||
// Prepare user-space arguments
|
||||
for (int i = 0; i < userParameterCount; i++) {
|
||||
|
||||
Parameter<Object, P> parameter = parameters.get(i);
|
||||
params[i] = provider.getParameterValue(parameter);
|
||||
}
|
||||
|
||||
KotlinDefaultMask defaultMask = KotlinDefaultMask.from(constructor, it -> {
|
||||
|
||||
int index = kParameters.indexOf(it);
|
||||
|
||||
Parameter<Object, P> parameter = parameters.get(index);
|
||||
Class<Object> type = parameter.getType().getType();
|
||||
|
||||
if (it.isOptional() && params[index] == null) {
|
||||
if (type.isPrimitive()) {
|
||||
|
||||
// apply primitive defaulting to prevent NPE on primitive downcast
|
||||
params[index] = ReflectionUtils.getPrimitiveDefault(type);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
int[] defaulting = defaultMask.getDefaulting();
|
||||
// append nullability masks to creation arguments
|
||||
for (int i = 0; i < defaulting.length; i++) {
|
||||
params[userParameterCount + i] = defaulting[i];
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
|
||||
ParameterValueProvider<P> provider) {
|
||||
return delegate.createInstance(entity, provider);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,18 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.convert;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.beans.BeanInstantiationException;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PreferredConstructor;
|
||||
import org.springframework.data.mapping.PreferredConstructor.Parameter;
|
||||
import org.springframework.data.mapping.model.MappingInstantiationException;
|
||||
import org.springframework.data.mapping.model.InternalEntityInstantiatorFactory;
|
||||
import org.springframework.data.mapping.model.ParameterValueProvider;
|
||||
|
||||
/**
|
||||
@@ -35,50 +27,15 @@ import org.springframework.data.mapping.model.ParameterValueProvider;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @deprecated since 2.3, use {@link org.springframework.data.mapping.model.ReflectionEntityInstantiator} instead.
|
||||
*/
|
||||
public enum ReflectionEntityInstantiator implements EntityInstantiator {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
private static final Object[] EMPTY_ARGS = new Object[0];
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
|
||||
ParameterValueProvider<P> provider) {
|
||||
|
||||
PreferredConstructor<? extends T, P> constructor = entity.getPersistenceConstructor();
|
||||
|
||||
if (constructor == null) {
|
||||
|
||||
try {
|
||||
Class<?> clazz = entity.getType();
|
||||
if (clazz.isArray()) {
|
||||
Class<?> ctype = clazz;
|
||||
int dims = 0;
|
||||
while (ctype.isArray()) {
|
||||
ctype = ctype.getComponentType();
|
||||
dims++;
|
||||
}
|
||||
return (T) Array.newInstance(clazz, dims);
|
||||
} else {
|
||||
return BeanUtils.instantiateClass(entity.getType());
|
||||
}
|
||||
} catch (BeanInstantiationException e) {
|
||||
throw new MappingInstantiationException(entity, Collections.emptyList(), e);
|
||||
}
|
||||
}
|
||||
int parameterCount = constructor.getConstructor().getParameterCount();
|
||||
|
||||
Object[] params = parameterCount == 0 ? EMPTY_ARGS : new Object[parameterCount];
|
||||
int i = 0;
|
||||
for (Parameter<?, P> parameter : constructor.getParameters()) {
|
||||
params[i++] = provider.getParameterValue(parameter);
|
||||
}
|
||||
|
||||
try {
|
||||
return BeanUtils.instantiateClass(constructor.getConstructor(), params);
|
||||
} catch (BeanInstantiationException e) {
|
||||
throw new MappingInstantiationException(entity, new ArrayList<>(Arrays.asList(params)), e);
|
||||
}
|
||||
return InternalEntityInstantiatorFactory.getReflectionEntityInstantiator().createInstance(entity, provider);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user