Browse Source

Polishing.

Move off TypeReference for known and loaded classes for easier handling. Introduce configuration for enabled and include/exclude filters. Refactor configuration to functional style.

Original Pull Request: #3318
pull/3357/head
Mark Paluch 4 months ago committed by Christoph Strobl
parent
commit
8eec269324
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 37
      src/main/java/org/springframework/data/aot/AotContext.java
  2. 52
      src/main/java/org/springframework/data/aot/AotMappingContext.java
  3. 36
      src/main/java/org/springframework/data/aot/AotTypeConfiguration.java
  4. 127
      src/main/java/org/springframework/data/aot/DefaultAotContext.java
  5. 11
      src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java
  6. 6
      src/main/java/org/springframework/data/mapping/context/ReflectionFallbackPersistentPropertyAccessorFactory.java
  7. 12
      src/main/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiator.java
  8. 7
      src/main/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactory.java
  9. 14
      src/main/java/org/springframework/data/mapping/model/EntityInstantiatorSource.java
  10. 3
      src/main/java/org/springframework/data/mapping/model/EntityInstantiators.java
  11. 12
      src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java
  12. 37
      src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java
  13. 4
      src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java
  14. 214
      src/test/java/org/springframework/data/aot/AotContextUnitTests.java
  15. 10
      src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java

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

