Browse Source

Open up AuditingBeanDefinitionRegistrarSupport.registerAuditHandlerBeanDefinition(…) to allow additional bean registrations.

Original Pull Request: #2624
pull/2652/head
Mark Paluch 4 years ago committed by Christoph Strobl
parent
commit
0a423ac3f5
No known key found for this signature in database
GPG Key ID: 8CC1AB53391458C8
  1. 264
      src/main/java/org/springframework/data/aot/AotContext.java
  2. 11
      src/main/java/org/springframework/data/aot/AotRepositoryInformation.java
  3. 139
      src/main/java/org/springframework/data/aot/DefaultAotContext.java
  4. 26
      src/main/java/org/springframework/data/aot/DefaultAotRepositoryContext.java
  5. 6
      src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java
  6. 10
      src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java
  7. 6
      src/main/java/org/springframework/data/aot/Predicates.java
  8. 47
      src/main/java/org/springframework/data/aot/RepositoryBeanDefinitionReader.java
  9. 189
      src/main/java/org/springframework/data/aot/RepositoryRegistrationAotContribution.java
  10. 67
      src/main/java/org/springframework/data/aot/RepositoryRegistrationAotProcessor.java
  11. 16
      src/main/java/org/springframework/data/aot/SpringDataBeanFactoryInitializationAotProcessor.java
  12. 46
      src/main/java/org/springframework/data/aot/TypeCollector.java
  13. 30
      src/main/java/org/springframework/data/aot/TypeUtils.java
  14. 5
      src/main/java/org/springframework/data/aot/hint/AuditingHints.java
  15. 5
      src/main/java/org/springframework/data/aot/hint/package-info.java
  16. 6
      src/main/java/org/springframework/data/aot/package-info.java
  17. 23
      src/main/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupport.java
  18. 32
      src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java
  19. 6
      src/main/java/org/springframework/data/repository/config/RepositoryConfigurationAdapter.java
  20. 12
      src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java
  21. 34
      src/main/java/org/springframework/data/repository/config/RepositoryFragmentConfigurationProvider.java
  22. 1
      src/main/java/org/springframework/data/repository/core/RepositoryInformationSupport.java
  23. 2
      src/main/java/org/springframework/data/repository/core/RepositoryMetadata.java
  24. 10
      src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java
  25. 37
      src/test/java/org/springframework/data/aot/CodeContributionAssert.java
  26. 1
      src/test/java/org/springframework/data/aot/JdkProxyAssert.java
  27. 4
      src/test/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessorUnitTests.java
  28. 56
      src/test/java/org/springframework/data/aot/RepositoryRegistrationAotContributionAssert.java
  29. 56
      src/test/java/org/springframework/data/aot/RepositoryRegistrationAotProcessorIntegrationTests.java
  30. 3
      src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java

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

