diff --git a/pom.xml b/pom.xml
index 13143c9f6f..670806f619 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-commons
- 4.0.0-SNAPSHOT
+ 4.0.0-GH-3267-SNAPSHOT
Spring Data Core
Core Spring concepts underpinning every Spring Data module.
diff --git a/src/main/java/org/springframework/data/aot/AotContext.java b/src/main/java/org/springframework/data/aot/AotContext.java
index 4c2247a013..29b071a423 100644
--- a/src/main/java/org/springframework/data/aot/AotContext.java
+++ b/src/main/java/org/springframework/data/aot/AotContext.java
@@ -24,6 +24,8 @@
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
+
+import org.springframework.aot.generate.GenerationContext;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -40,9 +42,7 @@
/**
* The context in which the AOT processing happens. Grants access to the {@link ConfigurableListableBeanFactory
- * beanFactory} and {@link ClassLoader}. Holds a few convenience methods to check if a type
- * {@link TypeIntrospector#isTypePresent() is present} and allows resolution of them through {@link TypeIntrospector}
- * and {@link IntrospectedBeanDefinition}.
+ * beanFactory} and {@link ClassLoader}.
*
* Mainly for internal use within the framework.
*
@@ -99,7 +99,7 @@ static AotContext from(BeanFactory beanFactory, Environment environment) {
* @param moduleName name of the module. Can be {@literal null} or {@literal empty}, in which case it will only check
* the general {@link #GENERATED_REPOSITORIES_ENABLED} flag.
* @return indicator if repository code generation is enabled.
- * @since 5.0
+ * @since 4.0
*/
default boolean isGeneratedRepositoriesEnabled(@Nullable String moduleName) {
@@ -119,13 +119,13 @@ default boolean isGeneratedRepositoriesEnabled(@Nullable String moduleName) {
}
/**
- * Checks if repository metadata file writing is enabled by checking environment variables for general
- * enablement ({@link #GENERATED_REPOSITORIES_JSON_ENABLED})
+ * Checks if repository metadata file writing is enabled by checking environment variables for general enablement
+ * ({@link #GENERATED_REPOSITORIES_JSON_ENABLED})
*
* Unset properties are considered being {@literal true}.
*
* @return indicator if repository metadata should be written
- * @since 5.0
+ * @since 4.0
*/
default boolean isGeneratedRepositoriesMetadataEnabled() {
return getEnvironment().getProperty(GENERATED_REPOSITORIES_JSON_ENABLED, Boolean.class, true);
@@ -177,8 +177,12 @@ default ClassLoader getRequiredClassLoader() {
*
* @param typeName {@link String name} of the {@link Class type} to evaluate; must not be {@literal null}.
* @return the type introspector for further type-based introspection.
+ * @deprecated since 4.0 as this isn't widely used and can be easily implemented within user code.
*/
- TypeIntrospector introspectType(String typeName);
+ @Deprecated(since = "4.0", forRemoval = true)
+ default TypeIntrospector introspectType(String typeName) {
+ throw new UnsupportedOperationException(); // preparation for implementation removal.
+ }
/**
* Returns a new {@link TypeScanner} used to scan for {@link Class types} that will be contributed to the AOT
@@ -201,7 +205,9 @@ default TypeScanner getTypeScanner() {
* @param packageNames {@link Collection} of {@link String package names} to scan.
* @return a {@link Set} of {@link Class types} found during the scan.
* @see #getTypeScanner()
+ * @deprecated since 4.0, use {@link #getTypeScanner()} directly
*/
+ @Deprecated(since = "4.0", forRemoval = true)
default Set> scanPackageForTypes(Collection> identifyingAnnotations,
Collection packageNames) {
@@ -214,7 +220,9 @@ default Set> scanPackageForTypes(Collection
*
* @param reference {@link BeanReference} to the managed bean.
* @return the introspected bean definition.
+ * @deprecated since 4.0, use {@link #getBeanFactory()} and interact with the bean factory directly.
*/
+ @Deprecated(since = "4.0", forRemoval = true)
default IntrospectedBeanDefinition introspectBeanDefinition(BeanReference reference) {
return introspectBeanDefinition(reference.getBeanName());
}
@@ -225,15 +233,20 @@ default IntrospectedBeanDefinition introspectBeanDefinition(BeanReference refere
*
* @param beanName {@link String} containing the {@literal name} of the bean to evaluate; must not be {@literal null}.
* @return the introspected bean definition.
+ * @deprecated since 4.0, use {@link #getBeanFactory()} and interact with the bean factory directly.
*/
- IntrospectedBeanDefinition introspectBeanDefinition(String beanName);
+ @Deprecated(since = "4.0", forRemoval = true)
+ default IntrospectedBeanDefinition introspectBeanDefinition(String beanName) {
+ throw new UnsupportedOperationException(); // preparation for implementation removal.
+ }
/**
* Obtain a {@link AotTypeConfiguration} for the given {@link ResolvableType} to customize the AOT processing for the
- * given type.
+ * given type. Repeated calls to the same type will result in merging the configuration.
*
* @param resolvableType the resolvable type to configure.
* @param configurationConsumer configuration consumer function.
+ * @since 4.0
*/
default void typeConfiguration(ResolvableType resolvableType, Consumer configurationConsumer) {
typeConfiguration(resolvableType.toClass(), configurationConsumer);
@@ -241,24 +254,29 @@ default void typeConfiguration(ResolvableType resolvableType, Consumer type, Consumer configurationConsumer);
/**
- * Return all type configurations registered with this {@link AotContext}.
+ * Contribute type configurations to the given {@link GenerationContext}. This method is called once per
+ * {@link GenerationContext} after all type configurations have been registered.
*
- * @return all type configurations registered with this {@link AotContext}.
+ * @param generationContext the context to contribute the type configurations to.
*/
- Collection typeConfigurations();
+ void contributeTypeConfigurations(GenerationContext generationContext);
/**
* Type-based introspector to resolve {@link Class} from a type name and to introspect the bean factory for presence
* of beans.
+ *
+ * @deprecated since 4.0 as this isn't widely used and can be easily implemented within user code.
*/
+ @Deprecated(since = "4.0", forRemoval = true)
interface TypeIntrospector {
/**
@@ -318,7 +336,10 @@ default void ifTypePresent(Consumer> action) {
/**
* Interface defining introspection methods for bean definitions.
+ *
+ * @deprecated since 4.0 as this isn't widely used and can be easily implemented within user code.
*/
+ @Deprecated(since = "4.0", forRemoval = true)
interface IntrospectedBeanDefinition {
/**
diff --git a/src/main/java/org/springframework/data/aot/AotTypeConfiguration.java b/src/main/java/org/springframework/data/aot/AotTypeConfiguration.java
index 2adebba35c..6879011e2c 100644
--- a/src/main/java/org/springframework/data/aot/AotTypeConfiguration.java
+++ b/src/main/java/org/springframework/data/aot/AotTypeConfiguration.java
@@ -22,19 +22,15 @@
import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.Advised;
-import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.TypeReference;
import org.springframework.core.DecoratingProxy;
-import org.springframework.core.env.Environment;
import org.springframework.data.projection.TargetAware;
/**
* 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.
- *
- * On {@link #contribute(Environment, GenerationContext)} the configuration is added to the {@link GenerationContext}.
*
* @author Christoph Strobl
* @since 4.0
@@ -134,11 +130,4 @@ default AotTypeConfiguration proxyInterface(Class>... proxyInterfaces) {
*/
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);
}
diff --git a/src/main/java/org/springframework/data/aot/DefaultAotContext.java b/src/main/java/org/springframework/data/aot/DefaultAotContext.java
index 1608cf3039..9231471213 100644
--- a/src/main/java/org/springframework/data/aot/DefaultAotContext.java
+++ b/src/main/java/org/springframework/data/aot/DefaultAotContext.java
@@ -17,7 +17,6 @@
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -30,9 +29,11 @@
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
+
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.TypeReference;
+import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.aot.AotProcessingException;
@@ -55,14 +56,15 @@
* @author Christoph Strobl
* @since 3.0
*/
+@SuppressWarnings("removal")
class DefaultAotContext implements AotContext {
private final AotMappingContext mappingContext;
private final ConfigurableListableBeanFactory factory;
- // TODO: should we reuse the config or potentially have multiple ones with different settings for the same type
- private final Map, AotTypeConfiguration> typeConfigurations = new HashMap<>();
+ private final Map, ContextualTypeConfiguration> typeConfigurations = new HashMap<>();
private final Environment environment;
+ private final ReflectiveRuntimeHintsRegistrar runtimeHintsRegistrar = new ReflectiveRuntimeHintsRegistrar();
public DefaultAotContext(BeanFactory beanFactory, Environment environment) {
this(beanFactory, environment, new AotMappingContext());
@@ -101,10 +103,13 @@ public void typeConfiguration(Class> type, Consumer conf
}
@Override
- public Collection typeConfigurations() {
- return typeConfigurations.values();
+ public void contributeTypeConfigurations(GenerationContext generationContext) {
+ typeConfigurations.forEach((type, configuration) -> {
+ configuration.contribute(this.environment, generationContext);
+ });
}
+ @SuppressWarnings("removal")
class DefaultTypeIntrospector implements TypeIntrospector {
private final String typeName;
@@ -144,6 +149,7 @@ public List getBeanNames() {
}
}
+ @SuppressWarnings("removal")
class DefaultIntrospectedBeanDefinition implements IntrospectedBeanDefinition {
private final String beanName;
@@ -227,7 +233,6 @@ public AotTypeConfiguration forQuerydsl() {
return this;
}
- @Override
public void contribute(Environment environment, GenerationContext generationContext) {
try {
@@ -254,6 +259,7 @@ private void doContribute(Environment environment, GenerationContext generationC
}
if (forDataBinding) {
+ runtimeHintsRegistrar.registerRuntimeHints(generationContext.getRuntimeHints(), type);
TypeContributor.contribute(type, Set.of(TypeContributor.DATA_NAMESPACE), generationContext);
}
diff --git a/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java b/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java
index f3a99e17a7..2e978a3c1f 100644
--- a/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java
+++ b/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java
@@ -119,7 +119,7 @@ private ManagedTypes resolveManagedTypes(RegisteredBean registeredBean) {
/**
* Hook to provide a customized flavor of {@link BeanRegistrationAotContribution}. By overriding this method calls to
- * {@link #contributeType(ResolvableType, GenerationContext, AotContext)} might no longer be issued.
+ * {@link #registerTypeHints(ResolvableType, AotContext, GenerationContext)} might no longer be issued.
*
* @param aotContext never {@literal null}.
* @param managedTypes never {@literal null}.
@@ -128,7 +128,7 @@ private ManagedTypes resolveManagedTypes(RegisteredBean registeredBean) {
protected BeanRegistrationAotContribution contribute(AotContext aotContext, ManagedTypes managedTypes,
RegisteredBean registeredBean) {
return new ManagedTypesRegistrationAotContribution(aotContext, managedTypes, registeredBean,
- typeCollectorCustomizer(), this::contributeType);
+ typeCollectorCustomizer(), this::registerTypeHints);
}
/**
@@ -140,13 +140,14 @@ protected BeanRegistrationAotContribution contribute(AotContext aotContext, Mana
protected Consumer typeCollectorCustomizer() {
return typeCollector -> {};
}
+
/**
* Hook to contribute configuration for a given {@literal type}.
*
* @param type never {@literal null}.
* @param generationContext never {@literal null}.
*/
- protected void contributeType(ResolvableType type, GenerationContext generationContext, AotContext aotContext) {
+ protected void registerTypeHints(ResolvableType type, AotContext aotContext, GenerationContext generationContext) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Contributing type information for [%s]", type.getType()));
@@ -156,10 +157,6 @@ protected void contributeType(ResolvableType type, GenerationContext generationC
configureTypeContribution(type.toClass(), aotContext);
- aotContext.typeConfiguration(type, config -> {
- config.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/aot/ManagedTypesRegistrationAotContribution.java b/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java
index 2463576a4d..daeb21b588 100644
--- a/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java
+++ b/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java
@@ -98,8 +98,10 @@ public void applyTo(GenerationContext generationContext, BeanRegistrationCode be
if (!types.isEmpty()) {
TypeCollector.inspect(typeCollectorCustomizer, types)
- .forEach(type -> contributionAction.register(type, generationContext, aotContext));
+ .forEach(type -> contributionAction.register(type, aotContext, generationContext));
}
+
+ aotContext.contributeTypeConfigurations(generationContext);
}
@Override
@@ -117,7 +119,7 @@ public RegisteredBean getSource() {
}
interface TypeRegistration {
- void register(ResolvableType type, GenerationContext generationContext, AotContext aotContext);
+ void register(ResolvableType type, AotContext aotContext, GenerationContext generationContext);
}
/**
diff --git a/src/main/java/org/springframework/data/javapoet/LordOfTheStrings.java b/src/main/java/org/springframework/data/javapoet/LordOfTheStrings.java
index 6b924d26e5..83cffb8aeb 100644
--- a/src/main/java/org/springframework/data/javapoet/LordOfTheStrings.java
+++ b/src/main/java/org/springframework/data/javapoet/LordOfTheStrings.java
@@ -858,10 +858,37 @@ public static class TypedReturnBuilder extends ReturnBuilderSupport {
*/
@Contract("_ -> this")
public TypedReturnBuilder number(String resultToReturn) {
+
return whenBoxedLong("$1L != null ? $1L.longValue() : null", resultToReturn)
.whenLong("$1L != null ? $1L.longValue() : 0L", resultToReturn)
.whenBoxedInteger("$1L != null ? $1L.intValue() : null", resultToReturn)
- .whenInt("$1L != null ? $1L.intValue() : 0", resultToReturn);
+ .whenInt("$1L != null ? $1L.intValue() : 0", resultToReturn)
+ .whenBoxed(Byte.class, "$1L != null ? $1L.byteValue() : null", resultToReturn)
+ .when(byte.class, "$1L != null ? $1L.byteValue() : 0", resultToReturn)
+ .whenBoxed(Short.class, "$1L != null ? $1L.shortValue() : null", resultToReturn)
+ .when(short.class, "$1L != null ? $1L.shortValue() : 0", resultToReturn)
+ .whenBoxed(Double.class, "$1L != null ? $1L.doubleValue() : null", resultToReturn)
+ .when(double.class, "$1L != null ? $1L.doubleValue() : 0", resultToReturn)
+ .whenBoxed(Float.class, "$1L != null ? $1L.floatValue() : null", resultToReturn)
+ .when(float.class, "$1L != null ? $1L.floatValue() : 0f", resultToReturn);
+ }
+
+ /**
+ * Add return statements for numeric types if the given {@code resultToReturn} points to a non-nullable
+ * {@link Number}. Considers all primitive numeric types assuming that {@code resultToReturn} is never
+ * {@literal null}.
+ *
+ * @param resultToReturn the argument or variable name holding the result.
+ * @return {@code this} builder.
+ */
+ @Contract("_ -> this")
+ public TypedReturnBuilder nonNullableNumber(String resultToReturn) {
+ return whenPrimitiveOrBoxed(long.class, "$1L.longValue()", resultToReturn)
+ .whenPrimitiveOrBoxed(int.class, "$1L.intValue()", resultToReturn)
+ .whenPrimitiveOrBoxed(short.class, "$1L.shortValue()", resultToReturn)
+ .whenPrimitiveOrBoxed(byte.class, "$1L.byteValue()", resultToReturn)
+ .whenPrimitiveOrBoxed(float.class, "$1L.floatValue()", resultToReturn)
+ .whenPrimitiveOrBoxed(double.class, "$1L.doubleValue()", resultToReturn);
}
/**
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecorator.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecorator.java
index 367162f879..f8abd36650 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecorator.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecorator.java
@@ -41,8 +41,8 @@
/**
* Delegate to decorate AOT {@code BeanDefinition} properties during AOT processing. Adds a {@link CodeBlock} for the
- * fragment function that resolves {@link RepositoryContributor#requiredArgs()} from the {@link BeanFactory} and
- * provides them to the generated repository fragment.
+ * fragment function that resolves {@link RepositoryContributor#getAotFragmentMetadata()} from the {@link BeanFactory}
+ * and provides them to the generated repository fragment.
*
* @author Mark Paluch
* @author Christoph Strobl
@@ -128,7 +128,8 @@ private CodeBlock buildCallbackBody() {
CodeBlock.Builder callback = CodeBlock.builder();
List arguments = new ArrayList<>();
- for (Entry entry : repositoryContributor.getConstructorArguments().entrySet()) {
+ for (Entry entry : repositoryContributor.getAotFragmentMetadata()
+ .getConstructorArguments().entrySet()) {
ConstructorArgument argument = entry.getValue();
AotRepositoryConstructorBuilder.ParameterOrigin parameterOrigin = argument.parameterOrigin();
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java
index f8c0787c97..4534592ba8 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java
@@ -19,9 +19,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
import java.util.function.Consumer;
import javax.lang.model.element.Modifier;
@@ -30,9 +28,7 @@
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
-import org.springframework.core.ResolvableType;
import org.springframework.data.projection.ProjectionFactory;
-import org.springframework.data.repository.aot.generate.AotRepositoryFragmentMetadata.ConstructorArgument;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryComposition;
import org.springframework.data.repository.core.support.RepositoryFragment;
@@ -100,18 +96,8 @@ String packageName() {
return repositoryInformation.getRepositoryInterface().getPackageName();
}
- Map getAutowireFields() {
-
- Map autowireFields = new LinkedHashMap<>(
- generationMetadata.getConstructorArguments().size());
- for (Map.Entry entry : generationMetadata.getConstructorArguments().entrySet()) {
- autowireFields.put(entry.getKey(), entry.getValue().parameterType());
- }
- return autowireFields;
- }
-
- Map getConstructorArguments() {
- return generationMetadata.getConstructorArguments();
+ AotRepositoryFragmentMetadata getRepositoryMetadata() {
+ return generationMetadata;
}
RepositoryInformation getRepositoryInformation() {
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryFragmentMetadata.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryFragmentMetadata.java
index 9f6c465f65..fe1ca30080 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryFragmentMetadata.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryFragmentMetadata.java
@@ -129,6 +129,15 @@ public Map getConstructorArguments() {
return constructorArguments;
}
+ Map getAutowireFields() {
+
+ Map autowireFields = new LinkedHashMap<>(getConstructorArguments().size());
+ for (Map.Entry entry : getConstructorArguments().entrySet()) {
+ autowireFields.put(entry.getKey(), entry.getValue().parameterType());
+ }
+ return autowireFields;
+ }
+
public Map getMethods() {
return methods;
}
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java b/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
index 725e4ac460..7823ba9806 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
@@ -18,19 +18,18 @@
import java.io.ByteArrayInputStream;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
-import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
+
import org.springframework.aot.generate.GeneratedClass;
import org.springframework.aot.generate.GeneratedFiles.Kind;
import org.springframework.aot.generate.GeneratedTypeReference;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.TypeReference;
-import org.springframework.core.ResolvableType;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.aot.generate.AotRepositoryCreator.AotBundle;
@@ -66,7 +65,7 @@ public class RepositoryContributor {
public RepositoryContributor(AotRepositoryContext repositoryContext) {
this.repositoryContext = repositoryContext;
- creator = AotRepositoryCreator.forRepository(repositoryContext.getRepositoryInformation(),
+ this.creator = AotRepositoryCreator.forRepository(repositoryContext.getRepositoryInformation(),
repositoryContext.getModuleName(), createProjectionFactory());
}
@@ -102,28 +101,10 @@ TypeReference getContributedTypeName() {
}
/**
- * Get the required constructor arguments for the to be generated repository implementation. Types will be obtained by
- * type from {@link org.springframework.beans.factory.BeanFactory} upon initialization of the generated fragment
- * during application startup.
- *
- * Can be overridden if required. Needs to match arguments of generated repository implementation.
- *
- * @return key/value pairs of required argument required to instantiate the generated fragment.
- */
- // TODO: should we switch from ResolvableType to some custom value object to cover qualifiers?
- java.util.Map requiredArgs() {
- return Collections.unmodifiableMap(creator.getAutowireFields());
- }
-
- /**
- * Get the required constructor arguments for the to be generated repository implementation.
- *
- * Can be overridden if required. Needs to match arguments of generated repository implementation.
- *
- * @return key/value pairs of required argument required to instantiate the generated fragment.
+ * @return the associated {@link AotRepositoryFragmentMetadata}.
*/
- java.util.Map getConstructorArguments() {
- return Collections.unmodifiableMap(creator.getConstructorArguments());
+ AotRepositoryFragmentMetadata getAotFragmentMetadata() {
+ return creator.getRepositoryMetadata();
}
/**
diff --git a/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java b/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java
index c0eac7cf29..f4e96b25fc 100644
--- a/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java
+++ b/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java
@@ -36,8 +36,12 @@ public interface AotRepositoryContext extends AotContext {
/**
* @return the {@link String bean name} of the repository / factory bean.
+ * @deprecated since 4.0, this doesn't really belong in here.
*/
- String getBeanName();
+ @Deprecated(since = "4.0", forRemoval = true)
+ default String getBeanName() {
+ throw new UnsupportedOperationException(); // prepare for removal
+ }
/**
* @return the Spring Data module name, see {@link RepositoryConfigurationExtension#getModuleName()}.
@@ -52,7 +56,10 @@ public interface AotRepositoryContext extends AotContext {
/**
* @return a {@link Set} of {@link String base packages} to search for repositories.
+ * @deprecated since 4.0, use {@link #getConfigurationSource()} and call
+ * {@link RepositoryConfigurationSource#getBasePackages()}
*/
+ @Deprecated(since = "4.0", forRemoval = true)
default Set getBasePackages() {
return getConfigurationSource().getBasePackages().toSet();
}
@@ -79,6 +86,4 @@ default Set getBasePackages() {
*/
Set> getResolvedTypes();
- Set> getUserDomainTypes();
-
}
diff --git a/src/main/java/org/springframework/data/repository/config/AotRepositoryContextSupport.java b/src/main/java/org/springframework/data/repository/config/AotRepositoryContextSupport.java
new file mode 100644
index 0000000000..9bf7eb16a0
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/config/AotRepositoryContextSupport.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.repository.config;
+
+import java.util.function.Consumer;
+
+import org.jspecify.annotations.Nullable;
+
+import org.springframework.aot.generate.GenerationContext;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.core.ResolvableType;
+import org.springframework.core.env.Environment;
+import org.springframework.data.aot.AotContext;
+import org.springframework.data.aot.AotTypeConfiguration;
+import org.springframework.data.util.TypeScanner;
+
+/**
+ * Support class for {@link AotRepositoryContext} implementations delegating to an underlying {@link AotContext}.
+ *
+ * @author Mark Paluch
+ * @since 4.0
+ */
+public abstract class AotRepositoryContextSupport implements AotRepositoryContext {
+
+ private final AotContext aotContext;
+
+ /**
+ * Create a new {@code AotRepositoryContextSupport} given the {@link AotContext}.
+ *
+ * @param aotContext
+ */
+ public AotRepositoryContextSupport(AotContext aotContext) {
+ this.aotContext = aotContext;
+ }
+
+ @Override
+ public boolean isGeneratedRepositoriesEnabled(@Nullable String moduleName) {
+ return aotContext.isGeneratedRepositoriesEnabled(moduleName);
+ }
+
+ @Override
+ public boolean isGeneratedRepositoriesMetadataEnabled() {
+ return aotContext.isGeneratedRepositoriesMetadataEnabled();
+ }
+
+ @Override
+ public ConfigurableListableBeanFactory getBeanFactory() {
+ return aotContext.getBeanFactory();
+ }
+
+ @Override
+ public Environment getEnvironment() {
+ return aotContext.getEnvironment();
+ }
+
+ @Override
+ public @Nullable ClassLoader getClassLoader() {
+ return aotContext.getClassLoader();
+ }
+
+ @Override
+ public ClassLoader getRequiredClassLoader() {
+ return aotContext.getRequiredClassLoader();
+ }
+
+ @Override
+ public TypeScanner getTypeScanner() {
+ return aotContext.getTypeScanner();
+ }
+
+ @Override
+ public void typeConfiguration(ResolvableType resolvableType, Consumer configurationConsumer) {
+ aotContext.typeConfiguration(resolvableType, configurationConsumer);
+ }
+
+ @Override
+ public void typeConfiguration(Class> type, Consumer configurationConsumer) {
+ aotContext.typeConfiguration(type, configurationConsumer);
+ }
+
+ @Override
+ public void contributeTypeConfigurations(GenerationContext generationContext) {
+ aotContext.contributeTypeConfigurations(generationContext);
+ }
+
+}
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 4c1d0e5384..8d22b31a26 100644
--- a/src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java
+++ b/src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java
@@ -20,19 +20,14 @@
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
-import java.util.function.Consumer;
import java.util.stream.Collectors;
-import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.core.annotation.MergedAnnotation;
-import org.springframework.core.env.Environment;
import org.springframework.data.aot.AotContext;
-import org.springframework.data.aot.AotTypeConfiguration;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeCollector;
-import org.springframework.data.util.TypeContributor;
import org.springframework.data.util.TypeUtils;
/**
@@ -44,10 +39,8 @@
* @see AotRepositoryContext
* @since 3.0
*/
-@SuppressWarnings("NullAway") // TODO
-class DefaultAotRepositoryContext implements AotRepositoryContext {
+class DefaultAotRepositoryContext extends AotRepositoryContextSupport {
- private final RegisteredBean bean;
private final String moduleName;
private final RepositoryConfigurationSource configurationSource;
private final AotContext aotContext;
@@ -55,23 +48,19 @@ class DefaultAotRepositoryContext implements AotRepositoryContext {
private final Lazy>> resolvedAnnotations = Lazy.of(this::discoverAnnotations);
private final Lazy>> managedTypes = Lazy.of(this::discoverTypes);
- private Set basePackages = Collections.emptySet();
private Collection> identifyingAnnotations = Collections.emptySet();
private String beanName;
public DefaultAotRepositoryContext(RegisteredBean bean, RepositoryInformation repositoryInformation,
String moduleName, AotContext aotContext, RepositoryConfigurationSource configurationSource) {
- this.bean = bean;
+
+ super(aotContext);
+
this.repositoryInformation = repositoryInformation;
this.moduleName = moduleName;
this.configurationSource = configurationSource;
this.aotContext = aotContext;
this.beanName = bean.getBeanName();
- this.basePackages = configurationSource.getBasePackages().toSet();
- }
-
- public AotContext getAotContext() {
- return aotContext;
}
@Override
@@ -84,25 +73,6 @@ public RepositoryConfigurationSource getConfigurationSource() {
return configurationSource;
}
- @Override
- public ConfigurableListableBeanFactory getBeanFactory() {
- return getAotContext().getBeanFactory();
- }
-
- @Override
- public Environment getEnvironment() {
- return getAotContext().getEnvironment();
- }
-
- @Override
- public Set getBasePackages() {
- return basePackages;
- }
-
- public void setBasePackages(Set basePackages) {
- this.basePackages = basePackages;
- }
-
@Override
public String getBeanName() {
return beanName;
@@ -136,29 +106,6 @@ public Set> getResolvedTypes() {
return managedTypes.get();
}
- @Override
- public Set> getUserDomainTypes() {
-
- return getResolvedTypes().stream()
- .filter(it -> TypeContributor.isPartOf(it, Set.of(repositoryInformation.getDomainType().getPackageName())))
- .collect(Collectors.toSet());
- }
-
- @Override
- public AotContext.TypeIntrospector introspectType(String typeName) {
- return aotContext.introspectType(typeName);
- }
-
- @Override
- public void typeConfiguration(Class> type, Consumer configurationConsumer) {
- aotContext.typeConfiguration(type, configurationConsumer);
- }
-
- @Override
- public Collection typeConfigurations() {
- return aotContext.typeConfigurations();
- }
-
@Override
public AotContext.IntrospectedBeanDefinition introspectBeanDefinition(String beanName) {
return aotContext.introspectBeanDefinition(beanName);
@@ -185,7 +132,8 @@ protected Set> discoverTypes() {
if (!getIdentifyingAnnotations().isEmpty()) {
- Set> classes = aotContext.getTypeScanner().scanPackages(getBasePackages())
+ Set> classes = aotContext.getTypeScanner()
+ .scanPackages(getConfigurationSource().getBasePackages().toSet())
.forTypesAnnotatedWith(getIdentifyingAnnotations()).collectAsSet();
types.addAll(TypeCollector.inspect(classes).list());
}
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 0d4ebbd053..c2b2db8209 100644
--- a/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java
@@ -15,43 +15,21 @@
*/
package org.springframework.data.repository.config;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
import java.util.function.Predicate;
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.aot.generate.GenerationContext;
-import org.springframework.aot.hint.MemberCategory;
-import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
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.data.aot.AotContext;
-import org.springframework.data.aot.AotTypeConfiguration;
-import org.springframework.data.projection.EntityProjectionIntrospector;
-import org.springframework.data.repository.Repository;
import org.springframework.data.repository.aot.generate.AotRepositoryBeanDefinitionPropertiesDecorator;
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.TypeUtils;
import org.springframework.javapoet.CodeBlock;
-import org.springframework.util.Assert;
-import org.springframework.util.ClassUtils;
/**
* {@link BeanRegistrationAotContribution} used to contribute repository registrations.
@@ -61,174 +39,32 @@
* @author Mark Paluch
* @since 3.0
*/
-public class RepositoryRegistrationAotContribution implements BeanRegistrationAotContribution, EnvironmentAware {
-
- private static final Log logger = LogFactory.getLog(RepositoryRegistrationAotContribution.class);
-
- private static final String KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME = "org.springframework.data.repository.kotlin.CoroutineCrudRepository";
-
- private final RepositoryRegistrationAotProcessor aotProcessor;
-
- private final AotRepositoryContext repositoryContext;
-
- private @Nullable RepositoryContributor repositoryContributor;
- private Lazy environment = Lazy.of(StandardEnvironment::new);
-
- private @Nullable BiFunction moduleContribution;
-
- /**
- * Constructs a new instance of the {@link RepositoryRegistrationAotContribution} initialized with the given, required
- * {@link RepositoryRegistrationAotProcessor} from which this contribution was created.
- *
- * @param processor reference back to the {@link RepositoryRegistrationAotProcessor} from which this contribution was
- * created.
- * @param context reference back to the {@link AotRepositoryContext} from which this contribution was created.
- * @throws IllegalArgumentException if the {@link RepositoryRegistrationAotProcessor} is {@literal null}.
- * @see RepositoryRegistrationAotProcessor
- */
- protected RepositoryRegistrationAotContribution(RepositoryRegistrationAotProcessor processor,
- AotRepositoryContext context) {
-
- Assert.notNull(processor, "RepositoryRegistrationAotProcessor must not be null");
- Assert.notNull(context, "AotRepositoryContext must not be null");
-
- this.aotProcessor = processor;
- this.repositoryContext = context;
- }
-
- /**
- * Factory method used to construct a new instance of {@link RepositoryRegistrationAotContribution} initialized with
- * the given, required {@link RepositoryRegistrationAotProcessor} from which this contribution was created.
- *
- * @param processor reference back to the {@link RepositoryRegistrationAotProcessor} from which this contribution was
- * created.
- * @return a new instance of {@link RepositoryRegistrationAotContribution} if a contribution can be made;
- * {@literal null} if no contribution can be made.
- * @see RepositoryRegistrationAotProcessor
- */
- public static @Nullable RepositoryRegistrationAotContribution load(RepositoryRegistrationAotProcessor processor,
- RegisteredBean repositoryBean) {
-
- RepositoryConfiguration> repositoryMetadata = processor.getRepositoryMetadata(repositoryBean);
-
- if (repositoryMetadata == null) {
- return null;
- }
-
- AotRepositoryContext repositoryContext = buildAotRepositoryContext(processor.getEnvironment(), repositoryBean);
-
- if (repositoryContext == null) {
- return null;
- }
-
- return new RepositoryRegistrationAotContribution(processor, repositoryContext);
- }
+public class RepositoryRegistrationAotContribution implements BeanRegistrationAotContribution {
- /**
- * Builds a {@link RepositoryRegistrationAotContribution} for given, required {@link RegisteredBean} representing the
- * {@link Repository} registered in the bean registry.
- *
- * @param repositoryBean {@link RegisteredBean} for the {@link Repository}; must not be {@literal null}.
- * @return a {@link RepositoryRegistrationAotContribution} to contribute AOT metadata and code for the
- * {@link Repository} {@link RegisteredBean}.
- * @throws IllegalArgumentException if the {@link RegisteredBean} is {@literal null}.
- * @deprecated since 4.0.
- */
- @Deprecated(since = "4.0", forRemoval = true)
- public @Nullable RepositoryRegistrationAotContribution forBean(RegisteredBean repositoryBean) {
+ private final AotRepositoryContext context;
+ private final BeanRegistrationAotContribution aotContribution;
+ private final @Nullable RepositoryContributor repositoryContribution;
- RepositoryConfiguration> repositoryMetadata = getRepositoryRegistrationAotProcessor()
- .getRepositoryMetadata(repositoryBean);
+ RepositoryRegistrationAotContribution(AotRepositoryContext context, BeanRegistrationAotContribution aotContribution,
+ @Nullable RepositoryContributor repositoryContribution) {
- if (repositoryMetadata == null) {
- return null;
- }
-
- AotRepositoryContext repositoryContext = buildAotRepositoryContext(aotProcessor.getEnvironment(), repositoryBean);
-
- if (repositoryContext == null) {
- return null;
- }
-
- return new RepositoryRegistrationAotContribution(getRepositoryRegistrationAotProcessor(), repositoryContext);
- }
-
- protected @Nullable BiFunction getModuleContribution() {
- return this.moduleContribution;
- }
-
- protected AotRepositoryContext getRepositoryContext() {
- return this.repositoryContext;
- }
-
- protected RepositoryRegistrationAotProcessor getRepositoryRegistrationAotProcessor() {
- return this.aotProcessor;
+ this.context = context;
+ this.aotContribution = aotContribution;
+ this.repositoryContribution = repositoryContribution;
}
public RepositoryInformation getRepositoryInformation() {
- return getRepositoryContext().getRepositoryInformation();
- }
-
- private void logTrace(String message, Object... arguments) {
- getRepositoryRegistrationAotProcessor().logTrace(message, arguments);
- }
-
- private static @Nullable AotRepositoryContext buildAotRepositoryContext(Environment environment,
- RegisteredBean bean) {
-
- RepositoryBeanDefinitionReader reader = new RepositoryBeanDefinitionReader(bean);
- RepositoryConfiguration> configuration = reader.getConfiguration();
- RepositoryConfigurationExtensionSupport extension = reader.getConfigurationExtension();
-
- if (configuration == null || extension == null) {
- logger.warn(
- "Cannot create AotRepositoryContext for bean [%s]. No RepositoryConfiguration/RepositoryConfigurationExtension. Please make sure to register the repository bean through @Enable…Repositories."
- .formatted(bean.getBeanName()));
- return null;
- }
- RepositoryInformation repositoryInformation = reader.getRepositoryInformation();
- DefaultAotRepositoryContext repositoryContext = new DefaultAotRepositoryContext(bean, repositoryInformation,
- extension.getModuleName(), AotContext.from(bean.getBeanFactory(), environment),
- configuration.getConfigurationSource());
-
- repositoryContext.setIdentifyingAnnotations(extension.getIdentifyingAnnotations());
-
- return repositoryContext;
- }
-
- /**
- * {@link BiConsumer Callback} for data module specific contributions.
- *
- * @param moduleContribution {@link BiConsumer} used by data modules to submit contributions; can be {@literal null}.
- * @return this.
- */
- public RepositoryRegistrationAotContribution withModuleContribution(
- @Nullable BiFunction moduleContribution) {
- this.moduleContribution = moduleContribution;
- return this;
- }
-
- @Override
- public void setEnvironment(Environment environment) {
- this.environment = Lazy.of(environment);
+ return context.getRepositoryInformation();
}
@Override
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
- contributeRepositoryInfo(this.repositoryContext, generationContext);
+ aotContribution.applyTo(generationContext, beanRegistrationCode);
- var moduleContribution = getModuleContribution();
- if (moduleContribution != null && this.repositoryContributor == null) {
-
- this.repositoryContributor = moduleContribution.apply(getRepositoryContext(), generationContext);
-
- if (this.repositoryContributor != null) {
- this.repositoryContributor.contribute(generationContext);
- }
+ if (this.repositoryContribution != null) {
+ this.repositoryContribution.contribute(generationContext);
}
- getRepositoryContext().typeConfigurations()
- .forEach(typeConfiguration -> typeConfiguration.contribute(environment.get(), generationContext));
}
@Override
@@ -245,107 +81,16 @@ public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext gener
Supplier inheritedProperties = () -> super.generateSetBeanDefinitionPropertiesCode(generationContext,
beanRegistrationCode, beanDefinition, attributeFilter);
- if (repositoryContributor == null) { // no aot implementation -> go on as
+ if (repositoryContribution == null) { // no aot implementation -> go on as
return inheritedProperties.get();
}
AotRepositoryBeanDefinitionPropertiesDecorator decorator = new AotRepositoryBeanDefinitionPropertiesDecorator(
- inheritedProperties, repositoryContributor);
+ inheritedProperties, repositoryContribution);
return decorator.decorate();
}
};
}
- private void contributeRepositoryInfo(AotRepositoryContext repositoryContext, GenerationContext contribution) {
-
- RepositoryInformation repositoryInformation = getRepositoryInformation();
-
- logTrace("Contributing repository information for [%s]", repositoryInformation.getRepositoryInterface());
-
- repositoryContext.typeConfiguration(repositoryInformation.getRepositoryInterface(),
- config -> config.forReflectiveAccess(MemberCategory.INVOKE_PUBLIC_METHODS).repositoryProxy());
-
- repositoryContext.typeConfiguration(repositoryInformation.getRepositoryBaseClass(), config -> config
- .forReflectiveAccess(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS));
-
- repositoryContext.typeConfiguration(repositoryInformation.getDomainType(),
- config -> config.forDataBinding().forQuerydsl());
-
- // TODO: purposeful api for uses cases to have some internal logic
- repositoryContext.getUserDomainTypes() //
- .forEach(it -> repositoryContext.typeConfiguration(it, AotTypeConfiguration::contributeAccessors));
-
- // Repository Fragments
- contributeFragments(contribution);
-
- // Kotlin
- if (isKotlinCoroutineRepository(repositoryContext, repositoryInformation)) {
- contribution.getRuntimeHints().reflection().registerTypes(kotlinRepositoryReflectionTypeReferences(), hint -> {});
- }
-
- // Repository query methods
- repositoryInformation.getQueryMethods().stream().map(repositoryInformation::getReturnedDomainClass)
- .filter(Class::isInterface).forEach(type -> {
- if (EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy().test(type,
- repositoryInformation.getDomainType())) {
- repositoryContext.typeConfiguration(type, AotTypeConfiguration::usedAsProjectionInterface);
- }
- });
- }
-
- private void contributeFragments(GenerationContext contribution) {
- for (RepositoryFragment> fragment : getRepositoryInformation().getFragments()) {
-
- Class> repositoryFragmentType = fragment.getSignatureContributor();
- Optional> implementation = fragment.getImplementationClass();
-
- contribution.getRuntimeHints().reflection().registerType(repositoryFragmentType, hint -> {
-
- hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
-
- if (!repositoryFragmentType.isInterface()) {
- hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
- }
- });
-
- implementation.ifPresent(typeToRegister -> {
- contribution.getRuntimeHints().reflection().registerType(typeToRegister, hint -> {
-
- hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
-
- if (!typeToRegister.isInterface()) {
- hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
- }
- });
- });
- }
- }
-
- private boolean isKotlinCoroutineRepository(AotRepositoryContext repositoryContext,
- RepositoryInformation repositoryInformation) {
-
- return repositoryContext.introspectType(KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME).resolveType()
- .filter(it -> ClassUtils.isAssignable(it, repositoryInformation.getRepositoryInterface())).isPresent();
- }
-
- private List kotlinRepositoryReflectionTypeReferences() {
-
- return new ArrayList<>(
- Arrays.asList(TypeReference.of("org.springframework.data.repository.kotlin.CoroutineCrudRepository"),
- TypeReference.of(Repository.class), //
- TypeReference.of(Iterable.class), //
- TypeReference.of("kotlinx.coroutines.flow.Flow"), //
- TypeReference.of("kotlin.collections.Iterable"), //
- TypeReference.of("kotlin.Unit"), //
- TypeReference.of("kotlin.Long"), //
- TypeReference.of("kotlin.Boolean")));
- }
-
- static boolean isJavaOrPrimitiveType(Class> type) {
- return TypeUtils.type(type).isPartOf("java") //
- || ClassUtils.isPrimitiveOrWrapper(type) //
- || ClassUtils.isPrimitiveArray(type); //
- }
-
}
diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java b/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java
index c94b648522..c5ae5d09ca 100644
--- a/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java
@@ -17,18 +17,18 @@
import java.lang.annotation.Annotation;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
-import java.util.function.BiConsumer;
-import java.util.function.Predicate;
+import java.util.Optional;
+import java.util.Set;
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;
-import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar;
+import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
@@ -43,12 +43,18 @@
import org.springframework.core.env.Environment;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.env.StandardEnvironment;
+import org.springframework.data.aot.AotContext;
import org.springframework.data.aot.AotTypeConfiguration;
+import org.springframework.data.projection.EntityProjectionIntrospector;
+import org.springframework.data.repository.Repository;
import org.springframework.data.repository.aot.generate.RepositoryContributor;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
+import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.util.TypeContributor;
+import org.springframework.data.util.TypeUtils;
import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
/**
* {@link BeanRegistrationAotProcessor} responsible processing and providing AOT configuration for repositories.
@@ -57,13 +63,13 @@
* AOT tooling to allow deriving target type from the {@link RootBeanDefinition bean definition}. If generic types do
* not match due to customization of the factory bean by the user, at least the target repository type is provided via
* the {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE}.
- *
*
- * With {@link RepositoryRegistrationAotProcessor#contribute(AotRepositoryContext, GenerationContext)}, stores can
- * provide custom logic for contributing additional (eg. reflection) configuration. By default, reflection configuration
- * will be added for types reachable from the repository declaration and query methods as well as all used
+ * With {@link #registerRepositoryCompositionHints(AotRepositoryContext, GenerationContext)} (specifically
+ * {@link #configureTypeContribution(Class, AotContext)} and {@link #contributeAotRepository(AotRepositoryContext)},
+ * stores can provide custom logic for contributing additional (e.g. reflection) configuration. By default, reflection
+ * configuration will be added for types reachable from the repository declaration and query methods as well as all used
* {@link Annotation annotations} from the {@literal org.springframework.data} namespace.
- *
+ *
* The processor is typically configured via {@link RepositoryConfigurationExtension#getRepositoryAotProcessor()} and
* gets added by the {@link org.springframework.data.repository.config.RepositoryConfigurationDelegate}.
*
@@ -75,6 +81,18 @@
public class RepositoryRegistrationAotProcessor
implements BeanRegistrationAotProcessor, BeanFactoryAware, EnvironmentAware, EnvironmentCapable {
+ private static final String KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME = "org.springframework.data.repository.kotlin.CoroutineCrudRepository";
+
+ private static final List KOTLIN_REFLECTION_TYPE_REFERENCES = List.of(
+ TypeReference.of("org.springframework.data.repository.kotlin.CoroutineCrudRepository"),
+ TypeReference.of(Repository.class), //
+ TypeReference.of(Iterable.class), //
+ TypeReference.of("kotlinx.coroutines.flow.Flow"), //
+ TypeReference.of("kotlin.collections.Iterable"), //
+ TypeReference.of("kotlin.Unit"), //
+ TypeReference.of("kotlin.Long"), //
+ TypeReference.of("kotlin.Boolean"));
+
private final Log logger = LogFactory.getLog(getClass());
private @Nullable ConfigurableListableBeanFactory beanFactory;
@@ -83,140 +101,266 @@ public class RepositoryRegistrationAotProcessor
private Map> configMap = Collections.emptyMap();
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+
+ Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory,
+ () -> "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
+
+ this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
+ }
+
+ @Override
+ public void setEnvironment(Environment environment) {
+ this.environment = environment;
+ }
+
+ @Override
+ public Environment getEnvironment() {
+ return this.environment;
+ }
+
+ /**
+ * Setter for the config map. See {@code RepositoryConfigurationDelegate#registerAotComponents}.
+ *
+ * @param configMap
+ */
+ @SuppressWarnings("unused")
+ public void setConfigMap(Map> configMap) {
+ this.configMap = configMap;
+ }
+
+ public Map> getConfigMap() {
+ return this.configMap;
+ }
+
+ protected ConfigurableListableBeanFactory getBeanFactory() {
+
+ if (this.beanFactory == null) {
+ throw new IllegalStateException(
+ "No BeanFactory available. Make sure to set the BeanFactory before using this processor.");
+ }
+
+ return this.beanFactory;
+ }
+
@Override
public @Nullable BeanRegistrationAotContribution processAheadOfTime(RegisteredBean bean) {
- return isRepositoryBean(bean) ? newRepositoryRegistrationAotContribution(bean) : null;
+
+ if (!isRepositoryBean(bean)) {
+ return null;
+ }
+
+ RepositoryConfiguration> repositoryMetadata = getRepositoryMetadata(bean);
+ AotRepositoryContext repositoryContext = potentiallyCreateContext(environment, bean);
+
+ if (repositoryMetadata == null || repositoryContext == null) {
+ return null;
+ }
+
+ BeanRegistrationAotContribution contribution = (generationContext, beanRegistrationCode) -> {
+
+ registerRepositoryCompositionHints(repositoryContext, generationContext);
+ configureTypeContributions(repositoryContext, generationContext);
+
+ repositoryContext.contributeTypeConfigurations(generationContext);
+ };
+
+ return new RepositoryRegistrationAotContribution(repositoryContext, contribution,
+ contributeAotRepository(repositoryContext));
}
- @Nullable
- protected RepositoryContributor contribute(AotRepositoryContext repositoryContext,
+ /**
+ * Contribute repository-specific hints, e.g. for repository proxy, base implementation, fragments. Customization hook
+ * for subclasses that wish to customize repository hint contribution.
+ *
+ * @param repositoryContext the repository context.
+ * @param generationContext the generation context.
+ * @since 4.0
+ */
+ protected void registerRepositoryCompositionHints(AotRepositoryContext repositoryContext,
GenerationContext generationContext) {
- repositoryContext.getResolvedTypes().stream()
- .filter(it -> !RepositoryRegistrationAotContribution.isJavaOrPrimitiveType(it))
- .forEach(it -> contributeType(it, generationContext));
+ RepositoryInformation repositoryInformation = repositoryContext.getRepositoryInformation();
+
+ if (logger.isTraceEnabled()) {
+ logger.trace(
+ "Contributing repository information for [%s]".formatted(repositoryInformation.getRepositoryInterface()));
+ }
+
+ // Native hints for repository proxy
+ repositoryContext.typeConfiguration(repositoryInformation.getRepositoryInterface(),
+ config -> config.forReflectiveAccess(MemberCategory.INVOKE_PUBLIC_METHODS).repositoryProxy());
+
+ // Native hints for reflective base implementation access
+ repositoryContext.typeConfiguration(repositoryInformation.getRepositoryBaseClass(), config -> config
+ .forReflectiveAccess(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS));
+
+ // Repository Fragments
+ registerFragmentsHints(repositoryInformation.getFragments(), generationContext);
+
+ // Kotlin
+ if (isKotlinCoroutineRepository(repositoryInformation)) {
+ generationContext.getRuntimeHints().reflection().registerTypes(KOTLIN_REFLECTION_TYPE_REFERENCES, hint -> {});
+ }
+ }
+
+ /**
+ * Register type-specific hints and AOT artifacts for domain types, reachable types, projection interfaces derived
+ * from query method return types, and annotations from {@literal org.springframework.data} packages.
+ *
+ * @param repositoryContext the repository context.
+ * @param generationContext the generation context.
+ * @since 4.0
+ */
+ protected void configureTypeContributions(AotRepositoryContext repositoryContext,
+ GenerationContext generationContext) {
+
+ RepositoryInformation information = repositoryContext.getRepositoryInformation();
+
+ configureDomainTypeContributions(repositoryContext, generationContext);
+
+ // Repository query methods
+ information.getQueryMethods().stream().map(information::getReturnedDomainClass).filter(Class::isInterface)
+ .forEach(type -> {
+ if (EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy().test(type,
+ information.getDomainType())) {
+ repositoryContext.typeConfiguration(type, AotTypeConfiguration::usedAsProjectionInterface);
+ }
+ });
repositoryContext.getResolvedAnnotations().stream()
.filter(RepositoryRegistrationAotProcessor::isSpringDataManagedAnnotation).map(MergedAnnotation::getType)
.forEach(it -> contributeType(it, generationContext));
-
- return null;
}
/**
- * Processes the repository's domain and alternative domain types to consider {@link Reflective} annotations used on
- * it.
+ * Customization hook for subclasses that wish to customize domain type hint contributions.
+ *
+ * Type hints are registered for the domain, alternative domain types, and types reachable from there
+ * ({@link AotRepositoryContext#getResolvedTypes()})
*
- * @param repositoryContext must not be {@literal null}.
- * @param generationContext must not be {@literal null}.
+ * @param repositoryContext the repository context.
+ * @param generationContext the generation context.
+ * @since 4.0
*/
- // TODO: Can we merge #contribute, #registerReflectiveForAggregateRoot into RepositoryRegistrationAotContribution?
- // hints and types are contributed from everywhere.
- private void registerReflectiveForAggregateRoot(AotRepositoryContext repositoryContext,
+ private void configureDomainTypeContributions(AotRepositoryContext repositoryContext,
GenerationContext generationContext) {
RepositoryInformation information = repositoryContext.getRepositoryInformation();
- ReflectiveRuntimeHintsRegistrar registrar = new ReflectiveRuntimeHintsRegistrar();
- RuntimeHints hints = generationContext.getRuntimeHints();
Stream.concat(Stream.of(information.getDomainType()), information.getAlternativeDomainTypes().stream())
.forEach(it -> {
+ configureTypeContribution(it, repositoryContext);
+ });
- // arent we already registering the types in RepositoryRegistrationAotContribution#contributeRepositoryInfo?
- registrar.registerRuntimeHints(hints, it);
+ // Domain types my be part of this, but it also contains reachable ones.
+ repositoryContext.getResolvedTypes().stream()
+ .filter(it -> TypeContributor.isPartOf(it, Set.of(information.getDomainType().getPackageName())))
+ .forEach(it -> configureTypeContribution(it, repositoryContext));
- repositoryContext.typeConfiguration(it, AotTypeConfiguration::contributeAccessors);
- });
+ repositoryContext.getResolvedTypes().stream().filter(it -> !isJavaOrPrimitiveType(it))
+ .forEach(it -> contributeType(it, generationContext));
+ }
+
+ /**
+ * Customization hook to configure the {@link TypeContributor} used to register the given {@literal type}.
+ *
+ * @param type the class to configure the contribution for.
+ * @param aotContext AOT context for type configuration.
+ * @since 4.0
+ */
+ protected void configureTypeContribution(Class> type, AotContext aotContext) {
+ aotContext.typeConfiguration(type, config -> config.forDataBinding().contributeAccessors().forQuerydsl());
+ }
+
+ /**
+ * This method allows for the creation to be overridden by subclasses.
+ *
+ * @param repositoryContext the context for the repository being processed.
+ * @return a {@link RepositoryContributor} to contribute store-specific AOT artifacts or {@literal null} to skip
+ * store-specific AOT contributions.
+ * @since 4.0
+ */
+ @Nullable
+ protected RepositoryContributor contributeAotRepository(AotRepositoryContext repositoryContext) {
+ return null;
}
private boolean isRepositoryBean(RegisteredBean bean) {
return getConfigMap().containsKey(bean.getBeanName());
}
- protected @Nullable RepositoryRegistrationAotContribution newRepositoryRegistrationAotContribution(
- RegisteredBean repositoryBean) {
+ private RepositoryConfiguration> getRepositoryMetadata(RegisteredBean bean) {
- RepositoryRegistrationAotContribution contribution = RepositoryRegistrationAotContribution.load(this,
- repositoryBean);
+ RepositoryConfiguration> configuration = getConfigMap().get(bean.getBeanName());
- // cannot contribute a repository bean.
- if (contribution == null) {
- return null;
+ if (configuration == null) {
+ throw new IllegalArgumentException("No configuration for bean [%s]".formatted(bean.getBeanName()));
}
- // TODO: add the hook for customizing bean initialization code here!
-
- return contribution.withModuleContribution((repositoryContext, generationContext) -> {
- registerReflectiveForAggregateRoot(repositoryContext, generationContext);
- return contribute(repositoryContext, generationContext);
- });
+ return configuration;
}
- @Override
- public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
-
- Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory,
- () -> "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
-
- this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
+ private void contributeType(Class> type, GenerationContext context) {
+ TypeContributor.contribute(type, it -> true, context);
}
- @Override
- public void setEnvironment(Environment environment) {
- this.environment = environment;
+ private void registerFragmentsHints(Iterable> fragments, GenerationContext contribution) {
+ fragments.forEach(it -> registerFragmentHints(it, contribution));
}
- @Override
- public Environment getEnvironment() {
- return this.environment;
- }
+ private static void registerFragmentHints(RepositoryFragment> fragment, GenerationContext context) {
- public void setConfigMap(Map> configMap) {
- this.configMap = configMap;
- }
+ Class> repositoryFragmentType = fragment.getSignatureContributor();
+ Optional> implementation = fragment.getImplementationClass();
- public Map> getConfigMap() {
- return this.configMap;
+ registerReflectiveHints(repositoryFragmentType, context);
+
+ implementation.ifPresent(typeToRegister -> registerReflectiveHints(typeToRegister, context));
}
- protected ConfigurableListableBeanFactory getBeanFactory() {
+ private static void registerReflectiveHints(Class> typeToRegister, GenerationContext context) {
- if (this.beanFactory == null) {
- throw new IllegalStateException(
- "No BeanFactory available. Make sure to set the BeanFactory before using this processor.");
- }
+ context.getRuntimeHints().reflection().registerType(typeToRegister, hint -> {
- return this.beanFactory;
- }
+ hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
- protected @Nullable RepositoryConfiguration> getRepositoryMetadata(RegisteredBean bean) {
- return getConfigMap().get(bean.getBeanName());
+ if (!typeToRegister.isInterface()) {
+ hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
+ }
+ });
}
- protected void contributeType(Class> type, GenerationContext generationContext) {
- TypeContributor.contribute(type, it -> true, generationContext);
- }
+ private @Nullable AotRepositoryContext potentiallyCreateContext(Environment environment, RegisteredBean bean) {
- protected Log getLogger() {
- return this.logger;
- }
+ RepositoryBeanDefinitionReader reader = new RepositoryBeanDefinitionReader(bean);
+ RepositoryConfiguration> configuration = reader.getConfiguration();
+ RepositoryConfigurationExtensionSupport extension = reader.getConfigurationExtension();
- protected void logDebug(String message, Object... arguments) {
- logAt(Log::isDebugEnabled, Log::debug, message, arguments);
- }
+ if (configuration == null || extension == null) {
+ logger.warn(
+ "Cannot create AotRepositoryContext for bean [%s]. No RepositoryConfiguration/RepositoryConfigurationExtension. Please make sure to register the repository bean through @Enable…Repositories."
+ .formatted(bean.getBeanName()));
+ return null;
+ }
+ RepositoryInformation repositoryInformation = reader.getRepositoryInformation();
+ DefaultAotRepositoryContext repositoryContext = new DefaultAotRepositoryContext(bean, repositoryInformation,
+ extension.getModuleName(), AotContext.from(bean.getBeanFactory(), environment),
+ configuration.getConfigurationSource());
- protected void logTrace(String message, Object... arguments) {
- logAt(Log::isTraceEnabled, Log::trace, message, arguments);
+ repositoryContext.setIdentifyingAnnotations(extension.getIdentifyingAnnotations());
+
+ return repositoryContext;
}
- private void logAt(Predicate logLevelPredicate, BiConsumer logOperation, String message,
- Object... arguments) {
+ private static boolean isKotlinCoroutineRepository(RepositoryInformation repositoryInformation) {
- Log logger = getLogger();
+ Class> coroutineRepository = org.springframework.data.util.ClassUtils.loadIfPresent(
+ KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME, repositoryInformation.getRepositoryInterface().getClassLoader());
- if (logLevelPredicate.test(logger)) {
- logOperation.accept(logger, String.format(message, arguments));
- }
+ return coroutineRepository != null
+ && ClassUtils.isAssignable(coroutineRepository, repositoryInformation.getRepositoryInterface());
}
private static boolean isSpringDataManagedAnnotation(MergedAnnotation> annotation) {
@@ -229,4 +373,10 @@ private static boolean isSpringDataType(Class> type) {
return type.getPackageName().startsWith(TypeContributor.DATA_NAMESPACE);
}
+ private static boolean isJavaOrPrimitiveType(Class> type) {
+ return ClassUtils.isPrimitiveOrWrapper(type) //
+ || ClassUtils.isPrimitiveArray(type) //
+ || TypeUtils.type(type).isPartOf("java");
+ }
+
}
diff --git a/src/test/java/org/springframework/data/aot/AotContextUnitTests.java b/src/test/java/org/springframework/data/aot/AotContextUnitTests.java
index d164779542..88a365c246 100644
--- a/src/test/java/org/springframework/data/aot/AotContextUnitTests.java
+++ b/src/test/java/org/springframework/data/aot/AotContextUnitTests.java
@@ -17,8 +17,6 @@
import static org.mockito.Mockito.*;
-import java.util.Collection;
-import java.util.List;
import java.util.function.Consumer;
import org.assertj.core.api.Assertions;
@@ -28,6 +26,8 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoSettings;
+
+import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -102,7 +102,7 @@ private void contributeAccessor(Class>... classes) {
context.typeConfiguration(aClass, AotTypeConfiguration::contributeAccessors);
}
- context.typeConfigurations().forEach(it -> it.contribute(mockEnvironment, new TestGenerationContext()));
+ context.contributeTypeConfigurations(new TestGenerationContext());
}
@ParameterizedTest // GH-3322
@@ -163,8 +163,8 @@ public void typeConfiguration(Class> type, Consumer conf
}
@Override
- public Collection typeConfigurations() {
- return List.of();
+ public void contributeTypeConfigurations(GenerationContext generationContext) {
+
}
@Override
diff --git a/src/test/java/org/springframework/data/javapoet/JavaPoetUnitTests.java b/src/test/java/org/springframework/data/javapoet/JavaPoetUnitTests.java
index a39d09fb64..d4346db76b 100644
--- a/src/test/java/org/springframework/data/javapoet/JavaPoetUnitTests.java
+++ b/src/test/java/org/springframework/data/javapoet/JavaPoetUnitTests.java
@@ -188,6 +188,50 @@ void shouldRenderConditionalNumericReturn() {
block = LordOfTheStrings.returning(Integer.class).number("someNumericVariable").otherwise(":-[").build();
assertThat(block).hasToString("return someNumericVariable != null ? someNumericVariable.intValue() : null");
+
+ block = LordOfTheStrings.returning(Short.class).number("someNumericVariable").otherwise(":-[").build();
+ assertThat(block).hasToString("return someNumericVariable != null ? someNumericVariable.shortValue() : null");
+
+ block = LordOfTheStrings.returning(short.class).number("someNumericVariable").otherwise(":-[").build();
+ assertThat(block).hasToString("return someNumericVariable != null ? someNumericVariable.shortValue() : 0");
+
+ block = LordOfTheStrings.returning(Byte.class).number("someNumericVariable").otherwise(":-[").build();
+ assertThat(block).hasToString("return someNumericVariable != null ? someNumericVariable.byteValue() : null");
+
+ block = LordOfTheStrings.returning(Float.class).number("someNumericVariable").otherwise(":-[").build();
+ assertThat(block).hasToString("return someNumericVariable != null ? someNumericVariable.floatValue() : null");
+
+ block = LordOfTheStrings.returning(float.class).number("someNumericVariable").otherwise(":-[").build();
+ assertThat(block).hasToString("return someNumericVariable != null ? someNumericVariable.floatValue() : 0f");
+ }
+
+ @Test // GH-3357
+ void shouldRenderConditionalSafeNumericReturn() {
+
+ CodeBlock block = LordOfTheStrings.returning(boolean.class).nonNullableNumber("someNumericVariable")
+ .otherwise(":-[").build();
+ assertThat(block).hasToString("return :-[");
+
+ block = LordOfTheStrings.returning(long.class).nonNullableNumber("someNumericVariable").otherwise(":-[").build();
+ assertThat(block).hasToString("return someNumericVariable.longValue()");
+
+ block = LordOfTheStrings.returning(Long.class).nonNullableNumber("someNumericVariable").otherwise(":-[").build();
+ assertThat(block).hasToString("return someNumericVariable.longValue()");
+
+ block = LordOfTheStrings.returning(Integer.class).nonNullableNumber("someNumericVariable").otherwise(":-[").build();
+ assertThat(block).hasToString("return someNumericVariable.intValue()");
+
+ block = LordOfTheStrings.returning(short.class).nonNullableNumber("someNumericVariable").otherwise(":-[").build();
+ assertThat(block).hasToString("return someNumericVariable.shortValue()");
+
+ block = LordOfTheStrings.returning(byte.class).nonNullableNumber("someNumericVariable").otherwise(":-[").build();
+ assertThat(block).hasToString("return someNumericVariable.byteValue()");
+
+ block = LordOfTheStrings.returning(Double.class).nonNullableNumber("someNumericVariable").otherwise(":-[").build();
+ assertThat(block).hasToString("return someNumericVariable.doubleValue()");
+
+ block = LordOfTheStrings.returning(Float.class).nonNullableNumber("someNumericVariable").otherwise(":-[").build();
+ assertThat(block).hasToString("return someNumericVariable.floatValue()");
}
@Test // GH-3357
diff --git a/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecoratorUnitTests.java b/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecoratorUnitTests.java
index dadad5311b..d82397b304 100644
--- a/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecoratorUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecoratorUnitTests.java
@@ -56,10 +56,9 @@ class AotRepositoryBeanDefinitionPropertiesDecoratorUnitTests {
void beforeEach() {
when(contributor.getContributedTypeName()).thenReturn(GeneratedTypeReference.of(ClassName.bestGuess(TYPE_NAME)));
+ when(contributor.getAotFragmentMetadata()).thenReturn(metadata);
inheritedSource = CodeBlock.builder();
decorator = new AotRepositoryBeanDefinitionPropertiesDecorator(() -> inheritedSource.build(), contributor);
-
- when(contributor.getConstructorArguments()).thenReturn(metadata.getConstructorArguments());
}
@Test // GH-3344
diff --git a/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryConfigurationUnitTests.java b/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryConfigurationUnitTests.java
index a38f869949..5249edf8f5 100644
--- a/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryConfigurationUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryConfigurationUnitTests.java
@@ -69,7 +69,8 @@ protected void customizeConstructor(AotRepositoryConstructorBuilder builder) {
assertThat(contributedTypeName).isNotNull();
// required constructor arguments need to be present at this point
- Map requiredArgs = new LinkedHashMap<>(contributor.requiredArgs());
+ Map requiredArgs = new LinkedHashMap<>(
+ contributor.getAotFragmentMetadata().getAutowireFields());
assertThat(requiredArgs).hasSize(1);
// decorator kicks in and enhanced the BeanDefinition. No files written so far.
@@ -86,7 +87,7 @@ protected void customizeConstructor(AotRepositoryConstructorBuilder builder) {
// make sure write operation for generated content did not change constructor nor type name
assertThat(contributor.getContributedTypeName()).isEqualTo(contributedTypeName);
- assertThat(contributor.requiredArgs()).containsExactlyEntriesOf(requiredArgs);
+ assertThat(contributor.getAotFragmentMetadata().getAutowireFields()).containsExactlyEntriesOf(requiredArgs);
// file is actually present now
assertThat(generationContext.getGeneratedFiles().getGeneratedFiles(Kind.SOURCE))
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 7e4e0747b8..1b3a7861c9 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
@@ -15,20 +15,17 @@
*/
package org.springframework.data.repository.aot.generate;
-import java.io.IOException;
import java.lang.annotation.Annotation;
-import java.util.Collection;
-import java.util.List;
import java.util.Set;
-import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.annotation.MergedAnnotation;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.test.tools.ClassFile;
-import org.springframework.data.aot.AotTypeConfiguration;
+import org.springframework.data.aot.AotContext;
import org.springframework.data.repository.config.AotRepositoryContext;
+import org.springframework.data.repository.config.AotRepositoryContextSupport;
import org.springframework.data.repository.config.RepositoryConfigurationSource;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryComposition;
@@ -39,12 +36,12 @@
*
* @author Christoph Strobl
*/
-class DummyModuleAotRepositoryContext implements AotRepositoryContext {
+class DummyModuleAotRepositoryContext extends AotRepositoryContextSupport {
private final StubRepositoryInformation repositoryInformation;
- private final MockEnvironment environment = new MockEnvironment();
public DummyModuleAotRepositoryContext(Class> repositoryInterface, @Nullable RepositoryComposition composition) {
+ super(AotContext.from(new DefaultListableBeanFactory(), new MockEnvironment()));
this.repositoryInformation = new StubRepositoryInformation(repositoryInterface, composition);
}
@@ -65,7 +62,7 @@ public ConfigurableListableBeanFactory getBeanFactory() {
@Override
public MockEnvironment getEnvironment() {
- return environment;
+ return (MockEnvironment) super.getEnvironment();
}
@Override
@@ -78,16 +75,6 @@ public IntrospectedBeanDefinition introspectBeanDefinition(String beanName) {
return null;
}
- @Override
- public void typeConfiguration(Class> type, Consumer configurationConsumer) {
-
- }
-
- @Override
- public Collection typeConfigurations() {
- return List.of();
- }
-
@Override
public String getBeanName() {
return "dummyRepository";
@@ -118,24 +105,4 @@ public Set> getResolvedTypes() {
return Set.of();
}
- @Override
- public Set> getUserDomainTypes() {
- return Set.of();
- }
-
- public List getRequiredContextFiles() {
- return List.of(classFileForType(repositoryInformation.getRepositoryBaseClass()));
- }
-
- static ClassFile classFileForType(Class> type) {
-
- String name = type.getName();
- ClassPathResource cpr = new ClassPathResource(name.replaceAll("\\.", "/") + ".class");
-
- try {
- return ClassFile.of(name, cpr.getContentAsByteArray());
- } catch (IOException e) {
- throw new IllegalArgumentException("Cannot open [%s].".formatted(cpr.getPath()));
- }
- }
}