Skip to content

Explore including generated PersistentPropertyAccessorFactory and EntityInstantiator classes #3318

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.0.0-GH-2595-SNAPSHOT</version>

<name>Spring Data Core</name>
<description>Core Spring concepts underpinning every Spring Data module.</description>
Expand Down
83 changes: 83 additions & 0 deletions src/main/java/org/springframework/data/aot/AotMappingContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright 2025 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.aot;

import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.context.AbstractMappingContext;
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory;
import org.springframework.data.mapping.model.EntityInstantiator;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.mapping.model.PersistentEntityClassInitializer;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.TypeInformation;

/**
* Simple {@link AbstractMappingContext} for processing of AOT contributions.
*
* @author Mark Paluch
* @since 4.0
*/
public class AotMappingContext extends
AbstractMappingContext<BasicPersistentEntity<?, AotMappingContext.BasicPersistentProperty>, AotMappingContext.BasicPersistentProperty> {

private final EntityInstantiators instantiators = new EntityInstantiators();
private final ClassGeneratingPropertyAccessorFactory propertyAccessorFactory = new ClassGeneratingPropertyAccessorFactory();

/**
* Contribute entity instantiators and property accessors for the given {@link PersistentEntity} that are captured
* through Spring's {@code CglibClassHandler}. Otherwise, this is a no-op if contributions are not ran through
* {@code CglibClassHandler}.
*
* @param entity
*/
public void contribute(PersistentEntity<?, ?> entity) {
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
if (instantiator instanceof PersistentEntityClassInitializer pec) {
pec.initialize(entity);
}
propertyAccessorFactory.initialize(entity);
}

@Override
protected <T> BasicPersistentEntity<?, BasicPersistentProperty> createPersistentEntity(
TypeInformation<T> typeInformation) {
return new BasicPersistentEntity<>(typeInformation);
}

@Override
protected BasicPersistentProperty createPersistentProperty(Property property,
BasicPersistentEntity<?, BasicPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
return new BasicPersistentProperty(property, owner, simpleTypeHolder);
}

static class BasicPersistentProperty extends AnnotationBasedPersistentProperty<BasicPersistentProperty> {

public BasicPersistentProperty(Property property, PersistentEntity<?, BasicPersistentProperty> owner,
SimpleTypeHolder simpleTypeHolder) {
super(property, owner, simpleTypeHolder);
}

@Override
protected Association<BasicPersistentProperty> createAssociation() {
return null;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.data.domain.ManagedTypes;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.QTypeContributor;
import org.springframework.data.util.TypeContributor;
Expand All @@ -56,6 +57,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
private final Log logger = LogFactory.getLog(getClass());
private @Nullable String moduleIdentifier;
private Lazy<Environment> environment = Lazy.of(StandardEnvironment::new);
private final AotMappingContext aotMappingContext = new AotMappingContext();

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

PersistentEntity<?, ?> entity = aotMappingContext.getPersistentEntity(resolvedType);
if (entity != null) {
aotMappingContext.contribute(entity);
}

TypeUtils.resolveUsedAnnotations(resolvedType).forEach(
annotation -> TypeContributor.contribute(annotation.getType(), annotationNamespaces, generationContext));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mapping.model.BeanWrapperPropertyAccessorFactory;
import org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.mapping.model.InstantiationAwarePropertyAccessorFactory;
Expand Down Expand Up @@ -125,7 +124,7 @@ protected AbstractMappingContext() {

EntityInstantiators instantiators = new EntityInstantiators();
PersistentPropertyAccessorFactory accessorFactory = NativeDetector.inNativeImage()
? BeanWrapperPropertyAccessorFactory.INSTANCE
? new ReflectionFallbackPersistentPropertyAccessorFactory()
: new ClassGeneratingPropertyAccessorFactory();

this.persistentPropertyAccessorFactory = new InstantiationAwarePropertyAccessorFactory(accessorFactory,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2025 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.mapping.context;

import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.model.BeanWrapperPropertyAccessorFactory;
import org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory;
import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory;

/**
* {@link PersistentPropertyAccessorFactory} that uses {@link ClassGeneratingPropertyAccessorFactory} if
* {@link ClassGeneratingPropertyAccessorFactory#isSupported(PersistentEntity) supported} and falls back to reflection.
*
* @author Mark Paluch
* @since 4.0
*/
class ReflectionFallbackPersistentPropertyAccessorFactory implements PersistentPropertyAccessorFactory {

private final ClassGeneratingPropertyAccessorFactory accessorFactory = new ClassGeneratingPropertyAccessorFactory();

@Override
public <T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<?, ?> entity, T bean) {

if (accessorFactory.isSupported(entity)) {
return accessorFactory.getPropertyAccessor(entity, bean);
}

return BeanWrapperPropertyAccessorFactory.INSTANCE.getPropertyAccessor(entity, bean);
}

@Override
public boolean isSupported(PersistentEntity<?, ?> entity) {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
* An {@link EntityInstantiator} that can generate byte code to speed-up dynamic object instantiation. Uses the
* {@link PersistentEntity}'s {@link PreferredConstructor} to instantiate an instance of the entity by dynamically
* generating factory methods with appropriate constructor invocations via ASM. If we cannot generate byte code for a
* type, we gracefully fallback to the {@link ReflectionEntityInstantiator}.
* type, we gracefully fall back to the {@link ReflectionEntityInstantiator}.
*
* @author Thomas Darimont
* @author Oliver Gierke
Expand All @@ -60,7 +60,7 @@
* @author Mark Paluch
* @since 1.11
*/
class ClassGeneratingEntityInstantiator implements EntityInstantiator {
class ClassGeneratingEntityInstantiator implements EntityInstantiator, PersistentEntityClassInitializer {

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

Expand All @@ -87,17 +87,29 @@ public ClassGeneratingEntityInstantiator() {
this.fallbackToReflectionOnError = fallbackToReflectionOnError;
}

@Override
public void initialize(PersistentEntity<?, ?> entity) {
getEntityInstantiator(entity);
}

@Override
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
ParameterValueProvider<P> provider) {

EntityInstantiator instantiator = getEntityInstantiator(entity);
return instantiator.createInstance(entity, provider);
}

private <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> EntityInstantiator getEntityInstantiator(
E entity) {

EntityInstantiator instantiator = this.entityInstantiators.get(entity.getTypeInformation());

if (instantiator == null) {
instantiator = potentiallyCreateAndRegisterEntityInstantiator(entity);
}

return instantiator.createInstance(entity, provider);
return instantiator;
}

/**
Expand Down Expand Up @@ -170,10 +182,19 @@ protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity<?, ?> e
*/
boolean shouldUseReflectionEntityInstantiator(PersistentEntity<?, ?> entity) {

String accessorClassName = ObjectInstantiatorClassGenerator.generateClassName(entity);

// already present in classloader
if (ClassUtils.isPresent(accessorClassName, entity.getType().getClassLoader())) {
return false;
}

if (NativeDetector.inNativeImage()) {

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

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

private static final String INIT = "<init>";
private static final String TAG = "_Instantiator_";
private static final String TAG = "__Instantiator_";
private static final String JAVA_LANG_OBJECT = Type.getInternalName(Object.class);
private static final String CREATE_METHOD_NAME = "newInstance";

Expand Down Expand Up @@ -431,8 +452,8 @@ public Class<?> generateCustomInstantiatorClass(PersistentEntity<?, ?> entity,
* @param entity
* @return
*/
private String generateClassName(PersistentEntity<?, ?> entity) {
return entity.getType().getName() + TAG + Integer.toString(entity.hashCode(), 36);
static String generateClassName(PersistentEntity<?, ?> entity) {
return entity.getType().getName() + TAG + Integer.toString(Math.abs(entity.getType().getName().hashCode()), 36);
}

/**
Expand Down
Loading