diff --git a/src/main/java/org/springframework/data/aot/AotContext.java b/src/main/java/org/springframework/data/aot/AotContext.java index 6d6f965be..fc9fa96df 100644 --- a/src/main/java/org/springframework/data/aot/AotContext.java +++ b/src/main/java/org/springframework/data/aot/AotContext.java @@ -22,7 +22,6 @@ 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; @@ -230,18 +229,31 @@ 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)); + /** + * Obtain a {@link AotTypeConfiguration} for the given {@link ResolvableType} to customize the AOT processing for the + * given type. + * + * @param resolvableType the resolvable type to configure. + * @param configurationConsumer configuration consumer function. + */ + default void typeConfiguration(ResolvableType resolvableType, Consumer configurationConsumer) { + typeConfiguration(resolvableType.toClass(), configurationConsumer); } - AotTypeConfiguration typeConfiguration(TypeReference typeReference); + /** + * Obtain a {@link AotTypeConfiguration} for the given {@link ResolvableType} to customize the AOT processing for the + * given type. + * + * @param type the type to configure. + * @param configurationConsumer configuration consumer function. + */ + void typeConfiguration(Class type, Consumer configurationConsumer); + /** + * Return all type configurations registered with this {@link AotContext}. + * + * @return all type configurations registered with this {@link AotContext}. + */ Collection typeConfigurations(); /** @@ -358,9 +370,4 @@ public interface AotContext extends EnvironmentCapable { 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 4b18eb77d..73d8c136d 100644 --- a/src/main/java/org/springframework/data/aot/AotMappingContext.java +++ b/src/main/java/org/springframework/data/aot/AotMappingContext.java @@ -24,8 +24,8 @@ import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory; import org.springframework.data.mapping.model.EntityInstantiator; +import org.springframework.data.mapping.model.EntityInstantiatorSource; 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; @@ -37,48 +37,53 @@ import org.springframework.data.util.TypeInformation; * @author Mark Paluch * @since 4.0 */ -class AotMappingContext extends // TODO: hide this one and delegate to other component - can we use the - // AotContext for it? - AbstractMappingContext, AotMappingContext.BasicPersistentProperty> { +class AotMappingContext extends + AbstractMappingContext, AotMappingContext.AotPersistentProperty> { private static final Log logger = LogFactory.getLog(AotMappingContext.class); private final EntityInstantiators instantiators = new EntityInstantiators(); - private final ClassGeneratingPropertyAccessorFactory propertyAccessorFactory = new ClassGeneratingPropertyAccessorFactory(); + private final AotAccessorFactory propertyAccessorFactory = new AotAccessorFactory(); /** * Contribute entity instantiators and property accessors for the given {@link PersistentEntity} that are captured * through Spring's {@code CglibClassHandler}. Otherwise, this is a no-op if contributions are not ran through * {@code CglibClassHandler}. * - * @param entity + * @param entityType */ - public void contribute(PersistentEntity entity) { - EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity); - if (instantiator instanceof PersistentEntityClassInitializer pec) { - pec.initialize(entity); + public void contribute(Class entityType) { + + BasicPersistentEntity entity = getPersistentEntity(entityType); + + if (entity != null) { + + EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity); + if (instantiator instanceof EntityInstantiatorSource source) { + source.getInstantiatorFor(entity); + } + + propertyAccessorFactory.initialize(entity); } - propertyAccessorFactory.initialize(entity); } - // TODO: can we extract some util for this using only type @Override - protected BasicPersistentEntity createPersistentEntity( + 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) { + protected AotPersistentProperty createPersistentProperty(Property property, + BasicPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { logger.info("creating property: " + property.getName()); - return new BasicPersistentProperty(property, owner, simpleTypeHolder); + return new AotPersistentProperty(property, owner, simpleTypeHolder); } - static class BasicPersistentProperty extends AnnotationBasedPersistentProperty { + static class AotPersistentProperty extends AnnotationBasedPersistentProperty { - public BasicPersistentProperty(Property property, PersistentEntity owner, + public AotPersistentProperty(Property property, PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { super(property, owner, simpleTypeHolder); } @@ -89,15 +94,22 @@ class AotMappingContext extends // TODO: hide this one and delegate to other com } @Override - protected Association createAssociation() { + protected Association createAssociation() { return new Association<>(this, null); } @Override - public Association getRequiredAssociation() { + public Association getAssociation() { return new Association<>(this, null); } } + static class AotAccessorFactory extends ClassGeneratingPropertyAccessorFactory { + + public void initialize(PersistentEntity entity) { + potentiallyCreateAndRegisterPersistentPropertyAccessorClass(entity); + } + } + } diff --git a/src/main/java/org/springframework/data/aot/AotTypeConfiguration.java b/src/main/java/org/springframework/data/aot/AotTypeConfiguration.java index 1dbc60a69..5510df924 100644 --- a/src/main/java/org/springframework/data/aot/AotTypeConfiguration.java +++ b/src/main/java/org/springframework/data/aot/AotTypeConfiguration.java @@ -39,10 +39,10 @@ public interface AotTypeConfiguration { AotTypeConfiguration forReflectiveAccess(MemberCategory... categories); - AotTypeConfiguration generateEntityInstantiator(); + AotTypeConfiguration contributeAccessors(); // TODO: ? should this be a global condition for the entire configuration or do we need it for certain aspects ? - AotTypeConfiguration conditional(Predicate filter); + AotTypeConfiguration filter(Predicate> filter); default AotTypeConfiguration usedAsProjectionInterface() { return proxyInterface(TargetAware.class, SpringProxy.class, DecoratingProxy.class); @@ -69,42 +69,12 @@ public interface AotTypeConfiguration { 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; - } - }; - } + void contribute(Environment environment, GenerationContext generationContext); } diff --git a/src/main/java/org/springframework/data/aot/DefaultAotContext.java b/src/main/java/org/springframework/data/aot/DefaultAotContext.java index a60f87af4..7d6bcc2f3 100644 --- a/src/main/java/org/springframework/data/aot/DefaultAotContext.java +++ b/src/main/java/org/springframework/data/aot/DefaultAotContext.java @@ -40,13 +40,14 @@ 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.core.env.Environment; +import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.util.QTypeContributor; import org.springframework.data.util.TypeContributor; +import org.springframework.util.AntPathMatcher; import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; /** * Default {@link AotContext} implementation. @@ -56,17 +57,16 @@ import org.springframework.util.ClassUtils; */ class DefaultAotContext implements AotContext { - private final AotMappingContext mappingContext = new AotMappingContext(); + 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; + private final Map, AotTypeConfiguration> typeConfigurations = new HashMap<>(); + private final Environment environment; public DefaultAotContext(BeanFactory beanFactory, Environment environment) { - factory = beanFactory instanceof ConfigurableListableBeanFactory cbf ? cbf + this.factory = beanFactory instanceof ConfigurableListableBeanFactory cbf ? cbf : new DefaultListableBeanFactory(beanFactory); this.environment = environment; } @@ -92,13 +92,8 @@ class DefaultAotContext implements AotContext { } @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)); + public void typeConfiguration(Class type, Consumer configurationConsumer) { + configurationConsumer.accept(typeConfigurations.computeIfAbsent(type, it -> new ContextualTypeConfiguration(type))); } @Override @@ -145,29 +140,6 @@ 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; @@ -210,15 +182,15 @@ class DefaultAotContext implements AotContext { class ContextualTypeConfiguration implements AotTypeConfiguration { - private final TypeReference type; + private final Class type; private boolean forDataBinding = false; private final Set categories = new HashSet<>(5); - private boolean generateEntityInstantiator = false; + private boolean contributeAccessors = false; private boolean forQuerydsl = false; private final List> proxies = new ArrayList<>(); - private Predicate filter; + private Predicate> filter; - ContextualTypeConfiguration(TypeReference type) { + ContextualTypeConfiguration(Class type) { this.type = type; } @@ -235,8 +207,8 @@ class DefaultAotContext implements AotContext { } @Override - public AotTypeConfiguration generateEntityInstantiator() { - this.generateEntityInstantiator = true; + public AotTypeConfiguration contributeAccessors() { + this.contributeAccessors = true; return this; } @@ -253,14 +225,14 @@ class DefaultAotContext implements AotContext { } @Override - public AotTypeConfiguration conditional(Predicate filter) { + public AotTypeConfiguration filter(Predicate> filter) { this.filter = filter; return this; } @Override - public void contribute(GenerationContext generationContext) { + public void contribute(Environment environment, GenerationContext generationContext) { if (filter != null && !filter.test(this.type)) { return; @@ -271,43 +243,70 @@ class DefaultAotContext implements AotContext { categories.toArray(MemberCategory[]::new)); } - if (generateEntityInstantiator) { - instantiationCreator(type).create(); + if (contributeAccessors) { + + boolean accessorsEnabled = environment.getProperty("spring.aot.data.accessors.enabled", Boolean.class, true); + String include = environment.getProperty("spring.aot.data.accessors.include", String.class, ""); + String exclude = environment.getProperty("spring.aot.data.accessors.exclude", String.class, ""); + + if (shouldContributeAccessors(type, accessorsEnabled, include, exclude)) { + mappingContext.contribute(type); + } } 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); - } + + TypeContributor.contribute(type, 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())); + QTypeContributor.contributeEntityPath(type, generationContext, factory.getBeanClassLoader()); } if (!proxies.isEmpty()) { for (List proxyInterfaces : proxies) { generationContext.getRuntimeHints().proxies() - .registerJdkProxy(Stream.concat(Stream.of(type), proxyInterfaces.stream()).toArray(TypeReference[]::new)); + .registerJdkProxy(Stream.concat(Stream.of(TypeReference.of(type)), proxyInterfaces.stream()) + .toArray(TypeReference[]::new)); } } } - private boolean doIfPresent(Consumer> consumer) { - if (!ClassUtils.isPresent(type.getName(), type.getClass().getClassLoader())) { + static boolean shouldContributeAccessors(Class type, boolean enabled, String include, String exclude) { + + if (!enabled) { return false; } - try { - Class resolved = ClassUtils.forName(type.getName(), type.getClass().getClassLoader()); - consumer.accept(resolved); - return true; - } catch (ClassNotFoundException e) { - return false; + + AntPathMatcher antPathMatcher = new AntPathMatcher("."); + + if (StringUtils.hasText(include)) { + + String[] includes = include.split(","); + + for (String includePattern : includes) { + if (antPathMatcher.match(includePattern.trim(), type.getName())) { + return true; + } + } } + + if (StringUtils.hasText(exclude)) { + + String[] excludes = exclude.split(","); + + for (String excludePattern : excludes) { + if (antPathMatcher.match(excludePattern.trim(), type.getName())) { + return false; + } + } + } + + return true; } } } diff --git a/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java b/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java index 78833f7a7..49e0f7475 100644 --- a/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java +++ b/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java @@ -53,8 +53,8 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio private final Log logger = LogFactory.getLog(getClass()); private @Nullable String moduleIdentifier; - private Lazy environment = Lazy.of(StandardEnvironment::new); private AotContext aotContext; + private Lazy environment = Lazy.of(StandardEnvironment::new); public void setModuleIdentifier(@Nullable String moduleIdentifier) { this.moduleIdentifier = moduleIdentifier; @@ -67,7 +67,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio @Override public void setEnvironment(Environment environment) { - this.environment = Lazy.of(() -> environment); + this.environment = Lazy.of(environment); } @Override @@ -144,10 +144,9 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio Set annotationNamespaces = Collections.singleton(TypeContributor.DATA_NAMESPACE); - aotContext.typeConfiguration(type).forDataBinding() // - .generateEntityInstantiator() // - .forQuerydsl() // - .contribute(generationContext); // + aotContext.typeConfiguration(type, config -> config.forDataBinding() // + .contributeAccessors() // + .forQuerydsl().contribute(environment.get(), generationContext)); TypeUtils.resolveUsedAnnotations(type.toClass()).forEach( annotation -> TypeContributor.contribute(annotation.getType(), annotationNamespaces, generationContext)); 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 e99edeacf..6640f925e 100644 --- a/src/main/java/org/springframework/data/mapping/context/ReflectionFallbackPersistentPropertyAccessorFactory.java +++ b/src/main/java/org/springframework/data/mapping/context/ReflectionFallbackPersistentPropertyAccessorFactory.java @@ -36,13 +36,9 @@ class ReflectionFallbackPersistentPropertyAccessorFactory implements PersistentP public PersistentPropertyAccessor getPropertyAccessor(PersistentEntity entity, T bean) { if (accessorFactory.isSupported(entity)) { - PersistentPropertyAccessor propertyAccessor = accessorFactory.getPropertyAccessor(entity, bean); - System.out.println("Accessor Factory: " + propertyAccessor.getClass().getName()); - return propertyAccessor; - + return accessorFactory.getPropertyAccessor(entity, bean); } - System.out.println("Fallback Accessor Factory :("); return BeanWrapperPropertyAccessorFactory.INSTANCE.getPropertyAccessor(entity, bean); } diff --git a/src/main/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiator.java b/src/main/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiator.java index 353336f81..589e225f7 100644 --- a/src/main/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiator.java +++ b/src/main/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiator.java @@ -60,7 +60,7 @@ import org.springframework.util.ClassUtils; * @author Mark Paluch * @since 1.11 */ -class ClassGeneratingEntityInstantiator implements EntityInstantiator, PersistentEntityClassInitializer { +class ClassGeneratingEntityInstantiator implements EntityInstantiator, EntityInstantiatorSource { private static final Log LOGGER = LogFactory.getLog(ClassGeneratingEntityInstantiator.class); @@ -87,11 +87,6 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator, Persisten this.fallbackToReflectionOnError = fallbackToReflectionOnError; } - @Override - public void initialize(PersistentEntity entity) { - getEntityInstantiator(entity); - } - @Override public , P extends PersistentProperty

