Skip to content

Commit af4c00a

Browse files
author
Thomas Darimont
committed
DATACMNS-578 - Speed up instance creation by generating Factory classes with ASM.
Introduced BytecodeGeneratingEntityInstantiator which dynamically generates classes to speed up the dynamic instantiation of objects. Since we cannot support every use case with byte code generation we gracefully fallback to the ReflectiveEntityInstantiator.
1 parent 4165b95 commit af4c00a

File tree

5 files changed

+680
-5
lines changed

5 files changed

+680
-5
lines changed
Lines changed: 376 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,376 @@
1+
/*
2+
* Copyright 2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.convert;
17+
18+
import static org.springframework.asm.Opcodes.*;
19+
20+
import java.lang.reflect.Constructor;
21+
import java.lang.reflect.Modifier;
22+
import java.util.ArrayList;
23+
import java.util.HashMap;
24+
import java.util.List;
25+
import java.util.Map;
26+
27+
import org.springframework.asm.ClassWriter;
28+
import org.springframework.asm.Label;
29+
import org.springframework.asm.MethodVisitor;
30+
import org.springframework.asm.Opcodes;
31+
import org.springframework.asm.Type;
32+
import org.springframework.data.mapping.PersistentEntity;
33+
import org.springframework.data.mapping.PersistentProperty;
34+
import org.springframework.data.mapping.PreferredConstructor;
35+
import org.springframework.data.mapping.PreferredConstructor.Parameter;
36+
import org.springframework.data.mapping.model.ParameterValueProvider;
37+
import org.springframework.util.ReflectionUtils;
38+
39+
/**
40+
* {@link EntityInstantiator} that uses the {@link PersistentEntity}'s {@link PreferredConstructor} to instantiate an
41+
* instance of the entity by dynamically generating factory methods via ASM. Since we cannot support every use case with byte
42+
* code generation we gracefully fall-back to the {@link ReflectionEntityInstantiator}.
43+
*
44+
* @author Thomas Darimont
45+
* @since 1.10
46+
*/
47+
public enum BytecodeGeneratingEntityInstantiator implements EntityInstantiator {
48+
49+
INSTANCE;
50+
51+
private final ObjectCreatorClassGenerator classGenerator = ObjectCreatorClassGenerator.INSTANCE;
52+
53+
private volatile Map<CreatorKey, ObjectCreator> creators = new HashMap<CreatorKey, ObjectCreator>(32);
54+
55+
@SuppressWarnings("unchecked")
56+
@Override
57+
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
58+
ParameterValueProvider<P> provider) {
59+
60+
Class<? extends T> type = entity.getType();
61+
62+
if (shouldUseReflectiveEntityInstantiator(entity)) {
63+
return ReflectionEntityInstantiator.INSTANCE.createInstance(entity, provider);
64+
}
65+
66+
PreferredConstructor<? extends T, P> ctor = entity.getPersistenceConstructor();
67+
68+
List<Object> params = new ArrayList<Object>();
69+
if (null != provider && ctor.hasParameters()) {
70+
for (Parameter<?, P> parameter : ctor.getParameters()) {
71+
params.add(provider.getParameterValue(parameter));
72+
}
73+
}
74+
75+
Object[] args = params.toArray();
76+
Constructor<?> ctorToUse = ctor != null && Modifier.isPublic(ctor.getConstructor().getModifiers()) ? ctor
77+
.getConstructor() : findAppropriateConstructorForArguments(type, args);
78+
79+
if (ctorToUse == null || !assertConstructorTypesAreCompatibleWithArgTypes(args, ctorToUse)) {
80+
return ReflectionEntityInstantiator.INSTANCE.createInstance(entity, provider);
81+
}
82+
83+
return (T) createInternal(entity.getClass(), ctor.getConstructor(), args);
84+
}
85+
86+
private boolean assertConstructorTypesAreCompatibleWithArgTypes(Object[] args, Constructor<?> ctorToUse) {
87+
88+
for (int i = 0; ctorToUse != null && i < args.length; i++) {
89+
Class<?>[] ctorParamTypes = ctorToUse.getParameterTypes();
90+
91+
// ignore null arguments
92+
if (args[i] == null) {
93+
continue;
94+
}
95+
96+
// types are not compatible
97+
if (!args[i].getClass().isAssignableFrom(ctorParamTypes[i])) {
98+
return false;
99+
}
100+
}
101+
102+
return true;
103+
}
104+
105+
private <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> boolean shouldUseReflectiveEntityInstantiator(
106+
E entity) {
107+
108+
Class<? extends T> type = entity.getType();
109+
110+
PreferredConstructor<? extends T, P> persistenceCtor = entity.getPersistenceConstructor();
111+
112+
return type.getName().startsWith("java.lang") //
113+
|| type.isInterface() //
114+
|| type.isArray() //
115+
|| !Modifier.isPublic(type.getModifiers()) //
116+
|| (persistenceCtor != null //
117+
// && persistenceCtor.getConstructor().getAnnotation(PersistenceConstructor.class) != null //
118+
&& !Modifier.isPublic(persistenceCtor.getConstructor().getModifiers())) //
119+
;
120+
}
121+
122+
@SuppressWarnings("unchecked")
123+
private <T> Constructor<T> findAppropriateConstructorForArguments(Class<T> clazz, Object[] args) {
124+
125+
Constructor<T> result = null;
126+
127+
ctorLoop: for (Constructor<?> ctorCandidate : clazz.getDeclaredConstructors()) {
128+
129+
if (ctorCandidate.getParameterCount() == args.length) {
130+
131+
Class<?>[] parameterTypes = ctorCandidate.getParameterTypes();
132+
for (int i = 0; i < args.length; i++) {
133+
134+
if (args[i] == null) {
135+
continue;
136+
}
137+
138+
if (!parameterTypes[i].isInstance(args[i])) {
139+
continue ctorLoop;
140+
}
141+
}
142+
143+
result = (Constructor<T>) ctorCandidate;
144+
}
145+
}
146+
147+
if (result != null) {
148+
ReflectionUtils.makeAccessible(result);
149+
}
150+
151+
return result;
152+
}
153+
154+
private Object createInternal(Class<?> clazz, Constructor<?> ctor, Object... args) {
155+
156+
CreatorKey key = new CreatorKey(clazz, ctor);
157+
158+
Map<CreatorKey, ObjectCreator> map = this.creators;
159+
ObjectCreator creator = map.get(key);
160+
161+
if (creator == null) {
162+
creator = createObjectCreator(key);
163+
164+
map = new HashMap<CreatorKey, ObjectCreator>(map);
165+
map.put(key, creator);
166+
167+
this.creators = map;
168+
}
169+
170+
// System.out.println("Use creator to construct new object: " + creator);
171+
172+
return creator.create(args);
173+
}
174+
175+
private ObjectCreator createObjectCreator(CreatorKey key) {
176+
177+
// System.out.println("######Generate class: " + key);
178+
179+
try {
180+
return (ObjectCreator) classGenerator.generateClass(key).newInstance();
181+
} catch (Exception e) {
182+
throw new RuntimeException(e);
183+
}
184+
}
185+
186+
/**
187+
* @author Thomas Darimont
188+
*/
189+
static class CreatorKey {
190+
191+
private final Class<?> type;
192+
private final Constructor<?> ctor;
193+
194+
CreatorKey(Class<?> type, Constructor<?> ctor) {
195+
this.type = type;
196+
this.ctor = ctor;
197+
}
198+
199+
@Override
200+
public boolean equals(Object o) {
201+
if (this == o)
202+
return true;
203+
if (o == null || getClass() != o.getClass())
204+
return false;
205+
206+
CreatorKey that = (CreatorKey) o;
207+
208+
if (ctor != null ? !ctor.equals(that.ctor) : that.ctor != null)
209+
return false;
210+
if (!type.equals(that.type))
211+
return false;
212+
213+
return true;
214+
}
215+
216+
@Override
217+
public int hashCode() {
218+
int result = type.hashCode();
219+
result = 31 * result + (ctor != null ? ctor.hashCode() : 0);
220+
return result;
221+
}
222+
223+
@Override
224+
public String toString() {
225+
return "CreatorKey [type=" + type + ", ctor=" + ctor + "]";
226+
}
227+
}
228+
229+
/**
230+
* @author Thomas Darimont
231+
*/
232+
static class ByteArrayClassLoader extends ClassLoader {
233+
234+
public ByteArrayClassLoader(ClassLoader parent) {
235+
super(parent);
236+
}
237+
238+
public Class<?> loadClass(String name, byte[] bytes) {
239+
240+
try {
241+
Class<?> clazz = findClass(name);
242+
if (clazz != null) {
243+
return clazz;
244+
}
245+
} catch (ClassNotFoundException ignore) {}
246+
247+
return defineClass(name, bytes, 0, bytes.length);
248+
}
249+
}
250+
251+
/**
252+
* @author Thomas Darimont
253+
*/
254+
public static interface ObjectCreator {
255+
256+
Object create(Object... args);
257+
}
258+
259+
/**
260+
* A dynamic factory class generator.
261+
*
262+
* This code generator will lazily generate a custom factory class implementing the {@link ObjectCreator} interface
263+
* for every publicly accessed constructor variant.
264+
*
265+
* Given a class {@code ObjCtor1ParamString} like:
266+
* <pre> {@code
267+
* public class ObjCtor1ParamString extends ObjCtorNoArgs {
268+
*
269+
* public final String param1;
270+
*
271+
* public ObjCtor1ParamString(String param1) {
272+
* this.param1 = param1;
273+
* }
274+
* }}</pre>
275+
*
276+
* The following factory class {@code ObjCtor1ParamString_Creator_asdf} is generated:
277+
* <pre> {@code
278+
* public class ObjCtor1ParamString_Creator_asdf implements ObjectCreator{
279+
*
280+
* public Object create(Object... args) {
281+
* return new ObjCtor1ParamString((String)args[0]);
282+
* }
283+
* }}</pre>
284+
*
285+
* @author Thomas Darimont
286+
*/
287+
enum ObjectCreatorClassGenerator {
288+
289+
INSTANCE;
290+
291+
private static final String[] INTERNAL_OBJECT_CREATOR_INTERFACE_NAME = new String[] { Type
292+
.getInternalName(ObjectCreator.class) };
293+
294+
private final ByteArrayClassLoader classLoader = new ByteArrayClassLoader(
295+
ObjectCreatorClassGenerator.class.getClassLoader());
296+
297+
/**
298+
* Generate a new class for the given {@link CreatorKey}.
299+
*
300+
* @param key
301+
* @return
302+
*/
303+
public Class<?> generateClass(CreatorKey key) {
304+
305+
Constructor<?> ctor = key.ctor;
306+
307+
Class<?> objectClass = ctor.getDeclaringClass();
308+
309+
String creatorSuffix = "_Creator_" + Integer.toString(key.hashCode(), 36);
310+
String creatorClassResourcePath = Type.getInternalName(objectClass) + creatorSuffix;
311+
String entityTypeResourcePath = Type.getInternalName(objectClass);
312+
313+
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
314+
MethodVisitor mv;
315+
316+
cw.visit(Opcodes.V1_6, ACC_PUBLIC + ACC_SUPER, creatorClassResourcePath, null, "java/lang/Object",
317+
INTERNAL_OBJECT_CREATOR_INTERFACE_NAME);
318+
319+
{
320+
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
321+
mv.visitCode();
322+
Label l0 = new Label();
323+
mv.visitLabel(l0);
324+
325+
mv.visitVarInsn(ALOAD, 0);
326+
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
327+
mv.visitInsn(RETURN);
328+
Label l1 = new Label();
329+
mv.visitLabel(l1);
330+
mv.visitLocalVariable("this", "L" + creatorClassResourcePath + ";", null, l0, l1, 0);
331+
mv.visitMaxs(1, 1);
332+
mv.visitEnd();
333+
}
334+
335+
{
336+
mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, "create", "([Ljava/lang/Object;)Ljava/lang/Object;", null, null);
337+
mv.visitCode();
338+
Label l0 = new Label();
339+
mv.visitLabel(l0);
340+
mv.visitTypeInsn(NEW, entityTypeResourcePath);
341+
mv.visitInsn(DUP);
342+
343+
if (ctor.getParameterCount() == 0) {
344+
mv.visitMethodInsn(INVOKESPECIAL, entityTypeResourcePath, "<init>", "()V", false);
345+
} else {
346+
347+
for (int i = 0; i < ctor.getParameterCount(); i++) {
348+
349+
mv.visitVarInsn(ALOAD, 1);
350+
mv.visitIntInsn(BIPUSH, i);
351+
352+
mv.visitInsn(AALOAD);
353+
mv.visitTypeInsn(CHECKCAST, Type.getInternalName(ctor.getParameterTypes()[i]));
354+
}
355+
356+
mv.visitMethodInsn(INVOKESPECIAL, entityTypeResourcePath, "<init>", Type.getConstructorDescriptor(ctor),
357+
false);
358+
}
359+
360+
mv.visitInsn(ARETURN);
361+
362+
Label l1 = new Label();
363+
mv.visitLabel(l1);
364+
mv.visitLocalVariable("this", "L" + creatorClassResourcePath + ";", null, l0, l1, 0);
365+
mv.visitLocalVariable("args", "[Ljava/lang/Object;", null, l0, l1, 1);
366+
367+
mv.visitMaxs(0, 0); // computed via ClassWriter.COMPUTE_MAXS
368+
mv.visitEnd();
369+
}
370+
371+
cw.visitEnd();
372+
373+
return classLoader.loadClass(objectClass.getName() + creatorSuffix, cw.toByteArray());
374+
}
375+
}
376+
}

0 commit comments

Comments
 (0)