@ -22,7 +22,6 @@ import java.util.Locale; @@ -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 { @@ -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<AotTypeConfiguration> 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<AotTypeConfiguration> configurationConsumer);
/**
* Return all type configurations registered with this {@link AotContext}.
*
* @return all type configurations registered with this {@link AotContext}.
*/
Collection<AotTypeConfiguration> typeConfigurations();
/**
@ -358,9 +370,4 @@ public interface AotContext extends EnvironmentCapable { @@ -358,9 +370,4 @@ public interface AotContext extends EnvironmentCapable {
Class<?> resolveType();
}
interface InstantiationCreator {
boolean isAvailable();
void create();
}
}

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

@ -24,8 +24,8 @@ import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; @@ -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; @@ -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<BasicPersistentEntity<?, AotMappingContext.BasicPersistentProperty>, AotMappingContext.BasicPersistentProperty> {
class AotMappingContext extends
AbstractMappingContext<BasicPersistentEntity<?, AotMappingContext.AotPersistentProperty>, 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<?, AotPersistentProperty> 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 <T> BasicPersistentEntity<?, BasicPersistentProperty> createPersistentEntity(
protected <T> BasicPersistentEntity<?, AotPersistentProperty> createPersistentEntity(
TypeInformation<T> typeInformation) {
logger.debug("I hate gradle: create persistent entity for type: " + typeInformation);
return new BasicPersistentEntity<>(typeInformation);
}
@Override
protected BasicPersistentProperty createPersistentProperty(Property property,
BasicPersistentEntity<?, BasicPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
protected AotPersistentProperty createPersistentProperty(Property property,
BasicPersistentEntity<?, AotPersistentProperty> 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<BasicPersistentProperty> {
static class AotPersistentProperty extends AnnotationBasedPersistentProperty<AotPersistentProperty> {
public BasicPersistentProperty(Property property, PersistentEntity<?, BasicPersistentProperty> owner,
public AotPersistentProperty(Property property, PersistentEntity<?, AotPersistentProperty> owner,
SimpleTypeHolder simpleTypeHolder) {
super(property, owner, simpleTypeHolder);
}
@ -89,15 +94,22 @@ class AotMappingContext extends // TODO: hide this one and delegate to other com @@ -89,15 +94,22 @@ class AotMappingContext extends // TODO: hide this one and delegate to other com
}
@Override
protected Association<BasicPersistentProperty> createAssociation() {
protected Association<AotPersistentProperty> createAssociation() {
return new Association<>(this, null);
}
@Override
public Association<BasicPersistentProperty> getRequiredAssociation() {
public Association<AotPersistentProperty> getAssociation() {
return new Association<>(this, null);
}
}
static class AotAccessorFactory extends ClassGeneratingPropertyAccessorFactory {
public void initialize(PersistentEntity<?, ?> entity) {
potentiallyCreateAndRegisterPersistentPropertyAccessorClass(entity);
}
}
}

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

@ -39,10 +39,10 @@ public interface AotTypeConfiguration { @@ -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<TypeReference> filter);
AotTypeConfiguration filter(Predicate<Class<?>> filter);
default AotTypeConfiguration usedAsProjectionInterface() {
return proxyInterface(TargetAware.class, SpringProxy.class, DecoratingProxy.class);
@ -69,42 +69,12 @@ public interface AotTypeConfiguration { @@ -69,42 +69,12 @@ public interface AotTypeConfiguration {
AotTypeConfiguration proxyInterface(List<TypeReference> proxyInterfaces);
default AotTypeConfiguration proxyInterface(TypeReference... proxyInterfaces) {
return proxyInterface(List.of(proxyInterfaces));
}
default AotTypeConfiguration proxyInterface(Class<?>... proxyInterfaces) {
return proxyInterface(Stream.of(proxyInterfaces).map(TypeReference::of).toList());
}
AotTypeConfiguration forQuerydsl();
void contribute(GenerationContext generationContext);
static Predicate<TypeReference> userConfiguredCondition(Environment environment) {
return new Predicate<TypeReference>() {
private final List<String> allowedAccessorTypes = environment.getProperty("spring.data.aot.generate.accessor",
List.class, List.of());
@Override
@SuppressWarnings("unchecked")
public boolean test(TypeReference typeReference) {
if (!allowedAccessorTypes.isEmpty()) {
if (allowedAccessorTypes.contains("none") || allowedAccessorTypes.contains("false")
|| allowedAccessorTypes.contains("off")) {
return false;
}
if (!allowedAccessorTypes.contains(typeReference.getName())) {
return false;
}
}
return true;
}
};
}
void contribute(Environment environment, GenerationContext generationContext);
}

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

@ -40,13 +40,14 @@ import org.springframework.beans.factory.config.BeanDefinition; @@ -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; @@ -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<TypeReference, AotTypeConfiguration> typeConfigurations = new HashMap<>();
private final Environment environment;
private final Map<Class<?>, 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 { @@ -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<AotTypeConfiguration> configurationConsumer) {
configurationConsumer.accept(typeConfigurations.computeIfAbsent(type, it -> new ContextualTypeConfiguration(type)));
}
@Override
@ -145,29 +140,6 @@ class DefaultAotContext implements AotContext { @@ -145,29 +140,6 @@ class DefaultAotContext implements AotContext {
}
}
class DefaultInstantiationCreator implements InstantiationCreator {
Lazy<BasicPersistentEntity<?, BasicPersistentProperty>> entity;
public DefaultInstantiationCreator(TypeIntrospector typeIntrospector) {
this.entity = Lazy.of(() -> mappingContext.getPersistentEntity(typeIntrospector.resolveRequiredType()));
}
@Override
public boolean isAvailable() {
return entity.getNullable() != null;
}
@Override
public void create() {
BasicPersistentEntity<?, BasicPersistentProperty> persistentEntity = entity.getNullable();
if (persistentEntity != null) {
mappingContext.contribute(persistentEntity);
}
}
}
class DefaultIntrospectedBeanDefinition implements IntrospectedBeanDefinition {
private final String beanName;
@ -210,15 +182,15 @@ class DefaultAotContext implements AotContext { @@ -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<MemberCategory> categories = new HashSet<>(5);
private boolean generateEntityInstantiator = false;
private boolean contributeAccessors = false;
private boolean forQuerydsl = false;
private final List<List<TypeReference>> proxies = new ArrayList<>();
private Predicate<TypeReference> filter;
private Predicate<Class<?>> filter;
ContextualTypeConfiguration(TypeReference type) {
ContextualTypeConfiguration(Class<?> type) {
this.type = type;
}
@ -235,8 +207,8 @@ class DefaultAotContext implements AotContext { @@ -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 { @@ -253,14 +225,14 @@ class DefaultAotContext implements AotContext {
}
@Override
public AotTypeConfiguration conditional(Predicate<TypeReference> filter) {
public AotTypeConfiguration filter(Predicate<Class<?>> 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 { @@ -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<TypeReference> 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<Class<?>> 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;
}
}
}

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

@ -53,8 +53,8 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio @@ -53,8 +53,8 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
private final Log logger = LogFactory.getLog(getClass());
private @Nullable String moduleIdentifier;
private Lazy<Environment> environment = Lazy.of(StandardEnvironment::new);
private AotContext aotContext;
private Lazy<Environment> environment = Lazy.of(StandardEnvironment::new);
public void setModuleIdentifier(@Nullable String moduleIdentifier) {
this.moduleIdentifier = moduleIdentifier;
@ -67,7 +67,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio @@ -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 @@ -144,10 +144,9 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
Set<String> 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));

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

@ -36,13 +36,9 @@ class ReflectionFallbackPersistentPropertyAccessorFactory implements PersistentP @@ -36,13 +36,9 @@ class ReflectionFallbackPersistentPropertyAccessorFactory implements PersistentP
public <T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<?, ?> entity, T bean) {
if (accessorFactory.isSupported(entity)) {
PersistentPropertyAccessor<T> 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);
}

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

@ -60,7 +60,7 @@ import org.springframework.util.ClassUtils; @@ -60,7 +60,7 @@ import org.springframework.util.ClassUtils;
* @author Mark Paluch
* @since 1.11
*/
class ClassGeneratingEntityInstantiator implements EntityInstantiator, PersistentEntityClassInitializer {
class ClassGeneratingEntityInstantiator implements EntityInstantiator, EntityInstantiatorSource {
private static final Log LOGGER = LogFactory.getLog(ClassGeneratingEntityInstantiator.class);
@ -87,11 +87,6 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator, Persisten @@ -87,11 +87,6 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator, Persisten
this.fallbackToReflectionOnError = fallbackToReflectionOnError;
}
@Override
public void initialize(PersistentEntity<?, ?> entity) {
getEntityInstantiator(entity);
}
@Override
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
ParameterValueProvider<P> provider) {
@ -100,6 +95,11 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator, Persisten @@ -100,6 +95,11 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator, Persisten
return instantiator.createInstance(entity, provider);
}
@Override
public EntityInstantiator getInstantiatorFor(PersistentEntity<?, ?> entity) {
return getEntityInstantiator(entity);
}
private <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> EntityInstantiator getEntityInstantiator(
E entity) {

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

@ -77,7 +77,7 @@ import org.springframework.util.StringUtils; @@ -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<Object[]> argumentCache = ThreadLocal.withInitial(() -> new Object[1]);
@ -89,11 +89,6 @@ public class ClassGeneratingPropertyAccessorFactory @@ -89,11 +89,6 @@ public class ClassGeneratingPropertyAccessorFactory
private final ConcurrentLruCache<PersistentProperty<?>, Function<@Nullable Object, @Nullable Object>> wrapperCache = new ConcurrentLruCache<>(
256, KotlinValueBoxingAdapter::getWrapper);
@Override
public void initialize(PersistentEntity<?, ?> entity) {
getPropertyAccessorConstructor(entity);
}
@Override
public <T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<?, ?> entity, T bean) {

14
src/main/java/org/springframework/data/mapping/model/PersistentEntityClassInitializer.java → src/main/java/org/springframework/data/mapping/model/EntityInstantiatorSource.java

@ -18,9 +18,19 @@ package org.springframework.data.mapping.model; @@ -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);
}

