Browse Source

Add AOT type configuration API

Update tests and add convenience API for registering configurations required to interact with certain types.

See: #2595
Original Pull Request: #3318
pull/3357/head
Christoph Strobl 4 months ago
parent
commit
54b8baa313
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 22
      src/main/java/org/springframework/data/aot/AotContext.java
  2. 24
      src/main/java/org/springframework/data/aot/AotMappingContext.java
  3. 110
      src/main/java/org/springframework/data/aot/AotTypeConfiguration.java
  4. 162
      src/main/java/org/springframework/data/aot/DefaultAotContext.java
  5. 26
      src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java
  6. 4
      src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java
  7. 1
      src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java
  8. 6
      src/main/java/org/springframework/data/mapping/context/ReflectionFallbackPersistentPropertyAccessorFactory.java
  9. 2
      src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java
  10. 27
      src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java
  11. 84
      src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java
  12. 10
      src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java
  13. 19
      src/test/java/org/springframework/data/aot/AotContextUnitTests.java
  14. 44
      src/test/java/org/springframework/data/aot/AotMappingContextUnitTests.java
  15. 19
      src/test/java/org/springframework/data/repository/aot/AotUtil.java
  16. 102
      src/test/java/org/springframework/data/repository/aot/GeneratedClassesCaptureIntegrationTests.java
  17. 3
      src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotContributionAssert.java
  18. 8
      src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessorIntegrationTests.java
  19. 24
      src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java

22
src/main/java/org/springframework/data/aot/AotContext.java

@ -22,14 +22,17 @@ import java.util.Locale;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Predicate;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.StandardEnvironment;
@ -227,6 +230,20 @@ public interface AotContext extends EnvironmentCapable {
*/ */
IntrospectedBeanDefinition introspectBeanDefinition(String beanName); IntrospectedBeanDefinition introspectBeanDefinition(String beanName);
InstantiationCreator instantiationCreator(TypeReference typeReference);
default AotTypeConfiguration typeConfiguration(ResolvableType resolvableType) {
return typeConfiguration(resolvableType.toClass());
}
default AotTypeConfiguration typeConfiguration(Class<?> type) {
return typeConfiguration(TypeReference.of(type));
}
AotTypeConfiguration typeConfiguration(TypeReference typeReference);
Collection<AotTypeConfiguration> typeConfigurations();
/** /**
* Type-based introspector to resolve {@link Class} from a type name and to introspect the bean factory for presence * Type-based introspector to resolve {@link Class} from a type name and to introspect the bean factory for presence
* of beans. * of beans.
@ -286,7 +303,6 @@ public interface AotContext extends EnvironmentCapable {
* @return a {@link List} of bean names. The list is empty if the bean factory does not hold any beans of this type. * @return a {@link List} of bean names. The list is empty if the bean factory does not hold any beans of this type.
*/ */
List<String> getBeanNames(); List<String> getBeanNames();
} }
/** /**
@ -340,7 +356,11 @@ public interface AotContext extends EnvironmentCapable {
*/ */
@Nullable @Nullable
Class<?> resolveType(); Class<?> resolveType();
}
interface InstantiationCreator {
boolean isAvailable();
void create();
} }
} }

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

@ -15,6 +15,8 @@
*/ */
package org.springframework.data.aot; package org.springframework.data.aot;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.data.mapping.Association; import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.context.AbstractMappingContext; import org.springframework.data.mapping.context.AbstractMappingContext;
@ -26,6 +28,7 @@ import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.mapping.model.PersistentEntityClassInitializer; import org.springframework.data.mapping.model.PersistentEntityClassInitializer;
import org.springframework.data.mapping.model.Property; import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.repository.aot.generate.RepositoryContributor;
import org.springframework.data.util.TypeInformation; import org.springframework.data.util.TypeInformation;
/** /**
@ -34,9 +37,12 @@ import org.springframework.data.util.TypeInformation;
* @author Mark Paluch * @author Mark Paluch
* @since 4.0 * @since 4.0
*/ */
public class AotMappingContext extends class AotMappingContext extends // TODO: hide this one and delegate to other component - can we use the
// AotContext for it?
AbstractMappingContext<BasicPersistentEntity<?, AotMappingContext.BasicPersistentProperty>, AotMappingContext.BasicPersistentProperty> { AbstractMappingContext<BasicPersistentEntity<?, AotMappingContext.BasicPersistentProperty>, AotMappingContext.BasicPersistentProperty> {
private static final Log logger = LogFactory.getLog(AotMappingContext.class);
private final EntityInstantiators instantiators = new EntityInstantiators(); private final EntityInstantiators instantiators = new EntityInstantiators();
private final ClassGeneratingPropertyAccessorFactory propertyAccessorFactory = new ClassGeneratingPropertyAccessorFactory(); private final ClassGeneratingPropertyAccessorFactory propertyAccessorFactory = new ClassGeneratingPropertyAccessorFactory();
@ -55,15 +61,18 @@ public class AotMappingContext extends
propertyAccessorFactory.initialize(entity); propertyAccessorFactory.initialize(entity);
} }
// TODO: can we extract some util for this using only type
@Override @Override
protected <T> BasicPersistentEntity<?, BasicPersistentProperty> createPersistentEntity( protected <T> BasicPersistentEntity<?, BasicPersistentProperty> createPersistentEntity(
TypeInformation<T> typeInformation) { TypeInformation<T> typeInformation) {
logger.debug("I hate gradle: create persistent entity for type: " + typeInformation);
return new BasicPersistentEntity<>(typeInformation); return new BasicPersistentEntity<>(typeInformation);
} }
@Override @Override
protected BasicPersistentProperty createPersistentProperty(Property property, protected BasicPersistentProperty createPersistentProperty(Property property,
BasicPersistentEntity<?, BasicPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) { BasicPersistentEntity<?, BasicPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
logger.info("creating property: " + property.getName());
return new BasicPersistentProperty(property, owner, simpleTypeHolder); return new BasicPersistentProperty(property, owner, simpleTypeHolder);
} }
@ -74,10 +83,21 @@ public class AotMappingContext extends
super(property, owner, simpleTypeHolder); super(property, owner, simpleTypeHolder);
} }
@Override
public boolean isAssociation() {
return false;
}
@Override @Override
protected Association<BasicPersistentProperty> createAssociation() { protected Association<BasicPersistentProperty> createAssociation() {
return null; return new Association<>(this, null);
} }
@Override
public Association<BasicPersistentProperty> getRequiredAssociation() {
return new Association<>(this, null);
}
} }
} }

