Browse Source

Refine AOT Repositories infrastructure.

Decouple RepositoryRegistrationAotContribution and RepositoryRegistrationAotProcessor, introduce support class for AotRepositoryContext implementations to better protect AotRepositoryContext implementations from changes to AotContext.

Remove contribute method from AotTypeConfiguration and move contribution code to AotContext.

Deprecate introspection methods for removal with only a single use in Commons and no usage in other modules.

Add more fine-grained customization hooks to RepositoryRegistrationAotProcessor and remove superfluous context objects that aren't necessary in their context.

Closes: #3267
Original Pull Request: #3367
labs/stable-value
Mark Paluch 4 months ago committed by Christoph Strobl
parent
commit
87565aefb6
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 49
      src/main/java/org/springframework/data/aot/AotContext.java
  2. 11
      src/main/java/org/springframework/data/aot/AotTypeConfiguration.java
  3. 15
      src/main/java/org/springframework/data/aot/DefaultAotContext.java
  4. 5
      src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java
  5. 7
      src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecorator.java
  6. 18
      src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java
  7. 9
      src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryFragmentMetadata.java
  8. 29
      src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
  9. 11
      src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java
  10. 99
      src/main/java/org/springframework/data/repository/config/AotRepositoryContextSupport.java
  11. 64
      src/main/java/org/springframework/data/repository/config/DefaultAotRepositoryContext.java
  12. 287
      src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java
  13. 324
      src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java
  14. 10
      src/test/java/org/springframework/data/aot/AotContextUnitTests.java
  15. 3
      src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecoratorUnitTests.java
  16. 5
      src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryConfigurationUnitTests.java
  17. 47
      src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java

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

