Browse Source

Update Documentation.

And fix some minor formatting issues.

Original Pull Request: #3318

move
pull/3357/head
Christoph Strobl 3 months ago
parent
commit
6ec81fdbbc
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 1
      src/main/antora/modules/ROOT/nav.adoc
  2. 71
      src/main/antora/modules/ROOT/pages/aot.adoc
  3. 1
      src/main/antora/modules/ROOT/pages/repositories/custom-implementations.adoc
  4. 1
      src/main/java/org/springframework/data/aot/AotContext.java
  5. 3
      src/main/java/org/springframework/data/aot/AotMappingContext.java
  6. 78
      src/main/java/org/springframework/data/aot/AotTypeConfiguration.java
  7. 38
      src/main/java/org/springframework/data/aot/DefaultAotContext.java
  8. 12
      src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java
  9. 16
      src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java
  10. 4
      src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecorator.java
  11. 3
      src/main/java/org/springframework/data/repository/aot/generate/RepositoryConstructorBuilder.java
  12. 18
      src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java
  13. 10
      src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java
  14. 185
      src/test/java/org/springframework/data/aot/AotContextUnitTests.java
  15. 26
      src/test/java/org/springframework/data/projection/ProjectionIntegrationTests.java
  16. 1
      src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotContributionAssert.java
  17. 23
      src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessorIntegrationTests.java

1
src/main/antora/modules/ROOT/nav.adoc

@ -21,6 +21,7 @@ @@ -21,6 +21,7 @@
* xref:custom-conversions.adoc[]
* xref:entity-callbacks.adoc[]
* xref:is-new-state-detection.adoc[]
* xref:aot.adoc[]
* xref:kotlin.adoc[]
** xref:kotlin/requirements.adoc[]
** xref:kotlin/null-safety.adoc[]

71
src/main/antora/modules/ROOT/pages/aot.adoc

@ -0,0 +1,71 @@ @@ -0,0 +1,71 @@
= Ahead of Time Optimizations
This chapter covers Spring Data's Ahead of Time (AOT) optimizations that build upon {spring-framework-docs}/core/aot.html[Spring's Ahead of Time Optimizations].
[[aot.bestpractices]]
== Best Practices
=== Annotate your Domain Types
During application startup, Spring scans the classpath for domain classes for early processing of entities.
By annotating your domain types with Spring Data Store specific `@Table`, `@Document` or `@Entity` annotations you can aid initial entity scanning and ensure that those types are registered with `ManagedTypes` for Runtime Hints.
Classpath scanning is not possible in native image arrangements and so Spring has to use `ManagedTypes` for the initial entity set.
[[aot.code-gen]]
== Ahead of Time Code Generation
Ahead of time code generation is not limited to usage with GraalVM Native Image but also offers benefits when working with regular deployments and can help optimize startup performance on the jvm.
If Ahead of Time compilation is enabled Spring Data can (depending on the actual Module in use) contribute several components during the AOT phase of your build.
* Bytecode for generated Type/Property Accessors
* Sourcecode for the defined Repository Interfaces
* Repository Metadata in JSON format
Each of the above is enabled by default.
However there users may fine tune the configuration with following options.
[options = "autowidth",cols="1,1"]
|===
|spring.aot.data.accessors.enabled
|boolean flag to control contribution of Bytecode for generated Type/Property Accessors
|spring.aot.data.accessors.exclude
|comma separated list of FQCN for which to skip contribution of Bytecode for generated Type/Property Accessors
|spring.aot.data.accessors.include
|comma separated list of FQCN for which to contribute Bytecode for generated Type/Property Accessors
|spring.aot.repositories.enabled
|boolean flag to control contribution of Source Code for Repository Interfaces
|spring.aot.[module-name].repositories.enabled
|boolean flag to control contribution of Source Code for Repository Interfaces for a certain module (eg. jdbc)
|===
[[aot.repositories]]
== Ahead of Time Repositories
AOT Repositories are an extension to AOT processing by pre-generating eligible query method implementations.
Query methods are opaque to developers regarding their underlying queries being executed in a query method call.
AOT repositories contribute query method implementations based on derived, annotated, and named queries that are known at build-time.
This optimization moves query method processing from runtime to build-time, which can lead to a significant performance improvement as query methods do not need to be analyzed reflectively upon each application start.
The resulting AOT repository fragment follows the naming scheme of `<Repository FQCN>Impl_AotRepository` and is placed in the same package as the repository interface.
[[aot.hints]]
== Native Image Runtime Hints
Running an application as a native image requires additional information compared to a regular JVM runtime.
Spring Data contributes {spring-framework-docs}/core/aot.html#aot.hints[Runtime Hints] during AOT processing for native image usage.
These are in particular hints for:
* Auditing
* `ManagedTypes` to capture the outcome of class-path scans
* Repositories
** Reflection hints for entities, return types, and Spring Data annotations
** Repository fragments
** Querydsl `Q` classes
** Kotlin Coroutine support
* Web support (Jackson Hints for `PagedModel`)

