diff --git a/src/main/java/org/springframework/data/aot/AotContext.java b/src/main/java/org/springframework/data/aot/AotContext.java index 0f9a0a5c8..6d6f965be 100644 --- a/src/main/java/org/springframework/data/aot/AotContext.java +++ b/src/main/java/org/springframework/data/aot/AotContext.java @@ -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 { */ 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 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 { * @return a {@link List} of bean names. The list is empty if the bean factory does not hold any beans of this type. */ List getBeanNames(); - } /** @@ -340,7 +356,11 @@ public interface AotContext extends EnvironmentCapable { */ @Nullable Class resolveType(); + } + interface InstantiationCreator { + boolean isAvailable(); + void create(); } } diff --git a/src/main/java/org/springframework/data/aot/AotMappingContext.java b/src/main/java/org/springframework/data/aot/AotMappingContext.java index 3a240684d..4b18eb77d 100644 --- a/src/main/java/org/springframework/data/aot/AotMappingContext.java +++ b/src/main/java/org/springframework/data/aot/AotMappingContext.java @@ -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; 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; * @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, 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 propertyAccessorFactory.initialize(entity); } + // TODO: can we extract some util for this using only type @Override protected BasicPersistentEntity createPersistentEntity( TypeInformation typeInformation) { + logger.debug("I hate gradle: create persistent entity for type: " + typeInformation); return new BasicPersistentEntity<>(typeInformation); } @Override protected BasicPersistentProperty createPersistentProperty(Property property, BasicPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { + logger.info("creating property: " + property.getName()); return new BasicPersistentProperty(property, owner, simpleTypeHolder); } @@ -74,10 +83,21 @@ public class AotMappingContext extends super(property, owner, simpleTypeHolder); } + @Override + public boolean isAssociation() { + return false; + } + @Override protected Association createAssociation() { - return null; + return new Association<>(this, null); } + + @Override + public Association getRequiredAssociation() { + return new Association<>(this, null); + } + } } diff --git a/src/main/java/org/springframework/data/aot/AotTypeConfiguration.java b/src/main/java/org/springframework/data/aot/AotTypeConfiguration.java new file mode 100644 index 000000000..1dbc60a69 --- /dev/null +++ b/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 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 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 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 userConfiguredCondition(Environment environment) { + + return new Predicate() { + + private final List 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; + } + }; + } + +} diff --git a/src/main/java/org/springframework/data/aot/DefaultAotContext.java b/src/main/java/org/springframework/data/aot/DefaultAotContext.java index 65cb8ab54..a60f87af4 100644 --- a/src/main/java/org/springframework/data/aot/DefaultAotContext.java +++ b/src/main/java/org/springframework/data/aot/DefaultAotContext.java @@ -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; */ 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 typeConfigurations = new HashMap<>(); + private final Environment environment; public DefaultAotContext(BeanFactory beanFactory, Environment environment) { @@ -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 typeConfigurations() { + return typeConfigurations.values(); + } + class DefaultTypeIntrospector implements TypeIntrospector { private final String typeName; @@ -108,6 +145,29 @@ class DefaultAotContext implements AotContext { } } + class DefaultInstantiationCreator implements InstantiationCreator { + + Lazy> 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 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 { } } + class ContextualTypeConfiguration implements AotTypeConfiguration { + + private final TypeReference type; + private boolean forDataBinding = false; + private final Set categories = new HashSet<>(5); + private boolean generateEntityInstantiator = false; + private boolean forQuerydsl = false; + private final List> proxies = new ArrayList<>(); + private Predicate 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 interfaces) { + this.proxies.add(interfaces); + return this; + } + + @Override + public AotTypeConfiguration forQuerydsl() { + this.forQuerydsl = true; + return this; + } + + @Override + public AotTypeConfiguration conditional(Predicate 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 proxyInterfaces : proxies) { + generationContext.getRuntimeHints().proxies() + .registerJdkProxy(Stream.concat(Stream.of(type), proxyInterfaces.stream()).toArray(TypeReference[]::new)); + } + } + + } + + private boolean doIfPresent(Consumer> 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; + } + } + } } diff --git a/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java b/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java index 0b865a876..78833f7a7 100644 --- a/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java +++ b/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.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; 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 private final Log logger = LogFactory.getLog(getClass()); private @Nullable String moduleIdentifier; private Lazy 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 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 */ 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 Set 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)); } diff --git a/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java b/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java index 1e59e7e85..ed7d32dc0 100644 --- a/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java +++ b/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java @@ -74,14 +74,16 @@ import org.springframework.util.ReflectionUtils; */ class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContribution { + private final AotContext aotContext; private final ManagedTypes managedTypes; private final Lazy>> sourceTypes; private final BiConsumer contributionAction; private final RegisteredBean source; - public ManagedTypesRegistrationAotContribution(ManagedTypes managedTypes, RegisteredBean registeredBean, + public ManagedTypesRegistrationAotContribution(AotContext aotContext, ManagedTypes managedTypes, RegisteredBean registeredBean, BiConsumer contributionAction) { + this.aotContext = aotContext; this.managedTypes = managedTypes; this.sourceTypes = Lazy.of(managedTypes::toList); this.contributionAction = contributionAction; diff --git a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java index 9a312ca38..feaedac47 100644 --- a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java +++ b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java @@ -243,6 +243,7 @@ public abstract class AbstractMappingContext type) { + LOGGER.info("obtain persistent entity for type: " + type); return getPersistentEntity(TypeInformation.of(type)); } diff --git a/src/main/java/org/springframework/data/mapping/context/ReflectionFallbackPersistentPropertyAccessorFactory.java b/src/main/java/org/springframework/data/mapping/context/ReflectionFallbackPersistentPropertyAccessorFactory.java index 6640f925e..e99edeacf 100644 --- a/src/main/java/org/springframework/data/mapping/context/ReflectionFallbackPersistentPropertyAccessorFactory.java +++ b/src/main/java/org/springframework/data/mapping/context/ReflectionFallbackPersistentPropertyAccessorFactory.java @@ -36,9 +36,13 @@ class ReflectionFallbackPersistentPropertyAccessorFactory implements PersistentP public PersistentPropertyAccessor getPropertyAccessor(PersistentEntity entity, T bean) { if (accessorFactory.isSupported(entity)) { - return accessorFactory.getPropertyAccessor(entity, bean); + PersistentPropertyAccessor 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); } diff --git a/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java b/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java index 0a8948688..c0eac7cf2 100644 --- a/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java +++ b/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java @@ -79,4 +79,6 @@ public interface AotRepositoryContext extends AotContext { */ Set> getResolvedTypes(); + Set> getUserDomainTypes(); + } diff --git a/src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java b/src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java index ccf60a01e..8541c5bb9 100644 --- a/src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java +++ b/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.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 { return managedTypes.get(); } + @Override + public Set> 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 typeConfigurations() { + return aotContext.typeConfigurations(); + } + @Override public AotContext.IntrospectedBeanDefinition introspectBeanDefinition(String beanName) { return aotContext.introspectBeanDefinition(beanName); diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java b/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java index 1a53247cc..d333cb350 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java +++ b/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.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; 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; 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 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 }; } - public Predicate> 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 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 .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 } } - 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 TypeReference.of("kotlin.Boolean"))); } - private List 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) // diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java b/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java index 535940d0d..5a66753af 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java +++ b/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.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; 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 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 // 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(); }); } diff --git a/src/test/java/org/springframework/data/aot/AotContextUnitTests.java b/src/test/java/org/springframework/data/aot/AotContextUnitTests.java index 4c257440f..635b3a814 100644 --- a/src/test/java/org/springframework/data/aot/AotContextUnitTests.java +++ b/src/test/java/org/springframework/data/aot/AotContextUnitTests.java @@ -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 { 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 typeConfigurations() { + return List.of(); + } + @Override public Environment getEnvironment() { return environment; diff --git a/src/test/java/org/springframework/data/aot/AotMappingContextUnitTests.java b/src/test/java/org/springframework/data/aot/AotMappingContextUnitTests.java new file mode 100644 index 000000000..4a165555d --- /dev/null +++ b/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; + } +} diff --git a/src/test/java/org/springframework/data/repository/aot/AotUtil.java b/src/test/java/org/springframework/data/repository/aot/AotUtil.java index 0ce1763a4..042ada7c6 100644 --- a/src/test/java/org/springframework/data/repository/aot/AotUtil.java +++ b/src/test/java/org/springframework/data/repository/aot/AotUtil.java @@ -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 { 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); } } diff --git a/src/test/java/org/springframework/data/repository/aot/GeneratedClassesCaptureIntegrationTests.java b/src/test/java/org/springframework/data/repository/aot/GeneratedClassesCaptureIntegrationTests.java index 39674dd06..eeecdc5eb 100644 --- a/src/test/java/org/springframework/data/repository/aot/GeneratedClassesCaptureIntegrationTests.java +++ b/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 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 { 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 { + public interface MyRepo extends CrudRepository { } + } - 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 { } + } + + @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 { - public static class Address { - String street; } + public interface Repo2 extends CrudRepository { + + } } } diff --git a/src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotContributionAssert.java b/src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotContributionAssert.java index 8e7343386..b6afe7a3f 100644 --- a/src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotContributionAssert.java +++ b/src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotContributionAssert.java @@ -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 return null; } }); - assertWith.accept(new CodeContributionAssert(generationContext)); } catch (Throwable o_O) { fail(o_O.getMessage(), o_O); diff --git a/src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessorIntegrationTests.java b/src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessorIntegrationTests.java index d71d0325f..1226c1640 100644 --- a/src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessorIntegrationTests.java +++ b/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, 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 { .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); }); } diff --git a/src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java b/src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java index ff578c394..297c95335 100644 --- a/src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java +++ b/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.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 { return null; } + @Override + public InstantiationCreator instantiationCreator(TypeReference typeReference) { + return null; + } + + @Override + public AotTypeConfiguration typeConfiguration(TypeReference typeReference) { + return null; + } + + @Override + public Collection typeConfigurations() { + return List.of(); + } + @Override public String getBeanName() { return "dummyRepository"; @@ -105,6 +124,11 @@ class DummyModuleAotRepositoryContext implements AotRepositoryContext { return Set.of(); } + @Override + public Set> getUserDomainTypes() { + return Set.of(); + } + public List getRequiredContextFiles() { return List.of(classFileForType(repositoryInformation.getRepositoryBaseClass())); }