110
src/main/java/org/springframework/data/aot/AotTypeConfiguration.java

@ -0,0 +1,110 @@
/*
* 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
*
* http://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 java.io.Serializable;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.Advised;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.TypeReference;
import org.springframework.core.DecoratingProxy;
import org.springframework.core.env.Environment;
import org.springframework.data.projection.TargetAware;
/**
* @author Christoph Strobl
*/
public interface AotTypeConfiguration {
AotTypeConfiguration forDataBinding();
AotTypeConfiguration forReflectiveAccess(MemberCategory... categories);
AotTypeConfiguration generateEntityInstantiator();
// TODO: ? should this be a global condition for the entire configuration or do we need it for certain aspects ?
AotTypeConfiguration conditional(Predicate<TypeReference> filter);
default AotTypeConfiguration usedAsProjectionInterface() {
return proxyInterface(TargetAware.class, SpringProxy.class, DecoratingProxy.class);
}
default AotTypeConfiguration springProxy() {
return proxyInterface(SpringProxy.class, Advised.class, DecoratingProxy.class);
}
default AotTypeConfiguration repositoryProxy() {
springProxy();
List<TypeReference> transactionalProxy = List.of(TypeReference.of("org.springframework.data.repository.Repository"),
TypeReference.of("org.springframework.transaction.interceptor.TransactionalProxy"),
TypeReference.of("org.springframework.aop.framework.Advised"), TypeReference.of(DecoratingProxy.class));
proxyInterface(transactionalProxy);
proxyInterface(
Stream.concat(transactionalProxy.stream(), Stream.of(TypeReference.of(Serializable.class))).toList());
return this;
}
AotTypeConfiguration proxyInterface(List<TypeReference> proxyInterfaces);
default AotTypeConfiguration proxyInterface(TypeReference... proxyInterfaces) {
return proxyInterface(List.of(proxyInterfaces));
}
default AotTypeConfiguration proxyInterface(Class<?>... proxyInterfaces) {
return proxyInterface(Stream.of(proxyInterfaces).map(TypeReference::of).toList());
}
AotTypeConfiguration forQuerydsl();
void contribute(GenerationContext generationContext);
static Predicate<TypeReference> userConfiguredCondition(Environment environment) {
return new Predicate<TypeReference>() {
private final List<String> allowedAccessorTypes = environment.getProperty("spring.data.aot.generate.accessor",
List.class, List.of());
@Override
@SuppressWarnings("unchecked")
public boolean test(TypeReference typeReference) {
if (!allowedAccessorTypes.isEmpty()) {
if (allowedAccessorTypes.contains("none") || allowedAccessorTypes.contains("false")
|| allowedAccessorTypes.contains("off")) {
return false;
}
if (!allowedAccessorTypes.contains(typeReference.getName())) {
return false;
}
}
return true;
}
};
}
}

162
src/main/java/org/springframework/data/aot/DefaultAotContext.java

