Skip to content

Commit 22b3780

Browse files
mp911deodrotbohm
authored andcommitted
DATACMNS-1126 - Add class-generating entity instantiation support for Kotlin.
We now discover Kotlin constructors and apply parameter defaulting to allow construction of immutable value objects. Constructor discovery uses primary constructors by default and considers PersistenceConstructor annotations. KotlinClassGeneratingEntityInstantiator can instantiate Kotlin classes with default parameters by resolving the synthetic constructor. Null values translate to parameter defaults. class Person(val firstname: String = "Walter") { } class Address(val street: String, val city: String) { @PersistenceConstructor constructor(street: String = "Unknown", city: String = "Unknown", country: String) : this(street, city) } Original pull request: #233.
1 parent e4e677d commit 22b3780

15 files changed

+887
-209
lines changed

pom.xml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,49 @@
231231
<scope>test</scope>
232232
</dependency>
233233

234+
<!-- Kotlin extension -->
235+
<dependency>
236+
<groupId>org.jetbrains.kotlin</groupId>
237+
<artifactId>kotlin-stdlib</artifactId>
238+
<version>${kotlin}</version>
239+
<optional>true</optional>
240+
</dependency>
241+
242+
<dependency>
243+
<groupId>org.jetbrains.kotlin</groupId>
244+
<artifactId>kotlin-reflect</artifactId>
245+
<version>${kotlin}</version>
246+
<optional>true</optional>
247+
</dependency>
248+
249+
<dependency>
250+
<groupId>org.jetbrains.kotlin</groupId>
251+
<artifactId>kotlin-test</artifactId>
252+
<version>${kotlin}</version>
253+
<scope>test</scope>
254+
</dependency>
255+
256+
<dependency>
257+
<groupId>com.nhaarman</groupId>
258+
<artifactId>mockito-kotlin</artifactId>
259+
<version>1.5.0</version>
260+
<scope>test</scope>
261+
<exclusions>
262+
<exclusion>
263+
<groupId>org.jetbrains.kotlin</groupId>
264+
<artifactId>kotlin-stdlib</artifactId>
265+
</exclusion>
266+
<exclusion>
267+
<groupId>org.jetbrains.kotlin</groupId>
268+
<artifactId>kotlin-reflect</artifactId>
269+
</exclusion>
270+
<exclusion>
271+
<groupId>org.mockito</groupId>
272+
<artifactId>mockito-core</artifactId>
273+
</exclusion>
274+
</exclusions>
275+
</dependency>
276+
234277
<!-- Scala -->
235278
<dependency>
236279
<groupId>org.scala-lang</groupId>

src/main/java/org/springframework/data/convert/ClassGeneratingEntityInstantiator.java

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@
2121
import java.lang.reflect.Modifier;
2222
import java.security.AccessController;
2323
import java.security.PrivilegedAction;
24-
import java.util.ArrayList;
2524
import java.util.Arrays;
2625
import java.util.HashMap;
27-
import java.util.List;
2826
import java.util.Map;
2927

3028
import org.springframework.asm.ClassWriter;
@@ -57,6 +55,19 @@
5755
*/
5856
public class ClassGeneratingEntityInstantiator implements EntityInstantiator {
5957

58+
private static final int ARG_CACHE_SIZE = 100;
59+
60+
private static final ThreadLocal<Object[][]> OBJECT_POOL = ThreadLocal.withInitial(() -> {
61+
62+
Object[][] cached = new Object[ARG_CACHE_SIZE][];
63+
64+
for (int i = 0; i < ARG_CACHE_SIZE; i++) {
65+
cached[i] = new Object[i];
66+
}
67+
68+
return cached;
69+
});
70+
6071
private final ObjectInstantiatorClassGenerator generator;
6172

6273
private volatile Map<TypeInformation<?>, EntityInstantiator> entityInstantiators = new HashMap<>(32);
@@ -120,12 +131,20 @@ private EntityInstantiator createEntityInstantiator(PersistentEntity<?, ?> entit
120131
}
121132

122133
try {
123-
return new EntityInstantiatorAdapter(createObjectInstantiator(entity));
134+
return doCreateEntityInstantiator(entity);
124135
} catch (Throwable ex) {
125136
return ReflectionEntityInstantiator.INSTANCE;
126137
}
127138
}
128139