> T createInstance(E entity, ParameterValueProvider

provider) { @@ -100,6 +95,11 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator, Persisten return instantiator.createInstance(entity, provider); } + @Override + public EntityInstantiator getInstantiatorFor(PersistentEntity entity) { + return getEntityInstantiator(entity); + } + private , P extends PersistentProperty

> EntityInstantiator getEntityInstantiator( E entity) { diff --git a/src/main/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactory.java b/src/main/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactory.java index 8c874d47f..39f102a30 100644 --- a/src/main/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactory.java +++ b/src/main/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactory.java @@ -77,7 +77,7 @@ import org.springframework.util.StringUtils; * @since 1.13 */ public class ClassGeneratingPropertyAccessorFactory - implements PersistentPropertyAccessorFactory, PersistentEntityClassInitializer { + implements PersistentPropertyAccessorFactory { // Pooling of parameter arrays to prevent excessive object allocation. private final ThreadLocal argumentCache = ThreadLocal.withInitial(() -> new Object[1]); @@ -89,11 +89,6 @@ public class ClassGeneratingPropertyAccessorFactory private final ConcurrentLruCache, Function<@Nullable Object, @Nullable Object>> wrapperCache = new ConcurrentLruCache<>( 256, KotlinValueBoxingAdapter::getWrapper); - @Override - public void initialize(PersistentEntity entity) { - getPropertyAccessorConstructor(entity); - } - @Override public PersistentPropertyAccessor getPropertyAccessor(PersistentEntity entity, T bean) { diff --git a/src/main/java/org/springframework/data/mapping/model/PersistentEntityClassInitializer.java b/src/main/java/org/springframework/data/mapping/model/EntityInstantiatorSource.java similarity index 65% rename from src/main/java/org/springframework/data/mapping/model/PersistentEntityClassInitializer.java rename to src/main/java/org/springframework/data/mapping/model/EntityInstantiatorSource.java index cdf025bd7..b43afd2f8 100644 --- a/src/main/java/org/springframework/data/mapping/model/PersistentEntityClassInitializer.java +++ b/src/main/java/org/springframework/data/mapping/model/EntityInstantiatorSource.java @@ -18,9 +18,19 @@ package org.springframework.data.mapping.model; import org.springframework.data.mapping.PersistentEntity; /** + * Interface declaring a source for {@link EntityInstantiator} objects. + * * @author Mark Paluch + * @since 4.0 */ -public interface PersistentEntityClassInitializer { +@FunctionalInterface +public interface EntityInstantiatorSource { + + /** + * Returns an {@link EntityInstantiator} for the given {@link PersistentEntity}. + * + * @return the {@link EntityInstantiator} for the given {@link PersistentEntity}. + */ + EntityInstantiator getInstantiatorFor(PersistentEntity entity); - void initialize(PersistentEntity entity); } diff --git a/src/main/java/org/springframework/data/mapping/model/EntityInstantiators.java b/src/main/java/org/springframework/data/mapping/model/EntityInstantiators.java index 4fd4f2cfa..71cfe042b 100644 --- a/src/main/java/org/springframework/data/mapping/model/EntityInstantiators.java +++ b/src/main/java/org/springframework/data/mapping/model/EntityInstantiators.java @@ -31,7 +31,7 @@ import org.springframework.util.Assert; * @author Mark Paluch * @since 2.3 */ -public class EntityInstantiators { +public class EntityInstantiators implements EntityInstantiatorSource { private final EntityInstantiator fallback; private final Map, EntityInstantiator> customInstantiators; @@ -84,6 +84,7 @@ public class EntityInstantiators { * @param entity must not be {@literal null}. * @return will never be {@literal null}. */ + @Override public EntityInstantiator getInstantiatorFor(PersistentEntity entity) { Assert.notNull(entity, "Entity must not be null"); 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 8541c5bb9..4c1d0e538 100644 --- a/src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java +++ b/src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java @@ -19,11 +19,10 @@ 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.function.Consumer; 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; @@ -151,13 +150,8 @@ class DefaultAotRepositoryContext implements AotRepositoryContext { } @Override - public InstantiationCreator instantiationCreator(TypeReference typeReference) { - return aotContext.instantiationCreator(typeReference); - } - - @Override - public AotTypeConfiguration typeConfiguration(TypeReference typeReference) { - return aotContext.typeConfiguration(typeReference); + public void typeConfiguration(Class type, Consumer configurationConsumer) { + aotContext.typeConfiguration(type, configurationConsumer); } @Override 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 d333cb350..cdebdfa1d 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java @@ -39,6 +39,9 @@ import org.springframework.beans.factory.aot.BeanRegistrationCode; import org.springframework.beans.factory.aot.BeanRegistrationCodeFragments; import org.springframework.beans.factory.aot.BeanRegistrationCodeFragmentsDecorator; import org.springframework.beans.factory.support.RegisteredBean; +import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.Environment; +import org.springframework.core.env.StandardEnvironment; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.DecoratingProxy; import org.springframework.core.annotation.AnnotationUtils; @@ -51,6 +54,7 @@ import org.springframework.data.repository.aot.generate.AotRepositoryBeanDefinit import org.springframework.data.repository.aot.generate.RepositoryContributor; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.support.RepositoryFragment; +import org.springframework.data.util.Lazy; import org.springframework.data.util.Predicates; import org.springframework.data.util.QTypeContributor; import org.springframework.data.util.TypeContributor; @@ -67,7 +71,7 @@ import org.springframework.util.ClassUtils; * @author Mark Paluch * @since 3.0 */ -public class RepositoryRegistrationAotContribution implements BeanRegistrationAotContribution { +public class RepositoryRegistrationAotContribution implements BeanRegistrationAotContribution, EnvironmentAware { private static final Log logger = LogFactory.getLog(RepositoryRegistrationAotContribution.class); @@ -77,7 +81,8 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo private final AotRepositoryContext repositoryContext; - private @Nullable RepositoryContributor repositoryContributor; + private @Nullable RepositoryContributor repositoryContributor; + private Lazy environment = Lazy.of(StandardEnvironment::new); private @Nullable BiFunction moduleContribution; @@ -213,6 +218,11 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo return this; } + @Override + public void setEnvironment(Environment environment) { + this.environment = Lazy.of(environment); + } + @Override public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) { @@ -227,6 +237,8 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo this.repositoryContributor.contribute(generationContext); } } + getRepositoryContext().typeConfigurations() + .forEach(typeConfiguration -> typeConfiguration.contribute(environment.get(), generationContext)); } @Override @@ -261,19 +273,18 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo logTrace("Contributing repository information for [%s]", repositoryInformation.getRepositoryInterface()); - repositoryContext.typeConfiguration(repositoryInformation.getRepositoryInterface()) - .forReflectiveAccess(MemberCategory.INVOKE_PUBLIC_METHODS) // - .repositoryProxy(); + repositoryContext.typeConfiguration(repositoryInformation.getRepositoryInterface(), + config -> config.forReflectiveAccess(MemberCategory.INVOKE_PUBLIC_METHODS).repositoryProxy()); - repositoryContext.typeConfiguration(repositoryInformation.getRepositoryBaseClass()) - .forReflectiveAccess(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS); + repositoryContext.typeConfiguration(repositoryInformation.getRepositoryBaseClass(), config -> config + .forReflectiveAccess(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS)); - repositoryContext.typeConfiguration(repositoryInformation.getDomainType()).forDataBinding().forQuerydsl(); + repositoryContext.typeConfiguration(repositoryInformation.getDomainType(), + config -> config.forDataBinding().forQuerydsl()); // TODO: purposeful api for uses cases to have some internal logic - repositoryContext.getUserDomainTypes().stream() // - .map(repositoryContext::typeConfiguration) // - .forEach(AotTypeConfiguration::generateEntityInstantiator); // + repositoryContext.getUserDomainTypes() // + .forEach(it -> repositoryContext.typeConfiguration(it, AotTypeConfiguration::contributeAccessors)); // Repository Fragments contributeFragments(contribution); @@ -288,7 +299,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo .filter(Class::isInterface).forEach(type -> { if (EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy().test(type, repositoryInformation.getDomainType())) { - repositoryContext.typeConfiguration(type).usedAsProjectionInterface(); + repositoryContext.typeConfiguration(type, AotTypeConfiguration::usedAsProjectionInterface); } }); } @@ -297,7 +308,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo for (RepositoryFragment fragment : getRepositoryInformation().getFragments()) { Class repositoryFragmentType = fragment.getSignatureContributor(); - Optional> implementation = fragment.getImplementationClass(); + Optional> implementation = fragment.getImplementationClass(); contribution.getRuntimeHints().reflection().registerType(repositoryFragmentType, hint -> { 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 5a66753af..108b44fd8 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java @@ -28,7 +28,6 @@ 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; @@ -42,6 +41,7 @@ import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.EnvironmentAware; import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.data.aot.AotTypeConfiguration; import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.env.StandardEnvironment; @@ -126,7 +126,7 @@ public class RepositoryRegistrationAotProcessor // arent we already registering the types in RepositoryRegistrationAotContribution#contributeRepositoryInfo? registrar.registerRuntimeHints(hints, it); - repositoryContext.instantiationCreator(TypeReference.of(it)).create(); + repositoryContext.typeConfiguration(it, AotTypeConfiguration::contributeAccessors); }); } diff --git a/src/test/java/org/springframework/data/aot/AotContextUnitTests.java b/src/test/java/org/springframework/data/aot/AotContextUnitTests.java index 635b3a814..2ece7e020 100644 --- a/src/test/java/org/springframework/data/aot/AotContextUnitTests.java +++ b/src/test/java/org/springframework/data/aot/AotContextUnitTests.java @@ -15,96 +15,164 @@ */ package org.springframework.data.aot; +import static org.mockito.Mockito.*; + import java.util.Collection; import java.util.List; +import java.util.function.Consumer; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoSettings; + import org.springframework.aot.hint.TypeReference; +import org.springframework.aot.test.generate.TestGenerationContext; +import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.core.env.Environment; +import org.springframework.data.aot.types.Address; +import org.springframework.data.aot.types.Customer; +import org.springframework.data.aot.types.EmptyType1; import org.springframework.mock.env.MockEnvironment; import org.springframework.util.StringUtils; /** - * Tests for {@link AotContext}. + * Unit tests for {@link AotContext}. + * * + * @author Mark Paluch * @author Christoph Strobl */ +@MockitoSettings(strictness = org.mockito.quality.Strictness.LENIENT) class AotContextUnitTests { - @ParameterizedTest // GH-3322 - @CsvSource({ // - "'spring.aot.repositories.enabled', '', '', '', true", // - "'spring.aot.repositories.enabled', 'true', '', '', true", // - "'spring.aot.repositories.enabled', 'false', '', '', false", // - "'spring.aot.repositories.enabled', '', 'commons', 'true', true", // - "'spring.aot.repositories.enabled', 'true', 'commons', 'true', true", // - "'spring.aot.repositories.enabled', '', 'commons', 'false', false", // - "'spring.aot.repositories.enabled', 'false', 'commons', 'true', false" // - }) - void considersEnvironmentSettingsForGeneratedRepositories(String generalFlag, String generalValue, String storeName, - String storeValue, boolean enabled) { - - MockAotContext ctx = new MockAotContext(); - if (StringUtils.hasText(generalFlag) && StringUtils.hasText(generalValue)) { - ctx.withProperty(generalFlag, generalValue); - } - if (StringUtils.hasText(storeName) && StringUtils.hasText(storeValue)) { - ctx.withProperty("spring.aot.%s.repositories.enabled".formatted(storeName), storeValue); - } - - Assertions.assertThat(ctx.isGeneratedRepositoriesEnabled(storeName)).isEqualTo(enabled); - } - - static class MockAotContext implements AotContext { - - private final MockEnvironment environment; - - public MockAotContext() { - this.environment = new MockEnvironment(); - } - - MockAotContext withProperty(String key, String value) { - environment.setProperty(key, value); - return this; - } - - @Override - public ConfigurableListableBeanFactory getBeanFactory() { - return Mockito.mock(ConfigurableListableBeanFactory.class); - } - - @Override - public TypeIntrospector introspectType(String typeName) { - return Mockito.mock(TypeIntrospector.class); - } - - @Override - public IntrospectedBeanDefinition introspectBeanDefinition(String beanName) { - 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; - } - } + @Mock BeanFactory beanFactory; + + @Mock AotMappingContext mappingContext; + + MockEnvironment mockEnvironment = new MockEnvironment(); + + @Test // GH-2595 + void shouldContributeAccessorByDefault() { + + contributeAccessor(Address.class); + verify(mappingContext).contribute(Address.class); + } + + @Test // GH-2595 + void shouldConsiderDisabledAccessors() { + + mockEnvironment.setProperty("spring.aot.data.accessors.enabled", "false"); + + contributeAccessor(Address.class); + + verifyNoInteractions(mappingContext); + } + + @Test // GH-2595 + void shouldApplyExcludeFilters() { + + mockEnvironment.setProperty("spring.aot.data.accessors.exclude", + Customer.class.getName() + " , " + EmptyType1.class.getName()); + + contributeAccessor(Address.class, Customer.class, EmptyType1.class); + + verify(mappingContext).contribute(Address.class); + verifyNoMoreInteractions(mappingContext); + } + + @Test // GH-2595 + void shouldApplyIncludeExcludeFilters() { + + mockEnvironment.setProperty("spring.aot.data.accessors.include", Customer.class.getPackageName() + ".Add*"); + mockEnvironment.setProperty("spring.aot.data.accessors.exclude", Customer.class.getPackageName() + ".**"); + + contributeAccessor(Address.class, Customer.class, EmptyType1.class); + + verify(mappingContext).contribute(Address.class); + verifyNoMoreInteractions(mappingContext); + } + + private void contributeAccessor(Class... classes) { + + DefaultAotContext context = new DefaultAotContext(beanFactory, mockEnvironment); + + for (Class aClass : classes) { + context.typeConfiguration(aClass, AotTypeConfiguration::contributeAccessors); + } + + context.typeConfigurations().forEach(it -> it.contribute(mockEnvironment, new TestGenerationContext())); + } + + @ParameterizedTest // GH-3322 + @CsvSource({ // + "'spring.aot.repositories.enabled', '', '', '', true", // + "'spring.aot.repositories.enabled', 'true', '', '', true", // + "'spring.aot.repositories.enabled', 'false', '', '', false", // + "'spring.aot.repositories.enabled', '', 'commons', 'true', true", // + "'spring.aot.repositories.enabled', 'true', 'commons', 'true', true", // + "'spring.aot.repositories.enabled', '', 'commons', 'false', false", // + "'spring.aot.repositories.enabled', 'false', 'commons', 'true', false" // + }) + void considersEnvironmentSettingsForGeneratedRepositories(String generalFlag, String generalValue, String storeName, + String storeValue, boolean enabled) { + + MockAotContext ctx = new MockAotContext(); + if (StringUtils.hasText(generalFlag) && StringUtils.hasText(generalValue)) { + ctx.withProperty(generalFlag, generalValue); + } + if (StringUtils.hasText(storeName) && StringUtils.hasText(storeValue)) { + ctx.withProperty("spring.aot.%s.repositories.enabled".formatted(storeName), storeValue); + } + + Assertions.assertThat(ctx.isGeneratedRepositoriesEnabled(storeName)).isEqualTo(enabled); + } + + static class MockAotContext implements AotContext { + + private final MockEnvironment environment; + + public MockAotContext() { + this.environment = new MockEnvironment(); + } + + MockAotContext withProperty(String key, String value) { + environment.setProperty(key, value); + return this; + } + + @Override + public ConfigurableListableBeanFactory getBeanFactory() { + return Mockito.mock(ConfigurableListableBeanFactory.class); + } + + @Override + public TypeIntrospector introspectType(String typeName) { + return Mockito.mock(TypeIntrospector.class); + } + + @Override + public IntrospectedBeanDefinition introspectBeanDefinition(String beanName) { + return Mockito.mock(IntrospectedBeanDefinition.class); + } + + @Override + public void typeConfiguration(Class type, Consumer configurationConsumer) { + + } + + @Override + public Collection typeConfigurations() { + return List.of(); + } + + @Override + public Environment getEnvironment() { + return environment; + } + } } 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 297c95335..7e4e0747b 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 @@ -20,10 +20,9 @@ import java.lang.annotation.Annotation; import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.function.Consumer; 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; @@ -80,13 +79,8 @@ class DummyModuleAotRepositoryContext implements AotRepositoryContext { } @Override - public InstantiationCreator instantiationCreator(TypeReference typeReference) { - return null; - } + public void typeConfiguration(Class type, Consumer configurationConsumer) { - @Override - public AotTypeConfiguration typeConfiguration(TypeReference typeReference) { - return null; } @Override