@ -15,20 +15,37 @@
*/ */
package org.springframework.data.aot; package org.springframework.data.aot;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.data.aot.AotMappingContext.BasicPersistentProperty;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.util.Lazy;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.data.util.QTypeContributor;
import org.springframework.data.util.TypeContributor;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
/** /**
@ -39,8 +56,13 @@ import org.springframework.util.ClassUtils;
*/ */
class DefaultAotContext implements AotContext { class DefaultAotContext implements AotContext {
private final AotMappingContext mappingContext = new AotMappingContext();
private final ConfigurableListableBeanFactory factory; private final ConfigurableListableBeanFactory factory;
// TODO: should we reuse the config or potentially have multiple ones with different settings - somehow targets the
// filtering issue
private final Map<TypeReference, AotTypeConfiguration> typeConfigurations = new HashMap<>();
private final Environment environment; private final Environment environment;
public DefaultAotContext(BeanFactory beanFactory, Environment environment) { public DefaultAotContext(BeanFactory beanFactory, Environment environment) {
@ -69,6 +91,21 @@ class DefaultAotContext implements AotContext {
return new DefaultIntrospectedBeanDefinition(beanName); return new DefaultIntrospectedBeanDefinition(beanName);
} }
@Override
public InstantiationCreator instantiationCreator(TypeReference typeReference) {
return new DefaultInstantiationCreator(introspectType(typeReference.getName()));
}
@Override
public AotTypeConfiguration typeConfiguration(TypeReference typeReference) {
return typeConfigurations.computeIfAbsent(typeReference, it -> new ContextualTypeConfiguration(typeReference));
}
@Override
public Collection<AotTypeConfiguration> typeConfigurations() {
return typeConfigurations.values();
}
class DefaultTypeIntrospector implements TypeIntrospector { class DefaultTypeIntrospector implements TypeIntrospector {
private final String typeName; private final String typeName;
@ -108,6 +145,29 @@ class DefaultAotContext implements AotContext {
} }
} }
class DefaultInstantiationCreator implements InstantiationCreator {
Lazy<BasicPersistentEntity<?, BasicPersistentProperty>> entity;
public DefaultInstantiationCreator(TypeIntrospector typeIntrospector) {
this.entity = Lazy.of(() -> mappingContext.getPersistentEntity(typeIntrospector.resolveRequiredType()));
}
@Override
public boolean isAvailable() {
return entity.getNullable() != null;
}
@Override
public void create() {
BasicPersistentEntity<?, BasicPersistentProperty> persistentEntity = entity.getNullable();
if (persistentEntity != null) {
mappingContext.contribute(persistentEntity);
}
}
}
class DefaultIntrospectedBeanDefinition implements IntrospectedBeanDefinition { class DefaultIntrospectedBeanDefinition implements IntrospectedBeanDefinition {
private final String beanName; private final String beanName;
@ -148,4 +208,106 @@ class DefaultAotContext implements AotContext {
} }
} }
class ContextualTypeConfiguration implements AotTypeConfiguration {
private final TypeReference type;
private boolean forDataBinding = false;
private final Set<MemberCategory> categories = new HashSet<>(5);
private boolean generateEntityInstantiator = false;
private boolean forQuerydsl = false;
private final List<List<TypeReference>> proxies = new ArrayList<>();
private Predicate<TypeReference> filter;
ContextualTypeConfiguration(TypeReference type) {
this.type = type;
}
@Override
public AotTypeConfiguration forDataBinding() {
this.forDataBinding = true;
return this;
}
@Override
public AotTypeConfiguration forReflectiveAccess(MemberCategory... categories) {
this.categories.addAll(Arrays.asList(categories));
return this;
}
@Override
public AotTypeConfiguration generateEntityInstantiator() {
this.generateEntityInstantiator = true;
return this;
}
@Override
public AotTypeConfiguration proxyInterface(List<TypeReference> interfaces) {
this.proxies.add(interfaces);
return this;
}
@Override
public AotTypeConfiguration forQuerydsl() {
this.forQuerydsl = true;
return this;
}
@Override
public AotTypeConfiguration conditional(Predicate<TypeReference> filter) {
this.filter = filter;
return this;
}
@Override
public void contribute(GenerationContext generationContext) {
if (filter != null && !filter.test(this.type)) {
return;
}
if (!this.categories.isEmpty()) {
generationContext.getRuntimeHints().reflection().registerType(this.type,
categories.toArray(MemberCategory[]::new));
}
if (generateEntityInstantiator) {
instantiationCreator(type).create();
}
if (forDataBinding) {
if (!doIfPresent(resolved -> TypeContributor.contribute(resolved, Set.of(TypeContributor.DATA_NAMESPACE),
generationContext))) {
generationContext.getRuntimeHints().reflection().registerType(type,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
}
}
if (forQuerydsl) {
doIfPresent(
resolved -> QTypeContributor.contributeEntityPath(resolved, generationContext, resolved.getClassLoader()));
}
if (!proxies.isEmpty()) {
for (List<TypeReference> proxyInterfaces : proxies) {
generationContext.getRuntimeHints().proxies()
.registerJdkProxy(Stream.concat(Stream.of(type), proxyInterfaces.stream()).toArray(TypeReference[]::new));
}
}
}
private boolean doIfPresent(Consumer<Class<?>> consumer) {
if (!ClassUtils.isPresent(type.getName(), type.getClass().getClassLoader())) {
return false;
}
try {
Class<?> resolved = ClassUtils.forName(type.getName(), type.getClass().getClassLoader());
consumer.accept(resolved);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
} }

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

@ -25,7 +25,6 @@ import org.jspecify.annotations.Nullable;
import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.generate.GenerationContext;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor; import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
@ -36,9 +35,7 @@ import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.StandardEnvironment;
import org.springframework.data.domain.ManagedTypes; import org.springframework.data.domain.ManagedTypes;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.util.Lazy; import org.springframework.data.util.Lazy;
import org.springframework.data.util.QTypeContributor;
import org.springframework.data.util.TypeContributor; import org.springframework.data.util.TypeContributor;
import org.springframework.data.util.TypeUtils; import org.springframework.data.util.TypeUtils;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -57,7 +54,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
private final Log logger = LogFactory.getLog(getClass()); private final Log logger = LogFactory.getLog(getClass());
private @Nullable String moduleIdentifier; private @Nullable String moduleIdentifier;
private Lazy<Environment> environment = Lazy.of(StandardEnvironment::new); private Lazy<Environment> environment = Lazy.of(StandardEnvironment::new);
private final AotMappingContext aotMappingContext = new AotMappingContext(); private AotContext aotContext;
public void setModuleIdentifier(@Nullable String moduleIdentifier) { public void setModuleIdentifier(@Nullable String moduleIdentifier) {
this.moduleIdentifier = moduleIdentifier; this.moduleIdentifier = moduleIdentifier;
@ -80,9 +77,8 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
return null; return null;
} }
BeanFactory beanFactory = registeredBean.getBeanFactory(); this.aotContext = new DefaultAotContext(registeredBean.getBeanFactory(), environment.get());
return contribute(AotContext.from(beanFactory, this.environment.get()), resolveManagedTypes(registeredBean), return contribute(this.aotContext, resolveManagedTypes(registeredBean), registeredBean);
registeredBean);
} }
private ManagedTypes resolveManagedTypes(RegisteredBean registeredBean) { private ManagedTypes resolveManagedTypes(RegisteredBean registeredBean) {
@ -131,7 +127,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
*/ */
protected BeanRegistrationAotContribution contribute(AotContext aotContext, ManagedTypes managedTypes, protected BeanRegistrationAotContribution contribute(AotContext aotContext, ManagedTypes managedTypes,
RegisteredBean registeredBean) { RegisteredBean registeredBean) {
return new ManagedTypesRegistrationAotContribution(managedTypes, registeredBean, this::contributeType); return new ManagedTypesRegistrationAotContribution(aotContext, managedTypes, registeredBean, this::contributeType);
} }
/** /**
@ -148,16 +144,12 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
Set<String> annotationNamespaces = Collections.singleton(TypeContributor.DATA_NAMESPACE); Set<String> annotationNamespaces = Collections.singleton(TypeContributor.DATA_NAMESPACE);
Class<?> resolvedType = type.toClass(); aotContext.typeConfiguration(type).forDataBinding() //
TypeContributor.contribute(resolvedType, annotationNamespaces, generationContext); .generateEntityInstantiator() //
QTypeContributor.contributeEntityPath(resolvedType, generationContext, resolvedType.getClassLoader()); .forQuerydsl() //
.contribute(generationContext); //
PersistentEntity<?, ?> entity = aotMappingContext.getPersistentEntity(resolvedType); TypeUtils.resolveUsedAnnotations(type.toClass()).forEach(
if (entity != null) {
aotMappingContext.contribute(entity);
}
TypeUtils.resolveUsedAnnotations(resolvedType).forEach(
annotation -> TypeContributor.contribute(annotation.getType(), annotationNamespaces, generationContext)); annotation -> TypeContributor.contribute(annotation.getType(), annotationNamespaces, generationContext));
} }

4
src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java

@ -74,14 +74,16 @@ import org.springframework.util.ReflectionUtils;
*/ */
class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContribution { class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContribution {
private final AotContext aotContext;
private final ManagedTypes managedTypes; private final ManagedTypes managedTypes;
private final Lazy<List<Class<?>>> sourceTypes; private final Lazy<List<Class<?>>> sourceTypes;
private final BiConsumer<ResolvableType, GenerationContext> contributionAction; private final BiConsumer<ResolvableType, GenerationContext> contributionAction;
private final RegisteredBean source; private final RegisteredBean source;
public ManagedTypesRegistrationAotContribution(ManagedTypes managedTypes, RegisteredBean registeredBean, public ManagedTypesRegistrationAotContribution(AotContext aotContext, ManagedTypes managedTypes, RegisteredBean registeredBean,
BiConsumer<ResolvableType, GenerationContext> contributionAction) { BiConsumer<ResolvableType, GenerationContext> contributionAction) {
this.aotContext = aotContext;
this.managedTypes = managedTypes; this.managedTypes = managedTypes;
this.sourceTypes = Lazy.of(managedTypes::toList); this.sourceTypes = Lazy.of(managedTypes::toList);
this.contributionAction = contributionAction; this.contributionAction = contributionAction;

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

@ -243,6 +243,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
@Override @Override
@Nullable @Nullable
public E getPersistentEntity(Class<?> type) { public E getPersistentEntity(Class<?> type) {
LOGGER.info("obtain persistent entity for type: " + type);
return getPersistentEntity(TypeInformation.of(type)); return getPersistentEntity(TypeInformation.of(type));
} }

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

@ -36,9 +36,13 @@ class ReflectionFallbackPersistentPropertyAccessorFactory implements PersistentP
public <T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<?, ?> entity, T bean) { public <T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<?, ?> entity, T bean) {
if (accessorFactory.isSupported(entity)) { if (accessorFactory.isSupported(entity)) {
return accessorFactory.getPropertyAccessor(entity, bean); PersistentPropertyAccessor<T> propertyAccessor = accessorFactory.getPropertyAccessor(entity, bean);
System.out.println("Accessor Factory: " + propertyAccessor.getClass().getName());
return propertyAccessor;
} }
System.out.println("Fallback Accessor Factory :(");
return BeanWrapperPropertyAccessorFactory.INSTANCE.getPropertyAccessor(entity, bean); return BeanWrapperPropertyAccessorFactory.INSTANCE.getPropertyAccessor(entity, bean);
} }

2
src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java

@ -79,4 +79,6 @@ public interface AotRepositoryContext extends AotContext {
*/ */
Set<Class<?>> getResolvedTypes(); Set<Class<?>> getResolvedTypes();
Set<Class<?>> getUserDomainTypes();
} }

27
src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java

@ -19,17 +19,21 @@ import java.lang.annotation.Annotation;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.data.aot.AotContext; import org.springframework.data.aot.AotContext;
import org.springframework.data.aot.AotTypeConfiguration;
import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.util.Lazy; import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeCollector; import org.springframework.data.util.TypeCollector;
import org.springframework.data.util.TypeContributor;
import org.springframework.data.util.TypeUtils; import org.springframework.data.util.TypeUtils;
/** /**
@ -133,11 +137,34 @@ class DefaultAotRepositoryContext implements AotRepositoryContext {
return managedTypes.get(); return managedTypes.get();
} }
@Override
public Set<Class<?>> getUserDomainTypes() {
return getResolvedTypes().stream()
.filter(it -> TypeContributor.isPartOf(it, Set.of(repositoryInformation.getDomainType().getPackageName())))
.collect(Collectors.toSet());
}
@Override @Override
public AotContext.TypeIntrospector introspectType(String typeName) { public AotContext.TypeIntrospector introspectType(String typeName) {
return aotContext.introspectType(typeName); return aotContext.introspectType(typeName);
} }
@Override
public InstantiationCreator instantiationCreator(TypeReference typeReference) {
return aotContext.instantiationCreator(typeReference);
}
@Override
public AotTypeConfiguration typeConfiguration(TypeReference typeReference) {
return aotContext.typeConfiguration(typeReference);
}
@Override
public Collection<AotTypeConfiguration> typeConfigurations() {
return aotContext.typeConfigurations();
}
@Override @Override
public AotContext.IntrospectedBeanDefinition introspectBeanDefinition(String beanName) { public AotContext.IntrospectedBeanDefinition introspectBeanDefinition(String beanName) {
return aotContext.introspectBeanDefinition(beanName); return aotContext.introspectBeanDefinition(beanName);

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

@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -43,10 +44,8 @@ import org.springframework.core.DecoratingProxy;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.data.aot.AotContext; import org.springframework.data.aot.AotContext;
import org.springframework.data.aot.AotMappingContext; import org.springframework.data.aot.AotTypeConfiguration;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.projection.EntityProjectionIntrospector; import org.springframework.data.projection.EntityProjectionIntrospector;
import org.springframework.data.projection.TargetAware;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.Repository;
import org.springframework.data.repository.aot.generate.AotRepositoryBeanDefinitionPropertiesDecorator; import org.springframework.data.repository.aot.generate.AotRepositoryBeanDefinitionPropertiesDecorator;
import org.springframework.data.repository.aot.generate.RepositoryContributor; import org.springframework.data.repository.aot.generate.RepositoryContributor;
@ -57,7 +56,6 @@ import org.springframework.data.util.QTypeContributor;
import org.springframework.data.util.TypeContributor; import org.springframework.data.util.TypeContributor;
import org.springframework.data.util.TypeUtils; import org.springframework.data.util.TypeUtils;
import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.CodeBlock;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -75,8 +73,6 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
private static final String KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME = "org.springframework.data.repository.kotlin.CoroutineCrudRepository"; 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 RepositoryRegistrationAotProcessor aotProcessor;
private final AotRepositoryContext repositoryContext; private final AotRepositoryContext repositoryContext;
@ -259,62 +255,29 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
}; };
} }
public Predicate<Class<?>> typeFilter() { // like only document ones. // TODO: As in MongoDB?
return Predicates.isTrue();
}
private void contributeRepositoryInfo(AotRepositoryContext repositoryContext, GenerationContext contribution) { private void contributeRepositoryInfo(AotRepositoryContext repositoryContext, GenerationContext contribution) {
RepositoryInformation repositoryInformation = getRepositoryInformation(); RepositoryInformation repositoryInformation = getRepositoryInformation();
logTrace("Contributing repository information for [%s]", repositoryInformation.getRepositoryInterface()); logTrace("Contributing repository information for [%s]", repositoryInformation.getRepositoryInterface());
contribution.getRuntimeHints().reflection() repositoryContext.typeConfiguration(repositoryInformation.getRepositoryInterface())
.registerType(repositoryInformation.getRepositoryInterface(), .forReflectiveAccess(MemberCategory.INVOKE_PUBLIC_METHODS) //
hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)) .repositoryProxy();
.registerType(repositoryInformation.getRepositoryBaseClass(), hint -> hint
.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS));
TypeContributor.contribute(repositoryInformation.getDomainType(), contribution); repositoryContext.typeConfiguration(repositoryInformation.getRepositoryBaseClass())
QTypeContributor.contributeEntityPath(repositoryInformation.getDomainType(), contribution, .forReflectiveAccess(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
repositoryContext.getClassLoader());
// TODO: what about embedded types or entity types that are entity types references from properties? repositoryContext.typeConfiguration(repositoryInformation.getDomainType()).forDataBinding().forQuerydsl();
PersistentEntity<?, ?> persistentEntity = aotMappingContext
.getPersistentEntity(repositoryInformation.getDomainType()); // TODO: purposeful api for uses cases to have some internal logic
if (persistentEntity != null) { repositoryContext.getUserDomainTypes().stream() //
aotMappingContext.contribute(persistentEntity); .map(repositoryContext::typeConfiguration) //
} .forEach(AotTypeConfiguration::generateEntityInstantiator); //
// Repository Fragments // Repository Fragments
contributeFragments(contribution); contributeFragments(contribution);
// Repository Proxy
contribution.getRuntimeHints().proxies().registerJdkProxy(repositoryInformation.getRepositoryInterface(),
SpringProxy.class, Advised.class, DecoratingProxy.class);
// Transactional Repository Proxy
// repositoryContext.ifTransactionManagerPresent(transactionManagerBeanNames -> {
// TODO: Is the following double JDK Proxy registration above necessary or would a single JDK Proxy
// registration suffice?
// In other words, simply having a single JDK Proxy registration either with or without
// the additional Serializable TypeReference?
// NOTE: Using a single JDK Proxy registration causes the
// simpleRepositoryWithTxManagerNoKotlinNoReactiveButComponent() test case method to fail.
List<TypeReference> transactionalRepositoryProxyTypeReferences = transactionalRepositoryProxyTypeReferences(
repositoryInformation);
contribution.getRuntimeHints().proxies()
.registerJdkProxy(transactionalRepositoryProxyTypeReferences.toArray(new TypeReference[0]));
if (isComponentAnnotatedRepository(repositoryInformation)) {
transactionalRepositoryProxyTypeReferences.add(TypeReference.of(Serializable.class));
contribution.getRuntimeHints().proxies()
.registerJdkProxy(transactionalRepositoryProxyTypeReferences.toArray(new TypeReference[0]));
}
// });
// Kotlin // Kotlin
if (isKotlinCoroutineRepository(repositoryContext, repositoryInformation)) { if (isKotlinCoroutineRepository(repositoryContext, repositoryInformation)) {
contribution.getRuntimeHints().reflection().registerTypes(kotlinRepositoryReflectionTypeReferences(), hint -> {}); contribution.getRuntimeHints().reflection().registerTypes(kotlinRepositoryReflectionTypeReferences(), hint -> {});
@ -325,7 +288,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
.filter(Class::isInterface).forEach(type -> { .filter(Class::isInterface).forEach(type -> {
if (EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy().test(type, if (EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy().test(type,
repositoryInformation.getDomainType())) { repositoryInformation.getDomainType())) {
contributeProjection(type, contribution); repositoryContext.typeConfiguration(type).usedAsProjectionInterface();
} }
}); });
} }
@ -358,10 +321,6 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
} }
} }
private boolean isComponentAnnotatedRepository(RepositoryInformation repositoryInformation) {
return AnnotationUtils.findAnnotation(repositoryInformation.getRepositoryInterface(), Component.class) != null;
}
private boolean isKotlinCoroutineRepository(AotRepositoryContext repositoryContext, private boolean isKotlinCoroutineRepository(AotRepositoryContext repositoryContext,
RepositoryInformation repositoryInformation) { RepositoryInformation repositoryInformation) {
@ -382,21 +341,6 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
TypeReference.of("kotlin.Boolean"))); TypeReference.of("kotlin.Boolean")));
} }
private List<TypeReference> transactionalRepositoryProxyTypeReferences(RepositoryInformation repositoryInformation) {
return new ArrayList<>(Arrays.asList(TypeReference.of(repositoryInformation.getRepositoryInterface()),
TypeReference.of(Repository.class), //
TypeReference.of("org.springframework.transaction.interceptor.TransactionalProxy"), //
TypeReference.of("org.springframework.aop.framework.Advised"), //
TypeReference.of(DecoratingProxy.class)));
}
private void contributeProjection(Class<?> type, GenerationContext generationContext) {
generationContext.getRuntimeHints().proxies().registerJdkProxy(type, TargetAware.class, SpringProxy.class,
DecoratingProxy.class);
}
static boolean isJavaOrPrimitiveType(Class<?> type) { static boolean isJavaOrPrimitiveType(Class<?> type) {
return TypeUtils.type(type).isPartOf("java") // return TypeUtils.type(type).isPartOf("java") //
|| ClassUtils.isPrimitiveOrWrapper(type) // || ClassUtils.isPrimitiveOrWrapper(type) //

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

@ -28,6 +28,7 @@ import org.jspecify.annotations.Nullable;
import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.hint.annotation.Reflective; import org.springframework.aot.hint.annotation.Reflective;
import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar; import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
@ -44,8 +45,6 @@ import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.env.StandardEnvironment; 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.aot.generate.RepositoryContributor;
import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
@ -79,8 +78,6 @@ public class RepositoryRegistrationAotProcessor
private final Log logger = LogFactory.getLog(getClass()); private final Log logger = LogFactory.getLog(getClass());
private final AotMappingContext aotMappingContext = new AotMappingContext();
private @Nullable ConfigurableListableBeanFactory beanFactory; private @Nullable ConfigurableListableBeanFactory beanFactory;
private Environment environment = new StandardEnvironment(); private Environment environment = new StandardEnvironment();
@ -129,10 +126,7 @@ public class RepositoryRegistrationAotProcessor
// arent we already registering the types in RepositoryRegistrationAotContribution#contributeRepositoryInfo? // arent we already registering the types in RepositoryRegistrationAotContribution#contributeRepositoryInfo?
registrar.registerRuntimeHints(hints, it); registrar.registerRuntimeHints(hints, it);
PersistentEntity<?, ?> persistentEntity = aotMappingContext.getPersistentEntity(it); repositoryContext.instantiationCreator(TypeReference.of(it)).create();
if (persistentEntity != null) {
aotMappingContext.contribute(persistentEntity);
}
}); });
} }

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

@ -15,10 +15,14 @@
*/ */
package org.springframework.data.aot; package org.springframework.data.aot;
import java.util.Collection;
import java.util.List;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.env.MockEnvironment;
@ -83,6 +87,21 @@ class AotContextUnitTests {
return Mockito.mock(IntrospectedBeanDefinition.class); return Mockito.mock(IntrospectedBeanDefinition.class);
} }
@Override
public InstantiationCreator instantiationCreator(TypeReference typeReference) {
return Mockito.mock(InstantiationCreator.class);
}
@Override
public AotTypeConfiguration typeConfiguration(TypeReference typeReference) {
return null;
}
@Override
public Collection<AotTypeConfiguration> typeConfigurations() {
return List.of();
}
@Override @Override
public Environment getEnvironment() { public Environment getEnvironment() {
return environment; return environment;

44
src/test/java/org/springframework/data/aot/AotMappingContextUnitTests.java

@ -0,0 +1,44 @@
/*
* Copyright 2025-present 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.junit.jupiter.api.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Reference;
import org.springframework.data.util.TypeInformation;
/**
* @author Christoph Strobl
*/
public class AotMappingContextUnitTests {
@Test // GH-2595
void obtainEntityWithReference() {
new AotMappingContext().getPersistentEntity(TypeInformation.of(DemoEntity.class));
}
static class DemoEntity {
@Id String id;
String name;
@Reference ReferencedEntity referencedEntity;
}
static class ReferencedEntity {
@Id String id;
}
}

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

@ -15,7 +15,7 @@
*/ */
package org.springframework.data.repository.aot; package org.springframework.data.repository.aot;
import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.assertThat;
import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHints;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
@ -42,7 +42,11 @@ class AotUtil {
applicationContext.register(configuration); applicationContext.register(configuration);
applicationContext.refreshForAotProcessing(new RuntimeHints()); applicationContext.refreshForAotProcessing(new RuntimeHints());
return repositoryType -> { return repositoryTypes -> {
BeanRegistrationAotContribution beanContribution = null;
for (Class<?> repositoryType : repositoryTypes) {
String[] repositoryBeanNames = applicationContext.getBeanNamesForType(repositoryType); String[] repositoryBeanNames = applicationContext.getBeanNamesForType(repositoryType);
@ -60,17 +64,20 @@ class AotUtil {
repositoryAotProcessor.setBeanFactory(beanFactory); repositoryAotProcessor.setBeanFactory(beanFactory);
RegisteredBean bean = RegisteredBean.of(beanFactory, repositoryBeanName); RegisteredBean bean = RegisteredBean.of(beanFactory, repositoryBeanName);
beanContribution = repositoryAotProcessor.processAheadOfTime(bean);
BeanRegistrationAotContribution beanContribution = repositoryAotProcessor.processAheadOfTime(bean); }
assertThat(beanContribution).isInstanceOf(RepositoryRegistrationAotContribution.class); assertThat(beanContribution).isInstanceOf(RepositoryRegistrationAotContribution.class);
return (RepositoryRegistrationAotContribution) beanContribution; return (RepositoryRegistrationAotContribution) beanContribution;
}; };
} }
@FunctionalInterface @FunctionalInterface
interface RepositoryRegistrationAotContributionBuilder { interface RepositoryRegistrationAotContributionBuilder {
RepositoryRegistrationAotContribution forRepository(Class<?> repositoryInterface); default RepositoryRegistrationAotContribution forRepository(Class<?> repositoryInterface) {
return forRepositories(repositoryInterface);
}
RepositoryRegistrationAotContribution forRepositories(Class<?>... repositoryInterface);
} }
} }

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

@ -17,22 +17,38 @@ package org.springframework.data.repository.aot;
import static org.springframework.data.repository.aot.RepositoryRegistrationAotContributionAssert.*; import static org.springframework.data.repository.aot.RepositoryRegistrationAotContributionAssert.*;
import java.util.Map;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.TypeReference; import org.springframework.aot.hint.TypeReference;
import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.FilterType;
import org.springframework.data.aot.types.BaseEntity;
import org.springframework.data.aot.types.CyclicPropertiesA;
import org.springframework.data.aot.types.CyclicPropertiesB;
import org.springframework.data.aot.types.EmptyType1;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.mapping.model.EntityInstantiator;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.aot.GeneratedClassesCaptureIntegrationTests.ConfigWithMultipleRepositories.Repo1;
import org.springframework.data.repository.aot.GeneratedClassesCaptureIntegrationTests.ConfigWithMultipleRepositories.Repo2;
import org.springframework.data.repository.config.EnableRepositories; import org.springframework.data.repository.config.EnableRepositories;
import org.springframework.data.repository.config.RepositoryRegistrationAotContribution; import org.springframework.data.repository.config.RepositoryRegistrationAotContribution;
import org.springframework.data.repository.config.RepositoryRegistrationAotProcessor; import org.springframework.data.repository.config.RepositoryRegistrationAotProcessor;
import org.springframework.data.util.TypeInformation;
import org.springframework.test.util.ReflectionTestUtils;
/** /**
* Integration Tests for {@link RepositoryRegistrationAotProcessor} to verify capturing generated instantiations and * Integration Tests for {@link RepositoryRegistrationAotProcessor} to verify capturing generated instantiations and
* property accessors. * property accessors.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
*/ */
public class GeneratedClassesCaptureIntegrationTests { public class GeneratedClassesCaptureIntegrationTests {
@ -44,39 +60,91 @@ public class GeneratedClassesCaptureIntegrationTests {
assertThatContribution(repositoryBeanContribution) // assertThatContribution(repositoryBeanContribution) //
.codeContributionSatisfies(contribution -> { .codeContributionSatisfies(contribution -> {
contribution.contributesReflectionFor(TypeReference.of( contribution.contributesReflectionFor(
"org.springframework.data.repository.aot.GeneratedClassesCaptureIntegrationTests$Config$Person__Accessor_xj7ohs")); TypeReference.of("org.springframework.data.aot.types.BaseEntity__Accessor_m5hoaa"));
contribution.contributesReflectionFor(TypeReference.of( contribution.contributesReflectionFor(
"org.springframework.data.repository.aot.GeneratedClassesCaptureIntegrationTests$Config$Person__Instantiator_xj7ohs")); TypeReference.of("org.springframework.data.aot.types.BaseEntity__Instantiator_m5hoaa"));
contribution.contributesReflectionFor(
TypeReference.of("org.springframework.data.aot.types.Address__Accessor_rf1iey"));
contribution.contributesReflectionFor(
TypeReference.of("org.springframework.data.aot.types.Address__Instantiator_rf1iey"));
});
}
// TODO: These should also appear @Test // GH-2595
/* @Disabled("caching issue in ClassGeneratingEntityInstantiator")
contribution.contributesReflectionFor(TypeReference.of( void registersGeneratedPropertyAccessorsEntityInstantiatorsForCyclicProperties() {
"org.springframework.data.repository.aot.GeneratedClassesCaptureIntegrationTests$Config$Address__Accessor_xj7ohs"));
contribution.contributesReflectionFor(TypeReference.of( RepositoryRegistrationAotContribution repositoryBeanContribution = AotUtil
"org.springframework.data.repository.aot.GeneratedClassesCaptureIntegrationTests$Config$Address__Instantiator_xj7ohs")); .contributionFor(ConfigWithCyclicReferences.class).forRepository(ConfigWithCyclicReferences.MyRepo.class);
*/
assertThatContribution(repositoryBeanContribution) //
.codeContributionSatisfies(contribution -> {
contribution.contributesReflectionFor(
TypeReference.of("org.springframework.data.aot.types.CyclicPropertiesB__Accessor_o13htw"));
contribution.contributesReflectionFor(
TypeReference.of("org.springframework.data.aot.types.CyclicPropertiesB__Instantiator_o13htw"));
contribution.contributesReflectionFor(
TypeReference.of("org.springframework.data.aot.types.CyclicPropertiesA__Accessor_o13htx"));
contribution.contributesReflectionFor(
TypeReference.of("org.springframework.data.aot.types.CyclicPropertiesA__Instantiator_o13htx"));
});
}
@Test // GH-2595
void registersGeneratedPropertyAccessorsEntityInstantiatorsForMultipleRepositoriesReferencingEachOther() {
RepositoryRegistrationAotContribution repositoryBeanContribution = AotUtil
.contributionFor(ConfigWithMultipleRepositories.class)
.forRepositories(ConfigWithMultipleRepositories.Repo1.class, ConfigWithMultipleRepositories.Repo2.class);
assertThatContribution(repositoryBeanContribution) //
.codeContributionSatisfies(contribution -> {
contribution.contributesReflectionFor(
TypeReference.of("org.springframework.data.aot.types.CyclicPropertiesB__Accessor_o13htw"));
contribution.contributesReflectionFor(
TypeReference.of("org.springframework.data.aot.types.CyclicPropertiesB__Instantiator_o13htw"));
contribution.contributesReflectionFor(
TypeReference.of("org.springframework.data.aot.types.CyclicPropertiesA__Accessor_o13htx"));
contribution.contributesReflectionFor(
TypeReference.of("org.springframework.data.aot.types.CyclicPropertiesA__Instantiator_o13htx"));
}); });
} }
@EnableRepositories(includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, value = Config.MyRepo.class) }, @EnableRepositories(includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, value = Config.MyRepo.class) },
basePackageClasses = Config.class, considerNestedRepositories = true) basePackageClasses = Config.class, considerNestedRepositories = true)
public class Config { public static class Config {
public interface MyRepo extends CrudRepository<Person, String> { public interface MyRepo extends CrudRepository<BaseEntity, String> {
} }
}
public static class Person { @EnableRepositories(
includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, value = ConfigWithCyclicReferences.MyRepo.class) },
basePackageClasses = ConfigWithCyclicReferences.class, considerNestedRepositories = true)
public static class ConfigWithCyclicReferences {
@Nullable Address address; public interface MyRepo extends CrudRepository<CyclicPropertiesA, String> {
} }
}
@EnableRepositories(
includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, value = { Repo1.class, Repo2.class }) },
basePackageClasses = ConfigWithCyclicReferences.class, considerNestedRepositories = true)
public static class ConfigWithMultipleRepositories {
public interface Repo1 extends CrudRepository<CyclicPropertiesA, String> {
public static class Address {
String street;
} }
public interface Repo2 extends CrudRepository<CyclicPropertiesB, String> {
}
} }
} }

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