140+
/**
141+
* @param entity
142+
* @return
143+
*/
144+
protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity<?, ?> entity) {
145+
return new EntityInstantiatorAdapter(createObjectInstantiator(entity, entity.getPersistenceConstructor()));
146+
}
147+
129148
/**
130149
* @param entity
131150
* @return
@@ -151,17 +170,46 @@ private boolean shouldUseReflectionEntityInstantiator(PersistentEntity<?, ?> ent
151170
}
152171

153172
/**
154-
* Creates a dynamically generated {@link ObjectInstantiator} for the given {@link PersistentEntity}. There will
155-
* always be exactly one {@link ObjectInstantiator} instance per {@link PersistentEntity}.
156-
* <p>
173+
* Allocates an object array for instance creation. This method uses the argument array cache if possible.
174+
*
175+
* @param argumentCount
176+
* @return
177+
* @since 2.0
178+
* @see #ARG_CACHE_SIZE
179+
*/
180+
static Object[] allocateArguments(int argumentCount) {
181+
return argumentCount < ARG_CACHE_SIZE ? OBJECT_POOL.get()[argumentCount] : new Object[argumentCount];
182+
}
183+
184+
/**
185+
* Deallocates an object array used for instance creation. Parameters are cleared if the array was cached.
186+
*
187+
* @param argumentCount
188+
* @return
189+
* @since 2.0
190+
* @see #ARG_CACHE_SIZE
191+
*/
192+
static void deallocateArguments(Object[] params) {
193+
194+
if (params.length != 0 && params.length < ARG_CACHE_SIZE) {
195+
Arrays.fill(params, null);
196+
}
197+
}
198+
199+
/**
200+
* Creates a dynamically generated {@link ObjectInstantiator} for the given {@link PersistentEntity} and
201+
* {@link PreferredConstructor}. There will always be exactly one {@link ObjectInstantiator} instance per
202+
* {@link PersistentEntity}.
157203
*
158204
* @param entity
205+
* @param constructor
159206
* @return
160207
*/
161-
private ObjectInstantiator createObjectInstantiator(PersistentEntity<?, ?> entity) {
208+
ObjectInstantiator createObjectInstantiator(PersistentEntity<?, ?> entity,
209+
@Nullable PreferredConstructor<?, ?> constructor) {
162210

163211
try {
164-
return (ObjectInstantiator) this.generator.generateCustomInstantiatorClass(entity).newInstance();
212+
return (ObjectInstantiator) this.generator.generateCustomInstantiatorClass(entity, constructor).newInstance();
165213
} catch (Exception e) {
166214
throw new RuntimeException(e);
167215
}
@@ -172,11 +220,10 @@ private ObjectInstantiator createObjectInstantiator(PersistentEntity<?, ?> entit
172220
*
173221
* @author Thomas Darimont
174222
* @author Oliver Gierke
223+
* @author Mark Paluch
175224
*/
176225
private static class EntityInstantiatorAdapter implements EntityInstantiator {
177226

178-
private static final Object[] EMPTY_ARRAY = new Object[0];
179-
180227
private final ObjectInstantiator instantiator;
181228

182229
/**
@@ -203,6 +250,8 @@ public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentPrope
203250
return (T) instantiator.newInstance(params);
204251
} catch (Exception e) {
205252
throw new MappingInstantiationException(entity, Arrays.asList(params), e);
253+
} finally {
254+
deallocateArguments(params);
206255
}
207256
}
208257

@@ -216,17 +265,18 @@ public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentPrope
216265
private <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments(
217266
@Nullable PreferredConstructor<? extends T, P> constructor, ParameterValueProvider<P> provider) {
218267

219-
if (provider == null || constructor == null || !constructor.hasParameters()) {
220-
return EMPTY_ARRAY;
268+
if (constructor == null || !constructor.hasParameters()) {
269+
return allocateArguments(0);
221270
}
222271

223-
List<Object> params = new ArrayList<>(constructor.getConstructor().getParameterCount());
272+
Object[] params = allocateArguments(constructor.getConstructor().getParameterCount());
224273

274+
int index = 0;
225275
for (Parameter<?, P> parameter : constructor.getParameters()) {
226-
params.add(provider.getParameterValue(parameter));
276+
params[index++] = provider.getParameterValue(parameter);
227277
}
228278

229-
return params.toArray();
279+
return params;
230280
}
231281
}
232282

@@ -290,7 +340,7 @@ static class ObjectInstantiatorClassGenerator {
290340

291341
private final ByteArrayClassLoader classLoader;
292342

293-
private ObjectInstantiatorClassGenerator() {
343+
ObjectInstantiatorClassGenerator() {
294344

295345
this.classLoader = AccessController.doPrivileged(
296346
(PrivilegedAction<ByteArrayClassLoader>) () -> new ByteArrayClassLoader(ClassUtils.getDefaultClassLoader()));
@@ -300,12 +350,14 @@ private ObjectInstantiatorClassGenerator() {
300350
* Generate a new class for the given {@link PersistentEntity}.
301351
*
302352
* @param entity
353+
* @param constructor
303354
* @return
304355
*/
305-
public Class<?> generateCustomInstantiatorClass(PersistentEntity<?, ?> entity) {
356+
public Class<?> generateCustomInstantiatorClass(PersistentEntity<?, ?> entity,
357+
@Nullable PreferredConstructor<?, ?> constructor) {
306358

307359
String className = generateClassName(entity);
308-
byte[] bytecode = generateBytecode(className, entity);
360+
byte[] bytecode = generateBytecode(className, entity, constructor);
309361

310362
return classLoader.loadClass(className, bytecode);
311363
}
@@ -323,9 +375,11 @@ private String generateClassName(PersistentEntity<?, ?> entity) {
323375
*
324376
* @param internalClassName
325377
* @param entity
378+
* @param constructor
326379
* @return
327380
*/
328-
public byte[] generateBytecode(String internalClassName, PersistentEntity<?, ?> entity) {
381+
public byte[] generateBytecode(String internalClassName, PersistentEntity<?, ?> entity,
382+
@Nullable PreferredConstructor<?, ?> constructor) {
329383

330384
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
331385

@@ -334,7 +388,7 @@ public byte[] generateBytecode(String internalClassName, PersistentEntity<?, ?>
334388

335389
visitDefaultConstructor(cw);
336390

337-
visitCreateMethod(cw, entity);
391+
visitCreateMethod(cw, entity, constructor);
338392

339393
cw.visitEnd();
340394

@@ -357,8 +411,10 @@ private void visitDefaultConstructor(ClassWriter cw) {
357411
*
358412
* @param cw
359413
* @param entity
414+
* @param constructor
360415
*/
361-
private void visitCreateMethod(ClassWriter cw, PersistentEntity<?, ?> entity) {
416+
private void visitCreateMethod(ClassWriter cw, PersistentEntity<?, ?> entity,
417+
@Nullable PreferredConstructor<?, ?> constructor) {
362418

363419
String entityTypeResourcePath = Type.getInternalName(entity.getType());
364420

@@ -368,8 +424,6 @@ private void visitCreateMethod(ClassWriter cw, PersistentEntity<?, ?> entity) {
368424
mv.visitTypeInsn(NEW, entityTypeResourcePath);
369425
mv.visitInsn(DUP);
370426

371-
PreferredConstructor<?, ?> constructor = entity.getPersistenceConstructor();
372-
373427
if (constructor != null) {
374428

375429
Constructor<?> ctor = constructor.getConstructor();

src/main/java/org/springframework/data/convert/EntityInstantiators.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@
2424
/**
2525
* Simple value object allowing access to {@link EntityInstantiator} instances for a given type falling back to a
2626
* default one.
27-
*
27+
*
2828
* @author Oliver Gierke
2929
* @author Thomas Darimont
3030
* @author Christoph Strobl
31+
* @author Mark Paluch
3132
*/
3233
public class EntityInstantiators {
3334

@@ -43,7 +44,7 @@ public EntityInstantiators() {
4344

4445
/**
4546
* Creates a new {@link EntityInstantiators} using the given {@link EntityInstantiator} as fallback.
46-
*
47+
*
4748
* @param fallback must not be {@literal null}.
4849
*/
4950
public EntityInstantiators(EntityInstantiator fallback) {
@@ -52,17 +53,17 @@ public EntityInstantiators(EntityInstantiator fallback) {
5253

5354
/**
5455
* Creates a new {@link EntityInstantiators} using the default fallback instantiator and the given custom ones.
55-
*
56+
*
5657
* @param customInstantiators must not be {@literal null}.
5758
*/
5859
public EntityInstantiators(Map<Class<?>, EntityInstantiator> customInstantiators) {
59-
this(new ClassGeneratingEntityInstantiator(), customInstantiators);
60+
this(new ClassGeneratingKotlinEntityInstantiator(), customInstantiators);
6061
}
6162

6263
/**
6364
* Creates a new {@link EntityInstantiator} using the given fallback {@link EntityInstantiator} and the given custom
6465
* ones.
65-
*
66+
*
6667
* @param defaultInstantiator must not be {@literal null}.
6768
* @param customInstantiators must not be {@literal null}.
6869
*/
@@ -78,7 +79,7 @@ public EntityInstantiators(EntityInstantiator defaultInstantiator,
7879

7980
/**
8081
* Returns the {@link EntityInstantiator} to be used to create the given {@link PersistentEntity}.
81-
*
82+
*
8283
* @param entity must not be {@literal null}.
8384
* @return will never be {@literal null}.
8485
*/

0 commit comments

Comments
 (0)