Skip to content

Commit 33939eb

Browse files
committed
Explore including generated PersistentPropertyAccessorFactory and EntityInstantiator classes.
We now pre-initialize ClassGeneratingPropertyAccessorFactory and ClassGeneratingEntityInstantiator infrastructure to generate bytecode for their respective classes so that we include the generated code for the target AOT package. Also, we check for presence of these types to conditionally load generated classes if these are on the classpath. This change required a stable class name therefore, we're hashing the fully-qualified class name and have aligned the class name from _Accessor to __Accessor (two underscores instead of one, same for Instantiator).
1 parent 8af529d commit 33939eb

14 files changed

+507
-106
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2025 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+
* https://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.aot;
17+
18+
import org.springframework.data.mapping.Association;
19+
import org.springframework.data.mapping.PersistentEntity;
20+
import org.springframework.data.mapping.context.AbstractMappingContext;
21+
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
22+
import org.springframework.data.mapping.model.BasicPersistentEntity;
23+
import org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory;
24+
import org.springframework.data.mapping.model.EntityInstantiator;
25+
import org.springframework.data.mapping.model.EntityInstantiators;
26+
import org.springframework.data.mapping.model.PersistentEntityClassInitializer;
27+
import org.springframework.data.mapping.model.Property;
28+
import org.springframework.data.mapping.model.SimpleTypeHolder;
29+
import org.springframework.data.util.TypeInformation;
30+
31+
/**
32+
* Simple {@link AbstractMappingContext} for processing of AOT contributions.
33+
*
34+
* @author Mark Paluch
35+
* @since 4.0
36+
*/
37+
public class AotMappingContext extends
38+
AbstractMappingContext<BasicPersistentEntity<?, AotMappingContext.BasicPersistentProperty>, AotMappingContext.BasicPersistentProperty> {
39+
40+
private final EntityInstantiators instantiators = new EntityInstantiators();
41+
private final ClassGeneratingPropertyAccessorFactory propertyAccessorFactory = new ClassGeneratingPropertyAccessorFactory();
42+
43+
/**
44+
* Contribute entity instantiators and property accessors for the given {@link PersistentEntity} that are captured
45+
* through Spring's {@code CglibClassHandler}. Otherwise, this is a no-op if contributions are not ran through
46+
* {@code CglibClassHandler}.
47+
*
48+
* @param entity
49+
*/
50+
public void contribute(PersistentEntity<?, ?> entity) {
51+
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
52+
if (instantiator instanceof PersistentEntityClassInitializer pec) {
53+
pec.initialize(entity);
54+
}
55+
propertyAccessorFactory.initialize(entity);
56+
}
57+
58+
@Override
59+
protected <T> BasicPersistentEntity<?, BasicPersistentProperty> createPersistentEntity(
60+
TypeInformation<T> typeInformation) {
61+
return new BasicPersistentEntity<>(typeInformation);
62+
}
63+
64+
@Override
65+
protected BasicPersistentProperty createPersistentProperty(Property property,
66+
BasicPersistentEntity<?, BasicPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
67+
return new BasicPersistentProperty(property, owner, simpleTypeHolder);
68+
}
69+
70+
static class BasicPersistentProperty extends AnnotationBasedPersistentProperty<BasicPersistentProperty> {
71+
72+
public BasicPersistentProperty(Property property, PersistentEntity<?, BasicPersistentProperty> owner,
73+
SimpleTypeHolder simpleTypeHolder) {
74+
super(property, owner, simpleTypeHolder);
75+
}
76+
77+
@Override
78+
protected Association<BasicPersistentProperty> createAssociation() {
79+
return null;
80+
}
81+
}
82+
83+
}