@ -116,7 +116,7 @@ public class RepositoryRegistrationAotContributionAssert
BeanRegistrationCode mockBeanRegistrationCode = mock(BeanRegistrationCode.class); BeanRegistrationCode mockBeanRegistrationCode = mock(BeanRegistrationCode.class);
GenerationContext generationContext = new TestGenerationContext(Object.class); TestGenerationContext generationContext = new TestGenerationContext(Object.class);
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator(); ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
@ -138,7 +138,6 @@ public class RepositoryRegistrationAotContributionAssert
return null; return null;
} }
}); });
assertWith.accept(new CodeContributionAssert(generationContext)); assertWith.accept(new CodeContributionAssert(generationContext));
} catch (Throwable o_O) { } catch (Throwable o_O) {
fail(o_O.getMessage(), o_O); fail(o_O.getMessage(), o_O);

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

@ -78,9 +78,7 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
.contributesJdkProxy(ConfigWithSimpleCrudRepository.MyRepo.class, SpringProxy.class, Advised.class, .contributesJdkProxy(ConfigWithSimpleCrudRepository.MyRepo.class, SpringProxy.class, Advised.class,
DecoratingProxy.class) // DecoratingProxy.class) //
.contributesJdkProxy(ConfigWithSimpleCrudRepository.MyRepo.class, Repository.class, .contributesJdkProxy(ConfigWithSimpleCrudRepository.MyRepo.class, Repository.class,
TransactionalProxy.class, Advised.class, DecoratingProxy.class) TransactionalProxy.class, Advised.class, DecoratingProxy.class);
.doesNotContributeJdkProxy(ConfigWithSimpleCrudRepository.MyRepo.class, Repository.class,
TransactionalProxy.class, Advised.class, DecoratingProxy.class, Serializable.class);
}); });
} }
@ -103,9 +101,7 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
.contributesJdkProxy(ConfigWithTransactionManagerPresent.MyTxRepo.class, SpringProxy.class, Advised.class, .contributesJdkProxy(ConfigWithTransactionManagerPresent.MyTxRepo.class, SpringProxy.class, Advised.class,
DecoratingProxy.class) DecoratingProxy.class)
.contributesJdkProxy(ConfigWithTransactionManagerPresent.MyTxRepo.class, Repository.class, .contributesJdkProxy(ConfigWithTransactionManagerPresent.MyTxRepo.class, Repository.class,
TransactionalProxy.class, Advised.class, DecoratingProxy.class) TransactionalProxy.class, Advised.class, DecoratingProxy.class);
.doesNotContributeJdkProxy(ConfigWithTransactionManagerPresent.MyTxRepo.class, Repository.class,
TransactionalProxy.class, Advised.class, DecoratingProxy.class, Serializable.class);
}); });
} }

