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. 88
      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. 37
      src/test/java/org/springframework/data/repository/aot/AotUtil.java
  16. 104
      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; @@ -22,14 +22,17 @@ import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.env.StandardEnvironment;
@ -227,6 +230,20 @@ public interface AotContext extends EnvironmentCapable { @@ -227,6 +230,20 @@ public interface AotContext extends EnvironmentCapable {
*/
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
* of beans.
@ -286,7 +303,6 @@ public interface AotContext extends EnvironmentCapable { @@ -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.
*/
List<String> getBeanNames();
}
/**
@ -340,7 +356,11 @@ public interface AotContext extends EnvironmentCapable { @@ -340,7 +356,11 @@ public interface AotContext extends EnvironmentCapable {
*/
@Nullable
Class<?> resolveType();
}
interface InstantiationCreator {
boolean isAvailable();
void create();
}
}

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

@ -15,6 +15,8 @@ @@ -15,6 +15,8 @@
*/
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.PersistentEntity;
import org.springframework.data.mapping.context.AbstractMappingContext;
@ -26,6 +28,7 @@ import org.springframework.data.mapping.model.EntityInstantiators; @@ -26,6 +28,7 @@ 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.repository.aot.generate.RepositoryContributor;
import org.springframework.data.util.TypeInformation;
/**
@ -34,9 +37,12 @@ import org.springframework.data.util.TypeInformation; @@ -34,9 +37,12 @@ import org.springframework.data.util.TypeInformation;
* @author Mark Paluch
* @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> {
private static final Log logger = LogFactory.getLog(AotMappingContext.class);
private final EntityInstantiators instantiators = new EntityInstantiators();
private final ClassGeneratingPropertyAccessorFactory propertyAccessorFactory = new ClassGeneratingPropertyAccessorFactory();
@ -55,15 +61,18 @@ public class AotMappingContext extends @@ -55,15 +61,18 @@ public class AotMappingContext extends
propertyAccessorFactory.initialize(entity);
}
// TODO: can we extract some util for this using only type
@Override
protected <T> BasicPersistentEntity<?, BasicPersistentProperty> createPersistentEntity(
TypeInformation<T> typeInformation) {
logger.debug("I hate gradle: create persistent entity for type: " + typeInformation);
return new BasicPersistentEntity<>(typeInformation);
}
@Override
protected BasicPersistentProperty createPersistentProperty(Property property,
BasicPersistentEntity<?, BasicPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
logger.info("creating property: " + property.getName());
return new BasicPersistentProperty(property, owner, simpleTypeHolder);
}
@ -74,10 +83,21 @@ public class AotMappingContext extends @@ -74,10 +83,21 @@ public class AotMappingContext extends
super(property, owner, simpleTypeHolder);
}
@Override
public boolean isAssociation() {
return false;
}
@Override
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 @@ @@ -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 @@ @@ -15,20 +15,37 @@
*/
package org.springframework.data.aot;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
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.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.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
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.data.util.QTypeContributor;
import org.springframework.data.util.TypeContributor;
import org.springframework.util.ClassUtils;
/**
@ -39,8 +56,13 @@ import org.springframework.util.ClassUtils; @@ -39,8 +56,13 @@ import org.springframework.util.ClassUtils;
*/
class DefaultAotContext implements AotContext {
private final AotMappingContext mappingContext = new AotMappingContext();
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;
public DefaultAotContext(BeanFactory beanFactory, Environment environment) {
@ -69,6 +91,21 @@ class DefaultAotContext implements AotContext { @@ -69,6 +91,21 @@ class DefaultAotContext implements AotContext {
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 {
private final String typeName;
@ -108,6 +145,29 @@ class DefaultAotContext implements AotContext { @@ -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 {
private final String beanName;
@ -148,4 +208,106 @@ class DefaultAotContext implements AotContext { @@ -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; @@ -25,7 +25,6 @@ import org.jspecify.annotations.Nullable;
import org.springframework.aot.generate.GenerationContext;
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.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
@ -36,9 +35,7 @@ import org.springframework.core.ResolvableType; @@ -36,9 +35,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;
import org.springframework.data.util.TypeUtils;
import org.springframework.util.ClassUtils;
@ -57,7 +54,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio @@ -57,7 +54,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();
private AotContext aotContext;
public void setModuleIdentifier(@Nullable String moduleIdentifier) {
this.moduleIdentifier = moduleIdentifier;
@ -80,9 +77,8 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio @@ -80,9 +77,8 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
return null;
}
BeanFactory beanFactory = registeredBean.getBeanFactory();
return contribute(AotContext.from(beanFactory, this.environment.get()), resolveManagedTypes(registeredBean),
registeredBean);
this.aotContext = new DefaultAotContext(registeredBean.getBeanFactory(), environment.get());
return contribute(this.aotContext, resolveManagedTypes(registeredBean), registeredBean);
}
private ManagedTypes resolveManagedTypes(RegisteredBean registeredBean) {
@ -131,7 +127,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio @@ -131,7 +127,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
*/
protected BeanRegistrationAotContribution contribute(AotContext aotContext, ManagedTypes managedTypes,
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 @@ -148,16 +144,12 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
Set<String> annotationNamespaces = Collections.singleton(TypeContributor.DATA_NAMESPACE);
Class<?> resolvedType = type.toClass();
TypeContributor.contribute(resolvedType, annotationNamespaces, generationContext);
QTypeContributor.contributeEntityPath(resolvedType, generationContext, resolvedType.getClassLoader());
aotContext.typeConfiguration(type).forDataBinding() //
.generateEntityInstantiator() //
.forQuerydsl() //
.contribute(generationContext); //
PersistentEntity<?, ?> entity = aotMappingContext.getPersistentEntity(resolvedType);
if (entity != null) {
aotMappingContext.contribute(entity);
}
TypeUtils.resolveUsedAnnotations(resolvedType).forEach(
TypeUtils.resolveUsedAnnotations(type.toClass()).forEach(
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; @@ -74,14 +74,16 @@ import org.springframework.util.ReflectionUtils;
*/
class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContribution {
private final AotContext aotContext;
private final ManagedTypes managedTypes;
private final Lazy<List<Class<?>>> sourceTypes;
private final BiConsumer<ResolvableType, GenerationContext> contributionAction;
private final RegisteredBean source;
public ManagedTypesRegistrationAotContribution(ManagedTypes managedTypes, RegisteredBean registeredBean,
public ManagedTypesRegistrationAotContribution(AotContext aotContext, ManagedTypes managedTypes, RegisteredBean registeredBean,
BiConsumer<ResolvableType, GenerationContext> contributionAction) {
this.aotContext = aotContext;
this.managedTypes = managedTypes;
this.sourceTypes = Lazy.of(managedTypes::toList);
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<? @@ -243,6 +243,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
@Override
@Nullable
public E getPersistentEntity(Class<?> type) {
LOGGER.info("obtain persistent entity for type: " + 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 @@ -36,9 +36,13 @@ class ReflectionFallbackPersistentPropertyAccessorFactory implements PersistentP
public <T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<?, ?> entity, T bean) {
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);
}

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

@ -79,4 +79,6 @@ public interface AotRepositoryContext extends AotContext { @@ -79,4 +79,6 @@ public interface AotRepositoryContext extends AotContext {
*/
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; @@ -19,17 +19,21 @@ import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.env.Environment;
import org.springframework.data.aot.AotContext;
import org.springframework.data.aot.AotTypeConfiguration;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeCollector;
import org.springframework.data.util.TypeContributor;
import org.springframework.data.util.TypeUtils;
/**
@ -133,11 +137,34 @@ class DefaultAotRepositoryContext implements AotRepositoryContext { @@ -133,11 +137,34 @@ class DefaultAotRepositoryContext implements AotRepositoryContext {
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
public AotContext.TypeIntrospector introspectType(String 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
public AotContext.IntrospectedBeanDefinition introspectBeanDefinition(String beanName) {
return aotContext.introspectBeanDefinition(beanName);

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

@ -20,6 +20,7 @@ import java.util.ArrayList; @@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Predicate;
@ -43,10 +44,8 @@ import org.springframework.core.DecoratingProxy; @@ -43,10 +44,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.aot.AotTypeConfiguration;
import org.springframework.data.projection.EntityProjectionIntrospector;
import org.springframework.data.projection.TargetAware;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.aot.generate.AotRepositoryBeanDefinitionPropertiesDecorator;
import org.springframework.data.repository.aot.generate.RepositoryContributor;
@ -57,7 +56,6 @@ import org.springframework.data.util.QTypeContributor; @@ -57,7 +56,6 @@ import org.springframework.data.util.QTypeContributor;
import org.springframework.data.util.TypeContributor;
import org.springframework.data.util.TypeUtils;
import org.springframework.javapoet.CodeBlock;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@ -75,8 +73,6 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -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 final AotMappingContext aotMappingContext = new AotMappingContext();
private final RepositoryRegistrationAotProcessor aotProcessor;
private final AotRepositoryContext repositoryContext;
@ -259,62 +255,29 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -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) {
RepositoryInformation repositoryInformation = getRepositoryInformation();
logTrace("Contributing repository information for [%s]", repositoryInformation.getRepositoryInterface());
contribution.getRuntimeHints().reflection()
.registerType(repositoryInformation.getRepositoryInterface(),
hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS))
.registerType(repositoryInformation.getRepositoryBaseClass(), hint -> hint
.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS));
TypeContributor.contribute(repositoryInformation.getDomainType(), contribution);
QTypeContributor.contributeEntityPath(repositoryInformation.getDomainType(), contribution,
repositoryContext.getClassLoader());
// 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);
}
repositoryContext.typeConfiguration(repositoryInformation.getRepositoryInterface())
.forReflectiveAccess(MemberCategory.INVOKE_PUBLIC_METHODS) //
.repositoryProxy();
repositoryContext.typeConfiguration(repositoryInformation.getRepositoryBaseClass())
.forReflectiveAccess(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
repositoryContext.typeConfiguration(repositoryInformation.getDomainType()).forDataBinding().forQuerydsl();
// TODO: purposeful api for uses cases to have some internal logic
repositoryContext.getUserDomainTypes().stream() //
.map(repositoryContext::typeConfiguration) //
.forEach(AotTypeConfiguration::generateEntityInstantiator); //
// Repository Fragments
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
if (isKotlinCoroutineRepository(repositoryContext, repositoryInformation)) {
contribution.getRuntimeHints().reflection().registerTypes(kotlinRepositoryReflectionTypeReferences(), hint -> {});
@ -325,7 +288,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -325,7 +288,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
.filter(Class::isInterface).forEach(type -> {
if (EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy().test(type,
repositoryInformation.getDomainType())) {
contributeProjection(type, contribution);
repositoryContext.typeConfiguration(type).usedAsProjectionInterface();
}
});
}
@ -358,10 +321,6 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -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,
RepositoryInformation repositoryInformation) {
@ -382,21 +341,6 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -382,21 +341,6 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
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) {
return TypeUtils.type(type).isPartOf("java") //
|| ClassUtils.isPrimitiveOrWrapper(type) //

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

@ -28,6 +28,7 @@ import org.jspecify.annotations.Nullable; @@ -28,6 +28,7 @@ import org.jspecify.annotations.Nullable;
import org.springframework.aot.generate.GenerationContext;
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.ReflectiveRuntimeHintsRegistrar;
import org.springframework.beans.BeansException;
@ -44,8 +45,6 @@ import org.springframework.core.annotation.MergedAnnotation; @@ -44,8 +45,6 @@ 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;
@ -79,8 +78,6 @@ public class RepositoryRegistrationAotProcessor @@ -79,8 +78,6 @@ 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();
@ -129,10 +126,7 @@ public class RepositoryRegistrationAotProcessor @@ -129,10 +126,7 @@ public class RepositoryRegistrationAotProcessor
// arent we already registering the types in RepositoryRegistrationAotContribution#contributeRepositoryInfo?
registrar.registerRuntimeHints(hints, it);
PersistentEntity<?, ?> persistentEntity = aotMappingContext.getPersistentEntity(it);
if (persistentEntity != null) {
aotMappingContext.contribute(persistentEntity);
}
repositoryContext.instantiationCreator(TypeReference.of(it)).create();
});
}

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

@ -15,10 +15,14 @@ @@ -15,10 +15,14 @@
*/
package org.springframework.data.aot;
import java.util.Collection;
import java.util.List;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.Mockito;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.env.Environment;
import org.springframework.mock.env.MockEnvironment;
@ -83,6 +87,21 @@ class AotContextUnitTests { @@ -83,6 +87,21 @@ class AotContextUnitTests {
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
public Environment getEnvironment() {
return environment;

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

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

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

@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
*/
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.beans.factory.aot.BeanRegistrationAotContribution;
@ -42,35 +42,42 @@ class AotUtil { @@ -42,35 +42,42 @@ class AotUtil {
applicationContext.register(configuration);
applicationContext.refreshForAotProcessing(new RuntimeHints());
return repositoryType -> {
return repositoryTypes -> {
String[] repositoryBeanNames = applicationContext.getBeanNamesForType(repositoryType);
BeanRegistrationAotContribution beanContribution = null;
assertThat(repositoryBeanNames)
.describedAs("Unable to find repository [%s] in configuration [%s]", repositoryType, configuration)
.hasSize(1);
for (Class<?> repositoryType : repositoryTypes) {
String repositoryBeanName = repositoryBeanNames[0];
String[] repositoryBeanNames = applicationContext.getBeanNamesForType(repositoryType);
ConfigurableListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();
assertThat(repositoryBeanNames)
.describedAs("Unable to find repository [%s] in configuration [%s]", repositoryType, configuration)
.hasSize(1);
RepositoryRegistrationAotProcessor repositoryAotProcessor = applicationContext
.getBean(RepositoryRegistrationAotProcessor.class);
String repositoryBeanName = repositoryBeanNames[0];
repositoryAotProcessor.setBeanFactory(beanFactory);
ConfigurableListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();
RegisteredBean bean = RegisteredBean.of(beanFactory, repositoryBeanName);
RepositoryRegistrationAotProcessor repositoryAotProcessor = applicationContext
.getBean(RepositoryRegistrationAotProcessor.class);
BeanRegistrationAotContribution beanContribution = repositoryAotProcessor.processAheadOfTime(bean);
repositoryAotProcessor.setBeanFactory(beanFactory);
assertThat(beanContribution).isInstanceOf(RepositoryRegistrationAotContribution.class);
RegisteredBean bean = RegisteredBean.of(beanFactory, repositoryBeanName);
beanContribution = repositoryAotProcessor.processAheadOfTime(bean);
}
assertThat(beanContribution).isInstanceOf(RepositoryRegistrationAotContribution.class);
return (RepositoryRegistrationAotContribution) beanContribution;
};
}
@FunctionalInterface
interface RepositoryRegistrationAotContributionBuilder {
RepositoryRegistrationAotContribution forRepository(Class<?> repositoryInterface);
default RepositoryRegistrationAotContribution forRepository(Class<?> repositoryInterface) {
return forRepositories(repositoryInterface);
}
RepositoryRegistrationAotContribution forRepositories(Class<?>... repositoryInterface);
}
}

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

@ -17,22 +17,38 @@ package org.springframework.data.repository.aot; @@ -17,22 +17,38 @@ package org.springframework.data.repository.aot;
import static org.springframework.data.repository.aot.RepositoryRegistrationAotContributionAssert.*;
import java.util.Map;
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.springframework.aot.hint.TypeReference;
import org.springframework.context.annotation.ComponentScan.Filter;
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.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.RepositoryRegistrationAotContribution;
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
* property accessors.
*
* @author Mark Paluch
* @author Christoph Strobl
*/
public class GeneratedClassesCaptureIntegrationTests {
@ -44,39 +60,91 @@ public class GeneratedClassesCaptureIntegrationTests { @@ -44,39 +60,91 @@ public class GeneratedClassesCaptureIntegrationTests {
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"));
*/
contribution.contributesReflectionFor(
TypeReference.of("org.springframework.data.aot.types.BaseEntity__Accessor_m5hoaa"));
contribution.contributesReflectionFor(
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"));
});
}
@Test // GH-2595
@Disabled("caching issue in ClassGeneratingEntityInstantiator")
void registersGeneratedPropertyAccessorsEntityInstantiatorsForCyclicProperties() {
RepositoryRegistrationAotContribution repositoryBeanContribution = AotUtil
.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) },
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 @@ -116,7 +116,7 @@ public class RepositoryRegistrationAotContributionAssert
BeanRegistrationCode mockBeanRegistrationCode = mock(BeanRegistrationCode.class);
GenerationContext generationContext = new TestGenerationContext(Object.class);
TestGenerationContext generationContext = new TestGenerationContext(Object.class);
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
@ -138,7 +138,6 @@ public class RepositoryRegistrationAotContributionAssert @@ -138,7 +138,6 @@ public class RepositoryRegistrationAotContributionAssert
return null;
}
});
assertWith.accept(new CodeContributionAssert(generationContext));
} catch (Throwable 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 { @@ -78,9 +78,7 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
.contributesJdkProxy(ConfigWithSimpleCrudRepository.MyRepo.class, SpringProxy.class, Advised.class,
DecoratingProxy.class) //
.contributesJdkProxy(ConfigWithSimpleCrudRepository.MyRepo.class, Repository.class,
TransactionalProxy.class, Advised.class, DecoratingProxy.class)
.doesNotContributeJdkProxy(ConfigWithSimpleCrudRepository.MyRepo.class, Repository.class,
TransactionalProxy.class, Advised.class, DecoratingProxy.class, Serializable.class);
TransactionalProxy.class, Advised.class, DecoratingProxy.class);
});
}
@ -103,9 +101,7 @@ public class RepositoryRegistrationAotProcessorIntegrationTests { @@ -103,9 +101,7 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
.contributesJdkProxy(ConfigWithTransactionManagerPresent.MyTxRepo.class, SpringProxy.class, Advised.class,
DecoratingProxy.class)
.contributesJdkProxy(ConfigWithTransactionManagerPresent.MyTxRepo.class, Repository.class,
TransactionalProxy.class, Advised.class, DecoratingProxy.class)
.doesNotContributeJdkProxy(ConfigWithTransactionManagerPresent.MyTxRepo.class, Repository.class,
TransactionalProxy.class, Advised.class, DecoratingProxy.class, Serializable.class);
TransactionalProxy.class, Advised.class, DecoratingProxy.class);
});
}

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

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

Loading…
Cancel
Save