@ -17,6 +17,7 @@ package org.springframework.data.aot;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -26,26 +27,23 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.data.util.TypeScanner; import org.springframework.data.util.TypeScanner;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/** /**
* The context in which the AOT processing happens. * 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
* Grants access to the {@link ConfigurableListableBeanFactory beanFactory} and {@link ClassLoader}. Holds a few * {@link #isTypePresent(String) is present} and allows resolution of them throug {@link TypeIntrospector} and
* convenience methods to check if a type {@link #isTypePresent(String) is present} and allows resolution of them. * {@link IntrospectedBeanDefinition}.
* * <p>
* <strong>WARNING:</strong> Unstable internal API! * Mainly for internal use within the framework.
* *
* @author Christoph Strobl * @author Christoph Strobl
* @author John Blum * @author John Blum
* @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory * @author Mark Paluch
* @see BeanFactory
* @since 3.0 * @since 3.0
*/ */
public interface AotContext { public interface AotContext {
@ -57,22 +55,11 @@ public interface AotContext {
* @return a new instance of {@link AotContext}. * @return a new instance of {@link AotContext}.
* @see BeanFactory * @see BeanFactory
*/ */
static AotContext from(@NonNull BeanFactory beanFactory) { static AotContext from(BeanFactory beanFactory) {
Assert.notNull(beanFactory, "BeanFactory must not be null"); Assert.notNull(beanFactory, "BeanFactory must not be null");
return new AotContext() { return new DefaultAotContext(beanFactory);
private final ConfigurableListableBeanFactory bf = beanFactory instanceof ConfigurableListableBeanFactory
? (ConfigurableListableBeanFactory) beanFactory
: new DefaultListableBeanFactory(beanFactory);
@NonNull
@Override
public ConfigurableListableBeanFactory getBeanFactory() {
return bf;
}
};
} }
/** /**
@ -84,10 +71,9 @@ public interface AotContext {
ConfigurableListableBeanFactory getBeanFactory(); ConfigurableListableBeanFactory getBeanFactory();
/** /**
* Returns the {@link ClassLoader} used by this {@link AotContext} to resolve {@link Class types}. * Returns the {@link ClassLoader} used by this {@link AotContext} to resolve {@link Class types}. By default, this is
* * the same {@link ClassLoader} used by the {@link BeanFactory} to resolve {@link Class types} declared in bean
* By default, this is the same {@link ClassLoader} used by the {@link BeanFactory} to resolve {@link Class types} * definitions.
* declared in bean definitions.
* *
* @return the {@link ClassLoader} used by this {@link AotContext} to resolve {@link Class types}. * @return the {@link ClassLoader} used by this {@link AotContext} to resolve {@link Class types}.
* @see ConfigurableListableBeanFactory#getBeanClassLoader() * @see ConfigurableListableBeanFactory#getBeanClassLoader()
@ -98,184 +84,196 @@ public interface AotContext {
} }
/** /**
* Determines whether the given {@link String named} {@link Class type} is present on the application classpath. * Returns the required {@link ClassLoader} used by this {@link AotContext} to resolve {@link Class types}. By
* default, this is the same {@link ClassLoader} used by the {@link BeanFactory} to resolve {@link Class types}
* declared in bean definitions.
* *
* @param typeName {@link String name} of the {@link Class type} to evaluate; must not be {@literal null}. * @return the {@link ClassLoader} used by this {@link AotContext} to resolve {@link Class types}.
* @return {@literal true} if the given {@link String named} {@link Class type} is present * @throws IllegalStateException if no {@link ClassLoader} is available.
* on the application classpath.
* @see #getClassLoader()
*/ */
default boolean isTypePresent(@NonNull String typeName) { default ClassLoader getRequiredClassLoader() {
return ClassUtils.isPresent(typeName, getClassLoader());
ClassLoader loader = getClassLoader();
if (loader == null) {
throw new IllegalStateException("Required ClassLoader is not available");
}
return loader;
} }
/**
* Returns a {@link TypeIntrospector} to obtain further detail about a {@link Class type} given its fully-qualified
* type name
*
* @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.
*/
TypeIntrospector introspectType(String typeName);
/** /**
* Returns a new {@link TypeScanner} used to scan for {@link Class types} that will be contributed to the AOT * Returns a new {@link TypeScanner} used to scan for {@link Class types} that will be contributed to the AOT
* processing infrastructure. * processing infrastructure.
* *
* @return a {@link TypeScanner} used to scan for {@link Class types} that will be contributed to the AOT * @return a {@link TypeScanner} used to scan for {@link Class types} that will be contributed to the AOT processing
* processing infrastructure. * infrastructure.
* @see TypeScanner * @see TypeScanner
*/ */
@NonNull
default TypeScanner getTypeScanner() { default TypeScanner getTypeScanner() {
return TypeScanner.typeScanner(getClassLoader()); return TypeScanner.typeScanner(getRequiredClassLoader());
} }
/** /**
* Scans for {@link Class types} in the given {@link String named packages} annotated with the store-specific * Scans for {@link Class types} in the given {@link String named packages} annotated with the store-specific
* {@link Annotation identifying annotations}. * {@link Annotation identifying annotations}.
* *
* @param identifyingAnnotations {@link Collection} of {@link Annotation Annotations} identifying store-specific * @param identifyingAnnotations {@link Collection} of {@link Annotation Annotations} identifying store-specific model
* model {@link Class types}; must not be {@literal null}. * {@link Class types}; must not be {@literal null}.
* @param packageNames {@link Collection} of {@link String package names} to scan. * @param packageNames {@link Collection} of {@link String package names} to scan.
* @return a {@link Set} of {@link Class types} found during the scan. * @return a {@link Set} of {@link Class types} found during the scan.
* @see #getTypeScanner() * @see #getTypeScanner()
*/ */
default Set<Class<?>> scanPackageForTypes(@NonNull Collection<Class<? extends Annotation>> identifyingAnnotations, default Set<Class<?>> scanPackageForTypes(Collection<Class<? extends Annotation>> identifyingAnnotations,
Collection<String> packageNames) { Collection<String> packageNames) {
return getTypeScanner().scanPackages(packageNames).forTypesAnnotatedWith(identifyingAnnotations).collectAsSet(); return getTypeScanner().scanPackages(packageNames).forTypesAnnotatedWith(identifyingAnnotations).collectAsSet();
} }
/** /**
* Resolves the required {@link String named} {@link Class type}. * Returns a {@link IntrospectedBeanDefinition} to obtain further detail about the underlying bean definition. A
* introspected bean definition can also point to an absent bean definition.
* *
* @param typeName {@link String} containing the {@literal fully-qualified class name} of the {@link Class type} * @param reference {@link BeanReference} to the managed bean.
* to resolve; must not be {@literal null}. * @return the introspected bean definition.
* @return a resolved {@link Class type} for the given, required {@link String name}.
* @throws TypeNotPresentException if the {@link String named} {@link Class type} cannot be found.
*/ */
@NonNull default IntrospectedBeanDefinition introspectBeanDefinition(BeanReference reference) {
default Class<?> resolveRequiredType(@NonNull String typeName) throws TypeNotPresentException { return introspectBeanDefinition(reference.getBeanName());
try {
return ClassUtils.forName(typeName, getClassLoader());
} catch (ClassNotFoundException cause) {
throw new TypeNotPresentException(typeName, cause);
}
} }
/** /**
* Resolves the given {@link String named} {@link Class type} if present. * Returns a {@link IntrospectedBeanDefinition} to obtain further detail about the underlying bean definition. A
* introspected bean definition can also point to an absent bean definition.
* *
* @param typeName {@link String} containing the {@literal fully-qualified class name} of the {@link Class type} * @param beanName {@link String} containing the {@literal name} of the bean to evaluate; must not be {@literal null}.
* to resolve; must not be {@literal null}. * @return the introspected bean definition.
* @return an {@link Optional} value containing the {@link Class type}
* if the {@link String fully-qualified class name} is present on the application classpath.
* @see #isTypePresent(String)
* @see #resolveRequiredType(String)
* @see java.util.Optional
*/ */
default Optional<Class<?>> resolveType(@NonNull String typeName) { IntrospectedBeanDefinition introspectBeanDefinition(String beanName);
return isTypePresent(typeName) /**
? Optional.of(resolveRequiredType(typeName)) * Type-based introspector to resolve {@link Class} from a type name and to introspect the bean factory for presence
: Optional.empty(); * of beans.
} */
interface TypeIntrospector {
/** /**
* Resolves the {@link BeanDefinition bean's} defined {@link Class type}. * Determines whether @link Class type} is present on the application classpath.
* *
* @param beanReference {@link BeanReference} to the managed bean. * @return {@literal true} if the {@link Class type} is present on the application classpath.
* @return the {@link Class type} of the {@link BeanReference referenced bean} if defined; may be {@literal null}. * @see #getClassLoader()
* @see BeanReference
*/ */
@Nullable boolean isTypePresent();
default Class<?> resolveType(@NonNull BeanReference beanReference) {
return getBeanFactory().getType(beanReference.getBeanName(), false);
}
/** /**
* Gets the {@link BeanDefinition} for the given, required {@link String named bean}. * Resolves the required {@link String named} {@link Class type}.
* *
* @param beanName {@link String} containing the {@literal name} of the bean; must not be {@literal null}. * @return a resolved {@link Class type} for the given.
* @return the {@link BeanDefinition} for the given, required {@link String named bean}. * @throws TypeNotPresentException if the {@link Class type} cannot be found.
* @throws NoSuchBeanDefinitionException if a {@link BeanDefinition} cannot be found for
* the {@link String named bean}.
* @see BeanDefinition
*/ */
@NonNull Class<?> resolveRequiredType() throws TypeNotPresentException;
default BeanDefinition getBeanDefinition(@NonNull String beanName) throws NoSuchBeanDefinitionException {
return getBeanFactory().getBeanDefinition(beanName); /**
* Resolves the {@link Class type} if present.
*
* @return an {@link Optional} value containing the {@link Class type} if the type is present on the application
* classpath.
* @see #isTypePresent()
* @see #resolveRequiredType()
* @see java.util.Optional
*/
Optional<Class<?>> resolveType();
/**
* Determines whether the {@link Class type} is declared on the application classpath and performs the given,
* required {@link Consumer action} if present.
*
* @param action {@link Consumer} defining the action to perform on the resolved {@link Class type}; must not be
* {@literal null}.
* @see java.util.function.Consumer
* @see #resolveType()
*/
default void ifTypePresent(Consumer<Class<?>> action) {
resolveType().ifPresent(action);
} }
/** /**
* Gets the {@link RootBeanDefinition} for the given, required {@link String bean name}. * Determines whether the associated bean factory contains at least one bean of this type.
* *
* @param beanName {@link String} containing the {@literal name} of the bean. * @return {@literal true} if the {@link Class type} is present on the application classpath.
* @return the {@link RootBeanDefinition} for the given, required {@link String bean name}.
* @throws NoSuchBeanDefinitionException if a {@link BeanDefinition} cannot be found for
* the {@link String named bean}.
* @throws IllegalStateException if the bean is not a {@link RootBeanDefinition root bean}.
* @see RootBeanDefinition
*/ */
@NonNull boolean hasBean();
default RootBeanDefinition getRootBeanDefinition(@NonNull String beanName) throws NoSuchBeanDefinitionException {
BeanDefinition beanDefinition = getBeanDefinition(beanName); /**
* Return a {@link List} containing bean names that implement this type.
*
* @return a {@link List} of bean names. The list is empty if the bean factory does not hold any beans of this type.
*/
List<String> getBeanNames();
if (beanDefinition instanceof RootBeanDefinition rootBeanDefinition) {
return rootBeanDefinition;
} }
throw new IllegalStateException(String.format("%s is not a root bean", beanName)); /**
} * Interface defining introspection methods for bean definitions.
*/
interface IntrospectedBeanDefinition {
/**
* Determines whether a bean definition identified by the given, required {@link String name} is present.
*
* @return {@literal true} if the bean definition identified by the given, required {@link String name} registered
* with.
*/
boolean isPresent();
/** /**
* Determines whether a bean identified by the given, required {@link String name} is a * Determines whether a bean identified by the given, required {@link String name} is a
* {@link org.springframework.beans.factory.FactoryBean}. * {@link org.springframework.beans.factory.FactoryBean}.
* *
* @param beanName {@link String} containing the {@literal name} of the bean to evaluate;
* must not be {@literal null}.
* @return {@literal true} if the bean identified by the given, required {@link String name} is a * @return {@literal true} if the bean identified by the given, required {@link String name} is a
* {@link org.springframework.beans.factory.FactoryBean}. * {@link org.springframework.beans.factory.FactoryBean}.
*/ */
default boolean isFactoryBean(@NonNull String beanName) { boolean isFactoryBean();
return getBeanFactory().isFactoryBean(beanName);
}
/** /**
* Determines whether a Spring {@link org.springframework.transaction.TransactionManager} is present. * Gets the {@link BeanDefinition} for the given, required {@link String named bean}.
* *
* @return {@literal true} if a Spring {@link org.springframework.transaction.TransactionManager} is present. * @return the {@link BeanDefinition} for the given, required {@link String named bean}.
* @throws NoSuchBeanDefinitionException if a {@link BeanDefinition} cannot be found for the {@link String named
* bean}.
* @see BeanDefinition
*/ */
default boolean isTransactionManagerPresent() {
return resolveType("org.springframework.transaction.TransactionManager") BeanDefinition getBeanDefinition() throws NoSuchBeanDefinitionException;
.filter(it -> !ObjectUtils.isEmpty(getBeanFactory().getBeanNamesForType(it)))
.isPresent();
}
/** /**
* Determines whether the given, required {@link String type name} is declared on the application classpath * Gets the {@link RootBeanDefinition} for the given, required {@link String bean name}.
* and performs the given, required {@link Consumer action} if present.
* *
* @param typeName {@link String name} of the {@link Class type} to process; must not be {@literal null}. * @return the {@link RootBeanDefinition} for the given, required {@link String bean name}.
* @param action {@link Consumer} defining the action to perform on the resolved {@link Class type}; * @throws NoSuchBeanDefinitionException if a {@link BeanDefinition} cannot be found for the {@link String named
* must not be {@literal null}. * bean}.
* @see java.util.function.Consumer * @throws IllegalStateException if the bean is not a {@link RootBeanDefinition root bean}.
* @see #resolveType(String) * @see RootBeanDefinition
*/ */
default void ifTypePresent(@NonNull String typeName, @NonNull Consumer<Class<?>> action) { RootBeanDefinition getRootBeanDefinition() throws NoSuchBeanDefinitionException;
resolveType(typeName).ifPresent(action);
}
/** /**
* Runs the given {@link Consumer action} on any {@link org.springframework.transaction.TransactionManager} beans * Resolves the {@link BeanDefinition bean's} defined {@link Class type}.
* defined in the application context.
* *
* @param beanNamesConsumer {@link Consumer} defining the action to perform on * @return the {@link Class type} of the {@link BeanReference referenced bean} if defined; may be {@literal null}.
* the {@link org.springframework.transaction.TransactionManager} beans if present; must not be {@literal null}. * @see BeanReference
* @see java.util.function.Consumer
*/ */
default void ifTransactionManagerPresent(@NonNull Consumer<String[]> beanNamesConsumer) { @Nullable
Class<?> resolveType();
ifTypePresent("org.springframework.transaction.TransactionManager", txMgrType -> {
String[] txMgrBeanNames = getBeanFactory().getBeanNamesForType(txMgrType);
if (!ObjectUtils.isEmpty(txMgrBeanNames)) {
beanNamesConsumer.accept(txMgrBeanNames);
}
});
} }
} }

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

@ -25,14 +25,11 @@ import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryInformationSupport; import org.springframework.data.repository.core.RepositoryInformationSupport;
import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFragment; import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.lang.NonNull;
/** /**
* {@link RepositoryInformation} based on {@link RepositoryMetadata} collected at build time. * {@link RepositoryInformation} based on {@link RepositoryMetadata} collected at build time.
* *
* @author Christoph Strobl * @author Christoph Strobl
* @see org.springframework.data.repository.core.RepositoryInformation
* @see org.springframework.data.repository.core.RepositoryMetadata
* @since 3.0 * @since 3.0
*/ */
class AotRepositoryInformation extends RepositoryInformationSupport implements RepositoryInformation { class AotRepositoryInformation extends RepositoryInformationSupport implements RepositoryInformation {
@ -47,20 +44,19 @@ class AotRepositoryInformation extends RepositoryInformationSupport implements R
} }
@Override @Override
public boolean isCustomMethod(@NonNull Method method) { public boolean isCustomMethod(Method method) {
// TODO: // TODO:
return false; return false;
} }
@Override @Override
public boolean isBaseClassMethod(@NonNull Method method) { public boolean isBaseClassMethod(Method method) {
// TODO // TODO
return false; return false;
} }
@NonNull
@Override @Override
public Method getTargetClassMethod(@NonNull Method method) { public Method getTargetClassMethod(Method method) {
// TODO // TODO
return method; return method;
} }
@ -69,7 +65,6 @@ class AotRepositoryInformation extends RepositoryInformationSupport implements R
* @return configured repository fragments. * @return configured repository fragments.
* @since 3.0 * @since 3.0
*/ */
@NonNull
public Set<RepositoryFragment<?>> getFragments() { public Set<RepositoryFragment<?>> getFragments() {
return new LinkedHashSet<>(fragments.get()); return new LinkedHashSet<>(fragments.get());
} }

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

@ -0,0 +1,139 @@
/*
* Copyright 2022 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.aot;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.util.ClassUtils;
/**
* Default {@link AotContext} implementation.
*
* @author Mark Paluch
* @since 3.0
*/
class DefaultAotContext implements AotContext {
private final ConfigurableListableBeanFactory factory;
public DefaultAotContext(BeanFactory beanFactory) {
factory = beanFactory instanceof ConfigurableListableBeanFactory ? (ConfigurableListableBeanFactory) beanFactory
: new DefaultListableBeanFactory(beanFactory);
}
@Override
public ConfigurableListableBeanFactory getBeanFactory() {
return factory;
}
@Override
public TypeIntrospector introspectType(String typeName) {
return new DefaultTypeIntrospector(typeName);
}
@Override
public IntrospectedBeanDefinition introspectBeanDefinition(String beanName) {
return new DefaultIntrospectedBeanDefinition(beanName);
}
class DefaultTypeIntrospector implements TypeIntrospector {
private final String typeName;
DefaultTypeIntrospector(String typeName) {
this.typeName = typeName;
}
@Override
public boolean isTypePresent() {
return ClassUtils.isPresent(typeName, getClassLoader());
}
@Override
public Class<?> resolveRequiredType() throws TypeNotPresentException {
try {
return ClassUtils.forName(typeName, getClassLoader());
} catch (ClassNotFoundException cause) {
throw new TypeNotPresentException(typeName, cause);
}
}
@Override
public Optional<Class<?>> resolveType() {
return isTypePresent() ? Optional.of(resolveRequiredType()) : Optional.empty();
}
@Override
public boolean hasBean() {
return !getBeanNames().isEmpty();
}
@Override
public List<String> getBeanNames() {
return isTypePresent() ? Arrays.asList(factory.getBeanNamesForType(resolveRequiredType()))
: Collections.emptyList();
}
}
class DefaultIntrospectedBeanDefinition implements IntrospectedBeanDefinition {
private final String beanName;
DefaultIntrospectedBeanDefinition(String beanName) {
this.beanName = beanName;
}
@Override
public boolean isPresent() {
return factory.containsBeanDefinition(beanName);
}
@Override
public boolean isFactoryBean() {
return factory.isFactoryBean(beanName);
}
@Override
public BeanDefinition getBeanDefinition() throws NoSuchBeanDefinitionException {
return factory.getBeanDefinition(beanName);
}
@Override
public RootBeanDefinition getRootBeanDefinition() throws NoSuchBeanDefinitionException {
BeanDefinition beanDefinition = getBeanDefinition();
if (beanDefinition instanceof RootBeanDefinition rootBeanDefinition) {
return rootBeanDefinition;
}
throw new IllegalStateException(String.format("%s is not a root bean", beanName));
}
@Override
public Class<?> resolveType() {
return factory.getType(beanName, false);
}
}
}

26
src/main/java/org/springframework/data/aot/DefaultAotRepositoryContext.java

@ -35,24 +35,21 @@ import org.springframework.data.util.Lazy;
*/ */
class DefaultAotRepositoryContext implements AotRepositoryContext { class DefaultAotRepositoryContext implements AotRepositoryContext {
private AotContext aotContext; private final AotContext aotContext;
private final Lazy<Set<MergedAnnotation<Annotation>>> resolvedAnnotations = Lazy.of(this::discoverAnnotations); private final Lazy<Set<MergedAnnotation<Annotation>>> resolvedAnnotations = Lazy.of(this::discoverAnnotations);
private final Lazy<Set<Class<?>>> managedTypes = Lazy.of(this::discoverTypes); private final Lazy<Set<Class<?>>> managedTypes = Lazy.of(this::discoverTypes);
private RepositoryInformation repositoryInformation; private RepositoryInformation repositoryInformation;
private Set<String> basePackages; private Set<String> basePackages;
private Set<Class<? extends Annotation>> identifyingAnnotations; private Set<Class<? extends Annotation>> identifyingAnnotations;
private String beanName; private String beanName;
public AotContext getAotContext() { public DefaultAotRepositoryContext(AotContext aotContext) {
return aotContext; this.aotContext = aotContext;
} }
public void setAotContext(AotContext aotContext) { public AotContext getAotContext() {
this.aotContext = aotContext; return aotContext;
} }
@Override @Override
@ -106,6 +103,16 @@ class DefaultAotRepositoryContext implements AotRepositoryContext {
return managedTypes.get(); return managedTypes.get();
} }
@Override
public TypeIntrospector introspectType(String typeName) {
return aotContext.introspectType(typeName);
}
@Override
public IntrospectedBeanDefinition introspectBeanDefinition(String beanName) {
return aotContext.introspectBeanDefinition(beanName);
}
protected Set<MergedAnnotation<Annotation>> discoverAnnotations() { protected Set<MergedAnnotation<Annotation>> discoverAnnotations() {
Set<MergedAnnotation<Annotation>> annotations = getResolvedTypes().stream() Set<MergedAnnotation<Annotation>> annotations = getResolvedTypes().stream()
@ -127,7 +134,8 @@ class DefaultAotRepositoryContext implements AotRepositoryContext {
if (!getIdentifyingAnnotations().isEmpty()) { if (!getIdentifyingAnnotations().isEmpty()) {
Set<Class<?>> classes = aotContext.getTypeScanner().scanPackages(getBasePackages()).forTypesAnnotatedWith(getIdentifyingAnnotations()).collectAsSet(); Set<Class<?>> classes = aotContext.getTypeScanner().scanPackages(getBasePackages())
.forTypesAnnotatedWith(getIdentifyingAnnotations()).collectAsSet();
types.addAll(TypeCollector.inspect(classes).list()); types.addAll(TypeCollector.inspect(classes).list());
} }

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

@ -20,6 +20,7 @@ import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.generate.GenerationContext;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
@ -27,7 +28,6 @@ import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.data.domain.ManagedTypes; import org.springframework.data.domain.ManagedTypes;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -38,8 +38,6 @@ import org.springframework.util.StringUtils;
* *
* @author Christoph Strobl * @author Christoph Strobl
* @author John Blum * @author John Blum
* @see org.springframework.beans.factory.BeanFactoryAware
* @see org.springframework.beans.factory.aot.BeanRegistrationAotProcessor
* @since 3.0 * @since 3.0
*/ */
public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor { public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
@ -48,7 +46,7 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio
@Nullable private String moduleIdentifier; @Nullable private String moduleIdentifier;
@Override @Override
public BeanRegistrationAotContribution processAheadOfTime(@NonNull RegisteredBean registeredBean) { public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
if (!isMatch(registeredBean.getBeanClass(), registeredBean.getBeanName())) { if (!isMatch(registeredBean.getBeanClass(), registeredBean.getBeanName())) {
return null; return null;

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

@ -23,7 +23,7 @@ import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationCode; import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.data.domain.ManagedTypes; import org.springframework.data.domain.ManagedTypes;
import org.springframework.lang.NonNull; import org.springframework.lang.Nullable;
/** /**
* {@link BeanRegistrationAotContribution} used to contribute a {@link ManagedTypes} registration. * {@link BeanRegistrationAotContribution} used to contribute a {@link ManagedTypes} registration.
@ -38,8 +38,8 @@ public class ManagedTypesRegistrationAotContribution implements BeanRegistration
private final ManagedTypes managedTypes; private final ManagedTypes managedTypes;
private final BiConsumer<ResolvableType, GenerationContext> contributionAction; private final BiConsumer<ResolvableType, GenerationContext> contributionAction;
public ManagedTypesRegistrationAotContribution(AotContext aotContext, ManagedTypes managedTypes, public ManagedTypesRegistrationAotContribution(AotContext aotContext, @Nullable ManagedTypes managedTypes,
@NonNull BiConsumer<ResolvableType, GenerationContext> contributionAction) { BiConsumer<ResolvableType, GenerationContext> contributionAction) {
this.aotContext = aotContext; this.aotContext = aotContext;
this.managedTypes = managedTypes; this.managedTypes = managedTypes;
@ -50,14 +50,12 @@ public class ManagedTypesRegistrationAotContribution implements BeanRegistration
return this.aotContext; return this.aotContext;
} }
@NonNull
protected ManagedTypes getManagedTypes() { protected ManagedTypes getManagedTypes() {
return managedTypes == null ? ManagedTypes.empty() : managedTypes; return managedTypes == null ? ManagedTypes.empty() : managedTypes;
} }
@Override @Override
public void applyTo(@NonNull GenerationContext generationContext, public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
@NonNull BeanRegistrationCode beanRegistrationCode) {
List<Class<?>> types = getManagedTypes().toList(); List<Class<?>> types = getManagedTypes().toList();

6
src/main/java/org/springframework/data/aot/Predicates.java

@ -32,7 +32,11 @@ import java.util.function.Predicate;
public abstract class Predicates { public abstract class Predicates {
public static final Predicate<Member> IS_ENUM_MEMBER = member -> member.getDeclaringClass().isEnum(); public static final Predicate<Member> IS_ENUM_MEMBER = member -> member.getDeclaringClass().isEnum();
public static final Predicate<Member> IS_HIBERNATE_MEMBER = member -> member.getName().startsWith("$$_hibernate"); public static final Predicate<Member> IS_HIBERNATE_MEMBER = member -> member.getName().startsWith("$$_hibernate"); // this
// should
// go
// into
// JPA
public static final Predicate<Member> IS_OBJECT_MEMBER = member -> Object.class.equals(member.getDeclaringClass()); public static final Predicate<Member> IS_OBJECT_MEMBER = member -> Object.class.equals(member.getDeclaringClass());
public static final Predicate<Member> IS_JAVA = member -> member.getDeclaringClass().getPackageName().startsWith("java."); public static final Predicate<Member> IS_JAVA = member -> member.getDeclaringClass().getPackageName().startsWith("java.");
public static final Predicate<Member> IS_NATIVE = member -> Modifier.isNative(member.getModifiers()); public static final Predicate<Member> IS_NATIVE = member -> Modifier.isNative(member.getModifiers());

47
src/main/java/org/springframework/data/aot/RepositoryBeanDefinitionReader.java

@ -17,13 +17,14 @@ package org.springframework.data.aot;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.data.repository.config.RepositoryFragmentConfiguration; import org.springframework.data.repository.config.RepositoryConfiguration;
import org.springframework.data.repository.config.RepositoryMetadata; import org.springframework.data.repository.config.RepositoryFragmentConfigurationProvider;
import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFragment; import org.springframework.data.repository.core.support.RepositoryFragment;
@ -31,19 +32,15 @@ import org.springframework.data.util.Lazy;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
/** /**
* Reader used to extract {@link RepositoryInformation} from {@link RepositoryMetadata}. * Reader used to extract {@link RepositoryInformation} from {@link RepositoryConfiguration}.
* *
* @author Christoph Strobl * @author Christoph Strobl
* @author John Blum * @author John Blum
* @see org.springframework.data.repository.config.RepositoryFragmentConfiguration
* @see org.springframework.data.repository.config.RepositoryMetadata
* @see org.springframework.data.repository.core.RepositoryInformation
* @see org.springframework.data.repository.core.support.RepositoryFragment
* @since 3.0.0 * @since 3.0.0
*/ */
class RepositoryBeanDefinitionReader { class RepositoryBeanDefinitionReader {
static RepositoryInformation readRepositoryInformation(RepositoryMetadata<?> metadata, static RepositoryInformation readRepositoryInformation(RepositoryConfiguration<?> metadata,
ConfigurableListableBeanFactory beanFactory) { ConfigurableListableBeanFactory beanFactory) {
return new AotRepositoryInformation(metadataSupplier(metadata, beanFactory), return new AotRepositoryInformation(metadataSupplier(metadata, beanFactory),
@ -51,33 +48,33 @@ class RepositoryBeanDefinitionReader {
} }
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
private static Supplier<Collection<RepositoryFragment<?>>> fragments(RepositoryMetadata metadata, private static Supplier<Collection<RepositoryFragment<?>>> fragments(RepositoryConfiguration<?> metadata,
ConfigurableListableBeanFactory beanFactory) { ConfigurableListableBeanFactory beanFactory) {
return Lazy.of(() -> (Collection<RepositoryFragment<?>>) metadata.getFragmentConfiguration().stream() if (metadata instanceof RepositoryFragmentConfigurationProvider provider) {
.flatMap(it -> {
RepositoryFragmentConfiguration fragmentConfiguration = (RepositoryFragmentConfiguration) it; return Lazy.of(() -> {
List<RepositoryFragment> fragments = new ArrayList<>(2); return provider.getFragmentConfiguration().stream().flatMap(it -> {
if (fragmentConfiguration.getClassName() != null) { List<RepositoryFragment<?>> fragments = new ArrayList<>(1);
fragments.add(RepositoryFragment.implemented(forName(fragmentConfiguration.getClassName(), beanFactory)));
} // TODO: Implemented accepts an Object, not a class.
if (fragmentConfiguration.getInterfaceName() != null) { fragments.add(RepositoryFragment.implemented(forName(it.getClassName(), beanFactory)));
fragments.add(RepositoryFragment.structural(forName(fragmentConfiguration.getInterfaceName(), beanFactory))); fragments.add(RepositoryFragment.structural(forName(it.getInterfaceName(), beanFactory)));
}
return fragments.stream(); return fragments.stream();
}) }).collect(Collectors.toList());
.collect(Collectors.toList())); });
}
return Lazy.of(Collections::emptyList);
} }
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
private static Supplier<Class<?>> repositoryBaseClass(RepositoryMetadata metadata, private static Supplier<Class<?>> repositoryBaseClass(RepositoryConfiguration metadata,
ConfigurableListableBeanFactory beanFactory) { ConfigurableListableBeanFactory beanFactory) {
return Lazy.of(() -> return Lazy.of(() -> (Class<?>) metadata.getRepositoryBaseClassName().map(it -> forName(it.toString(), beanFactory))
(Class<?>) metadata.getRepositoryBaseClassName().map(it -> forName(it.toString(), beanFactory))
.orElseGet(() -> { .orElseGet(() -> {
// TODO: retrieve the default without loading the actual RepositoryBeanFactory // TODO: retrieve the default without loading the actual RepositoryBeanFactory
return Object.class; return Object.class;
@ -85,7 +82,7 @@ class RepositoryBeanDefinitionReader {
} }
static Supplier<org.springframework.data.repository.core.RepositoryMetadata> metadataSupplier( static Supplier<org.springframework.data.repository.core.RepositoryMetadata> metadataSupplier(
RepositoryMetadata<?> metadata, ConfigurableListableBeanFactory beanFactory) { RepositoryConfiguration<?> metadata, ConfigurableListableBeanFactory beanFactory) {
return Lazy.of(() -> new DefaultRepositoryMetadata(forName(metadata.getRepositoryInterface(), beanFactory))); return Lazy.of(() -> new DefaultRepositoryMetadata(forName(metadata.getRepositoryInterface(), beanFactory)));
} }

189
src/main/java/org/springframework/data/aot/RepositoryRegistrationAotContribution.java

@ -19,7 +19,6 @@ import java.io.Serializable;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -27,7 +26,6 @@ import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.springframework.aop.SpringProxy; import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.Advised; import org.springframework.aop.framework.Advised;
@ -47,12 +45,11 @@ import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.data.projection.EntityProjectionIntrospector; import org.springframework.data.projection.EntityProjectionIntrospector;
import org.springframework.data.projection.TargetAware; import org.springframework.data.projection.TargetAware;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.Repository;
import org.springframework.data.repository.config.RepositoryConfiguration;
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport; import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
import org.springframework.data.repository.config.RepositoryMetadata;
import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.data.repository.core.support.RepositoryFragment; import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -62,29 +59,24 @@ import org.springframework.util.ClassUtils;
* {@link BeanRegistrationAotContribution} used to contribute repository registrations. * {@link BeanRegistrationAotContribution} used to contribute repository registrations.
* *
* @author John Blum * @author John Blum
* @see org.springframework.aot.generate.GenerationContext * @since 3.0
* @see org.springframework.beans.factory.aot.BeanRegistrationAotContribution
* @see org.springframework.beans.factory.support.RegisteredBean
* @see org.springframework.data.aot.RepositoryRegistrationAotProcessor
* @since 3.0.0
*/ */
public class RepositoryRegistrationAotContribution implements BeanRegistrationAotContribution { public class RepositoryRegistrationAotContribution implements BeanRegistrationAotContribution {
private static final String KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME = private static final String KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME = "org.springframework.data.repository.kotlin.CoroutineCrudRepository";
"org.springframework.data.repository.kotlin.CoroutineCrudRepository";
/** /**
* Factory method used to construct a new instance of {@link RepositoryRegistrationAotContribution} initialized with * Factory method used to construct a new instance of {@link RepositoryRegistrationAotContribution} initialized with
* the given, required {@link RepositoryRegistrationAotProcessor} from which this contribution was created. * the given, required {@link RepositoryRegistrationAotProcessor} from which this contribution was created.
* *
* @param repositoryRegistrationAotProcessor reference back to the {@link RepositoryRegistrationAotProcessor} from which this * @param repositoryRegistrationAotProcessor reference back to the {@link RepositoryRegistrationAotProcessor} from
* contribution was created. * which this contribution was created.
* @return a new instance of {@link RepositoryRegistrationAotContribution}. * @return a new instance of {@link RepositoryRegistrationAotContribution}.
* @throws IllegalArgumentException if the {@link RepositoryRegistrationAotProcessor} is {@literal null}. * @throws IllegalArgumentException if the {@link RepositoryRegistrationAotProcessor} is {@literal null}.
* @see org.springframework.data.aot.RepositoryRegistrationAotProcessor * @see org.springframework.data.aot.RepositoryRegistrationAotProcessor
*/ */
public static RepositoryRegistrationAotContribution fromProcessor( public static RepositoryRegistrationAotContribution fromProcessor(
@NonNull RepositoryRegistrationAotProcessor repositoryRegistrationAotProcessor) { RepositoryRegistrationAotProcessor repositoryRegistrationAotProcessor) {
return new RepositoryRegistrationAotContribution(repositoryRegistrationAotProcessor); return new RepositoryRegistrationAotContribution(repositoryRegistrationAotProcessor);
} }
@ -93,28 +85,25 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
private BiConsumer<AotRepositoryContext, GenerationContext> moduleContribution; private BiConsumer<AotRepositoryContext, GenerationContext> moduleContribution;
@NonNull
private final RepositoryRegistrationAotProcessor repositoryRegistrationAotProcessor; private final RepositoryRegistrationAotProcessor repositoryRegistrationAotProcessor;
/** /**
* Constructs a new instance of the {@link RepositoryRegistrationAotContribution} initialized with the given, * Constructs a new instance of the {@link RepositoryRegistrationAotContribution} initialized with the given, required
* required {@link RepositoryRegistrationAotProcessor} from which this contribution was created. * {@link RepositoryRegistrationAotProcessor} from which this contribution was created.
* *
* @param repositoryRegistrationAotProcessor reference back to the {@link RepositoryRegistrationAotProcessor} from which this * @param repositoryRegistrationAotProcessor reference back to the {@link RepositoryRegistrationAotProcessor} from
* contribution was created. * which this contribution was created.
* @throws IllegalArgumentException if the {@link RepositoryRegistrationAotProcessor} is {@literal null}. * @throws IllegalArgumentException if the {@link RepositoryRegistrationAotProcessor} is {@literal null}.
* @see org.springframework.data.aot.RepositoryRegistrationAotProcessor * @see org.springframework.data.aot.RepositoryRegistrationAotProcessor
*/ */
protected RepositoryRegistrationAotContribution( protected RepositoryRegistrationAotContribution(
@NonNull RepositoryRegistrationAotProcessor repositoryRegistrationAotProcessor) { RepositoryRegistrationAotProcessor repositoryRegistrationAotProcessor) {
Assert.notNull(repositoryRegistrationAotProcessor, Assert.notNull(repositoryRegistrationAotProcessor, "RepositoryRegistrationAotProcessor must not be null");
"RepositoryRegistrationAotProcessor must not be null");
this.repositoryRegistrationAotProcessor = repositoryRegistrationAotProcessor; this.repositoryRegistrationAotProcessor = repositoryRegistrationAotProcessor;
} }
@NonNull
protected ConfigurableListableBeanFactory getBeanFactory() { protected ConfigurableListableBeanFactory getBeanFactory() {
return getRepositoryRegistrationAotProcessor().getBeanFactory(); return getRepositoryRegistrationAotProcessor().getBeanFactory();
} }
@ -123,7 +112,6 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
return Optional.ofNullable(this.moduleContribution); return Optional.ofNullable(this.moduleContribution);
} }
@NonNull
protected AotRepositoryContext getRepositoryContext() { protected AotRepositoryContext getRepositoryContext() {
Assert.state(this.repositoryContext != null, Assert.state(this.repositoryContext != null,
@ -132,7 +120,6 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
return this.repositoryContext; return this.repositoryContext;
} }
@NonNull
protected RepositoryRegistrationAotProcessor getRepositoryRegistrationAotProcessor() { protected RepositoryRegistrationAotProcessor getRepositoryRegistrationAotProcessor() {
return this.repositoryRegistrationAotProcessor; return this.repositoryRegistrationAotProcessor;
} }
@ -146,20 +133,21 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
} }
/** /**
* Builds a {@link RepositoryRegistrationAotContribution} for given, required {@link RegisteredBean} * Builds a {@link RepositoryRegistrationAotContribution} for given, required {@link RegisteredBean} representing the
* representing the {@link Repository} registered in the bean registry. * {@link Repository} registered in the bean registry.
* *
* @param repositoryBean {@link RegisteredBean} for the {@link Repository}; must not be {@literal null}. * @param repositoryBean {@link RegisteredBean} for the {@link Repository}; must not be {@literal null}.
* @return a {@link RepositoryRegistrationAotContribution} to contribute AOT metadata and code * @return a {@link RepositoryRegistrationAotContribution} to contribute AOT metadata and code for the
* for the {@link Repository} {@link RegisteredBean}. * {@link Repository} {@link RegisteredBean}.
* @throws IllegalArgumentException if the {@link RegisteredBean} is {@literal null}. * @throws IllegalArgumentException if the {@link RegisteredBean} is {@literal null}.
* @see org.springframework.beans.factory.support.RegisteredBean * @see org.springframework.beans.factory.support.RegisteredBean
*/ */
public RepositoryRegistrationAotContribution forBean(@NonNull RegisteredBean repositoryBean) { public RepositoryRegistrationAotContribution forBean(RegisteredBean repositoryBean) {
Assert.notNull(repositoryBean, "The RegisteredBean for the repository must not be null"); Assert.notNull(repositoryBean, "The RegisteredBean for the repository must not be null");
RepositoryMetadata<?> repositoryMetadata = getRepositoryRegistrationAotProcessor().getRepositoryMetadata(repositoryBean); RepositoryConfiguration<?> repositoryMetadata = getRepositoryRegistrationAotProcessor()
.getRepositoryMetadata(repositoryBean);
this.repositoryContext = buildAotRepositoryContext(repositoryBean, repositoryMetadata); this.repositoryContext = buildAotRepositoryContext(repositoryBean, repositoryMetadata);
@ -168,14 +156,14 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
return this; return this;
} }
protected DefaultAotRepositoryContext buildAotRepositoryContext(@NonNull RegisteredBean bean, protected DefaultAotRepositoryContext buildAotRepositoryContext(RegisteredBean bean,
@NonNull RepositoryMetadata<?> repositoryMetadata) { RepositoryConfiguration<?> repositoryMetadata) {
RepositoryInformation repositoryInformation = resolveRepositoryInformation(repositoryMetadata); RepositoryInformation repositoryInformation = resolveRepositoryInformation(repositoryMetadata);
DefaultAotRepositoryContext repositoryContext = new DefaultAotRepositoryContext(); DefaultAotRepositoryContext repositoryContext = new DefaultAotRepositoryContext(
AotContext.from(this.getBeanFactory()));
repositoryContext.setAotContext(this::getBeanFactory);
repositoryContext.setBeanName(bean.getBeanName()); repositoryContext.setBeanName(bean.getBeanName());
repositoryContext.setBasePackages(repositoryMetadata.getBasePackages().toSet()); repositoryContext.setBasePackages(repositoryMetadata.getBasePackages().toSet());
repositoryContext.setIdentifyingAnnotations(resolveIdentifyingAnnotations()); repositoryContext.setIdentifyingAnnotations(resolveIdentifyingAnnotations());
@ -194,13 +182,13 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
// all stores will be included in the resulting Set. // all stores will be included in the resulting Set.
// When using AOT, is multi-store mode allowed? I don't see why not, but does this work correctly // When using AOT, is multi-store mode allowed? I don't see why not, but does this work correctly
// with AOT, ATM? // with AOT, ATM?
Map<String, RepositoryConfigurationExtensionSupport> repositoryConfigurationExtensionBeans = Map<String, RepositoryConfigurationExtensionSupport> repositoryConfigurationExtensionBeans = getBeanFactory()
getBeanFactory().getBeansOfType(RepositoryConfigurationExtensionSupport.class); .getBeansOfType(RepositoryConfigurationExtensionSupport.class);
// repositoryConfigurationExtensionBeans.values().stream() // repositoryConfigurationExtensionBeans.values().stream()
// .map(RepositoryConfigurationExtensionSupport::getIdentifyingAnnotations) // .map(RepositoryConfigurationExtensionSupport::getIdentifyingAnnotations)
// .flatMap(Collection::stream) // .flatMap(Collection::stream)
// .collect(Collectors.toCollection(() -> identifyingAnnotations)); // .collect(Collectors.toCollection(() -> identifyingAnnotations));
} catch (Throwable ignore) { } catch (Throwable ignore) {
// Possible BeansException because no bean exists of type RepositoryConfigurationExtension, // Possible BeansException because no bean exists of type RepositoryConfigurationExtension,
@ -210,25 +198,24 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
return identifyingAnnotations; return identifyingAnnotations;
} }
private RepositoryInformation resolveRepositoryInformation(RepositoryMetadata<?> repositoryMetadata) { private RepositoryInformation resolveRepositoryInformation(RepositoryConfiguration<?> repositoryMetadata) {
return RepositoryBeanDefinitionReader.readRepositoryInformation(repositoryMetadata, getBeanFactory()); return RepositoryBeanDefinitionReader.readRepositoryInformation(repositoryMetadata, getBeanFactory());
} }
/** /**
* Helps the AOT processing render the {@link FactoryBean} type correctly that is used to tell the outcome of the {@link FactoryBean}. * Helps the AOT processing render the {@link FactoryBean} type correctly that is used to tell the outcome of the
* * {@link FactoryBean}. We just need to set the target {@link Repository} {@link Class type} of the
* We just need to set the target {@link Repository} {@link Class type} of the {@link RepositoryFactoryBeanSupport} * {@link RepositoryFactoryBeanSupport} while keeping the actual ID and DomainType set to {@link Object}. If the
* while keeping the actual ID and DomainType set to {@link Object}. If the generic type signature does not match, * generic type signature does not match, then we do not try to resolve and remap the types, but rather set the
* then we do not try to resolve and remap the types, but rather set the {@literal factoryBeanObjectType} attribute * {@literal factoryBeanObjectType} attribute on the {@link RootBeanDefinition}.
* on the {@link RootBeanDefinition}.
*/ */
protected void enhanceRepositoryBeanDefinition(@NonNull RegisteredBean repositoryBean, protected void enhanceRepositoryBeanDefinition(RegisteredBean repositoryBean,
@NonNull RepositoryMetadata<?> repositoryMetadata, @NonNull AotRepositoryContext repositoryContext) { RepositoryConfiguration<?> repositoryMetadata, AotRepositoryContext repositoryContext) {
logTrace(String.format("Enhancing repository factory bean definition [%s]", repositoryBean.getBeanName())); logTrace(String.format("Enhancing repository factory bean definition [%s]", repositoryBean.getBeanName()));
Class<?> repositoryFactoryBeanType = repositoryContext Class<?> repositoryFactoryBeanType = repositoryContext
.resolveType(repositoryMetadata.getRepositoryFactoryBeanClassName()) .introspectType(repositoryMetadata.getRepositoryFactoryBeanClassName()).resolveType()
.orElse(RepositoryFactoryBeanSupport.class); .orElse(RepositoryFactoryBeanSupport.class);
ResolvableType resolvedRepositoryFactoryBeanType = ResolvableType.forClass(repositoryFactoryBeanType); ResolvableType resolvedRepositoryFactoryBeanType = ResolvableType.forClass(repositoryFactoryBeanType);
@ -246,7 +233,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
} }
private boolean isRepositoryWithTypeParameters(ResolvableType type) { private boolean isRepositoryWithTypeParameters(ResolvableType type) {
return type != null && type.getGenerics().length == 3; return type.getGenerics().length == 3;
} }
/** /**
@ -255,7 +242,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
* @param moduleContribution {@link BiConsumer} used by data modules to submit contributions; can be {@literal null}. * @param moduleContribution {@link BiConsumer} used by data modules to submit contributions; can be {@literal null}.
* @return this. * @return this.
*/ */
@NonNull
@SuppressWarnings("unused") @SuppressWarnings("unused")
public RepositoryRegistrationAotContribution withModuleContribution( public RepositoryRegistrationAotContribution withModuleContribution(
@Nullable BiConsumer<AotRepositoryContext, GenerationContext> moduleContribution) { @Nullable BiConsumer<AotRepositoryContext, GenerationContext> moduleContribution) {
@ -264,29 +251,27 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
} }
@Override @Override
public void applyTo(@NonNull GenerationContext generationContext, public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
@NonNull BeanRegistrationCode beanRegistrationCode) {
contributeRepositoryInfo(this.repositoryContext, generationContext); contributeRepositoryInfo(this.repositoryContext, generationContext);
getModuleContribution().ifPresent(it -> it.accept(getRepositoryContext(), generationContext)); getModuleContribution().ifPresent(it -> it.accept(getRepositoryContext(), generationContext));
} }
private void contributeRepositoryInfo(@NonNull AotRepositoryContext repositoryContext, private void contributeRepositoryInfo(AotRepositoryContext repositoryContext, GenerationContext contribution) {
@NonNull GenerationContext contribution) {
RepositoryInformation repositoryInformation = getRepositoryInformation(); RepositoryInformation repositoryInformation = getRepositoryInformation();
logTrace("Contributing repository information for [%s]", logTrace("Contributing repository information for [%s]", repositoryInformation.getRepositoryInterface());
repositoryInformation.getRepositoryInterface());
// TODO: is this the way? // TODO: is this the way?
contribution.getRuntimeHints().reflection() contribution.getRuntimeHints().reflection()
.registerType(repositoryInformation.getRepositoryInterface(), hint -> .registerType(repositoryInformation.getRepositoryInterface(),
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)) hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS))
.registerType(repositoryInformation.getRepositoryBaseClass(), hint -> .registerType(repositoryInformation.getRepositoryBaseClass(),
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS)) hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS))
.registerType(repositoryInformation.getDomainType(), hint -> .registerType(repositoryInformation.getDomainType(),
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS)); hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS));
// Repository Fragments // Repository Fragments
for (RepositoryFragment<?> fragment : getRepositoryInformation().getFragments()) { for (RepositoryFragment<?> fragment : getRepositoryInformation().getFragments()) {
@ -308,7 +293,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
SpringProxy.class, Advised.class, DecoratingProxy.class); SpringProxy.class, Advised.class, DecoratingProxy.class);
// Transactional Repository Proxy // Transactional Repository Proxy
//repositoryContext.ifTransactionManagerPresent(transactionManagerBeanNames -> { // repositoryContext.ifTransactionManagerPresent(transactionManagerBeanNames -> {
// TODO: Is the following double JDK Proxy registration above necessary or would a single JDK Proxy // TODO: Is the following double JDK Proxy registration above necessary or would a single JDK Proxy
// registration suffice? // registration suffice?
@ -316,8 +301,8 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
// the additional Serializable TypeReference? // the additional Serializable TypeReference?
// NOTE: Using a single JDK Proxy registration causes the // NOTE: Using a single JDK Proxy registration causes the
// simpleRepositoryWithTxManagerNoKotlinNoReactiveButComponent() test case method to fail. // simpleRepositoryWithTxManagerNoKotlinNoReactiveButComponent() test case method to fail.
List<TypeReference> transactionalRepositoryProxyTypeReferences = List<TypeReference> transactionalRepositoryProxyTypeReferences = transactionalRepositoryProxyTypeReferences(
transactionalRepositoryProxyTypeReferences(repositoryInformation); repositoryInformation);
contribution.getRuntimeHints().proxies() contribution.getRuntimeHints().proxies()
.registerJdkProxy(transactionalRepositoryProxyTypeReferences.toArray(new TypeReference[0])); .registerJdkProxy(transactionalRepositoryProxyTypeReferences.toArray(new TypeReference[0]));
@ -327,7 +312,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
contribution.getRuntimeHints().proxies() contribution.getRuntimeHints().proxies()
.registerJdkProxy(transactionalRepositoryProxyTypeReferences.toArray(new TypeReference[0])); .registerJdkProxy(transactionalRepositoryProxyTypeReferences.toArray(new TypeReference[0]));
} }
//}); // });
// Reactive Repositories // Reactive Repositories
if (repositoryInformation.isReactiveRepository()) { if (repositoryInformation.isReactiveRepository()) {
@ -342,12 +327,10 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
} }
// Repository query methods // Repository query methods
repositoryInformation.getQueryMethods() repositoryInformation.getQueryMethods().map(repositoryInformation::getReturnedDomainClass)
.map(repositoryInformation::getReturnedDomainClass) .filter(Class::isInterface).forEach(type -> {
.filter(Class::isInterface) if (EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy().test(type,
.forEach(type -> { repositoryInformation.getDomainType())) {
if (EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy()
.test(type, repositoryInformation.getDomainType())) {
contributeProjection(type, contribution); contributeProjection(type, contribution);
} }
}); });
@ -360,58 +343,52 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
private boolean isKotlinCoroutineRepository(AotRepositoryContext repositoryContext, private boolean isKotlinCoroutineRepository(AotRepositoryContext repositoryContext,
RepositoryInformation repositoryInformation) { RepositoryInformation repositoryInformation) {
return repositoryContext.resolveType(KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME) return repositoryContext.introspectType(KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME).resolveType()
.filter(it -> ClassUtils.isAssignable(it, repositoryInformation.getRepositoryInterface())) .filter(it -> ClassUtils.isAssignable(it, repositoryInformation.getRepositoryInterface())).isPresent();
.isPresent();
} }
private List<TypeReference> kotlinRepositoryReflectionTypeReferences() { private List<TypeReference> kotlinRepositoryReflectionTypeReferences() {
return new ArrayList<>(Arrays.asList( return new ArrayList<>(
TypeReference.of("org.springframework.data.repository.kotlin.CoroutineCrudRepository"), Arrays.asList(TypeReference.of("org.springframework.data.repository.kotlin.CoroutineCrudRepository"),
TypeReference.of(Repository.class), TypeReference.of(Repository.class), //
TypeReference.of(Iterable.class), TypeReference.of(Iterable.class), //
TypeReference.of("kotlinx.coroutines.flow.Flow"), TypeReference.of("kotlinx.coroutines.flow.Flow"), //
TypeReference.of("kotlin.collections.Iterable"), TypeReference.of("kotlin.collections.Iterable"), //
TypeReference.of("kotlin.Unit"), TypeReference.of("kotlin.Unit"), //
TypeReference.of("kotlin.Long"), TypeReference.of("kotlin.Long"), //
TypeReference.of("kotlin.Boolean") TypeReference.of("kotlin.Boolean")));
));
} }
private List<TypeReference> transactionalRepositoryProxyTypeReferences(RepositoryInformation repositoryInformation) { private List<TypeReference> transactionalRepositoryProxyTypeReferences(RepositoryInformation repositoryInformation) {
return new ArrayList<>(Arrays.asList( return new ArrayList<>(Arrays.asList(TypeReference.of(repositoryInformation.getRepositoryInterface()),
TypeReference.of(repositoryInformation.getRepositoryInterface()), TypeReference.of(Repository.class), //
TypeReference.of(Repository.class), TypeReference.of("org.springframework.transaction.interceptor.TransactionalProxy"), //
TypeReference.of("org.springframework.transaction.interceptor.TransactionalProxy"), TypeReference.of("org.springframework.aop.framework.Advised"), //
TypeReference.of("org.springframework.aop.framework.Advised"), TypeReference.of(DecoratingProxy.class)));
TypeReference.of(DecoratingProxy.class)
));
} }
private void contributeProjection(Class<?> type, GenerationContext generationContext) { private void contributeProjection(Class<?> type, GenerationContext generationContext) {
generationContext.getRuntimeHints().proxies() generationContext.getRuntimeHints().proxies().registerJdkProxy(type, TargetAware.class, SpringProxy.class,
.registerJdkProxy(type, TargetAware.class, SpringProxy.class, DecoratingProxy.class); DecoratingProxy.class);
} }
static boolean isJavaOrPrimitiveType(Class<?> type) { static boolean isJavaOrPrimitiveType(Class<?> type) {
return TypeUtils.type(type).isPartOf("java") //
return TypeUtils.type(type).isPartOf("java") || ClassUtils.isPrimitiveOrWrapper(type) //
|| type.isPrimitive() || ClassUtils.isPrimitiveArray(type); //
|| ClassUtils.isPrimitiveArray(type);
} }
static boolean isSpringDataManagedAnnotation(MergedAnnotation<?> annotation) { static boolean isSpringDataManagedAnnotation(@Nullable MergedAnnotation<?> annotation) {
return annotation != null return annotation != null && (isInSpringDataNamespace(annotation.getType())
&& (isInSpringDataNamespace(annotation.getType()) || annotation.getMetaTypes().stream() || annotation.getMetaTypes().stream().anyMatch(RepositoryRegistrationAotContribution::isInSpringDataNamespace));
.anyMatch(RepositoryRegistrationAotContribution::isInSpringDataNamespace));
} }
static boolean isInSpringDataNamespace(Class<?> type) { static boolean isInSpringDataNamespace(Class<?> type) {
return type != null && type.getPackage().getName().startsWith(TypeContributor.DATA_NAMESPACE); return type.getPackage().getName().startsWith(TypeContributor.DATA_NAMESPACE);
} }
static void contributeType(Class<?> type, GenerationContext generationContext) { static void contributeType(Class<?> type, GenerationContext generationContext) {

67
src/main/java/org/springframework/data/aot/RepositoryRegistrationAotProcessor.java

@ -35,10 +35,9 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.data.repository.config.RepositoryConfiguration;
import org.springframework.data.repository.config.RepositoryConfigurationExtension; import org.springframework.data.repository.config.RepositoryConfigurationExtension;
import org.springframework.data.repository.config.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -46,24 +45,22 @@ import org.springframework.util.StringUtils;
/** /**
* {@link BeanRegistrationAotProcessor} responsible processing and providing AOT configuration for repositories. * {@link BeanRegistrationAotProcessor} responsible processing and providing AOT configuration for repositories.
* <p> * <p>
* Processes {@link RepositoryFactoryBeanSupport repository factory beans} to provide generic type information to * Processes {@link RepositoryFactoryBeanSupport repository factory beans} to provide generic type information to the
* the AOT tooling to allow deriving target type from the {@link RootBeanDefinition bean definition}. If generic types * AOT tooling to allow deriving target type from the {@link RootBeanDefinition bean definition}. If generic types do
* do not match due to customization of the factory bean by the user, at least the target repository type is provided * not match due to customization of the factory bean by the user, at least the target repository type is provided via
* via the {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE}. * the {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE}.
* </p> * </p>
* <p> * <p>
* With {@link RepositoryRegistrationAotProcessor#contribute(AotRepositoryContext, GenerationContext)}, stores * With {@link RepositoryRegistrationAotProcessor#contribute(AotRepositoryContext, GenerationContext)}, stores can
* can provide custom logic for contributing additional (eg. reflection) configuration. By default, reflection * provide custom logic for contributing additional (eg. reflection) configuration. By default, reflection configuration
* configuration will be added for types reachable from the repository declaration and query methods as well as * will be added for types reachable from the repository declaration and query methods as well as all used
* all used {@link Annotation annotations} from the {@literal org.springframework.data} namespace. * {@link Annotation annotations} from the {@literal org.springframework.data} namespace.
* </p> * </p>
* The processor is typically configured via {@link RepositoryConfigurationExtension#getRepositoryAotProcessor()} * The processor is typically configured via {@link RepositoryConfigurationExtension#getRepositoryAotProcessor()} and
* and gets added by the {@link org.springframework.data.repository.config.RepositoryConfigurationDelegate}. * gets added by the {@link org.springframework.data.repository.config.RepositoryConfigurationDelegate}.
* *
* @author Christoph Strobl * @author Christoph Strobl
* @author John Blum * @author John Blum
* @see org.springframework.beans.factory.BeanFactoryAware
* @see org.springframework.beans.factory.aot.BeanRegistrationAotProcessor
* @since 3.0.0 * @since 3.0.0
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -73,45 +70,41 @@ public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotPr
private final Log logger = LogFactory.getLog(getClass()); private final Log logger = LogFactory.getLog(getClass());
private Map<String, RepositoryMetadata<?>> configMap; private Map<String, RepositoryConfiguration<?>> configMap;
@Nullable @Nullable
@Override @Override
public BeanRegistrationAotContribution processAheadOfTime(@NonNull RegisteredBean bean) { public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean bean) {
return isRepositoryBean(bean) return isRepositoryBean(bean) ? newRepositoryRegistrationAotContribution(bean) : null;
? newRepositoryRegistrationAotContribution(bean)
: null;
} }
protected void contribute(AotRepositoryContext repositoryContext, GenerationContext generationContext) { protected void contribute(AotRepositoryContext repositoryContext, GenerationContext generationContext) {
repositoryContext.getResolvedTypes().stream() repositoryContext.getResolvedTypes().stream()
.filter(it -> !RepositoryRegistrationAotContribution .filter(it -> !RepositoryRegistrationAotContribution.isJavaOrPrimitiveType(it))
.isJavaOrPrimitiveType(it))
.forEach(it -> RepositoryRegistrationAotContribution.contributeType(it, generationContext)); .forEach(it -> RepositoryRegistrationAotContribution.contributeType(it, generationContext));
repositoryContext.getResolvedAnnotations().stream() repositoryContext.getResolvedAnnotations().stream()
.filter(RepositoryRegistrationAotContribution::isSpringDataManagedAnnotation) .filter(RepositoryRegistrationAotContribution::isSpringDataManagedAnnotation).map(MergedAnnotation::getType)
.map(MergedAnnotation::getType)
.forEach(it -> RepositoryRegistrationAotContribution.contributeType(it, generationContext)); .forEach(it -> RepositoryRegistrationAotContribution.contributeType(it, generationContext));
} }
private boolean isRepositoryBean(RegisteredBean bean) { private boolean isRepositoryBean(RegisteredBean bean) {
return bean != null && getConfigMap().containsKey(bean.getBeanName()); return getConfigMap().containsKey(bean.getBeanName());
} }
protected RepositoryRegistrationAotContribution newRepositoryRegistrationAotContribution( protected RepositoryRegistrationAotContribution newRepositoryRegistrationAotContribution(
@NonNull RegisteredBean repositoryBean) { RegisteredBean repositoryBean) {
RepositoryRegistrationAotContribution contribution = RepositoryRegistrationAotContribution contribution = RepositoryRegistrationAotContribution.fromProcessor(this)
RepositoryRegistrationAotContribution.fromProcessor(this).forBean(repositoryBean); .forBean(repositoryBean);
return contribution.withModuleContribution(this::contribute); return contribution.withModuleContribution(this::contribute);
} }
@Override @Override
public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException { public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory, Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory,
() -> "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory); () -> "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
@ -119,44 +112,40 @@ public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotPr
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
} }
@NonNull
protected ConfigurableListableBeanFactory getBeanFactory() { protected ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory; return this.beanFactory;
} }
public void setConfigMap(@Nullable Map<String, RepositoryMetadata<?>> configMap) { public void setConfigMap(@Nullable Map<String, RepositoryConfiguration<?>> configMap) {
this.configMap = configMap; this.configMap = configMap;
} }
@NonNull public Map<String, RepositoryConfiguration<?>> getConfigMap() {
public Map<String, RepositoryMetadata<?>> getConfigMap() {
return nullSafeMap(this.configMap); return nullSafeMap(this.configMap);
} }
@NonNull
private <K, V> Map<K, V> nullSafeMap(@Nullable Map<K, V> map) { private <K, V> Map<K, V> nullSafeMap(@Nullable Map<K, V> map) {
return map != null ? map : Collections.emptyMap(); return map != null ? map : Collections.emptyMap();
} }
@Nullable @Nullable
protected RepositoryMetadata<?> getRepositoryMetadata(@NonNull RegisteredBean bean) { protected RepositoryConfiguration<?> getRepositoryMetadata(RegisteredBean bean) {
return getConfigMap().get(nullSafeBeanName(bean)); return getConfigMap().get(nullSafeBeanName(bean));
} }
private String nullSafeBeanName(@Nullable RegisteredBean bean) { private String nullSafeBeanName(RegisteredBean bean) {
String beanName = bean != null ? bean.getBeanName() : null; String beanName = bean.getBeanName();
return StringUtils.hasText(beanName) ? beanName : ""; return StringUtils.hasText(beanName) ? beanName : "";
} }
@NonNull
protected Log getLogger() { protected Log getLogger() {
return this.logger; return this.logger;
} }
private void logAt(Predicate<Log> logLevelPredicate, BiConsumer<Log, String> logOperation, private void logAt(Predicate<Log> logLevelPredicate, BiConsumer<Log, String> logOperation, String message,
String message, Object... arguments) { Object... arguments) {
Log logger = getLogger(); Log logger = getLogger();

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

@ -15,12 +15,12 @@
*/ */
package org.springframework.data.aot; package org.springframework.data.aot;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
@ -29,7 +29,6 @@ import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueH
import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.data.domain.ManagedTypes; import org.springframework.data.domain.ManagedTypes;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
@ -52,37 +51,36 @@ public class SpringDataBeanFactoryInitializationAotProcessor implements BeanFact
@Nullable @Nullable
@Override @Override
public BeanFactoryInitializationAotContribution processAheadOfTime( public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
@NonNull ConfigurableListableBeanFactory beanFactory) {
processManagedTypes(beanFactory); processManagedTypes(beanFactory);
return null; return null;
} }
private void processManagedTypes(@NonNull ConfigurableListableBeanFactory beanFactory) { private void processManagedTypes(ConfigurableListableBeanFactory beanFactory) {
if (beanFactory instanceof BeanDefinitionRegistry registry) { if (beanFactory instanceof BeanDefinitionRegistry registry) {
for (String beanName : beanFactory.getBeanNamesForType(ManagedTypes.class)) { for (String beanName : beanFactory.getBeanNamesForType(ManagedTypes.class)) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if(beanDefinition.getConstructorArgumentValues().isEmpty()) { if (beanDefinition.getConstructorArgumentValues().isEmpty()) {
return; return;
} }
ValueHolder argumentValue = beanDefinition.getConstructorArgumentValues().getArgumentValue(0, null, null, null); ValueHolder argumentValue = beanDefinition.getConstructorArgumentValues().getArgumentValue(0, null, null, null);
if (argumentValue.getValue() instanceof Supplier supplier) { if (argumentValue.getValue()instanceof Supplier supplier) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.info(String.format("Replacing ManagedType bean definition %s.", beanName)); logger.info(String.format("Replacing ManagedType bean definition %s.", beanName));
} }
Object value = supplier.get(); Object value = supplier.get();
if(ObjectUtils.isArray(value)) { if (ObjectUtils.isArray(value)) {
value = CollectionUtils.arrayToList(value); value = CollectionUtils.arrayToList(value);
} }
if(!(value instanceof Iterable<?>)) { if (!(value instanceof Iterable<?>)) {
value = Collections.singleton(value); value = Collections.singleton(value);
} }

46
src/main/java/org/springframework/data/aot/TypeCollector.java

@ -35,9 +35,9 @@ import java.util.function.Predicate;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.data.util.Lazy; import org.springframework.data.util.Lazy;
import org.springframework.lang.NonNull;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@ -62,39 +62,39 @@ public class TypeCollector {
private final Predicate<Method> methodFilter = method -> { private final Predicate<Method> methodFilter = method -> {
Predicate<Method> excludedDomainsPredicate = methodToTest -> // TODO: Eagerly construct predicate objects to avoid object allocations during test(…)
excludedDomainsFilter.test(methodToTest.getDeclaringClass()); Predicate<Method> excludedDomainsPredicate = methodToTest -> excludedDomainsFilter
.test(methodToTest.getDeclaringClass());
Predicate<Method> excludedMethodsPredicate = Predicates.IS_BRIDGE_METHOD Predicate<Method> excludedMethodsPredicate = Predicates.IS_BRIDGE_METHOD //
.or(Predicates.IS_OBJECT_MEMBER) .or(Predicates.IS_OBJECT_MEMBER) //
.or(Predicates.IS_HIBERNATE_MEMBER) .or(Predicates.IS_HIBERNATE_MEMBER) //
.or(Predicates.IS_ENUM_MEMBER) .or(Predicates.IS_ENUM_MEMBER) //
.or(Predicates.IS_NATIVE) .or(Predicates.IS_NATIVE) //
.or(Predicates.IS_PRIVATE) .or(Predicates.IS_PRIVATE) //
.or(Predicates.IS_PROTECTED) .or(Predicates.IS_PROTECTED) //
.or(Predicates.IS_SYNTHETIC) .or(Predicates.IS_SYNTHETIC) //
.or(excludedDomainsPredicate.negate()); .or(excludedDomainsPredicate.negate()); //
return !excludedMethodsPredicate.test(method); return !excludedMethodsPredicate.test(method);
}; };
private Predicate<Field> fieldFilter = field -> { private Predicate<Field> fieldFilter = field -> {
Predicate<Member> excludedFieldPredicate = Predicates.IS_HIBERNATE_MEMBER // TODO: Eagerly construct predicate objects to avoid object allocations during test(…)
.or(Predicates.IS_SYNTHETIC) Predicate<Member> excludedFieldPredicate = Predicates.IS_HIBERNATE_MEMBER //
.or(Predicates.IS_SYNTHETIC) //
.or(Predicates.IS_JAVA); .or(Predicates.IS_JAVA);
return !excludedFieldPredicate.test(field); return !excludedFieldPredicate.test(field);
}; };
@NonNull public TypeCollector filterFields(Predicate<Field> filter) {
public TypeCollector filterFields(@NonNull Predicate<Field> filter) {
this.fieldFilter = filter.and(filter); this.fieldFilter = filter.and(filter);
return this; return this;
} }
@NonNull public TypeCollector filterTypes(Predicate<Class<?>> filter) {
public TypeCollector filterTypes(@NonNull Predicate<Class<?>> filter) {
this.typeFilter = this.typeFilter.and(filter); this.typeFilter = this.typeFilter.and(filter);
return this; return this;
} }
@ -105,12 +105,10 @@ public class TypeCollector {
* @param types the types to inspect * @param types the types to inspect
* @return a type model collector for the type * @return a type model collector for the type
*/ */
@NonNull
public static ReachableTypes inspect(Class<?>... types) { public static ReachableTypes inspect(Class<?>... types) {
return inspect(Arrays.asList(types)); return inspect(Arrays.asList(types));
} }
@NonNull
public static ReachableTypes inspect(Collection<Class<?>> types) { public static ReachableTypes inspect(Collection<Class<?>> types) {
return new ReachableTypes(new TypeCollector(), types); return new ReachableTypes(new TypeCollector(), types);
} }
@ -137,9 +135,11 @@ public class TypeCollector {
additionalTypes.addAll(visitConstructorsOfType(type)); additionalTypes.addAll(visitConstructorsOfType(type));
additionalTypes.addAll(visitMethodsOfType(type)); additionalTypes.addAll(visitMethodsOfType(type));
additionalTypes.addAll(visitFieldsOfType(type)); additionalTypes.addAll(visitFieldsOfType(type));
if (!ObjectUtils.isEmpty(type.toClass().getDeclaredClasses())) { if (!ObjectUtils.isEmpty(type.toClass().getDeclaredClasses())) {
additionalTypes.addAll(Arrays.asList(type.toClass().getDeclaredClasses())); additionalTypes.addAll(Arrays.asList(type.toClass().getDeclaredClasses()));
} }
for (Type discoveredType : additionalTypes) { for (Type discoveredType : additionalTypes) {
processType(ResolvableType.forType(discoveredType, type), cache, callback); processType(ResolvableType.forType(discoveredType, type), cache, callback);
} }
@ -204,7 +204,7 @@ public class TypeCollector {
private final Lazy<List<Class<?>>> reachableTypes = Lazy.of(this::collect); private final Lazy<List<Class<?>>> reachableTypes = Lazy.of(this::collect);
private final TypeCollector typeCollector; private final TypeCollector typeCollector;
public ReachableTypes(@NonNull TypeCollector typeCollector, @NonNull Iterable<Class<?>> roots) { public ReachableTypes(TypeCollector typeCollector, Iterable<Class<?>> roots) {
this.typeCollector = typeCollector; this.typeCollector = typeCollector;
this.roots = roots; this.roots = roots;
@ -229,7 +229,7 @@ public class TypeCollector {
private final Map<String, ResolvableType> mutableCache = new LinkedHashMap<>(); private final Map<String, ResolvableType> mutableCache = new LinkedHashMap<>();
public void add(@NonNull ResolvableType resolvableType) { public void add(ResolvableType resolvableType) {
mutableCache.put(resolvableType.toString(), resolvableType); mutableCache.put(resolvableType.toString(), resolvableType);
} }
@ -237,7 +237,7 @@ public class TypeCollector {
mutableCache.clear(); mutableCache.clear();
} }
public boolean contains(@NonNull ResolvableType key) { public boolean contains(ResolvableType key) {
return mutableCache.containsKey(key.toString()); return mutableCache.containsKey(key.toString());
} }

30
src/main/java/org/springframework/data/aot/TypeUtils.java

@ -21,9 +21,9 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter; import java.lang.reflect.Parameter;
import java.util.Collection;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
@ -38,9 +38,12 @@ import org.springframework.util.ObjectUtils;
/** /**
* @author Christoph Strobl * @author Christoph Strobl
*/ */
// TODO: Consider moving to the org.springframework.data.util package. // TODO: Consider moving to the org.springframework.data.util package or make this package-private if not used somewhere
// else.
public class TypeUtils { public class TypeUtils {
public static String TRANSACTION_MANAGER_CLASS_NAME = "org.springframework.transaction.TransactionManager";
/** /**
* Resolve ALL annotations present for a given type. Will inspect type, constructors, parameters, methods, fields,... * Resolve ALL annotations present for a given type. Will inspect type, constructors, parameters, methods, fields,...
* *
@ -50,25 +53,25 @@ public class TypeUtils {
public static Set<MergedAnnotation<Annotation>> resolveUsedAnnotations(Class<?> type) { public static Set<MergedAnnotation<Annotation>> resolveUsedAnnotations(Class<?> type) {
Set<MergedAnnotation<Annotation>> annotations = new LinkedHashSet<>(); Set<MergedAnnotation<Annotation>> annotations = new LinkedHashSet<>();
annotations.addAll(TypeUtils.resolveAnnotationsFor(type).collect(Collectors.toSet())); annotations.addAll(TypeUtils.resolveAnnotationsFor(type).toList());
for (Constructor<?> ctor : type.getDeclaredConstructors()) { for (Constructor<?> ctor : type.getDeclaredConstructors()) {
annotations.addAll(TypeUtils.resolveAnnotationsFor(ctor).collect(Collectors.toSet())); annotations.addAll(TypeUtils.resolveAnnotationsFor(ctor).toList());
for (Parameter parameter : ctor.getParameters()) { for (Parameter parameter : ctor.getParameters()) {
annotations.addAll(TypeUtils.resolveAnnotationsFor(parameter).collect(Collectors.toSet())); annotations.addAll(TypeUtils.resolveAnnotationsFor(parameter).toList());
} }
} }
for (Field field : type.getDeclaredFields()) { for (Field field : type.getDeclaredFields()) {
annotations.addAll(TypeUtils.resolveAnnotationsFor(field).collect(Collectors.toSet())); annotations.addAll(TypeUtils.resolveAnnotationsFor(field).toList());
} }
try { try {
for (Method method : type.getDeclaredMethods()) { for (Method method : type.getDeclaredMethods()) {
annotations.addAll(TypeUtils.resolveAnnotationsFor(method).collect(Collectors.toSet())); annotations.addAll(TypeUtils.resolveAnnotationsFor(method).toList());
for (Parameter parameter : method.getParameters()) { for (Parameter parameter : method.getParameters()) {
annotations.addAll(TypeUtils.resolveAnnotationsFor(parameter).collect(Collectors.toSet())); annotations.addAll(TypeUtils.resolveAnnotationsFor(parameter).toList());
} }
} }
} catch (NoClassDefFoundError e) { } catch (NoClassDefFoundError e) {
// ignore an move on // ignore and move on
} }
return annotations; return annotations;
} }
@ -83,13 +86,14 @@ public class TypeUtils {
.from(element, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.standardRepeatables(), filter).stream(); .from(element, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.standardRepeatables(), filter).stream();
} }
public static Set<Class<?>> resolveAnnotationTypesFor(AnnotatedElement element, AnnotationFilter filter) { public static Collection<Class<Annotation>> resolveAnnotationTypesFor(AnnotatedElement element,
AnnotationFilter filter) {
return MergedAnnotations return MergedAnnotations
.from(element, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.standardRepeatables(), filter).stream() .from(element, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.standardRepeatables(), filter).stream()
.map(MergedAnnotation::getType).collect(Collectors.toSet()); .map(MergedAnnotation::getType).toList();
} }
public static Set<Class<?>> resolveAnnotationTypesFor(AnnotatedElement element) { public static Collection<Class<Annotation>> resolveAnnotationTypesFor(AnnotatedElement element) {
return resolveAnnotationTypesFor(element, AnnotationFilter.PLAIN); return resolveAnnotationTypesFor(element, AnnotationFilter.PLAIN);
} }
@ -214,7 +218,7 @@ public class TypeUtils {
private static class TypeOpsImpl implements TypeOps { private static class TypeOpsImpl implements TypeOps {
private Class<?> type; private final Class<?> type;
TypeOpsImpl(Class<?> type) { TypeOpsImpl(Class<?> type) {
this.type = type; this.type = type;

5
src/main/java/org/springframework/data/aot/hint/AuditingHints.java

@ -26,8 +26,13 @@ import org.springframework.data.domain.ReactiveAuditorAware;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
/** /**
* Runtime hint registrars for Spring Data modules that provide auditing functionality.
* <p>
* Mainly for internal use within the framework.
*
* @author Christoph Strobl * @author Christoph Strobl
* @since 3.0 * @since 3.0
* @see RuntimeHintsRegistrar
*/ */
public class AuditingHints { public class AuditingHints {

5
src/main/java/org/springframework/data/aot/hint/package-info.java

@ -0,0 +1,5 @@
/**
* Predefined Runtime Hints.
*/
@org.springframework.lang.NonNullApi
package org.springframework.data.aot.hint;

6
src/main/java/org/springframework/data/aot/package-info.java

@ -0,0 +1,6 @@
/**
* Support for registering the need for reflection, resources, java serialization and proxies at runtime for Ahead of
* Time compilation.
*/
@org.springframework.lang.NonNullApi
package org.springframework.data.aot;

23
src/main/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupport.java

@ -56,28 +56,41 @@ public abstract class AuditingBeanDefinitionRegistrarSupport implements ImportBe
Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null"); Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
AbstractBeanDefinition ahbd = registerAuditHandlerBeanDefinition(registry, getConfiguration(annotationMetadata)); AbstractBeanDefinition ahbd = registerAuditHandlerBeanDefinition(getConfiguration(annotationMetadata), registry);
registerAuditListenerBeanDefinition(ahbd, registry); registerAuditListenerBeanDefinition(ahbd, registry);
} }
/** /**
* Registers an appropriate BeanDefinition for an {@link AuditingHandler}. * Registers an appropriate BeanDefinition for an {@link AuditingHandler}.
* *
* @param registry must not be {@literal null}.
* @param configuration must not be {@literal null}. * @param configuration must not be {@literal null}.
* @param registry must not be {@literal null}.
* @return * @return
*/ */
private AbstractBeanDefinition registerAuditHandlerBeanDefinition(BeanDefinitionRegistry registry, protected AbstractBeanDefinition registerAuditHandlerBeanDefinition(AuditingConfiguration configuration,
AuditingConfiguration configuration) { BeanDefinitionRegistry registry) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
Assert.notNull(configuration, "AuditingConfiguration must not be null"); Assert.notNull(configuration, "AuditingConfiguration must not be null");
AbstractBeanDefinition ahbd = getAuditHandlerBeanDefinitionBuilder(configuration).getBeanDefinition(); BeanDefinitionBuilder builder = getAuditHandlerBeanDefinitionBuilder(configuration);
postProcess(builder, configuration, registry);
AbstractBeanDefinition ahbd = builder.getBeanDefinition();
registry.registerBeanDefinition(getAuditingHandlerBeanName(), ahbd); registry.registerBeanDefinition(getAuditingHandlerBeanName(), ahbd);
return ahbd; return ahbd;
} }
/**
* Customization hook to post-process the AuditHandler BeanDefinition.
*
* @param builder must not be {@literal null}.
* @param registry must not be {@literal null}.
* @param configuration must not be {@literal null}.
* @since 3.0
*/
protected void postProcess(BeanDefinitionBuilder builder, AuditingConfiguration configuration,
BeanDefinitionRegistry registry) {}
/** /**
* Creates a {@link BeanDefinitionBuilder} to ease the definition of store specific {@link AuditingHandler} * Creates a {@link BeanDefinitionBuilder} to ease the definition of store specific {@link AuditingHandler}
* implementations. * implementations.

32
src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java

@ -17,8 +17,8 @@ package org.springframework.data.repository.config;
import static org.springframework.beans.factory.config.BeanDefinition.*; import static org.springframework.beans.factory.config.BeanDefinition.*;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Optional; import java.util.Optional;
@ -27,6 +27,7 @@ import java.util.stream.Stream;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition;
@ -120,7 +121,8 @@ class RepositoryBeanDefinitionBuilder {
extension.getDefaultNamedQueryLocation()); extension.getDefaultNamedQueryLocation());
configuration.getNamedQueriesLocation().ifPresent(definitionBuilder::setLocations); configuration.getNamedQueriesLocation().ifPresent(definitionBuilder::setLocations);
String namedQueriesBeanName = BeanDefinitionReaderUtils.uniqueBeanName(extension.getModuleIdentifier() + ".named-queries", registry); String namedQueriesBeanName = BeanDefinitionReaderUtils
.uniqueBeanName(extension.getModuleIdentifier() + ".named-queries", registry);
BeanDefinition namedQueries = definitionBuilder.build(configuration.getSource()); BeanDefinition namedQueries = definitionBuilder.build(configuration.getSource());
registry.registerBeanDefinition(namedQueriesBeanName, namedQueries); registry.registerBeanDefinition(namedQueriesBeanName, namedQueries);
@ -138,41 +140,43 @@ class RepositoryBeanDefinitionBuilder {
} }
// TODO: merge that with the one that creates the BD // TODO: merge that with the one that creates the BD
RepositoryMetadata buildMetadata(RepositoryConfiguration<?> configuration) { RepositoryConfigurationAdapter<?> buildMetadata(RepositoryConfiguration<?> configuration) {
ImplementationDetectionConfiguration config = configuration ImplementationDetectionConfiguration config = configuration
.toImplementationDetectionConfiguration(metadataReaderFactory); .toImplementationDetectionConfiguration(metadataReaderFactory);
List<RepositoryFragmentConfiguration> repositoryFragmentConfigurationStream = fragmentMetadata.getFragmentInterfaces(configuration.getRepositoryInterface()) // List<RepositoryFragmentConfiguration> repositoryFragmentConfigurationStream = fragmentMetadata
.getFragmentInterfaces(configuration.getRepositoryInterface()) //
.map(it -> detectRepositoryFragmentConfiguration(it, config)) // .map(it -> detectRepositoryFragmentConfiguration(it, config)) //
.flatMap(Optionals::toStream) .flatMap(Optionals::toStream).toList();
.collect(Collectors.toList());//
if(repositoryFragmentConfigurationStream.isEmpty()) { if (repositoryFragmentConfigurationStream.isEmpty()) {
ImplementationLookupConfiguration lookup = configuration.toLookupConfiguration(metadataReaderFactory); ImplementationLookupConfiguration lookup = configuration.toLookupConfiguration(metadataReaderFactory);
Optional<AbstractBeanDefinition> beanDefinition = implementationDetector.detectCustomImplementation(lookup); Optional<AbstractBeanDefinition> beanDefinition = implementationDetector.detectCustomImplementation(lookup);
if(beanDefinition.isPresent()) { if (beanDefinition.isPresent()) {
repositoryFragmentConfigurationStream = new ArrayList<>(1); repositoryFragmentConfigurationStream = new ArrayList<>(1);
List<String> interfaceNames = fragmentMetadata.getFragmentInterfaces(configuration.getRepositoryInterface()).collect(Collectors.toList()); List<String> interfaceNames = fragmentMetadata.getFragmentInterfaces(configuration.getRepositoryInterface())
.toList();
String implClassName = beanDefinition.get().getBeanClassName(); String implClassName = beanDefinition.get().getBeanClassName();
try { try {
for (String iName : metadataReaderFactory.getMetadataReader(implClassName).getClassMetadata().getInterfaceNames()) { for (String iName : metadataReaderFactory.getMetadataReader(implClassName).getClassMetadata()
if(interfaceNames.contains(iName)) { .getInterfaceNames()) {
if (interfaceNames.contains(iName)) {
repositoryFragmentConfigurationStream.add(new RepositoryFragmentConfiguration(iName, implClassName)); repositoryFragmentConfigurationStream.add(new RepositoryFragmentConfiguration(iName, implClassName));
break; break;
} }
} }
} catch (Exception e) { } catch (IOException e) {
e.printStackTrace(); throw new IllegalStateException(e);
} }
} }
} }
return new RepositoryMetadata(configuration, repositoryFragmentConfigurationStream); return new RepositoryConfigurationAdapter<>(configuration, repositoryFragmentConfigurationStream);
} }
private Optional<String> registerCustomImplementation(RepositoryConfiguration<?> configuration) { private Optional<String> registerCustomImplementation(RepositoryConfiguration<?> configuration) {

6
src/main/java/org/springframework/data/repository/config/RepositoryMetadata.java → src/main/java/org/springframework/data/repository/config/RepositoryConfigurationAdapter.java

@ -27,12 +27,13 @@ import org.springframework.lang.Nullable;
* @author Christoph Strobl * @author Christoph Strobl
* @since 3.0 * @since 3.0
*/ */
public class RepositoryMetadata<T extends RepositoryConfigurationSource> implements RepositoryConfiguration<T> { class RepositoryConfigurationAdapter<T extends RepositoryConfigurationSource>
implements RepositoryConfiguration<T>, RepositoryFragmentConfigurationProvider {
RepositoryConfiguration<T> repositoryConfiguration; RepositoryConfiguration<T> repositoryConfiguration;
List<RepositoryFragmentConfiguration> fragmentConfiguration; List<RepositoryFragmentConfiguration> fragmentConfiguration;
public RepositoryMetadata(RepositoryConfiguration<T> repositoryConfiguration, public RepositoryConfigurationAdapter(RepositoryConfiguration<T> repositoryConfiguration,
List<RepositoryFragmentConfiguration> fragmentConfiguration) { List<RepositoryFragmentConfiguration> fragmentConfiguration) {
this.repositoryConfiguration = repositoryConfiguration; this.repositoryConfiguration = repositoryConfiguration;
@ -116,6 +117,7 @@ public class RepositoryMetadata<T extends RepositoryConfigurationSource> impleme
return repositoryConfiguration.getResourceDescription(); return repositoryConfiguration.getResourceDescription();
} }
@Override
public List<RepositoryFragmentConfiguration> getFragmentConfiguration() { public List<RepositoryFragmentConfiguration> getFragmentConfiguration() {
return fragmentConfiguration; return fragmentConfiguration;
} }

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

@ -21,13 +21,11 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.aot.AotDetector;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
@ -48,7 +46,6 @@ import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.log.LogMessage; import org.springframework.core.log.LogMessage;
import org.springframework.core.metrics.ApplicationStartup; import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.core.metrics.StartupStep; import org.springframework.core.metrics.StartupStep;
import org.springframework.data.util.TypeScanner;
import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -71,8 +68,7 @@ public class RepositoryConfigurationDelegate {
private static final String REPOSITORY_REGISTRATION = "Spring Data %s - Registering repository: %s - Interface: %s - Factory: %s"; private static final String REPOSITORY_REGISTRATION = "Spring Data %s - Registering repository: %s - Interface: %s - Factory: %s";
private static final String MULTIPLE_MODULES = "Multiple Spring Data modules found, entering strict repository configuration mode"; private static final String MULTIPLE_MODULES = "Multiple Spring Data modules found, entering strict repository configuration mode";
private static final String NON_DEFAULT_AUTOWIRE_CANDIDATE_RESOLVER = "Non-default AutowireCandidateResolver (%s) detected. Skipping the registration of LazyRepositoryInjectionPointResolver. Lazy repository injection will not be working"; private static final String NON_DEFAULT_AUTOWIRE_CANDIDATE_RESOLVER = "Non-default AutowireCandidateResolver (%s) detected. Skipping the registration of LazyRepositoryInjectionPointResolver. Lazy repository injection will not be working";
private static final String FACTORY_BEAN_OBJECT_TYPE = FactoryBean.OBJECT_TYPE_ATTRIBUTE;
static final String FACTORY_BEAN_OBJECT_TYPE = FactoryBean.OBJECT_TYPE_ATTRIBUTE; // "factoryBeanObjectType";
private static final Log logger = LogFactory.getLog(RepositoryConfigurationDelegate.class); private static final Log logger = LogFactory.getLog(RepositoryConfigurationDelegate.class);
@ -169,7 +165,7 @@ public class RepositoryConfigurationDelegate {
List<BeanComponentDefinition> definitions = new ArrayList<>(); List<BeanComponentDefinition> definitions = new ArrayList<>();
Map<String, RepositoryConfiguration<?>> configurationsByRepositoryName = new HashMap<>(configurations.size()); Map<String, RepositoryConfiguration<?>> configurationsByRepositoryName = new HashMap<>(configurations.size());
Map<String, RepositoryMetadata<?>> metadataByRepositoryBeanName = new HashMap<>(configurations.size()); Map<String, RepositoryConfigurationAdapter<?>> metadataByRepositoryBeanName = new HashMap<>(configurations.size());
for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : configurations) { for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : configurations) {
@ -223,7 +219,7 @@ public class RepositoryConfigurationDelegate {
} }
private void registerAotComponents(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension, private void registerAotComponents(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension,
Map<String, RepositoryMetadata<?>> metadataByRepositoryBeanName) { Map<String, RepositoryConfigurationAdapter<?>> metadataByRepositoryBeanName) {
// module-specific repository aot processor // module-specific repository aot processor
String repositoryAotProcessorBeanName = String.format("data-%s.repository-aot-processor" /* might be duplicate */, String repositoryAotProcessorBeanName = String.format("data-%s.repository-aot-processor" /* might be duplicate */,

34
src/main/java/org/springframework/data/repository/config/RepositoryFragmentConfigurationProvider.java

@ -0,0 +1,34 @@
/*
* Copyright 2022 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.List;
/**
* Interface exposing {@link RepositoryFragmentConfiguration}.
*
* @author Mark Paluch
* @since 3.0
*/
public interface RepositoryFragmentConfigurationProvider {
/**
* Returns the fragment configuration.
*
* @return the fragment configuration.
*/
List<RepositoryFragmentConfiguration> getFragmentConfiguration();
}

1
src/main/java/org/springframework/data/repository/core/RepositoryInformationSupport.java

@ -164,7 +164,6 @@ public abstract class RepositoryInformationSupport implements RepositoryInformat
* @return * @return
*/ */
protected boolean isQueryAnnotationPresentOn(Method method) { protected boolean isQueryAnnotationPresentOn(Method method) {
return AnnotationUtils.findAnnotation(method, QueryAnnotation.class) != null; return AnnotationUtils.findAnnotation(method, QueryAnnotation.class) != null;
} }

2
src/main/java/org/springframework/data/repository/core/RepositoryMetadata.java

@ -129,10 +129,8 @@ public interface RepositoryMetadata {
boolean isReactiveRepository(); boolean isReactiveRepository();
/** /**
*
* @return * @return
* @since 3.0 * @since 3.0
*
*/ */
Set<RepositoryFragment<?>> getFragments(); Set<RepositoryFragment<?>> getFragments();
} }

10
src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java

@ -25,8 +25,6 @@ import java.util.concurrent.ConcurrentHashMap;
import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryInformationSupport; import org.springframework.data.repository.core.RepositoryInformationSupport;
import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@ -102,13 +100,7 @@ class DefaultRepositoryInformation extends RepositoryInformationSupport implemen
return baseComposition.getMethod(method) != null; return baseComposition.getMethod(method) != null;
} }
/** @Override
*
* @return
* @since 3.0
*
*/
@Nullable
public Set<RepositoryFragment<?>> getFragments() { public Set<RepositoryFragment<?>> getFragments() {
return composition.getFragments().toSet(); return composition.getFragments().toSet();
} }

37
src/test/java/org/springframework/data/aot/CodeContributionAssert.java

@ -15,7 +15,7 @@
*/ */
package org.springframework.data.aot; package org.springframework.data.aot;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.*;
import java.util.Arrays; import java.util.Arrays;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -25,19 +25,17 @@ import org.assertj.core.api.AbstractAssert;
import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.ClassProxyHint; import org.springframework.aot.hint.ClassProxyHint;
import org.springframework.aot.hint.JdkProxyHint; import org.springframework.aot.hint.JdkProxyHint;
import org.springframework.aot.hint.ProxyHintsPredicates;
import org.springframework.aot.hint.RuntimeHintsPredicates; import org.springframework.aot.hint.RuntimeHintsPredicates;
/** /**
* AssertJ {@link AbstractAssert Assertion} for code contributions originating from * AssertJ {@link AbstractAssert Assertion} for code contributions originating from Spring Data Repository
* Spring Data Repository infrastructure AOT processing. * infrastructure AOT processing.
* *
* @author Christoph Strobl * @author Christoph Strobl
* @author John Blum * @author John Blum
* @see org.assertj.core.api.AbstractAssert * @since 3.0
* @see org.springframework.aot.generate.GenerationContext
* @since 3.0.0
*/ */
@SuppressWarnings("UnusedReturnValue")
public class CodeContributionAssert extends AbstractAssert<CodeContributionAssert, GenerationContext> { public class CodeContributionAssert extends AbstractAssert<CodeContributionAssert, GenerationContext> {
public CodeContributionAssert(GenerationContext contribution) { public CodeContributionAssert(GenerationContext contribution) {
@ -47,8 +45,7 @@ public class CodeContributionAssert extends AbstractAssert<CodeContributionAsser
public CodeContributionAssert contributesReflectionFor(Class<?>... types) { public CodeContributionAssert contributesReflectionFor(Class<?>... types) {
for (Class<?> type : types) { for (Class<?> type : types) {
assertThat(this.actual.getRuntimeHints()) assertThat(this.actual.getRuntimeHints()).describedAs("No reflection entry found for [%s]", type)
.describedAs("No reflection entry found for [%s]", type)
.matches(RuntimeHintsPredicates.reflection().onType(type)); .matches(RuntimeHintsPredicates.reflection().onType(type));
} }
@ -58,8 +55,7 @@ public class CodeContributionAssert extends AbstractAssert<CodeContributionAsser
public CodeContributionAssert doesNotContributeReflectionFor(Class<?>... types) { public CodeContributionAssert doesNotContributeReflectionFor(Class<?>... types) {
for (Class<?> type : types) { for (Class<?> type : types) {
assertThat(this.actual.getRuntimeHints()) assertThat(this.actual.getRuntimeHints()).describedAs("Reflection entry found for [%s]", type)
.describedAs("Reflection entry found for [%s]", type)
.matches(RuntimeHintsPredicates.reflection().onType(type).negate()); .matches(RuntimeHintsPredicates.reflection().onType(type).negate());
} }
@ -68,8 +64,7 @@ public class CodeContributionAssert extends AbstractAssert<CodeContributionAsser
public CodeContributionAssert contributesJdkProxyFor(Class<?> entryPoint) { public CodeContributionAssert contributesJdkProxyFor(Class<?> entryPoint) {
assertThat(jdkProxiesFor(entryPoint).findFirst()) assertThat(jdkProxiesFor(entryPoint).findFirst()).describedAs("No JDK proxy found for [%s]", entryPoint)
.describedAs("No JDK proxy found for [%s]", entryPoint)
.isPresent(); .isPresent();
return this; return this;
@ -78,8 +73,7 @@ public class CodeContributionAssert extends AbstractAssert<CodeContributionAsser
public CodeContributionAssert doesNotContributeJdkProxyFor(Class<?> entryPoint) { public CodeContributionAssert doesNotContributeJdkProxyFor(Class<?> entryPoint) {
assertThat(jdkProxiesFor(entryPoint).findFirst()) assertThat(jdkProxiesFor(entryPoint).findFirst())
.describedAs("Found JDK proxy matching [%s] though it should not be present", entryPoint) .describedAs("Found JDK proxy matching [%s] though it should not be present", entryPoint).isNotPresent();
.isNotPresent();
return this; return this;
} }
@ -96,8 +90,7 @@ public class CodeContributionAssert extends AbstractAssert<CodeContributionAsser
public CodeContributionAssert doesNotContributeJdkProxy(Class<?>... proxyInterfaces) { public CodeContributionAssert doesNotContributeJdkProxy(Class<?>... proxyInterfaces) {
assertThat(jdkProxiesFor(proxyInterfaces[0])) assertThat(jdkProxiesFor(proxyInterfaces[0]))
.describedAs("Found JDK proxy matching [%s] though it should not be present", .describedAs("Found JDK proxy matching [%s] though it should not be present", Arrays.asList(proxyInterfaces))
Arrays.asList(proxyInterfaces))
.noneSatisfy(it -> new JdkProxyAssert(it).matches(proxyInterfaces)); .noneSatisfy(it -> new JdkProxyAssert(it).matches(proxyInterfaces));
return this; return this;
@ -105,9 +98,8 @@ public class CodeContributionAssert extends AbstractAssert<CodeContributionAsser
private Stream<JdkProxyHint> jdkProxiesFor(Class<?> entryPoint) { private Stream<JdkProxyHint> jdkProxiesFor(Class<?> entryPoint) {
return this.actual.getRuntimeHints().proxies().jdkProxies() return this.actual.getRuntimeHints().proxies().jdkProxies().filter(jdkProxyHint -> jdkProxyHint
.filter(jdkProxyHint -> jdkProxyHint.getProxiedInterfaces().get(0).getCanonicalName() .getProxiedInterfaces().get(0).getCanonicalName().equals(entryPoint.getCanonicalName()));
.equals(entryPoint.getCanonicalName()));
} }
public CodeContributionAssert contributesClassProxy(Class<?>... proxyInterfaces) { public CodeContributionAssert contributesClassProxy(Class<?>... proxyInterfaces) {
@ -121,8 +113,7 @@ public class CodeContributionAssert extends AbstractAssert<CodeContributionAsser
private Stream<ClassProxyHint> classProxiesFor(Class<?> entryPoint) { private Stream<ClassProxyHint> classProxiesFor(Class<?> entryPoint) {
return this.actual.getRuntimeHints().proxies().classProxies() return this.actual.getRuntimeHints().proxies().classProxies().filter(jdkProxyHint -> jdkProxyHint
.filter(jdkProxyHint -> jdkProxyHint.getProxiedInterfaces().get(0).getCanonicalName() .getProxiedInterfaces().get(0).getCanonicalName().equals(entryPoint.getCanonicalName()));
.equals(entryPoint.getCanonicalName()));
} }
} }

1
src/test/java/org/springframework/data/aot/JdkProxyAssert.java

@ -26,7 +26,6 @@ import org.springframework.aot.hint.TypeReference;
/** /**
* @author Christoph Strobl * @author Christoph Strobl
* @since 2022/04
*/ */
public class JdkProxyAssert extends AbstractAssert<JdkProxyAssert, JdkProxyHint> { public class JdkProxyAssert extends AbstractAssert<JdkProxyAssert, JdkProxyHint> {

4
src/test/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessorUnitTests.java

@ -24,6 +24,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.ClassNameGenerator; import org.springframework.aot.generate.ClassNameGenerator;
import org.springframework.aot.generate.DefaultGenerationContext; import org.springframework.aot.generate.DefaultGenerationContext;
import org.springframework.aot.generate.GeneratedClasses;
import org.springframework.aot.generate.InMemoryGeneratedFiles; import org.springframework.aot.generate.InMemoryGeneratedFiles;
import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsPredicates; import org.springframework.aot.hint.RuntimeHintsPredicates;
@ -72,7 +73,8 @@ class ManagedTypesBeanRegistrationAotProcessorUnitTests {
BeanRegistrationAotContribution contribution = createPostProcessor("commons") BeanRegistrationAotContribution contribution = createPostProcessor("commons")
.processAheadOfTime(RegisteredBean.of(beanFactory, "commons.managed-types")); .processAheadOfTime(RegisteredBean.of(beanFactory, "commons.managed-types"));
DefaultGenerationContext generationContext = new DefaultGenerationContext(new ClassNameGenerator(), DefaultGenerationContext generationContext = new DefaultGenerationContext(
new GeneratedClasses(new ClassNameGenerator(Object.class)),
new InMemoryGeneratedFiles(), new RuntimeHints()); new InMemoryGeneratedFiles(), new RuntimeHints());
contribution.applyTo(generationContext, null); contribution.applyTo(generationContext, null);

56
src/test/java/org/springframework/data/aot/RepositoryRegistrationAotContributionAssert.java

@ -15,8 +15,8 @@
*/ */
package org.springframework.data.aot; package org.springframework.data.aot;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.*;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
@ -27,36 +27,41 @@ import org.assertj.core.api.AbstractAssert;
import org.springframework.aot.generate.ClassNameGenerator; import org.springframework.aot.generate.ClassNameGenerator;
import org.springframework.aot.generate.DefaultGenerationContext; import org.springframework.aot.generate.DefaultGenerationContext;
import org.springframework.aot.generate.InMemoryGeneratedFiles; import org.springframework.aot.generate.InMemoryGeneratedFiles;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.beans.factory.aot.BeanRegistrationCode; import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFragment; import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.lang.NonNull;
/** /**
* AssertJ {@link AbstractAssert Assertion} for {@link RepositoryRegistrationAotContribution}. * AssertJ {@link AbstractAssert Assertion} for {@link RepositoryRegistrationAotContribution}.
* *
* @author Christoph Strobl * @author Christoph Strobl
* @author John Blum * @author John Blum
* @see org.mockito.Mockito * @since 3.0
* @see org.assertj.core.api.AbstractAssert
* @see org.springframework.data.aot.RepositoryRegistrationAotContribution
* @since 3.0.0
*/ */
public class RepositoryRegistrationAotContributionAssert public class RepositoryRegistrationAotContributionAssert
extends AbstractAssert<RepositoryRegistrationAotContributionAssert, RepositoryRegistrationAotContribution> { extends AbstractAssert<RepositoryRegistrationAotContributionAssert, RepositoryRegistrationAotContribution> {
@NonNull
public static RepositoryRegistrationAotContributionAssert assertThatContribution( public static RepositoryRegistrationAotContributionAssert assertThatContribution(
@NonNull RepositoryRegistrationAotContribution actual) { RepositoryRegistrationAotContribution actual) {
return new RepositoryRegistrationAotContributionAssert(actual); return new RepositoryRegistrationAotContributionAssert(actual);
} }
public RepositoryRegistrationAotContributionAssert(@NonNull RepositoryRegistrationAotContribution actual) { /**
* Create the assertion object.
*
* @param actual
*/
public RepositoryRegistrationAotContributionAssert(RepositoryRegistrationAotContribution actual) {
super(actual, RepositoryRegistrationAotContributionAssert.class); super(actual, RepositoryRegistrationAotContributionAssert.class);
} }
/**
* Verifies that the actual repository type is equal to the given one.
*
* @param expected
* @return {@code this} assertion object.
*/
public RepositoryRegistrationAotContributionAssert targetRepositoryTypeIs(Class<?> expected) { public RepositoryRegistrationAotContributionAssert targetRepositoryTypeIs(Class<?> expected) {
assertThat(getRepositoryInformation().getRepositoryInterface()).isEqualTo(expected); assertThat(getRepositoryInformation().getRepositoryInterface()).isEqualTo(expected);
@ -64,6 +69,11 @@ public class RepositoryRegistrationAotContributionAssert
return this.myself; return this.myself;
} }
/**
* Verifies that the actual repository has no repository fragments.
*
* @return {@code this} assertion object.
*/
public RepositoryRegistrationAotContributionAssert hasNoFragments() { public RepositoryRegistrationAotContributionAssert hasNoFragments() {
assertThat(getRepositoryInformation().getFragments()).isEmpty(); assertThat(getRepositoryInformation().getFragments()).isEmpty();
@ -71,6 +81,11 @@ public class RepositoryRegistrationAotContributionAssert
return this; return this;
} }
/**
* Verifies that the actual repository has repository fragments.
*
* @return {@code this} assertion object.
*/
public RepositoryRegistrationAotContributionAssert hasFragments() { public RepositoryRegistrationAotContributionAssert hasFragments() {
assertThat(getRepositoryInformation().getFragments()).isNotEmpty(); assertThat(getRepositoryInformation().getFragments()).isNotEmpty();
@ -78,10 +93,14 @@ public class RepositoryRegistrationAotContributionAssert
return this; return this;
} }
/**
* Verifies that the actual repository fragments satisfy the given {@link Consumer}.
*
* @return {@code this} assertion object.
*/
public RepositoryRegistrationAotContributionAssert verifyFragments(Consumer<Set<RepositoryFragment<?>>> consumer) { public RepositoryRegistrationAotContributionAssert verifyFragments(Consumer<Set<RepositoryFragment<?>>> consumer) {
assertThat(getRepositoryInformation().getFragments()) assertThat(getRepositoryInformation().getFragments()).satisfies(it -> consumer.accept(new LinkedHashSet<>(it)));
.satisfies(it -> consumer.accept(new LinkedHashSet<>(it)));
return this; return this;
} }
@ -91,8 +110,8 @@ public class RepositoryRegistrationAotContributionAssert
BeanRegistrationCode mockBeanRegistrationCode = mock(BeanRegistrationCode.class); BeanRegistrationCode mockBeanRegistrationCode = mock(BeanRegistrationCode.class);
DefaultGenerationContext generationContext = DefaultGenerationContext generationContext = new DefaultGenerationContext(new ClassNameGenerator(Object.class),
new DefaultGenerationContext(new ClassNameGenerator(), new InMemoryGeneratedFiles(), new RuntimeHints()); new InMemoryGeneratedFiles());
this.actual.applyTo(generationContext, mockBeanRegistrationCode); this.actual.applyTo(generationContext, mockBeanRegistrationCode);
@ -103,13 +122,10 @@ public class RepositoryRegistrationAotContributionAssert
private RepositoryInformation getRepositoryInformation() { private RepositoryInformation getRepositoryInformation() {
assertThat(this.actual) assertThat(this.actual).describedAs("No repository interface found on null bean contribution").isNotNull();
.describedAs("No repository interface found on null bean contribution")
.isNotNull();
assertThat(this.actual.getRepositoryInformation()) assertThat(this.actual.getRepositoryInformation())
.describedAs("No repository interface found on null repository information") .describedAs("No repository interface found on null repository information").isNotNull();
.isNotNull();
return this.actual.getRepositoryInformation(); return this.actual.getRepositoryInformation();
} }

56
src/test/java/org/springframework/data/aot/RepositoryRegistrationAotProcessorIntegrationTests.java

@ -15,8 +15,8 @@
*/ */
package org.springframework.data.aot; package org.springframework.data.aot;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.aot.RepositoryRegistrationAotContributionAssert.assertThatContribution; import static org.springframework.data.aot.RepositoryRegistrationAotContributionAssert.*;
import java.io.Serializable; import java.io.Serializable;
@ -51,9 +51,6 @@ import org.springframework.transaction.interceptor.TransactionalProxy;
* Integration Tests for {@link RepositoryRegistrationAotProcessor}. * Integration Tests for {@link RepositoryRegistrationAotProcessor}.
* *
* @author Christoph Strobl * @author Christoph Strobl
* @see org.junit.jupiter.api.Test
* @see org.springframework.data.aot.RepositoryRegistrationAotProcessor
* @see org.springframework.data.aot.RepositoryRegistrationAotContributionAssert
* @author John Blum * @author John Blum
*/ */
public class RepositoryRegistrationAotProcessorIntegrationTests { public class RepositoryRegistrationAotProcessorIntegrationTests {
@ -61,8 +58,8 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
@Test // GH-2593 @Test // GH-2593
void simpleRepositoryNoTxManagerNoKotlinNoReactiveNoComponent() { void simpleRepositoryNoTxManagerNoKotlinNoReactiveNoComponent() {
RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(ConfigWithSimpleCrudRepository.class) RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(
.forRepository(ConfigWithSimpleCrudRepository.MyRepo.class); ConfigWithSimpleCrudRepository.class).forRepository(ConfigWithSimpleCrudRepository.MyRepo.class);
assertThatContribution(repositoryBeanContribution) // assertThatContribution(repositoryBeanContribution) //
.targetRepositoryTypeIs(ConfigWithSimpleCrudRepository.MyRepo.class) // .targetRepositoryTypeIs(ConfigWithSimpleCrudRepository.MyRepo.class) //
@ -123,8 +120,7 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
// interface // interface
.contributesReflectionFor(PagingAndSortingRepository.class) // base repository .contributesReflectionFor(PagingAndSortingRepository.class) // base repository
.contributesReflectionFor( .contributesReflectionFor(
ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty.Person.class) // repository domain ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepoisoty.Person.class) // domain type
// type
// proxies // proxies
.contributesJdkProxy( .contributesJdkProxy(
@ -142,8 +138,8 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
@Test // GH-2593 @Test // GH-2593
void contributesFragmentsCorrectly() { void contributesFragmentsCorrectly() {
RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(ConfigWithFragments.class) RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(
.forRepository(ConfigWithFragments.RepositoryWithFragments.class); ConfigWithFragments.class).forRepository(ConfigWithFragments.RepositoryWithFragments.class);
assertThatContribution(repositoryBeanContribution) // assertThatContribution(repositoryBeanContribution) //
.targetRepositoryTypeIs(ConfigWithFragments.RepositoryWithFragments.class) // .targetRepositoryTypeIs(ConfigWithFragments.RepositoryWithFragments.class) //
@ -175,7 +171,8 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
@Test // GH-2593 @Test // GH-2593
void contributesCustomImplementationCorrectly() { void contributesCustomImplementationCorrectly() {
RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(ConfigWithCustomImplementation.class) RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(
ConfigWithCustomImplementation.class)
.forRepository(ConfigWithCustomImplementation.RepositoryWithCustomImplementation.class); .forRepository(ConfigWithCustomImplementation.RepositoryWithCustomImplementation.class);
assertThatContribution(repositoryBeanContribution) // assertThatContribution(repositoryBeanContribution) //
@ -197,12 +194,11 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
@Test // GH-2593 @Test // GH-2593
void contributesDomainTypeAndReachableTypesCorrectly() { void contributesDomainTypeAndReachableTypesCorrectly() {
RepositoryRegistrationAotContribution repositoryBeanContribution = RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(
computeAotConfiguration(ConfigWithSimpleCrudRepository.class) ConfigWithSimpleCrudRepository.class).forRepository(ConfigWithSimpleCrudRepository.MyRepo.class);
.forRepository(ConfigWithSimpleCrudRepository.MyRepo.class);
assertThatContribution(repositoryBeanContribution).codeContributionSatisfies(contribution -> assertThatContribution(repositoryBeanContribution).codeContributionSatisfies(
contribution.contributesReflectionFor(ConfigWithSimpleCrudRepository.Person.class, contribution -> contribution.contributesReflectionFor(ConfigWithSimpleCrudRepository.Person.class,
ConfigWithSimpleCrudRepository.Address.class)); ConfigWithSimpleCrudRepository.Address.class));
} }
@ -245,19 +241,18 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
@Test // GH-2593 @Test // GH-2593
void contributesTypesFromQueryMethods() { void contributesTypesFromQueryMethods() {
RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(ConfigWithQueryMethods.class) RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(
.forRepository(ConfigWithQueryMethods.CustomerRepositoryWithQueryMethods.class); ConfigWithQueryMethods.class).forRepository(ConfigWithQueryMethods.CustomerRepositoryWithQueryMethods.class);
assertThatContribution(repositoryBeanContribution) assertThatContribution(repositoryBeanContribution)
.codeContributionSatisfies(contribution -> .codeContributionSatisfies(contribution -> contribution.contributesReflectionFor(ProjectionInterface.class));
contribution.contributesReflectionFor(ProjectionInterface.class));
} }
@Test // GH-2593 @Test // GH-2593
void contributesProxiesForPotentialProjections() { void contributesProxiesForPotentialProjections() {
RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(ConfigWithQueryMethods.class) RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(
.forRepository(ConfigWithQueryMethods.CustomerRepositoryWithQueryMethods.class); ConfigWithQueryMethods.class).forRepository(ConfigWithQueryMethods.CustomerRepositoryWithQueryMethods.class);
assertThatContribution(repositoryBeanContribution) // assertThatContribution(repositoryBeanContribution) //
.codeContributionSatisfies(contribution -> { .codeContributionSatisfies(contribution -> {
@ -271,8 +266,8 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
@Test // GH-2593 @Test // GH-2593
void contributesProxiesForDataAnnotations() { void contributesProxiesForDataAnnotations() {
RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(ConfigWithQueryMethods.class) RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(
.forRepository(ConfigWithQueryMethods.CustomerRepositoryWithQueryMethods.class); ConfigWithQueryMethods.class).forRepository(ConfigWithQueryMethods.CustomerRepositoryWithQueryMethods.class);
assertThatContribution(repositoryBeanContribution) // assertThatContribution(repositoryBeanContribution) //
.codeContributionSatisfies(contribution -> { .codeContributionSatisfies(contribution -> {
@ -286,8 +281,8 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
@Test // GH-2593 @Test // GH-2593
void doesNotCareAboutNonDataAnnotations() { void doesNotCareAboutNonDataAnnotations() {
RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(ConfigWithSimpleCrudRepository.class) RepositoryRegistrationAotContribution repositoryBeanContribution = computeAotConfiguration(
.forRepository(ConfigWithSimpleCrudRepository.MyRepo.class); ConfigWithSimpleCrudRepository.class).forRepository(ConfigWithSimpleCrudRepository.MyRepo.class);
assertThatContribution(repositoryBeanContribution) // assertThatContribution(repositoryBeanContribution) //
.codeContributionSatisfies(contribution -> { .codeContributionSatisfies(contribution -> {
@ -311,16 +306,15 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
String[] repositoryBeanNames = applicationContext.getBeanNamesForType(repositoryType); String[] repositoryBeanNames = applicationContext.getBeanNamesForType(repositoryType);
assertThat(repositoryBeanNames) assertThat(repositoryBeanNames)
.describedAs("Unable to find repository [%s] in configuration [%s]", .describedAs("Unable to find repository [%s] in configuration [%s]", repositoryType, configuration)
repositoryType, configuration)
.hasSize(1); .hasSize(1);
String repositoryBeanName = repositoryBeanNames[0]; String repositoryBeanName = repositoryBeanNames[0];
ConfigurableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory(); ConfigurableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();
RepositoryRegistrationAotProcessor repositoryAotProcessor = RepositoryRegistrationAotProcessor repositoryAotProcessor = applicationContext
applicationContext.getBean(RepositoryRegistrationAotProcessor.class); .getBean(RepositoryRegistrationAotProcessor.class);
repositoryAotProcessor.setBeanFactory(beanFactory); repositoryAotProcessor.setBeanFactory(beanFactory);

3
src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java

@ -25,6 +25,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness; import org.mockito.quality.Strictness;
import org.springframework.aop.framework.Advised; import org.springframework.aop.framework.Advised;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
@ -67,7 +68,7 @@ class RepositoryConfigurationDelegateUnitTests {
var beanDefinition = definition.getBeanDefinition(); var beanDefinition = definition.getBeanDefinition();
assertThat(beanDefinition.getAttribute(RepositoryConfigurationDelegate.FACTORY_BEAN_OBJECT_TYPE).toString()) assertThat(beanDefinition.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE).toString())
.endsWith("Repository"); .endsWith("Repository");
} }
} }

Loading…
Cancel
Save