24
src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java

@ -17,14 +17,18 @@ package org.springframework.data.repository.aot.generate;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.test.tools.ClassFile; import org.springframework.core.test.tools.ClassFile;
import org.springframework.data.aot.AotTypeConfiguration;
import org.springframework.data.repository.config.AotRepositoryContext; import org.springframework.data.repository.config.AotRepositoryContext;
import org.springframework.data.repository.config.RepositoryConfigurationSource; import org.springframework.data.repository.config.RepositoryConfigurationSource;
import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryInformation;
@ -75,6 +79,21 @@ class DummyModuleAotRepositoryContext implements AotRepositoryContext {
return null; return null;
} }
@Override
public InstantiationCreator instantiationCreator(TypeReference typeReference) {
return null;
}
@Override
public AotTypeConfiguration typeConfiguration(TypeReference typeReference) {
return null;
}
@Override
public Collection<AotTypeConfiguration> typeConfigurations() {
return List.of();
}
@Override @Override
public String getBeanName() { public String getBeanName() {
return "dummyRepository"; return "dummyRepository";
@ -105,6 +124,11 @@ class DummyModuleAotRepositoryContext implements AotRepositoryContext {
return Set.of(); return Set.of();
} }
@Override
public Set<Class<?>> getUserDomainTypes() {
return Set.of();
}
public List<ClassFile> getRequiredContextFiles() { public List<ClassFile> getRequiredContextFiles() {
return List.of(classFileForType(repositoryInformation.getRepositoryBaseClass())); return List.of(classFileForType(repositoryInformation.getRepositoryBaseClass()));
} }

Loading…
Cancel
Save