@ -24,6 +24,8 @@ import java.util.Set; @@ -24,6 +24,8 @@ import java.util.Set;
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 @@ import org.springframework.util.StringUtils; @@ -40,9 +42,7 @@ import org.springframework.util.StringUtils;
/**
* 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}.
* <p>
* Mainly for internal use within the framework.
*
@ -99,7 +99,7 @@ public interface AotContext extends EnvironmentCapable { @@ -99,7 +99,7 @@ public interface AotContext extends EnvironmentCapable {
* @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 @@ public interface AotContext extends EnvironmentCapable { @@ -119,13 +119,13 @@ public interface AotContext extends EnvironmentCapable {
}
/**
* 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})
* <p>
* 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 @@ public interface AotContext extends EnvironmentCapable { @@ -177,8 +177,12 @@ public interface AotContext extends EnvironmentCapable {
*
* @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 @@ public interface AotContext extends EnvironmentCapable { @@ -201,7 +205,9 @@ public interface AotContext extends EnvironmentCapable {
* @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<Class<?>> scanPackageForTypes(Collection<Class<? extends Annotation>> identifyingAnnotations,
Collection<String> packageNames) {
@ -214,7 +220,9 @@ public interface AotContext extends EnvironmentCapable { @@ -214,7 +220,9 @@ public interface AotContext extends EnvironmentCapable {
*
* @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 @@ public interface AotContext extends EnvironmentCapable { @@ -225,15 +233,20 @@ public interface AotContext extends EnvironmentCapable {
*
* @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<AotTypeConfiguration> configurationConsumer) {
typeConfiguration(resolvableType.toClass(), configurationConsumer);
@ -241,24 +254,29 @@ public interface AotContext extends EnvironmentCapable { @@ -241,24 +254,29 @@ public interface AotContext extends EnvironmentCapable {
/**
* 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 type the type to configure.
* @param configurationConsumer configuration consumer function.
* @since 4.0
*/
void typeConfiguration(Class<?> type, Consumer<AotTypeConfiguration> 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<AotTypeConfiguration> 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 @@ public interface AotContext extends EnvironmentCapable { @@ -318,7 +336,10 @@ public interface AotContext extends EnvironmentCapable {
/**
* 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 {
/**

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

@ -22,19 +22,15 @@ import java.util.stream.Stream; @@ -22,19 +22,15 @@ import java.util.stream.Stream;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.Advised;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.TypeReference;
import org.springframework.core.DecoratingProxy;
import org.springframework.core.env.Environment;
import org.springframework.data.projection.TargetAware;
/**
* 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
@ -134,11 +130,4 @@ public interface AotTypeConfiguration { @@ -134,11 +130,4 @@ public interface AotTypeConfiguration {
*/
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);
}

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

@ -17,7 +17,6 @@ package org.springframework.data.aot; @@ -17,7 +17,6 @@ package org.springframework.data.aot;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -30,6 +29,7 @@ import java.util.function.Supplier; @@ -30,6 +29,7 @@ import java.util.function.Supplier;
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;
@ -55,13 +55,13 @@ import org.springframework.util.StringUtils; @@ -55,13 +55,13 @@ import org.springframework.util.StringUtils;
* @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<Class<?>, AotTypeConfiguration> typeConfigurations = new HashMap<>();
private final Map<Class<?>, ContextualTypeConfiguration> typeConfigurations = new HashMap<>();
private final Environment environment;
public DefaultAotContext(BeanFactory beanFactory, Environment environment) {
@ -101,10 +101,13 @@ class DefaultAotContext implements AotContext { @@ -101,10 +101,13 @@ class DefaultAotContext implements AotContext {
}
@Override
public Collection<AotTypeConfiguration> 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 +147,7 @@ class DefaultAotContext implements AotContext { @@ -144,6 +147,7 @@ class DefaultAotContext implements AotContext {
}
}
@SuppressWarnings("removal")
class DefaultIntrospectedBeanDefinition implements IntrospectedBeanDefinition {
private final String beanName;
@ -227,7 +231,6 @@ class DefaultAotContext implements AotContext { @@ -227,7 +231,6 @@ class DefaultAotContext implements AotContext {
return this;
}
@Override
public void contribute(Environment environment, GenerationContext generationContext) {
try {

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

@ -155,10 +155,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio @@ -155,10 +155,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
Set<String> annotationNamespaces = Collections.singleton(TypeContributor.DATA_NAMESPACE);
configureTypeContribution(type.toClass(), aotContext);
aotContext.typeConfiguration(type, config -> {
config.contribute(environment.get(), generationContext);
});
aotContext.contributeTypeConfigurations(generationContext);
TypeUtils.resolveUsedAnnotations(type.toClass()).forEach(
annotation -> TypeContributor.contribute(annotation.getType(), annotationNamespaces, generationContext));

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

@ -41,8 +41,8 @@ import org.springframework.util.StringUtils; @@ -41,8 +41,8 @@ import org.springframework.util.StringUtils;
/**
* 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 @@ public class AotRepositoryBeanDefinitionPropertiesDecorator { @@ -128,7 +128,8 @@ public class AotRepositoryBeanDefinitionPropertiesDecorator {
CodeBlock.Builder callback = CodeBlock.builder();
List<Object> arguments = new ArrayList<>();
for (Entry<String, ConstructorArgument> entry : repositoryContributor.getConstructorArguments().entrySet()) {
for (Entry<String, ConstructorArgument> entry : repositoryContributor.getAotFragmentMetadata()
.getConstructorArguments().entrySet()) {
ConstructorArgument argument = entry.getValue();
AotRepositoryConstructorBuilder.ParameterOrigin parameterOrigin = argument.parameterOrigin();

18
src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java

@ -19,9 +19,7 @@ import java.lang.reflect.Method; @@ -19,9 +19,7 @@ import java.lang.reflect.Method;
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.Log; @@ -30,9 +28,7 @@ import org.apache.commons.logging.Log;
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 @@ class AotRepositoryCreator { @@ -100,18 +96,8 @@ class AotRepositoryCreator {
return repositoryInformation.getRepositoryInterface().getPackageName();
}
Map<String, ResolvableType> getAutowireFields() {
Map<String, ResolvableType> autowireFields = new LinkedHashMap<>(
generationMetadata.getConstructorArguments().size());
for (Map.Entry<String, ConstructorArgument> entry : generationMetadata.getConstructorArguments().entrySet()) {
autowireFields.put(entry.getKey(), entry.getValue().parameterType());
}
return autowireFields;
}
Map<String, ConstructorArgument> getConstructorArguments() {
return generationMetadata.getConstructorArguments();
AotRepositoryFragmentMetadata getRepositoryMetadata() {
return generationMetadata;
}
RepositoryInformation getRepositoryInformation() {

9
src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryFragmentMetadata.java

@ -129,6 +129,15 @@ class AotRepositoryFragmentMetadata { @@ -129,6 +129,15 @@ class AotRepositoryFragmentMetadata {
return constructorArguments;
}
Map<String, ResolvableType> getAutowireFields() {
Map<String, ResolvableType> autowireFields = new LinkedHashMap<>(getConstructorArguments().size());
for (Map.Entry<String, ConstructorArgument> entry : getConstructorArguments().entrySet()) {
autowireFields.put(entry.getKey(), entry.getValue().parameterType());
}
return autowireFields;
}
public Map<String, LocalMethod> getMethods() {
return methods;
}

29
src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java

@ -18,19 +18,18 @@ package org.springframework.data.repository.aot.generate; @@ -18,19 +18,18 @@ package org.springframework.data.repository.aot.generate;
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 { @@ -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 @@ public class RepositoryContributor { @@ -102,28 +101,10 @@ public class RepositoryContributor {
}
/**
* 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.
* <p>
* 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<String, ResolvableType> requiredArgs() {
return Collections.unmodifiableMap(creator.getAutowireFields());
}
/**
* Get the required constructor arguments for the to be generated repository implementation.
* <p>
* 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<String, AotRepositoryFragmentMetadata.ConstructorArgument> getConstructorArguments() {
return Collections.unmodifiableMap(creator.getConstructorArguments());
AotRepositoryFragmentMetadata getAotFragmentMetadata() {
return creator.getRepositoryMetadata();
}
/**

11
src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java

@ -36,8 +36,12 @@ public interface AotRepositoryContext extends AotContext { @@ -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 { @@ -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<String> getBasePackages() {
return getConfigurationSource().getBasePackages().toSet();
}
@ -79,6 +86,4 @@ public interface AotRepositoryContext extends AotContext { @@ -79,6 +86,4 @@ public interface AotRepositoryContext extends AotContext {
*/
Set<Class<?>> getResolvedTypes();
Set<Class<?>> getUserDomainTypes();
}

99
src/main/java/org/springframework/data/repository/config/AotRepositoryContextSupport.java

@ -0,0 +1,99 @@ @@ -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<AotTypeConfiguration> configurationConsumer) {
aotContext.typeConfiguration(resolvableType, configurationConsumer);
}
@Override
public void typeConfiguration(Class<?> type, Consumer<AotTypeConfiguration> configurationConsumer) {
aotContext.typeConfiguration(type, configurationConsumer);
}
@Override
public void contributeTypeConfigurations(GenerationContext generationContext) {
aotContext.contributeTypeConfigurations(generationContext);
}
}

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

@ -20,19 +20,14 @@ import java.util.Collection; @@ -20,19 +20,14 @@ import java.util.Collection;
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 @@ import org.springframework.data.util.TypeUtils; @@ -44,10 +39,8 @@ import org.springframework.data.util.TypeUtils;
* @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 { @@ -55,23 +48,19 @@ class DefaultAotRepositoryContext implements AotRepositoryContext {
private final Lazy<Set<MergedAnnotation<Annotation>>> resolvedAnnotations = Lazy.of(this::discoverAnnotations);
private final Lazy<Set<Class<?>>> managedTypes = Lazy.of(this::discoverTypes);
private Set<String> basePackages = Collections.emptySet();
private Collection<Class<? extends Annotation>> 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 @@ class DefaultAotRepositoryContext implements AotRepositoryContext { @@ -84,25 +73,6 @@ class DefaultAotRepositoryContext implements AotRepositoryContext {
return configurationSource;
}
@Override
public ConfigurableListableBeanFactory getBeanFactory() {
return getAotContext().getBeanFactory();
}
@Override
public Environment getEnvironment() {
return getAotContext().getEnvironment();
}
@Override
public Set<String> getBasePackages() {
return basePackages;
}
public void setBasePackages(Set<String> basePackages) {
this.basePackages = basePackages;
}
@Override
public String getBeanName() {
return beanName;
@ -136,29 +106,6 @@ class DefaultAotRepositoryContext implements AotRepositoryContext { @@ -136,29 +106,6 @@ class DefaultAotRepositoryContext implements AotRepositoryContext {
return managedTypes.get();
}
@Override
public Set<Class<?>> 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<AotTypeConfiguration> configurationConsumer) {
aotContext.typeConfiguration(type, configurationConsumer);
}
@Override
public Collection<AotTypeConfiguration> typeConfigurations() {
return aotContext.typeConfigurations();
}
@Override
public AotContext.IntrospectedBeanDefinition introspectBeanDefinition(String beanName) {
return aotContext.introspectBeanDefinition(beanName);
@ -185,7 +132,8 @@ class DefaultAotRepositoryContext implements AotRepositoryContext { @@ -185,7 +132,8 @@ class DefaultAotRepositoryContext implements AotRepositoryContext {
if (!getIdentifyingAnnotations().isEmpty()) {
Set<Class<?>> classes = aotContext.getTypeScanner().scanPackages(getBasePackages())
Set<Class<?>> classes = aotContext.getTypeScanner()
.scanPackages(getConfigurationSource().getBasePackages().toSet())
.forTypesAnnotatedWith(getIdentifyingAnnotations()).collectAsSet();
types.addAll(TypeCollector.inspect(classes).list());
}

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

@ -15,43 +15,21 @@ @@ -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 @@ import org.springframework.util.ClassUtils; @@ -61,174 +39,32 @@ import org.springframework.util.ClassUtils;
* @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> environment = Lazy.of(StandardEnvironment::new);
private @Nullable BiFunction<AotRepositoryContext, GenerationContext, @Nullable RepositoryContributor> 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<AotRepositoryContext, GenerationContext, @Nullable RepositoryContributor> 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<AotRepositoryContext, GenerationContext, @Nullable RepositoryContributor> 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 class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -245,107 +81,16 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
Supplier<CodeBlock> 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<Class<?>> 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<TypeReference> 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); //
}
}

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

@ -17,17 +17,20 @@ package org.springframework.data.repository.config; @@ -17,17 +17,20 @@ package org.springframework.data.repository.config;
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.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.annotation.Reflective;
import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
@ -43,12 +46,18 @@ import org.springframework.core.annotation.MergedAnnotation; @@ -43,12 +46,18 @@ import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.env.Environment;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.data.aot.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 +66,13 @@ import org.springframework.util.Assert; @@ -57,13 +66,13 @@ import org.springframework.util.Assert;
* 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}.
* </p>
* <p>
* 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
* {@link Annotation annotations} from the {@literal org.springframework.data} namespace.
* </p>
* With {@link RepositoryRegistrationAotProcessor#contributeRepositoryHints(AotRepositoryContext, GenerationContext)}
* and {@link RepositoryRegistrationAotProcessor#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.
* <p>
* The processor is typically configured via {@link RepositoryConfigurationExtension#getRepositoryAotProcessor()} and
* gets added by the {@link org.springframework.data.repository.config.RepositoryConfigurationDelegate}.
*
@ -75,6 +84,18 @@ import org.springframework.util.Assert; @@ -75,6 +84,18 @@ import org.springframework.util.Assert;
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<TypeReference> 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 +104,265 @@ public class RepositoryRegistrationAotProcessor @@ -83,140 +104,265 @@ public class RepositoryRegistrationAotProcessor
private Map<String, RepositoryConfiguration<?>> 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<String, RepositoryConfiguration<?>> configMap) {
this.configMap = configMap;
}
public Map<String, RepositoryConfiguration<?>> 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) -> {
contributeRepositoryHints(repositoryContext, generationContext);
contributeTypes(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 contributeRepositoryHints(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
contributeFragments(repositoryInformation.getFragments(), generationContext);
// Kotlin
if (isKotlinCoroutineRepository(repositoryInformation)) {
generationContext.getRuntimeHints().reflection().registerTypes(KOTLIN_REFLECTION_TYPE_REFERENCES, hint -> {});
}
}
/**
* Contribute types for reflection, proxies, etc. Customization hook for subclasses that wish to customize type
* contribution hints.
*
* @param repositoryContext the repository context.
* @param generationContext the generation context.
* @since 4.0
*/
protected void contributeTypes(AotRepositoryContext repositoryContext, GenerationContext generationContext) {
contributeDomainTypes(repositoryContext, generationContext);
contributeResolvedTypes(repositoryContext, generationContext);
RepositoryInformation information = repositoryContext.getRepositoryInformation();
// 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 contribution hints.
*
* @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,
GenerationContext generationContext) {
protected void contributeDomainTypes(AotRepositoryContext repositoryContext, GenerationContext generationContext) {
RepositoryInformation information = repositoryContext.getRepositoryInformation();
ReflectiveRuntimeHintsRegistrar registrar = new ReflectiveRuntimeHintsRegistrar();
RuntimeHints hints = generationContext.getRuntimeHints();
// Domain types, related types, projections
repositoryContext.typeConfiguration(information.getDomainType(), config -> config.forDataBinding().forQuerydsl());
ReflectiveRuntimeHintsRegistrar registrar = new ReflectiveRuntimeHintsRegistrar();
Stream.concat(Stream.of(information.getDomainType()), information.getAlternativeDomainTypes().stream())
.forEach(it -> {
// arent we already registering the types in RepositoryRegistrationAotContribution#contributeRepositoryInfo?
// TODO cross check with #contributeResolvedTypes
registrar.registerRuntimeHints(hints, it);
repositoryContext.typeConfiguration(it, AotTypeConfiguration::contributeAccessors);
});
}
private boolean isRepositoryBean(RegisteredBean bean) {
return getConfigMap().containsKey(bean.getBeanName());
}
private void contributeResolvedTypes(AotRepositoryContext repositoryContext, GenerationContext generationContext) {
protected @Nullable RepositoryRegistrationAotContribution newRepositoryRegistrationAotContribution(
RegisteredBean repositoryBean) {
RepositoryInformation information = repositoryContext.getRepositoryInformation();
RepositoryRegistrationAotContribution contribution = RepositoryRegistrationAotContribution.load(this,
repositoryBean);
// TODO: These are twice.
repositoryContext.getResolvedTypes().stream()
.filter(it -> TypeContributor.isPartOf(it, Set.of(information.getDomainType().getPackageName())))
.forEach(it -> repositoryContext.typeConfiguration(it, AotTypeConfiguration::contributeAccessors));
// cannot contribute a repository bean.
if (contribution == null) {
return null;
}
repositoryContext.getResolvedTypes().stream().filter(it -> !isJavaOrPrimitiveType(it))
.forEach(it -> contributeType(it, generationContext));
}
// TODO: add the hook for customizing bean initialization code here!
/**
* 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;
}
return contribution.withModuleContribution((repositoryContext, generationContext) -> {
registerReflectiveForAggregateRoot(repositoryContext, generationContext);
return contribute(repositoryContext, generationContext);
});
private boolean isRepositoryBean(RegisteredBean bean) {
return getConfigMap().containsKey(bean.getBeanName());
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
private RepositoryConfiguration<?> getRepositoryMetadata(RegisteredBean bean) {
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory,
() -> "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
RepositoryConfiguration<?> configuration = getConfigMap().get(bean.getBeanName());
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
if (configuration == null) {
throw new IllegalArgumentException("No configuration for bean [%s]".formatted(bean.getBeanName()));
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
return configuration;
}
@Override
public Environment getEnvironment() {
return this.environment;
private void contributeType(Class<?> type, GenerationContext context) {
TypeContributor.contribute(type, it -> true, context);
}
public void setConfigMap(Map<String, RepositoryConfiguration<?>> configMap) {
this.configMap = configMap;
private void contributeFragments(Iterable<RepositoryFragment<?>> fragments, GenerationContext contribution) {
fragments.forEach(it -> contributeFragment(it, contribution));
}
public Map<String, RepositoryConfiguration<?>> getConfigMap() {
return this.configMap;
}
private static void contributeFragment(RepositoryFragment<?> fragment, GenerationContext context) {
protected ConfigurableListableBeanFactory getBeanFactory() {
Class<?> repositoryFragmentType = fragment.getSignatureContributor();
Optional<Class<?>> implementation = fragment.getImplementationClass();
if (this.beanFactory == null) {
throw new IllegalStateException(
"No BeanFactory available. Make sure to set the BeanFactory before using this processor.");
}
registerReflectiveHints(repositoryFragmentType, context);
return this.beanFactory;
implementation.ifPresent(typeToRegister -> registerReflectiveHints(typeToRegister, context));
}
protected @Nullable RepositoryConfiguration<?> getRepositoryMetadata(RegisteredBean bean) {
return getConfigMap().get(bean.getBeanName());
}
private static void registerReflectiveHints(Class<?> typeToRegister, GenerationContext context) {
protected void contributeType(Class<?> type, GenerationContext generationContext) {
TypeContributor.contribute(type, it -> true, generationContext);
}
context.getRuntimeHints().reflection().registerType(typeToRegister, hint -> {
protected Log getLogger() {
return this.logger;
}
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
protected void logDebug(String message, Object... arguments) {
logAt(Log::isDebugEnabled, Log::debug, message, arguments);
if (!typeToRegister.isInterface()) {
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
});
}
protected void logTrace(String message, Object... arguments) {
logAt(Log::isTraceEnabled, Log::trace, message, arguments);
private @Nullable AotRepositoryContext potentiallyCreateContext(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;
}
private void logAt(Predicate<Log> logLevelPredicate, BiConsumer<Log, String> 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 +375,10 @@ public class RepositoryRegistrationAotProcessor @@ -229,4 +375,10 @@ public class RepositoryRegistrationAotProcessor
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");
}
}

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

@ -17,8 +17,6 @@ package org.springframework.data.aot; @@ -17,8 +17,6 @@ package org.springframework.data.aot;
import static org.mockito.Mockito.*;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import org.assertj.core.api.Assertions;
@ -28,6 +26,8 @@ import org.junit.jupiter.params.provider.CsvSource; @@ -28,6 +26,8 @@ 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.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 @@ class AotContextUnitTests { @@ -102,7 +102,7 @@ class AotContextUnitTests {
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 @@ class AotContextUnitTests { @@ -163,8 +163,8 @@ class AotContextUnitTests {
}
@Override
public Collection<AotTypeConfiguration> typeConfigurations() {
return List.of();
public void contributeTypeConfigurations(GenerationContext generationContext) {
}
@Override

3
src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecoratorUnitTests.java

@ -56,10 +56,9 @@ class AotRepositoryBeanDefinitionPropertiesDecoratorUnitTests { @@ -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

5
src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryConfigurationUnitTests.java

@ -69,7 +69,8 @@ class AotRepositoryConfigurationUnitTests { @@ -69,7 +69,8 @@ class AotRepositoryConfigurationUnitTests {
assertThat(contributedTypeName).isNotNull();
// required constructor arguments need to be present at this point
Map<String, ResolvableType> requiredArgs = new LinkedHashMap<>(contributor.requiredArgs());
Map<String, ResolvableType> 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 @@ class AotRepositoryConfigurationUnitTests { @@ -86,7 +87,7 @@ class AotRepositoryConfigurationUnitTests {
// 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))

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

@ -15,20 +15,17 @@ @@ -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 @@ import org.springframework.mock.env.MockEnvironment; @@ -39,12 +36,12 @@ import org.springframework.mock.env.MockEnvironment;
*
* @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 @@ class DummyModuleAotRepositoryContext implements AotRepositoryContext { @@ -65,7 +62,7 @@ class DummyModuleAotRepositoryContext implements AotRepositoryContext {
@Override
public MockEnvironment getEnvironment() {
return environment;
return (MockEnvironment) super.getEnvironment();
}
@Override
@ -78,16 +75,6 @@ class DummyModuleAotRepositoryContext implements AotRepositoryContext { @@ -78,16 +75,6 @@ class DummyModuleAotRepositoryContext implements AotRepositoryContext {
return null;
}
@Override
public void typeConfiguration(Class<?> type, Consumer<AotTypeConfiguration> configurationConsumer) {
}
@Override
public Collection<AotTypeConfiguration> typeConfigurations() {
return List.of();
}
@Override
public String getBeanName() {
return "dummyRepository";
@ -118,24 +105,4 @@ class DummyModuleAotRepositoryContext implements AotRepositoryContext { @@ -118,24 +105,4 @@ class DummyModuleAotRepositoryContext implements AotRepositoryContext {
return Set.of();
}
@Override
public Set<Class<?>> getUserDomainTypes() {
return Set.of();
}
public List<ClassFile> 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()));
}
}
}

Loading…
Cancel
Save