Browse Source

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).

Closes: #2595
Original Pull Request: #3318
pull/3357/head
Mark Paluch 6 months ago committed by Christoph Strobl
parent
commit
6e606031bf
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 83
      src/main/java/org/springframework/data/aot/AotMappingContext.java
  2. 7
      src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java
  3. 3
      src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java
  4. 49
      src/main/java/org/springframework/data/mapping/context/ReflectionFallbackPersistentPropertyAccessorFactory.java
  5. 35
      src/main/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiator.java
  6. 72
      src/main/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactory.java
  7. 26
      src/main/java/org/springframework/data/mapping/model/PersistentEntityClassInitializer.java
  8. 65
      src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java
  9. 19
      src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java
  10. 19
      src/test/java/org/springframework/data/aot/CodeContributionAssert.java
  11. 76
      src/test/java/org/springframework/data/repository/aot/AotUtil.java
  12. 82
      src/test/java/org/springframework/data/repository/aot/GeneratedClassesCaptureIntegrationTests.java
  13. 25
      src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotContributionAssert.java
  14. 52
      src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessorIntegrationTests.java

83
src/main/java/org/springframework/data/aot/AotMappingContext.java

@ -0,0 +1,83 @@ @@ -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;
}
}
}

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

@ -36,6 +36,7 @@ import org.springframework.core.ResolvableType; @@ -36,6 +36,7 @@ import org.springframework.core.ResolvableType;
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;
@ -56,6 +57,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio @@ -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;
@ -150,6 +152,11 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio @@ -150,6 +152,11 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
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));
}

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

@ -56,7 +56,6 @@ import org.springframework.data.mapping.PersistentProperty; @@ -56,7 +56,6 @@ import org.springframework.data.mapping.PersistentProperty;
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;
@ -125,7 +124,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<? @@ -125,7 +124,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
EntityInstantiators instantiators = new EntityInstantiators();
PersistentPropertyAccessorFactory accessorFactory = NativeDetector.inNativeImage()
? BeanWrapperPropertyAccessorFactory.INSTANCE
? new ReflectionFallbackPersistentPropertyAccessorFactory()
: new ClassGeneratingPropertyAccessorFactory();
this.persistentPropertyAccessorFactory = new InstantiationAwarePropertyAccessorFactory(accessorFactory,

49
src/main/java/org/springframework/data/mapping/context/ReflectionFallbackPersistentPropertyAccessorFactory.java

@ -0,0 +1,49 @@ @@ -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;
}
}

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