src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.core.env.Environment;
3737
import org.springframework.core.env.StandardEnvironment;
3838
import org.springframework.data.domain.ManagedTypes;
39+
import org.springframework.data.mapping.PersistentEntity;
3940
import org.springframework.data.util.Lazy;
4041
import org.springframework.data.util.QTypeContributor;
4142
import org.springframework.data.util.TypeContributor;
@@ -56,6 +57,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
5657
private final Log logger = LogFactory.getLog(getClass());
5758
private @Nullable String moduleIdentifier;
5859
private Lazy<Environment> environment = Lazy.of(StandardEnvironment::new);
60+
private final AotMappingContext aotMappingContext = new AotMappingContext();
5961

6062
public void setModuleIdentifier(@Nullable String moduleIdentifier) {
6163
this.moduleIdentifier = moduleIdentifier;
@@ -150,6 +152,11 @@ protected void contributeType(ResolvableType type, GenerationContext generationC
150152
TypeContributor.contribute(resolvedType, annotationNamespaces, generationContext);
151153
QTypeContributor.contributeEntityPath(resolvedType, generationContext, resolvedType.getClassLoader());
152154

155+
PersistentEntity<?, ?> entity = aotMappingContext.getPersistentEntity(resolvedType);
156+
if (entity != null) {
157+
aotMappingContext.contribute(entity);
158+
}
159+
153160
TypeUtils.resolveUsedAnnotations(resolvedType).forEach(
154161
annotation -> TypeContributor.contribute(annotation.getType(), annotationNamespaces, generationContext));
155162
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
import org.springframework.data.mapping.PersistentPropertyPath;
5757
import org.springframework.data.mapping.PersistentPropertyPaths;
5858
import org.springframework.data.mapping.PropertyPath;
59-
import org.springframework.data.mapping.model.BeanWrapperPropertyAccessorFactory;
6059
import org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory;
6160
import org.springframework.data.mapping.model.EntityInstantiators;
6261
import org.springframework.data.mapping.model.InstantiationAwarePropertyAccessorFactory;
@@ -125,7 +124,7 @@ protected AbstractMappingContext() {
125124

126125
EntityInstantiators instantiators = new EntityInstantiators();
127126
PersistentPropertyAccessorFactory accessorFactory = NativeDetector.inNativeImage()
128-
? BeanWrapperPropertyAccessorFactory.INSTANCE
127+
? new ReflectionFallbackPersistentPropertyAccessorFactory()
129128
: new ClassGeneratingPropertyAccessorFactory();
130129

131130
this.persistentPropertyAccessorFactory = new InstantiationAwarePropertyAccessorFactory(accessorFactory,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2025 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+
* https://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.mapping.context;
17+
18+
import org.springframework.data.mapping.PersistentEntity;
19+
import org.springframework.data.mapping.PersistentPropertyAccessor;
20+
import org.springframework.data.mapping.model.BeanWrapperPropertyAccessorFactory;
21+
import org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory;
22+
import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory;
23+
24+
/**
25+
* {@link PersistentPropertyAccessorFactory} that uses {@link ClassGeneratingPropertyAccessorFactory} if
26+
* {@link ClassGeneratingPropertyAccessorFactory#isSupported(PersistentEntity) supported} and falls back to reflection.
27+
*
28+
* @author Mark Paluch
29+
* @since 4.0
30+
*/
31+
class ReflectionFallbackPersistentPropertyAccessorFactory implements PersistentPropertyAccessorFactory {
32+
33+
private final ClassGeneratingPropertyAccessorFactory accessorFactory = new ClassGeneratingPropertyAccessorFactory();
34+
35+
@Override
36+
public <T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<?, ?> entity, T bean) {
37+
38+
if (accessorFactory.isSupported(entity)) {
39+
return accessorFactory.getPropertyAccessor(entity, bean);
40+
}
41+
42+
return BeanWrapperPropertyAccessorFactory.INSTANCE.getPropertyAccessor(entity, bean);
43+
}
44+
45+
@Override
46+
public boolean isSupported(PersistentEntity<?, ?> entity) {
47+
return true;
48+
}
49+
}

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

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
* An {@link EntityInstantiator} that can generate byte code to speed-up dynamic object instantiation. Uses the
5252
* {@link PersistentEntity}'s {@link PreferredConstructor} to instantiate an instance of the entity by dynamically
5353
* generating factory methods with appropriate constructor invocations via ASM. If we cannot generate byte code for a
54-
* type, we gracefully fallback to the {@link ReflectionEntityInstantiator}.
54+
* type, we gracefully fall back to the {@link ReflectionEntityInstantiator}.
5555
*
5656
* @author Thomas Darimont
5757
* @author Oliver Gierke
@@ -60,7 +60,7 @@
6060
* @author Mark Paluch
6161
* @since 1.11
6262
*/
63-
class ClassGeneratingEntityInstantiator implements EntityInstantiator {
63+
class ClassGeneratingEntityInstantiator implements EntityInstantiator, PersistentEntityClassInitializer {
6464

6565
private static final Log LOGGER = LogFactory.getLog(ClassGeneratingEntityInstantiator.class);
6666

@@ -87,17 +87,29 @@ public ClassGeneratingEntityInstantiator() {
8787
this.fallbackToReflectionOnError = fallbackToReflectionOnError;
8888
}
8989

90+
@Override
91+
public void initialize(PersistentEntity<?, ?> entity) {
92+
getEntityInstantiator(entity);
93+
}
94+
9095
@Override
9196
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
9297
ParameterValueProvider<P> provider) {
9398

99+
EntityInstantiator instantiator = getEntityInstantiator(entity);
100+
return instantiator.createInstance(entity, provider);
101+
}
102+
103+
private <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> EntityInstantiator getEntityInstantiator(
104+
E entity) {
105+
94106
EntityInstantiator instantiator = this.entityInstantiators.get(entity.getTypeInformation());
95107

96108
if (instantiator == null) {
97109
instantiator = potentiallyCreateAndRegisterEntityInstantiator(entity);
98110
}
99111

100-
return instantiator.createInstance(entity, provider);
112+
return instantiator;
101113
}
102114

103115
/**
@@ -170,10 +182,19 @@ protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity<?, ?> e
170182
*/
171183
boolean shouldUseReflectionEntityInstantiator(PersistentEntity<?, ?> entity) {
172184

185+
String accessorClassName = ObjectInstantiatorClassGenerator.generateClassName(entity);
186+
187+
// already present in classloader
188+
if (ClassUtils.isPresent(accessorClassName, entity.getType().getClassLoader())) {
189+
return false;
190+
}
191+
173192
if (NativeDetector.inNativeImage()) {
174193

175194
if (LOGGER.isDebugEnabled()) {
176-
LOGGER.debug(String.format("graalvm.nativeimage - fall back to reflection for %s", entity.getName()));
195+
LOGGER.debug(String.format(
196+
"[org.graalvm.nativeimage.imagecode=true] and no AOT-generated EntityInstantiator for %s. Falling back to reflection.",
197+
entity.getName()));
177198
}
178199

179200
return true;
@@ -388,7 +409,7 @@ public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentPrope
388409
static class ObjectInstantiatorClassGenerator {
389410

390411
private static final String INIT = "<init>";
391-
private static final String TAG = "_Instantiator_";
412+
private static final String TAG = "__Instantiator_";
392413
private static final String JAVA_LANG_OBJECT = Type.getInternalName(Object.class);
393414
private static final String CREATE_METHOD_NAME = "newInstance";
394415

@@ -431,8 +452,8 @@ public Class<?> generateCustomInstantiatorClass(PersistentEntity<?, ?> entity,
431452
* @param entity
432453
* @return
433454
*/
434-
private String generateClassName(PersistentEntity<?, ?> entity) {
435-
return entity.getType().getName() + TAG + Integer.toString(entity.hashCode(), 36);
455+
static String generateClassName(PersistentEntity<?, ?> entity) {
456+
return entity.getType().getName() + TAG + Integer.toString(Math.abs(entity.getType().getName().hashCode()), 36);
436457
}
437458

438459
/**

0 commit comments

Comments
 (0)