3
src/main/java/org/springframework/data/mapping/model/EntityInstantiators.java

@ -31,7 +31,7 @@ import org.springframework.util.Assert; @@ -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<Class<?>, EntityInstantiator> customInstantiators;
@ -84,6 +84,7 @@ public class EntityInstantiators { @@ -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");

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

@ -19,11 +19,10 @@ import java.lang.annotation.Annotation; @@ -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 { @@ -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<AotTypeConfiguration> configurationConsumer) {
aotContext.typeConfiguration(type, configurationConsumer);
}
@Override

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

@ -39,6 +39,9 @@ import org.springframework.beans.factory.aot.BeanRegistrationCode; @@ -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 @@ -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; @@ -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 @@ -77,7 +81,8 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
private final AotRepositoryContext repositoryContext;
private @Nullable RepositoryContributor repositoryContributor;
private @Nullable RepositoryContributor repositoryContributor;
private Lazy<Environment> environment = Lazy.of(StandardEnvironment::new);
private @Nullable BiFunction<AotRepositoryContext, GenerationContext, @Nullable RepositoryContributor> moduleContribution;
@ -213,6 +218,11 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -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 @@ -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 @@ -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 @@ -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 @@ -297,7 +308,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
for (RepositoryFragment<?> fragment : getRepositoryInformation().getFragments()) {
Class<?> repositoryFragmentType = fragment.getSignatureContributor();
Optional<Class<?>> implementation = fragment.getImplementationClass();
Optional<Class<?>> implementation = fragment.getImplementationClass();
contribution.getRuntimeHints().reflection().registerType(repositoryFragmentType, hint -> {

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

@ -28,7 +28,6 @@ import org.jspecify.annotations.Nullable; @@ -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; @@ -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 @@ -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);
});
}

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

@ -15,96 +15,164 @@ @@ -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<AotTypeConfiguration> 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<AotTypeConfiguration> configurationConsumer) {
}
@Override
public Collection<AotTypeConfiguration> typeConfigurations() {
return List.of();
}
@Override
public Environment getEnvironment() {
return environment;
}
}
}

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

@ -20,10 +20,9 @@ import java.lang.annotation.Annotation; @@ -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 { @@ -80,13 +79,8 @@ class DummyModuleAotRepositoryContext implements AotRepositoryContext {
}
@Override
public InstantiationCreator instantiationCreator(TypeReference typeReference) {
return null;
}
public void typeConfiguration(Class<?> type, Consumer<AotTypeConfiguration> configurationConsumer) {
@Override
public AotTypeConfiguration typeConfiguration(TypeReference typeReference) {
return null;
}
@Override

Loading…
Cancel
Save