@ -51,7 +51,7 @@ import org.springframework.util.ClassUtils; @@ -51,7 +51,7 @@ import org.springframework.util.ClassUtils;
* 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
@ -60,7 +60,7 @@ import org.springframework.util.ClassUtils; @@ -60,7 +60,7 @@ import org.springframework.util.ClassUtils;
* @author Mark Paluch
* @since 1.11
*/
class ClassGeneratingEntityInstantiator implements EntityInstantiator {
class ClassGeneratingEntityInstantiator implements EntityInstantiator, PersistentEntityClassInitializer {
private static final Log LOGGER = LogFactory.getLog(ClassGeneratingEntityInstantiator.class);
@ -87,17 +87,29 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { @@ -87,17 +87,29 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
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;
}
/**
@ -170,10 +182,19 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { @@ -170,10 +182,19 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
*/
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;
@ -388,7 +409,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { @@ -388,7 +409,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
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";
@ -431,8 +452,8 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { @@ -431,8 +452,8 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
* @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);
}
/**

72
src/main/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactory.java

@ -76,7 +76,8 @@ import org.springframework.util.StringUtils; @@ -76,7 +76,8 @@ import org.springframework.util.StringUtils;
* @author Johannes Englmeier
* @since 1.13
*/
public class ClassGeneratingPropertyAccessorFactory implements PersistentPropertyAccessorFactory {
public class ClassGeneratingPropertyAccessorFactory
implements PersistentPropertyAccessorFactory, PersistentEntityClassInitializer {
// Pooling of parameter arrays to prevent excessive object allocation.
private final ThreadLocal<Object[]> argumentCache = ThreadLocal.withInitial(() -> new Object[1]);
@ -89,20 +90,14 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert @@ -89,20 +90,14 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
256, KotlinValueBoxingAdapter::getWrapper);
@Override
public <T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<?, ?> entity, T bean) {
Constructor<?> constructor = constructorMap.get(entity);
public void initialize(PersistentEntity<?, ?> entity) {
getPropertyAccessorConstructor(entity);
}
if (constructor == null) {
@Override
public <T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<?, ?> entity, T bean) {
Class<PersistentPropertyAccessor<?>> accessorClass = potentiallyCreateAndRegisterPersistentPropertyAccessorClass(
entity);
constructor = accessorClass.getConstructors()[0];
Map<PersistentEntity<?, ?>, Constructor<?>> constructorMap = new HashMap<>(this.constructorMap);
constructorMap.put(entity, constructor);
this.constructorMap = constructorMap;
}
Constructor<?> constructor = getPropertyAccessorConstructor(entity);
Object[] args = argumentCache.get();
args[0] = bean;
@ -123,6 +118,24 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert @@ -123,6 +118,24 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
}
}
private Constructor<?> getPropertyAccessorConstructor(PersistentEntity<?, ?> entity) {
Constructor<?> constructor = constructorMap.get(entity);
if (constructor == null) {
Class<PersistentPropertyAccessor<?>> accessorClass = potentiallyCreateAndRegisterPersistentPropertyAccessorClass(
entity);
constructor = accessorClass.getConstructors()[0];
Map<PersistentEntity<?, ?>, Constructor<?>> constructorMap = new HashMap<>(this.constructorMap);
constructorMap.put(entity, constructor);
this.constructorMap = constructorMap;
}
return constructor;
}
/**
* Checks whether an accessor class can be generated.
*
@ -136,6 +149,11 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert @@ -136,6 +149,11 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
Assert.notNull(entity, "PersistentEntity must not be null");
// already present in classloader
if (findAccessorClass(entity) != null) {
return true;
}
return isClassLoaderDefineClassAvailable(entity) && isTypeInjectable(entity) && hasUniquePropertyHashCodes(entity);
}
@ -184,7 +202,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert @@ -184,7 +202,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
/**
* @param entity must not be {@literal null}.
*/
private synchronized Class<PersistentPropertyAccessor<?>> potentiallyCreateAndRegisterPersistentPropertyAccessorClass(
protected synchronized Class<PersistentPropertyAccessor<?>> potentiallyCreateAndRegisterPersistentPropertyAccessorClass(
PersistentEntity<?, ?> entity) {
Map<TypeInformation<?>, Class<PersistentPropertyAccessor<?>>> map = this.propertyAccessorClasses;
@ -194,7 +212,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert @@ -194,7 +212,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
return propertyAccessorClass;
}
propertyAccessorClass = createAccessorClass(entity);
propertyAccessorClass = loadOrCreateAccessorClass(entity);
map = new HashMap<>(map);
map.put(entity.getTypeInformation(), propertyAccessorClass);
@ -204,16 +222,29 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert @@ -204,16 +222,29 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
return propertyAccessorClass;
}
@SuppressWarnings("unchecked")
private Class<PersistentPropertyAccessor<?>> createAccessorClass(PersistentEntity<?, ?> entity) {
@SuppressWarnings({ "unchecked" })
private Class<PersistentPropertyAccessor<?>> loadOrCreateAccessorClass(PersistentEntity<?, ?> entity) {
try {
Class<?> accessorClass = findAccessorClass(entity);
if (accessorClass != null) {
return (Class<PersistentPropertyAccessor<?>>) accessorClass;
}
return (Class<PersistentPropertyAccessor<?>>) PropertyAccessorClassGenerator.generateCustomAccessorClass(entity);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static @Nullable Class<?> findAccessorClass(PersistentEntity<?, ?> entity) {
String accessorClassName = PropertyAccessorClassGenerator.generateClassName(entity);
return org.springframework.data.util.ClassUtils.loadIfPresent(accessorClassName, entity.getType().getClassLoader());
}
/**
* Generates {@link PersistentPropertyAccessor} classes to access properties of a {@link PersistentEntity}. This code
* uses {@code private static final} held method handles which perform about the speed of native method invocations
@ -306,7 +337,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert @@ -306,7 +337,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
private static final String INIT = "<init>";
private static final String CLINIT = "<clinit>";
private static final String TAG = "_Accessor_";
private static final String TAG = "__Accessor_";
private static final String JAVA_LANG_OBJECT = "java/lang/Object";
private static final String JAVA_LANG_STRING = "java/lang/String";
private static final String JAVA_LANG_REFLECT_METHOD = "java/lang/reflect/Method";
@ -347,7 +378,6 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert @@ -347,7 +378,6 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
try {
return ReflectUtils.defineClass(className, bytecode, classLoader, type.getProtectionDomain(), type);
} catch (Exception o_O) {
throw new IllegalStateException(o_O);
}
@ -1372,8 +1402,8 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert @@ -1372,8 +1402,8 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
return 5 + list.indexOf(item);
}
private static 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);
}
}

26
src/main/java/org/springframework/data/mapping/model/PersistentEntityClassInitializer.java

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
/*
* 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.model;
import org.springframework.data.mapping.PersistentEntity;
/**
* @author Mark Paluch
*/
public interface PersistentEntityClassInitializer {
void initialize(PersistentEntity<?, ?> entity);
}

65
src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java

@ -43,6 +43,8 @@ import org.springframework.core.DecoratingProxy; @@ -43,6 +43,8 @@ import org.springframework.core.DecoratingProxy;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.data.aot.AotContext;
import org.springframework.data.aot.AotMappingContext;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.projection.EntityProjectionIntrospector;
import org.springframework.data.projection.TargetAware;
import org.springframework.data.repository.Repository;
@ -73,6 +75,8 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -73,6 +75,8 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
private static final String KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME = "org.springframework.data.repository.kotlin.CoroutineCrudRepository";
private final AotMappingContext aotMappingContext = new AotMappingContext();
private final RepositoryRegistrationAotProcessor aotProcessor;
private final AotRepositoryContext repositoryContext;
@ -275,33 +279,16 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -275,33 +279,16 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
QTypeContributor.contributeEntityPath(repositoryInformation.getDomainType(), contribution,
repositoryContext.getClassLoader());
// Repository Fragments
for (RepositoryFragment<?> fragment : getRepositoryInformation().getFragments()) {
Class<?> repositoryFragmentType = fragment.getSignatureContributor();
Optional<Class<?>> implementation = fragment.getImplementationClass();
contribution.getRuntimeHints().reflection().registerType(repositoryFragmentType, hint -> {
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
if (!repositoryFragmentType.isInterface()) {
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
});
implementation.ifPresent(typeToRegister -> {
contribution.getRuntimeHints().reflection().registerType(typeToRegister, hint -> {
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
if (!typeToRegister.isInterface()) {
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
});
});
// TODO: what about embedded types or entity types that are entity types references from properties?
PersistentEntity<?, ?> persistentEntity = aotMappingContext
.getPersistentEntity(repositoryInformation.getDomainType());
if (persistentEntity != null) {
aotMappingContext.contribute(persistentEntity);
}
// Repository Fragments
contributeFragments(contribution);
// Repository Proxy
contribution.getRuntimeHints().proxies().registerJdkProxy(repositoryInformation.getRepositoryInterface(),
SpringProxy.class, Advised.class, DecoratingProxy.class);
@ -343,6 +330,34 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -343,6 +330,34 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
});
}
private void contributeFragments(GenerationContext contribution) {
for (RepositoryFragment<?> fragment : getRepositoryInformation().getFragments()) {
Class<?> repositoryFragmentType = fragment.getSignatureContributor();
Optional<Class<?>> implementation = fragment.getImplementationClass();
contribution.getRuntimeHints().reflection().registerType(repositoryFragmentType, hint -> {
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
if (!repositoryFragmentType.isInterface()) {
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
});
implementation.ifPresent(typeToRegister -> {
contribution.getRuntimeHints().reflection().registerType(typeToRegister, hint -> {
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
if (!typeToRegister.isInterface()) {
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
});
});
}
}
private boolean isComponentAnnotatedRepository(RepositoryInformation repositoryInformation) {
return AnnotationUtils.findAnnotation(repositoryInformation.getRepositoryInterface(), Component.class) != null;
}

19
src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java

@ -44,6 +44,8 @@ import org.springframework.core.annotation.MergedAnnotation; @@ -44,6 +44,8 @@ import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.env.Environment;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.data.aot.AotMappingContext;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.repository.aot.generate.RepositoryContributor;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
@ -77,6 +79,8 @@ public class RepositoryRegistrationAotProcessor @@ -77,6 +79,8 @@ public class RepositoryRegistrationAotProcessor
private final Log logger = LogFactory.getLog(getClass());
private final AotMappingContext aotMappingContext = new AotMappingContext();
private @Nullable ConfigurableListableBeanFactory beanFactory;
private Environment environment = new StandardEnvironment();
@ -88,6 +92,7 @@ public class RepositoryRegistrationAotProcessor @@ -88,6 +92,7 @@ public class RepositoryRegistrationAotProcessor
return isRepositoryBean(bean) ? newRepositoryRegistrationAotContribution(bean) : null;
}
@Nullable
protected RepositoryContributor contribute(AotRepositoryContext repositoryContext, GenerationContext generationContext) {
@ -109,6 +114,8 @@ public class RepositoryRegistrationAotProcessor @@ -109,6 +114,8 @@ public class RepositoryRegistrationAotProcessor
* @param repositoryContext must not be {@literal null}.
* @param generationContext must not be {@literal null}.
*/
// TODO: Can we merge #contribute, #registerReflectiveForAggregateRoot into RepositoryRegistrationAotContribution?
// hints and types are contributed from everywhere.
private void registerReflectiveForAggregateRoot(AotRepositoryContext repositoryContext,
GenerationContext generationContext) {
@ -117,7 +124,16 @@ public class RepositoryRegistrationAotProcessor @@ -117,7 +124,16 @@ public class RepositoryRegistrationAotProcessor
RuntimeHints hints = generationContext.getRuntimeHints();
Stream.concat(Stream.of(information.getDomainType()), information.getAlternativeDomainTypes().stream())
.forEach(it -> registrar.registerRuntimeHints(hints, it));
.forEach(it -> {
// arent we already registering the types in RepositoryRegistrationAotContribution#contributeRepositoryInfo?
registrar.registerRuntimeHints(hints, it);
PersistentEntity<?, ?> persistentEntity = aotMappingContext.getPersistentEntity(it);
if (persistentEntity != null) {
aotMappingContext.contribute(persistentEntity);
}
});
}
private boolean isRepositoryBean(RegisteredBean bean) {
@ -186,6 +202,7 @@ public class RepositoryRegistrationAotProcessor @@ -186,6 +202,7 @@ public class RepositoryRegistrationAotProcessor
protected void contributeType(Class<?> type, GenerationContext generationContext) {
TypeContributor.contribute(type, it -> true, generationContext);
}
protected Log getLogger() {

19
src/test/java/org/springframework/data/aot/CodeContributionAssert.java

@ -15,15 +15,18 @@ @@ -15,15 +15,18 @@
*/
package org.springframework.data.aot;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.*;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.assertj.core.api.AbstractAssert;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.JdkProxyHint;
import org.springframework.aot.hint.TypeHint;
import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
@ -33,6 +36,7 @@ import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; @@ -33,6 +36,7 @@ import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
*
* @author Christoph Strobl
* @author John Blum
* @author Mark Paluch
* @since 3.0
*/
@SuppressWarnings("UnusedReturnValue")
@ -52,6 +56,19 @@ public class CodeContributionAssert extends AbstractAssert<CodeContributionAsser @@ -52,6 +56,19 @@ public class CodeContributionAssert extends AbstractAssert<CodeContributionAsser
return this;
}
public CodeContributionAssert contributesReflectionFor(TypeReference typeReference) {
assertThat(this.actual.getRuntimeHints()).describedAs(() -> {
return "Existing hints: " + System.lineSeparator() + this.actual().getRuntimeHints().reflection().typeHints()
.map(TypeHint::toString).map(" - "::concat).collect(Collectors.joining(System.lineSeparator()));
}).matches(RuntimeHintsPredicates.reflection().onType(typeReference),
String.format("No reflection entry found for [%s]", typeReference));
return this;
}
public CodeContributionAssert contributesReflectionFor(String... types) {
for (String type : types) {

76
src/test/java/org/springframework/data/repository/aot/AotUtil.java

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
/*
* 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.repository.aot;
import static org.assertj.core.api.Assertions.*;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.data.repository.config.RepositoryRegistrationAotContribution;
import org.springframework.data.repository.config.RepositoryRegistrationAotProcessor;
/**
* Utility class to create {@link RepositoryRegistrationAotContribution} instances for a given configuration class.
*
* @author Mark Paluch
*/
class AotUtil {
static RepositoryRegistrationAotContributionBuilder contributionFor(Class<?> configuration) {
return contributionFor(configuration, new AnnotationConfigApplicationContext());
}
static RepositoryRegistrationAotContributionBuilder contributionFor(Class<?> configuration,
AnnotationConfigApplicationContext applicationContext) {
applicationContext.register(configuration);
applicationContext.refreshForAotProcessing(new RuntimeHints());
return repositoryType -> {
String[] repositoryBeanNames = applicationContext.getBeanNamesForType(repositoryType);
assertThat(repositoryBeanNames)
.describedAs("Unable to find repository [%s] in configuration [%s]", repositoryType, configuration)
.hasSize(1);
String repositoryBeanName = repositoryBeanNames[0];
ConfigurableListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();
RepositoryRegistrationAotProcessor repositoryAotProcessor = applicationContext
.getBean(RepositoryRegistrationAotProcessor.class);
repositoryAotProcessor.setBeanFactory(beanFactory);
RegisteredBean bean = RegisteredBean.of(beanFactory, repositoryBeanName);
BeanRegistrationAotContribution beanContribution = repositoryAotProcessor.processAheadOfTime(bean);
assertThat(beanContribution).isInstanceOf(RepositoryRegistrationAotContribution.class);
return (RepositoryRegistrationAotContribution) beanContribution;
};
}
@FunctionalInterface
interface RepositoryRegistrationAotContributionBuilder {
RepositoryRegistrationAotContribution forRepository(Class<?> repositoryInterface);
}
}

82
src/test/java/org/springframework/data/repository/aot/GeneratedClassesCaptureIntegrationTests.java

@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
/*
* Copyright 2022-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.repository.aot;
import static org.springframework.data.repository.aot.RepositoryRegistrationAotContributionAssert.*;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.TypeReference;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.config.EnableRepositories;
import org.springframework.data.repository.config.RepositoryRegistrationAotContribution;
import org.springframework.data.repository.config.RepositoryRegistrationAotProcessor;
/**
* Integration Tests for {@link RepositoryRegistrationAotProcessor} to verify capturing generated instantiations and
* property accessors.
*
* @author Mark Paluch
*/
public class GeneratedClassesCaptureIntegrationTests {
@Test // GH-2595
void registersGeneratedPropertyAccessorsEntityInstantiators() {
RepositoryRegistrationAotContribution repositoryBeanContribution = AotUtil.contributionFor(Config.class)
.forRepository(Config.MyRepo.class);
assertThatContribution(repositoryBeanContribution) //
.codeContributionSatisfies(contribution -> {
contribution.contributesReflectionFor(TypeReference.of(
"org.springframework.data.repository.aot.GeneratedClassesCaptureIntegrationTests$Config$Person__Accessor_xj7ohs"));
contribution.contributesReflectionFor(TypeReference.of(
"org.springframework.data.repository.aot.GeneratedClassesCaptureIntegrationTests$Config$Person__Instantiator_xj7ohs"));
// TODO: These should also appear
/*
contribution.contributesReflectionFor(TypeReference.of(
"org.springframework.data.repository.aot.GeneratedClassesCaptureIntegrationTests$Config$Address__Accessor_xj7ohs"));
contribution.contributesReflectionFor(TypeReference.of(
"org.springframework.data.repository.aot.GeneratedClassesCaptureIntegrationTests$Config$Address__Instantiator_xj7ohs"));
*/
});
}
@EnableRepositories(includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, value = Config.MyRepo.class) },
basePackageClasses = Config.class, considerNestedRepositories = true)
public class Config {
public interface MyRepo extends CrudRepository<Person, String> {
}
public static class Person {
@Nullable Address address;
}
public static class Address {
String street;
}
}
}

25
src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotContributionAssert.java

@ -18,15 +18,20 @@ package org.springframework.data.repository.aot; @@ -18,15 +18,20 @@ package org.springframework.data.repository.aot;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.assertj.core.api.AbstractAssert;
import org.junit.jupiter.api.function.ThrowingConsumer;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.context.aot.ApplicationContextAotGenerator;
import org.springframework.data.aot.CodeContributionAssert;
import org.springframework.data.repository.config.RepositoryRegistrationAotContribution;
import org.springframework.data.repository.core.RepositoryInformation;
@ -113,9 +118,27 @@ public class RepositoryRegistrationAotContributionAssert @@ -113,9 +118,27 @@ public class RepositoryRegistrationAotContributionAssert
GenerationContext generationContext = new TestGenerationContext(Object.class);
this.actual.applyTo(generationContext, mockBeanRegistrationCode);
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
try {
Class<?> handlerClass = Class.forName("org.springframework.context.aot.CglibClassHandler");
Constructor<?> constructor = handlerClass.getDeclaredConstructors()[0];
constructor.setAccessible(true);
Object handler = BeanUtils.instantiateClass(constructor, generationContext);
Method withCglibClassHandler = generator.getClass().getDeclaredMethod("withCglibClassHandler", handlerClass,
Supplier.class);
withCglibClassHandler.setAccessible(true);
withCglibClassHandler.invoke(generator, handler, new Supplier<Object>() {
@Override
public Object get() {
actual.applyTo(generationContext, mockBeanRegistrationCode);
return null;
}
});
assertWith.accept(new CodeContributionAssert(generationContext));
} catch (Throwable o_O) {
fail(o_O.getMessage(), o_O);

52
src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessorIntegrationTests.java

@ -15,7 +15,6 @@ @@ -15,7 +15,6 @@
*/
package org.springframework.data.repository.aot;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.repository.aot.RepositoryRegistrationAotContributionAssert.*;
import java.io.Serializable;
@ -24,11 +23,6 @@ import org.junit.jupiter.api.Test; @@ -24,11 +23,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.Advised;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.DecoratingProxy;
@ -65,6 +59,7 @@ import org.springframework.transaction.interceptor.TransactionalProxy; @@ -65,6 +59,7 @@ import org.springframework.transaction.interceptor.TransactionalProxy;
* @author Christoph Strobl
* @author John Blum
*/
// TODO: This is verifying repository.config code. Move to repository.config package?
public class RepositoryRegistrationAotProcessorIntegrationTests {
@Test // GH-2593
@ -296,8 +291,7 @@ public class RepositoryRegistrationAotProcessorIntegrationTests { @@ -296,8 +291,7 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
assertThatContribution(repositoryBeanContribution) //
.codeContributionSatisfies(contribution -> {
contribution.contributesReflectionFor(Person.class);
contribution.contributesReflectionFor(
QConfigWithQuerydslPredicateExecutor_Person.class);
contribution.contributesReflectionFor(QConfigWithQuerydslPredicateExecutor_Person.class);
});
}
@ -325,46 +319,8 @@ public class RepositoryRegistrationAotProcessorIntegrationTests { @@ -325,46 +319,8 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
});
}
RepositoryRegistrationAotContributionBuilder computeAotConfiguration(Class<?> configuration) {
return computeAotConfiguration(configuration, new AnnotationConfigApplicationContext());
}
RepositoryRegistrationAotContributionBuilder computeAotConfiguration(Class<?> configuration,
AnnotationConfigApplicationContext applicationContext) {
applicationContext.register(configuration);
applicationContext.refreshForAotProcessing(new RuntimeHints());
return repositoryType -> {
String[] repositoryBeanNames = applicationContext.getBeanNamesForType(repositoryType);
assertThat(repositoryBeanNames)
.describedAs("Unable to find repository [%s] in configuration [%s]", repositoryType, configuration)
.hasSize(1);
String repositoryBeanName = repositoryBeanNames[0];
ConfigurableListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();
RepositoryRegistrationAotProcessor repositoryAotProcessor = applicationContext
.getBean(RepositoryRegistrationAotProcessor.class);
repositoryAotProcessor.setBeanFactory(beanFactory);
RegisteredBean bean = RegisteredBean.of(beanFactory, repositoryBeanName);
BeanRegistrationAotContribution beanContribution = repositoryAotProcessor.processAheadOfTime(bean);
assertThat(beanContribution).isInstanceOf(RepositoryRegistrationAotContribution.class);
return (RepositoryRegistrationAotContribution) beanContribution;
};
}
@FunctionalInterface
interface RepositoryRegistrationAotContributionBuilder {
RepositoryRegistrationAotContribution forRepository(Class<?> repositoryInterface);
AotUtil.RepositoryRegistrationAotContributionBuilder computeAotConfiguration(Class<?> configuration) {
return AotUtil.contributionFor(configuration);
}
@EnableRepositories(includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, value = SampleRepository.class) },

Loading…
Cancel
Save