1
src/main/antora/modules/ROOT/pages/repositories/custom-implementations.adoc

@ -362,7 +362,6 @@ The `exposeMetadata` flag can be set directly on the repository factory bean via @@ -362,7 +362,6 @@ The `exposeMetadata` flag can be set directly on the repository factory bean via
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.lang.Nullable;
@Configuration
class MyConfiguration {

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

@ -24,7 +24,6 @@ import java.util.Set; @@ -24,7 +24,6 @@ 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.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;

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

@ -28,7 +28,6 @@ import org.springframework.data.mapping.model.EntityInstantiatorSource; @@ -28,7 +28,6 @@ import org.springframework.data.mapping.model.EntityInstantiatorSource;
import org.springframework.data.mapping.model.EntityInstantiators;
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;
/**
@ -70,14 +69,12 @@ class AotMappingContext extends @@ -70,14 +69,12 @@ class AotMappingContext extends
@Override
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 AotPersistentProperty createPersistentProperty(Property property,
BasicPersistentEntity<?, AotPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
logger.info("creating property: " + property.getName());
return new AotPersistentProperty(property, owner, simpleTypeHolder);
}

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

@ -1,11 +1,11 @@ @@ -1,11 +1,11 @@
/*
* Copyright 2025. the original author or authors.
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@ -18,7 +18,6 @@ package org.springframework.data.aot; @@ -18,7 +18,6 @@ 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;
@ -31,27 +30,70 @@ import org.springframework.core.env.Environment; @@ -31,27 +30,70 @@ import org.springframework.core.env.Environment;
import org.springframework.data.projection.TargetAware;
/**
* Configuration object that captures various AOT configuration aspects of types within the data context by offering
* predefined methods to register native configuration necessary for data binding, projection proxy definitions, AOT
* cglib bytecode generation and other common tasks.
* <p>
* On {@link #contribute(Environment, GenerationContext)} the configuration is added to the {@link GenerationContext}.
*
* @author Christoph Strobl
* @since 4.0
*/
public interface AotTypeConfiguration {
/**
* Configure the referenced type for data binding. In case of {@link java.lang.annotation.Annotation} only data ones
* are considered. For more fine grained control use {@link #forReflectiveAccess(MemberCategory...)}.
*
* @return this.
*/
AotTypeConfiguration forDataBinding();
/**
* Configure the referenced type for reflective access by providing at least one {@link MemberCategory}.
*
* @param categories must not contain {@literal null}.
* @return this.
*/
AotTypeConfiguration forReflectiveAccess(MemberCategory... categories);
/**
* Contribute generated cglib accessors for the referenced type.
* <p>
* Can be disabled by user configuration ({@code spring.aot.data.accessors.enabled}). Honors in/exclusions set by user
* configuration {@code spring.aot.data.accessors.include} / {@code spring.aot.data.accessors.exclude}
*
* @return this.
*/
AotTypeConfiguration contributeAccessors();
// TODO: ? should this be a global condition for the entire configuration or do we need it for certain aspects ?
AotTypeConfiguration filter(Predicate<Class<?>> filter);
/**
* Configure the referenced type as a projection interface returned by eg. a query method.
* <p>
* Shortcut for {@link #proxyInterface(Class[]) proxyInterface(TargetAware, SpringProxy, DecoratingProxy)}
*
* @return this.
*/
default AotTypeConfiguration usedAsProjectionInterface() {
return proxyInterface(TargetAware.class, SpringProxy.class, DecoratingProxy.class);
}
/**
* Configure the referenced type as a spring proxy interface.
* <p>
* Shortcut for {@link #proxyInterface(Class[]) proxyInterface(SpringProxy, Advised, DecoratingProxy)}
*
* @return this.
*/
default AotTypeConfiguration springProxy() {
return proxyInterface(SpringProxy.class, Advised.class, DecoratingProxy.class);
}
/**
* Configure the referenced type as a repository proxy.
*
* @return this.
*/
default AotTypeConfiguration repositoryProxy() {
springProxy();
@ -67,14 +109,36 @@ public interface AotTypeConfiguration { @@ -67,14 +109,36 @@ public interface AotTypeConfiguration {
return this;
}
/**
* Register a proxy for the referenced type that also implements the given proxyInterfaces.
*
* @param proxyInterfaces additional interfaces the proxy implements. Order matters!
* @return this.
*/
AotTypeConfiguration proxyInterface(List<TypeReference> proxyInterfaces);
/**
* Register a proxy for the referenced type that also implements the given proxyInterfaces.
*
* @param proxyInterfaces additional interfaces the proxy implements. Order matters!
* @return this.
*/
default AotTypeConfiguration proxyInterface(Class<?>... proxyInterfaces) {
return proxyInterface(Stream.of(proxyInterfaces).map(TypeReference::of).toList());
}
/**
* Configure the referenced type for usage with Querydsl by registering hints for potential {@code Q} types.
*
* @return this.
*/
AotTypeConfiguration forQuerydsl();
/**
* Write the configuration to the given {@link GenerationContext}.
*
* @param environment must not be {@literal null}.
* @param generationContext must not be {@literal null}.
*/
void contribute(Environment environment, GenerationContext generationContext);
}

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

@ -26,11 +26,9 @@ import java.util.Map; @@ -26,11 +26,9 @@ 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;
@ -41,8 +39,6 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -41,8 +39,6 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
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;
@ -53,22 +49,27 @@ import org.springframework.util.StringUtils; @@ -53,22 +49,27 @@ import org.springframework.util.StringUtils;
* Default {@link AotContext} implementation.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 3.0
*/
class DefaultAotContext implements AotContext {
private final AotMappingContext mappingContext = new AotMappingContext();;
private final AotMappingContext mappingContext;
private final ConfigurableListableBeanFactory factory;
// TODO: should we reuse the config or potentially have multiple ones with different settings - somehow targets the
// filtering issue
// TODO: should we reuse the config or potentially have multiple ones with different settings for the same type
private final Map<Class<?>, AotTypeConfiguration> typeConfigurations = new HashMap<>();
private final Environment environment;
private final Environment environment;
public DefaultAotContext(BeanFactory beanFactory, Environment environment) {
this(beanFactory, environment, new AotMappingContext());
}
DefaultAotContext(BeanFactory beanFactory, Environment environment, AotMappingContext mappingContext) {
this.factory = beanFactory instanceof ConfigurableListableBeanFactory cbf ? cbf
: new DefaultListableBeanFactory(beanFactory);
this.environment = environment;
this.mappingContext = mappingContext;
}
@Override
@ -188,7 +189,6 @@ class DefaultAotContext implements AotContext { @@ -188,7 +189,6 @@ class DefaultAotContext implements AotContext {
private boolean contributeAccessors = false;
private boolean forQuerydsl = false;
private final List<List<TypeReference>> proxies = new ArrayList<>();
private Predicate<Class<?>> filter;
ContextualTypeConfiguration(Class<?> type) {
this.type = type;
@ -224,20 +224,9 @@ class DefaultAotContext implements AotContext { @@ -224,20 +224,9 @@ class DefaultAotContext implements AotContext {
return this;
}
@Override
public AotTypeConfiguration filter(Predicate<Class<?>> filter) {
this.filter = filter;
return this;
}
@Override
public void contribute(Environment environment, GenerationContext generationContext) {
if (filter != null && !filter.test(this.type)) {
return;
}
if (!this.categories.isEmpty()) {
generationContext.getRuntimeHints().reflection().registerType(this.type,
categories.toArray(MemberCategory[]::new));
@ -255,11 +244,7 @@ class DefaultAotContext implements AotContext { @@ -255,11 +244,7 @@ class DefaultAotContext implements AotContext {
}
if (forDataBinding) {
TypeContributor.contribute(type, Set.of(TypeContributor.DATA_NAMESPACE), generationContext);
generationContext.getRuntimeHints().reflection().registerType(type, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS);
}
if (forQuerydsl) {
@ -268,9 +253,8 @@ class DefaultAotContext implements AotContext { @@ -268,9 +253,8 @@ class DefaultAotContext implements AotContext {
if (!proxies.isEmpty()) {
for (List<TypeReference> proxyInterfaces : proxies) {
generationContext.getRuntimeHints().proxies()
.registerJdkProxy(Stream.concat(Stream.of(TypeReference.of(type)), proxyInterfaces.stream())
.toArray(TypeReference[]::new));
generationContext.getRuntimeHints().proxies().registerJdkProxy(
Stream.concat(Stream.of(TypeReference.of(type)), proxyInterfaces.stream()).toArray(TypeReference[]::new));
}
}

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

@ -22,7 +22,6 @@ import java.util.Set; @@ -22,7 +22,6 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
@ -53,7 +52,6 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio @@ -53,7 +52,6 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
private final Log logger = LogFactory.getLog(getClass());
private @Nullable String moduleIdentifier;
private AotContext aotContext;
private Lazy<Environment> environment = Lazy.of(StandardEnvironment::new);
public void setModuleIdentifier(@Nullable String moduleIdentifier) {
@ -77,8 +75,8 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio @@ -77,8 +75,8 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
return null;
}
this.aotContext = new DefaultAotContext(registeredBean.getBeanFactory(), environment.get());
return contribute(this.aotContext, resolveManagedTypes(registeredBean), registeredBean);
DefaultAotContext aotContext = new DefaultAotContext(registeredBean.getBeanFactory(), environment.get());
return contribute(aotContext, resolveManagedTypes(registeredBean), registeredBean);
}
private ManagedTypes resolveManagedTypes(RegisteredBean registeredBean) {
@ -119,7 +117,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio @@ -119,7 +117,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
/**
* Hook to provide a customized flavor of {@link BeanRegistrationAotContribution}. By overriding this method calls to
* {@link #contributeType(ResolvableType, GenerationContext)} might no longer be issued.
* {@link #contributeType(ResolvableType, GenerationContext, AotContext)} might no longer be issued.
*
* @param aotContext never {@literal null}.
* @param managedTypes never {@literal null}.
@ -136,7 +134,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio @@ -136,7 +134,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
* @param type never {@literal null}.
* @param generationContext never {@literal null}.
*/
protected void contributeType(ResolvableType type, GenerationContext generationContext) {
protected void contributeType(ResolvableType type, GenerationContext generationContext, AotContext aotContext) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Contributing type information for [%s]", type.getType()));
@ -148,7 +146,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio @@ -148,7 +146,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
.contributeAccessors() //
.forQuerydsl().contribute(environment.get(), generationContext));
TypeUtils.resolveUsedAnnotations(type.toClass()).forEach(
TypeUtils.resolveUsedAnnotations(type.toClass()).forEach(
annotation -> TypeContributor.contribute(annotation.getType(), annotationNamespaces, generationContext));
}

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

@ -23,7 +23,6 @@ import java.util.function.BiConsumer; @@ -23,7 +23,6 @@ import java.util.function.BiConsumer;
import javax.lang.model.element.Modifier;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.generate.AccessControl;
import org.springframework.aot.generate.GeneratedMethod;
import org.springframework.aot.generate.GenerationContext;
@ -77,11 +76,11 @@ class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContri @@ -77,11 +76,11 @@ class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContri
private final AotContext aotContext;
private final ManagedTypes managedTypes;
private final Lazy<List<Class<?>>> sourceTypes;
private final BiConsumer<ResolvableType, GenerationContext> contributionAction;
private final TypeRegistration contributionAction;
private final RegisteredBean source;
public ManagedTypesRegistrationAotContribution(AotContext aotContext, ManagedTypes managedTypes, RegisteredBean registeredBean,
BiConsumer<ResolvableType, GenerationContext> contributionAction) {
public ManagedTypesRegistrationAotContribution(AotContext aotContext, ManagedTypes managedTypes,
RegisteredBean registeredBean, TypeRegistration contributionAction) {
this.aotContext = aotContext;
this.managedTypes = managedTypes;
@ -96,7 +95,7 @@ class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContri @@ -96,7 +95,7 @@ class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContri
List<Class<?>> types = sourceTypes.get();
if (!types.isEmpty()) {
TypeCollector.inspect(types).forEach(type -> contributionAction.accept(type, generationContext));
TypeCollector.inspect(types).forEach(type -> contributionAction.register(type, generationContext, aotContext));
}
}
@ -118,6 +117,10 @@ class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContri @@ -118,6 +117,10 @@ class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContri
return source;
}
interface TypeRegistration {
void register(ResolvableType type, GenerationContext generationContext, AotContext aotContext);
}
/**
* Class used to generate the fragment of code needed to define a {@link ManagedTypes} bean from previously discovered
* managed types.
@ -145,7 +148,8 @@ class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContri @@ -145,7 +148,8 @@ class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContri
}
@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {
GeneratedMethod generatedMethod = beanRegistrationCode.getMethods().add("Instance",
this::generateInstanceFactory);

4
src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecorator.java

@ -123,6 +123,8 @@ public class AotRepositoryBeanDefinitionPropertiesDecorator { @@ -123,6 +123,8 @@ public class AotRepositoryBeanDefinitionPropertiesDecorator {
private CodeBlock buildCallbackBody() {
Assert.state(repositoryContributor.getContributedTypeName() != null, "ContributedTypeName must not be null");
CodeBlock.Builder callback = CodeBlock.builder();
List<Object> arguments = new ArrayList<>();
@ -146,7 +148,7 @@ public class AotRepositoryBeanDefinitionPropertiesDecorator { @@ -146,7 +148,7 @@ public class AotRepositoryBeanDefinitionPropertiesDecorator {
List<Object> args = new ArrayList<>();
args.add(RepositoryComposition.RepositoryFragments.class);
args.add(repositoryContributor.getContributedTypeName().getCanonicalName());
args.add(repositoryContributor.getContributedTypeName().getName());
args.addAll(arguments);
callback.addStatement("return $T.just(new $L(%s%s))".formatted("$L".repeat(arguments.isEmpty() ? 0 : 1),

3
src/main/java/org/springframework/data/repository/aot/generate/RepositoryConstructorBuilder.java

@ -48,8 +48,11 @@ import org.springframework.util.Assert; @@ -48,8 +48,11 @@ import org.springframework.util.Assert;
*/
class RepositoryConstructorBuilder implements AotRepositoryConstructorBuilder {
@SuppressWarnings("NullAway")
private final String beanFactory = AotRepositoryBeanDefinitionPropertiesDecorator.RESERVED_TYPES
.get(ResolvableType.forClass(BeanFactory.class));
@SuppressWarnings("NullAway")
private final String fragmentCreationContext = AotRepositoryBeanDefinitionPropertiesDecorator.RESERVED_TYPES
.get(ResolvableType.forClass(RepositoryFactoryBeanSupport.FragmentCreationContext.class));

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

@ -15,12 +15,10 @@ @@ -15,12 +15,10 @@
*/
package org.springframework.data.repository.config;
import java.io.Serializable;
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;
@ -29,8 +27,6 @@ import java.util.function.Supplier; @@ -29,8 +27,6 @@ import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
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;
@ -39,13 +35,10 @@ import org.springframework.beans.factory.aot.BeanRegistrationCode; @@ -39,13 +35,10 @@ 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.beans.factory.support.RootBeanDefinition;
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;
import org.springframework.core.env.Environment;
import org.springframework.data.aot.AotContext;
import org.springframework.data.aot.AotTypeConfiguration;
import org.springframework.data.projection.EntityProjectionIntrospector;
@ -55,9 +48,6 @@ import org.springframework.data.repository.aot.generate.RepositoryContributor; @@ -55,9 +48,6 @@ 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;
import org.springframework.data.util.TypeUtils;
import org.springframework.javapoet.CodeBlock;
import org.springframework.util.Assert;
@ -81,7 +71,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -81,7 +71,7 @@ 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;
@ -238,7 +228,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -238,7 +228,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
}
}
getRepositoryContext().typeConfigurations()
.forEach(typeConfiguration -> typeConfiguration.contribute(environment.get(), generationContext));
.forEach(typeConfiguration -> typeConfiguration.contribute(environment.get(), generationContext));
}
@Override
@ -308,7 +298,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -308,7 +298,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 -> {

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

@ -25,7 +25,6 @@ import java.util.stream.Stream; @@ -25,7 +25,6 @@ import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.annotation.Reflective;
@ -41,10 +40,10 @@ import org.springframework.beans.factory.support.RegisteredBean; @@ -41,10 +40,10 @@ 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;
import org.springframework.data.aot.AotTypeConfiguration;
import org.springframework.data.repository.aot.generate.RepositoryContributor;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
@ -89,9 +88,9 @@ public class RepositoryRegistrationAotProcessor @@ -89,9 +88,9 @@ public class RepositoryRegistrationAotProcessor
return isRepositoryBean(bean) ? newRepositoryRegistrationAotContribution(bean) : null;
}
@Nullable
protected RepositoryContributor contribute(AotRepositoryContext repositoryContext, GenerationContext generationContext) {
protected RepositoryContributor contribute(AotRepositoryContext repositoryContext,
GenerationContext generationContext) {
repositoryContext.getResolvedTypes().stream()
.filter(it -> !RepositoryRegistrationAotContribution.isJavaOrPrimitiveType(it))
@ -145,7 +144,7 @@ public class RepositoryRegistrationAotProcessor @@ -145,7 +144,7 @@ public class RepositoryRegistrationAotProcessor
return null;
}
//TODO: add the hook for customizing bean initialization code here!
// TODO: add the hook for customizing bean initialization code here!
return contribution.withModuleContribution((repositoryContext, generationContext) -> {
registerReflectiveForAggregateRoot(repositoryContext, generationContext);
@ -196,7 +195,6 @@ public class RepositoryRegistrationAotProcessor @@ -196,7 +195,6 @@ public class RepositoryRegistrationAotProcessor
protected void contributeType(Class<?> type, GenerationContext generationContext) {
TypeContributor.contribute(type, it -> true, generationContext);
}
protected Log getLogger() {

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

@ -28,8 +28,6 @@ import org.junit.jupiter.params.provider.CsvSource; @@ -28,8 +28,6 @@ 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;
@ -43,136 +41,135 @@ import org.springframework.util.StringUtils; @@ -43,136 +41,135 @@ import org.springframework.util.StringUtils;
/**
* Unit tests for {@link AotContext}.
*
*
* @author Mark Paluch
* @author Christoph Strobl
*/
@MockitoSettings(strictness = org.mockito.quality.Strictness.LENIENT)
class AotContextUnitTests {
@Mock BeanFactory beanFactory;
@Mock BeanFactory beanFactory;
@Mock AotMappingContext mappingContext;
@Mock AotMappingContext mappingContext;
MockEnvironment mockEnvironment = new MockEnvironment();
MockEnvironment mockEnvironment = new MockEnvironment();
@Test // GH-2595
void shouldContributeAccessorByDefault() {
@Test // GH-2595
void shouldContributeAccessorByDefault() {
contributeAccessor(Address.class);
verify(mappingContext).contribute(Address.class);
}
contributeAccessor(Address.class);
verify(mappingContext).contribute(Address.class);
}
@Test // GH-2595
void shouldConsiderDisabledAccessors() {
@Test // GH-2595
void shouldConsiderDisabledAccessors() {
mockEnvironment.setProperty("spring.aot.data.accessors.enabled", "false");
mockEnvironment.setProperty("spring.aot.data.accessors.enabled", "false");
contributeAccessor(Address.class);
contributeAccessor(Address.class);
verifyNoInteractions(mappingContext);
}
verifyNoInteractions(mappingContext);
}
@Test // GH-2595
void shouldApplyExcludeFilters() {
@Test // GH-2595
void shouldApplyExcludeFilters() {
mockEnvironment.setProperty("spring.aot.data.accessors.exclude",
Customer.class.getName() + " , " + EmptyType1.class.getName());
mockEnvironment.setProperty("spring.aot.data.accessors.exclude",
Customer.class.getName() + " , " + EmptyType1.class.getName());
contributeAccessor(Address.class, Customer.class, EmptyType1.class);
contributeAccessor(Address.class, Customer.class, EmptyType1.class);
verify(mappingContext).contribute(Address.class);
verifyNoMoreInteractions(mappingContext);
}
verify(mappingContext).contribute(Address.class);
verifyNoMoreInteractions(mappingContext);
}
@Test // GH-2595
void shouldApplyIncludeExcludeFilters() {
@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() + ".**");
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);
contributeAccessor(Address.class, Customer.class, EmptyType1.class);
verify(mappingContext).contribute(Address.class);
verifyNoMoreInteractions(mappingContext);
}
verify(mappingContext).contribute(Address.class);
verifyNoMoreInteractions(mappingContext);
}
private void contributeAccessor(Class<?>... classes) {
private void contributeAccessor(Class<?>... classes) {
DefaultAotContext context = new DefaultAotContext(beanFactory, mockEnvironment);
DefaultAotContext context = new DefaultAotContext(beanFactory, mockEnvironment, mappingContext);
for (Class<?> aClass : classes) {
context.typeConfiguration(aClass, AotTypeConfiguration::contributeAccessors);
}
for (Class<?> aClass : classes) {
context.typeConfiguration(aClass, AotTypeConfiguration::contributeAccessors);
}
context.typeConfigurations().forEach(it -> it.contribute(mockEnvironment, new TestGenerationContext()));
}
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) {
@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);
}
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);
}
Assertions.assertThat(ctx.isGeneratedRepositoriesEnabled(storeName)).isEqualTo(enabled);
}
static class MockAotContext implements AotContext {
static class MockAotContext implements AotContext {
private final MockEnvironment environment;
private final MockEnvironment environment;
public MockAotContext() {
this.environment = new MockEnvironment();
}
public MockAotContext() {
this.environment = new MockEnvironment();
}
MockAotContext withProperty(String key, String value) {
environment.setProperty(key, value);
return this;
}
MockAotContext withProperty(String key, String value) {
environment.setProperty(key, value);
return this;
}
@Override
public ConfigurableListableBeanFactory getBeanFactory() {
return Mockito.mock(ConfigurableListableBeanFactory.class);
}
@Override
public ConfigurableListableBeanFactory getBeanFactory() {
return Mockito.mock(ConfigurableListableBeanFactory.class);
}
@Override
public TypeIntrospector introspectType(String typeName) {
return Mockito.mock(TypeIntrospector.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 IntrospectedBeanDefinition introspectBeanDefinition(String beanName) {
return Mockito.mock(IntrospectedBeanDefinition.class);
}
@Override
public void typeConfiguration(Class<?> type, Consumer<AotTypeConfiguration> configurationConsumer) {
@Override
public void typeConfiguration(Class<?> type, Consumer<AotTypeConfiguration> configurationConsumer) {
}
}
@Override
public Collection<AotTypeConfiguration> typeConfigurations() {
return List.of();
}
@Override
public Collection<AotTypeConfiguration> typeConfigurations() {
return List.of();
}
@Override
public Environment getEnvironment() {
return environment;
}
}
@Override
public Environment getEnvironment() {
return environment;
}
}
}

26
src/test/java/org/springframework/data/projection/ProjectionIntegrationTests.java

@ -15,20 +15,21 @@ @@ -15,20 +15,21 @@
*/
package org.springframework.data.projection;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.lang.Nullable;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.Configuration.ConfigurationBuilder;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.Option;
import org.springframework.lang.Nullable;
/**
* Integration tests for projections.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
class ProjectionIntegrationTests {
@ -44,7 +45,26 @@ class ProjectionIntegrationTests { @@ -44,7 +45,26 @@ class ProjectionIntegrationTests {
assertThat(json.read("$.decoratedClass", String.class)).isNull();
}
@Test // GH-3170
void jacksonSerializationConsidersJspecifyNullableAnnotations() throws Exception {
var factory = new ProxyProjectionFactory();
var projection = factory.createProjection(SampleProjectionJSpecify.class);
var context = JsonPath.using(new ConfigurationBuilder().options(Option.SUPPRESS_EXCEPTIONS).build());
var json = context.parse(new ObjectMapper().writeValueAsString(projection));
assertThat(json.read("$.decoratedClass", String.class)).isNull();
}
@SuppressWarnings("deprecation")
interface SampleProjection {
@Nullable String getName();
@Nullable
String getName();
}
interface SampleProjectionJSpecify {
@org.jspecify.annotations.Nullable
String getName();
}
}

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

@ -27,7 +27,6 @@ import java.util.function.Supplier; @@ -27,7 +27,6 @@ import java.util.function.Supplier;
import org.assertj.core.api.AbstractAssert;
import org.junit.jupiter.api.function.ThrowingConsumer;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.aot.BeanRegistrationCode;

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

@ -15,12 +15,11 @@ @@ -15,12 +15,11 @@
*/
package org.springframework.data.repository.aot;
import static org.springframework.data.repository.aot.RepositoryRegistrationAotContributionAssert.*;
import static org.springframework.data.repository.aot.RepositoryRegistrationAotContributionAssert.assertThatContribution;
import java.io.Serializable;
import org.junit.jupiter.api.Test;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.Advised;
import org.springframework.context.annotation.ComponentScan.Filter;
@ -109,8 +108,8 @@ public class RepositoryRegistrationAotProcessorIntegrationTests { @@ -109,8 +108,8 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
void simpleRepositoryWithTxManagerNoKotlinNoReactiveButComponent() {
RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(
ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepository.class).forRepository(
ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepository.MyComponentTxRepo.class);
ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepository.class)
.forRepository(ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepository.MyComponentTxRepo.class);
assertThatContribution(repositoryBeanContribution) //
.targetRepositoryTypeIs(
@ -176,7 +175,7 @@ public class RepositoryRegistrationAotProcessorIntegrationTests { @@ -176,7 +175,7 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(
ConfigWithCustomImplementation.class)
.forRepository(ConfigWithCustomImplementation.RepositoryWithCustomImplementation.class);
.forRepository(ConfigWithCustomImplementation.RepositoryWithCustomImplementation.class);
assertThatContribution(repositoryBeanContribution) //
.targetRepositoryTypeIs(ConfigWithCustomImplementation.RepositoryWithCustomImplementation.class) //
@ -225,15 +224,15 @@ public class RepositoryRegistrationAotProcessorIntegrationTests { @@ -225,15 +224,15 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(
ConfigWithCustomRepositoryBaseClass.class)
.forRepository(ConfigWithCustomRepositoryBaseClass.CustomerRepositoryWithCustomBaseRepo.class);
.forRepository(ConfigWithCustomRepositoryBaseClass.CustomerRepositoryWithCustomBaseRepo.class);
assertThatContribution(repositoryBeanContribution) //
.targetRepositoryTypeIs(ConfigWithCustomRepositoryBaseClass.CustomerRepositoryWithCustomBaseRepo.class) //
.hasFragments() //
.codeContributionSatisfies(contribution -> { //
// interface
contribution
.contributesReflectionFor(SampleRepositoryFragmentsContributor.class) // repository structural fragment
contribution.contributesReflectionFor(SampleRepositoryFragmentsContributor.class) // repository structural
// fragment
.contributesReflectionFor(ConfigWithCustomRepositoryBaseClass.CustomerRepositoryWithCustomBaseRepo.class) // repository
.contributesReflectionFor(ConfigWithCustomRepositoryBaseClass.RepoBaseClass.class) // base repo class
.contributesReflectionFor(ConfigWithCustomRepositoryBaseClass.Person.class); // repository domain type
@ -308,7 +307,7 @@ public class RepositoryRegistrationAotProcessorIntegrationTests { @@ -308,7 +307,7 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
RepositoryRegistrationAotContribution contribution = computeAotConfiguration(
InheritedEventPublicationConfiguration.class)
.forRepository(InheritedEventPublicationConfiguration.SampleRepository.class);
.forRepository(InheritedEventPublicationConfiguration.SampleRepository.class);
assertThatContribution(contribution).codeContributionSatisfies(it -> {
it.contributesReflectionFor(AbstractAggregateRoot.class);
@ -335,10 +334,8 @@ public class RepositoryRegistrationAotProcessorIntegrationTests { @@ -335,10 +334,8 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
interface SampleRepository extends Repository<Sample, Object> {}
}
@EnableRepositories(
includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE,
value = InheritedEventPublicationConfiguration.SampleRepository.class) },
considerNestedRepositories = true)
@EnableRepositories(includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE,
value = InheritedEventPublicationConfiguration.SampleRepository.class) }, considerNestedRepositories = true)
public class InheritedEventPublicationConfiguration {
static class Sample extends AbstractAggregateRoot<Sample> {}

Loading…
Cancel
Save