Review, refactor and polish Spring Data AOT infrastructure classes.
* Refactored logic in AOT infrastructure classes. * Annotated AOT API with Spring's @NonNull and @Nullable annotations. * Edited Javadoc. * Introduced PredicateUtils abstract utility class encapsulating common Predicates on types and class members. * Added comments for review and clarification. Original Pull Request: #2624
This commit is contained in:
committed by
Christoph Strobl
parent
21ff2a7e34
commit
d8118de90f
@@ -16,49 +16,137 @@
|
||||
package org.springframework.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Types managed by a Spring Data implementation. Used to predefine a set of know entities that might need processing
|
||||
* during container/repository initialization phase.
|
||||
* Types managed by a Spring Data implementation.
|
||||
*
|
||||
* Used to predefine a set of know entities that might need processing during the Spring container,
|
||||
* Spring Data Repository initialization phase.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author John Blum
|
||||
* @see java.lang.FunctionalInterface
|
||||
* @since 3.0
|
||||
*/
|
||||
// TODO: Does this need to be in org.springframework.data? Maybe move to org.springframework.data.domain?
|
||||
@FunctionalInterface
|
||||
public interface ManagedTypes {
|
||||
|
||||
void forEach(Consumer<Class<?>> action);
|
||||
|
||||
default List<Class<?>> toList() {
|
||||
|
||||
List<Class<?>> tmp = new ArrayList<>(100);
|
||||
forEach(tmp::add);
|
||||
return tmp;
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link ManagedTypes} containing no {@link Class types}.
|
||||
*
|
||||
* @return an empty {@link ManagedTypes} instance.
|
||||
* @see java.util.Collections#emptySet()
|
||||
* @see #of(Iterable)
|
||||
*/
|
||||
@NonNull
|
||||
static ManagedTypes empty() {
|
||||
return of(Collections.emptySet());
|
||||
}
|
||||
|
||||
static ManagedTypes of(Iterable<? extends Class<?>> types) {
|
||||
/**
|
||||
* Factory method used to return a {@literal null-safe} instance of {@link ManagedTypes}.
|
||||
*
|
||||
* @param types {@link ManagedTypes} to evaluate.
|
||||
* @return the given {@link ManagedTypes} if not {@literal null}
|
||||
* or an {@link #empty()} {@link ManagedTypes} instance.
|
||||
* @see #empty()
|
||||
*/
|
||||
@NonNull
|
||||
static ManagedTypes nullSafeManagedTypes(@Nullable ManagedTypes types) {
|
||||
return types != null ? types : empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to construct {@link ManagedTypes} from the given, required {@link Iterable}
|
||||
* of {@link Class types}.
|
||||
*
|
||||
* @param types {@link Iterable} of {@link Class types} used to initialize the {@link ManagedTypes};
|
||||
* must not be {@literal null}.
|
||||
* @return new instance of {@link ManagedTypes} initialized the given, required {@link Iterable}
|
||||
* of {@link Class types}.
|
||||
* @see java.lang.Iterable
|
||||
* @see #of(Stream)
|
||||
* @see #of(Supplier)
|
||||
*/
|
||||
@NonNull
|
||||
static ManagedTypes of(@NonNull Iterable<? extends Class<?>> types) {
|
||||
return types::forEach;
|
||||
}
|
||||
|
||||
static ManagedTypes of(Stream<? extends Class<?>> types) {
|
||||
/**
|
||||
* Factory method used to construct {@link ManagedTypes} from the given, required {@link Stream}
|
||||
* of {@link Class types}.
|
||||
*
|
||||
* @param types {@link Stream} of {@link Class types} used to initialize the {@link ManagedTypes};
|
||||
* must not be {@literal null}.
|
||||
* @return new instance of {@link ManagedTypes} initialized the given, required {@link Stream}
|
||||
* of {@link Class types}.
|
||||
* @see java.util.stream.Stream
|
||||
* @see #of(Iterable)
|
||||
* @see #of(Supplier)
|
||||
*/
|
||||
@NonNull
|
||||
static ManagedTypes of(@NonNull Stream<? extends Class<?>> types) {
|
||||
return types::forEach;
|
||||
}
|
||||
|
||||
static ManagedTypes o(Supplier<Iterable<? extends Class<?>>> dataProvider) {
|
||||
/**
|
||||
* Factory method used to construct {@link ManagedTypes} from the given, required {@link Supplier} of
|
||||
* an {@link Iterable} of {@link Class types}.
|
||||
*
|
||||
* @param dataProvider {@link Supplier} of an {@link Iterable} of {@link Class types} used to lazily initialize
|
||||
* the {@link ManagedTypes}; must not be {@literal null}.
|
||||
* @return new instance of {@link ManagedTypes} initialized the given, required {@link Supplier} of
|
||||
* an {@link Iterable} of {@link Class types}.
|
||||
* @see java.util.function.Supplier
|
||||
* @see java.lang.Iterable
|
||||
* @see #of(Iterable)
|
||||
* @see #of(Stream)
|
||||
*/
|
||||
@NonNull
|
||||
static ManagedTypes of(@NonNull Supplier<Iterable<? extends Class<?>>> dataProvider) {
|
||||
|
||||
return new ManagedTypes() {
|
||||
|
||||
Lazy<Iterable<? extends Class<?>>> lazyProvider = Lazy.of(dataProvider);
|
||||
final Lazy<Iterable<? extends Class<?>>> lazyProvider = Lazy.of(dataProvider);
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<Class<?>> action) {
|
||||
public void forEach(@NonNull Consumer<Class<?>> action) {
|
||||
lazyProvider.get().forEach(action);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the given {@link Consumer action} to each of the {@link Class types} contained in
|
||||
* this {@link ManagedTypes} instance.
|
||||
*
|
||||
* @param action {@link Consumer} defining the action to perform on the {@link Class types}
|
||||
* contained in this {@link ManagedTypes} instance; must not be {@literal null}.
|
||||
* @see java.util.function.Consumer
|
||||
*/
|
||||
void forEach(Consumer<Class<?>> action);
|
||||
|
||||
/**
|
||||
* Returns all the {@link ManagedTypes} in a {@link List}.
|
||||
*
|
||||
* @return these {@link ManagedTypes} in a {@link List}; never {@literal null}.
|
||||
* @see java.util.List
|
||||
*/
|
||||
default List<Class<?>> toList() {
|
||||
|
||||
List<Class<?>> list = new ArrayList<>(100);
|
||||
forEach(list::add);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,30 +28,37 @@ import org.springframework.beans.factory.config.BeanReference;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* The context in which the AOT processing happens. Grants access to the {@link ConfigurableListableBeanFactory
|
||||
* beanFactory} and {@link ClassLoader}. Holds a few convenience methods to check if a type
|
||||
* {@link #isTypePresent(String) is present} and allows resolution of them. <strong>WARNING:</strong> Unstable internal
|
||||
* API!
|
||||
* The context in which the AOT processing happens.
|
||||
*
|
||||
* Grants access to the {@link ConfigurableListableBeanFactory beanFactory} and {@link ClassLoader}. Holds a few
|
||||
* convenience methods to check if a type {@link #isTypePresent(String) is present} and allows resolution of them.
|
||||
*
|
||||
* <strong>WARNING:</strong> Unstable internal API!
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author John Blum
|
||||
* @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface AotContext {
|
||||
|
||||
/**
|
||||
* Create an {@link AotContext} backed by the given {@link BeanFactory}.
|
||||
*
|
||||
* @param beanFactory must not be {@literal null}.
|
||||
* @return new instance of {@link AotContext}.
|
||||
* @param beanFactory reference to the {@link BeanFactory}; must not be {@literal null}.
|
||||
* @return a new instance of {@link AotContext}.
|
||||
* @see BeanFactory
|
||||
*/
|
||||
static AotContext context(BeanFactory beanFactory) {
|
||||
static AotContext from(@NonNull BeanFactory beanFactory) {
|
||||
|
||||
Assert.notNull(beanFactory, "BeanFactory must not be null!");
|
||||
Assert.notNull(beanFactory, "BeanFactory must not be null");
|
||||
|
||||
return new AotContext() {
|
||||
|
||||
@@ -59,6 +66,7 @@ public interface AotContext {
|
||||
? (ConfigurableListableBeanFactory) beanFactory
|
||||
: new DefaultListableBeanFactory(beanFactory);
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ConfigurableListableBeanFactory getBeanFactory() {
|
||||
return bf;
|
||||
@@ -66,75 +74,203 @@ public interface AotContext {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the {@link ConfigurableListableBeanFactory} backing this {@link AotContext}.
|
||||
*
|
||||
* @return a reference to the {@link ConfigurableListableBeanFactory} backing this {@link AotContext}.
|
||||
* @see ConfigurableListableBeanFactory
|
||||
*/
|
||||
ConfigurableListableBeanFactory getBeanFactory();
|
||||
|
||||
/**
|
||||
* 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 definitions.
|
||||
*
|
||||
* @return the {@link ClassLoader} used by this {@link AotContext} to resolve {@link Class types}.
|
||||
* @see ConfigurableListableBeanFactory#getBeanClassLoader()
|
||||
*/
|
||||
@Nullable
|
||||
default ClassLoader getClassLoader() {
|
||||
return getBeanFactory().getBeanClassLoader();
|
||||
}
|
||||
|
||||
default boolean isTypePresent(String typeName) {
|
||||
return ClassUtils.isPresent(typeName, getBeanFactory().getBeanClassLoader());
|
||||
/**
|
||||
* Determines whether the given {@link String named} {@link Class type} is present on the application classpath.
|
||||
*
|
||||
* @param typeName {@link String name} of the {@link Class type} to evaluate; must not be {@literal null}.
|
||||
* @return {@literal true} if the given {@link String named} {@link Class type} is present
|
||||
* on the application classpath.
|
||||
* @see #getClassLoader()
|
||||
*/
|
||||
default boolean isTypePresent(@NonNull String typeName) {
|
||||
return ClassUtils.isPresent(typeName, getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link TypeScanner} used to scan for {@link Class types} that will be contributed to the AOT
|
||||
* processing infrastructure.
|
||||
*
|
||||
* @return a {@link TypeScanner} used to scan for {@link Class types} that will be contributed to the AOT
|
||||
* processing infrastructure.
|
||||
* @see TypeScanner
|
||||
*/
|
||||
@NonNull
|
||||
default TypeScanner getTypeScanner() {
|
||||
return new TypeScanner(getClassLoader());
|
||||
}
|
||||
|
||||
default Set<Class<?>> scanPackageForTypes(Collection<Class<? extends Annotation>> identifyingAnnotations,
|
||||
/**
|
||||
* Scans for {@link Class types} in the given {@link String named packages} annotated with the store-specific
|
||||
* {@link Annotation identifying annotations}.
|
||||
*
|
||||
* @param identifyingAnnotations {@link Collection} of {@link Annotation Annotations} identifying store-specific
|
||||
* model {@link Class types}; must not be {@literal null}.
|
||||
* @param packageNames {@link Collection} of {@link String package names} to scan.
|
||||
* @return a {@link Set} of {@link Class types} found during the scan.
|
||||
* @see TypeScanner#scanForTypesAnnotatedWith(Class[])
|
||||
* @see TypeScanner.Scanner#inPackages(Collection)
|
||||
* @see #getTypeScanner()
|
||||
*/
|
||||
default Set<Class<?>> scanPackageForTypes(@NonNull Collection<Class<? extends Annotation>> identifyingAnnotations,
|
||||
Collection<String> packageNames) {
|
||||
|
||||
return getTypeScanner().scanForTypesAnnotatedWith(identifyingAnnotations).inPackages(packageNames);
|
||||
}
|
||||
|
||||
default Optional<Class<?>> resolveType(String typeName) {
|
||||
/**
|
||||
* Resolves the required {@link String named} {@link Class type}.
|
||||
*
|
||||
* @param typeName {@link String} containing the {@literal fully-qualified class name} of the {@link Class type}
|
||||
* to resolve; must not be {@literal null}.
|
||||
* @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 Class<?> resolveRequiredType(@NonNull String typeName) throws TypeNotPresentException {
|
||||
|
||||
if (!isTypePresent(typeName)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(resolveRequiredType(typeName));
|
||||
}
|
||||
|
||||
default Class<?> resolveRequiredType(String typeName) throws TypeNotPresentException {
|
||||
try {
|
||||
return ClassUtils.forName(typeName, getClassLoader());
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new TypeNotPresentException(typeName, e);
|
||||
} catch (ClassNotFoundException cause) {
|
||||
throw new TypeNotPresentException(typeName, cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the given {@link String named} {@link Class type} if present.
|
||||
*
|
||||
* @param typeName {@link String} containing the {@literal fully-qualified class name} of the {@link Class type}
|
||||
* to resolve; must not be {@literal null}.
|
||||
* @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) {
|
||||
|
||||
return isTypePresent(typeName)
|
||||
? Optional.of(resolveRequiredType(typeName))
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the {@link BeanDefinition bean's} defined {@link Class type}.
|
||||
*
|
||||
* @param beanReference {@link BeanReference} to the managed bean.
|
||||
* @return the {@link Class type} of the {@link BeanReference referenced bean} if defined; may be {@literal null}.
|
||||
* @see BeanReference
|
||||
*/
|
||||
@Nullable
|
||||
default Class<?> resolveType(BeanReference beanReference) {
|
||||
default Class<?> resolveType(@NonNull BeanReference beanReference) {
|
||||
return getBeanFactory().getType(beanReference.getBeanName(), false);
|
||||
}
|
||||
|
||||
default BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
|
||||
/**
|
||||
* Gets the {@link BeanDefinition} for the given, required {@link String named bean}.
|
||||
*
|
||||
* @param beanName {@link String} containing the {@literal name} of the bean; must not be {@literal null}.
|
||||
* @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
|
||||
*/
|
||||
@NonNull
|
||||
default BeanDefinition getBeanDefinition(@NonNull String beanName) throws NoSuchBeanDefinitionException {
|
||||
return getBeanFactory().getBeanDefinition(beanName);
|
||||
}
|
||||
|
||||
default RootBeanDefinition getRootBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
|
||||
/**
|
||||
* Gets the {@link RootBeanDefinition} for the given, required {@link String bean name}.
|
||||
*
|
||||
* @param beanName {@link String} containing the {@literal name} of the bean.
|
||||
* @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
|
||||
default RootBeanDefinition getRootBeanDefinition(@NonNull String beanName) throws NoSuchBeanDefinitionException {
|
||||
|
||||
BeanDefinition val = getBeanFactory().getBeanDefinition(beanName);
|
||||
if (!(val instanceof RootBeanDefinition)) {
|
||||
throw new IllegalStateException(String.format("%s is not a root bean", beanName));
|
||||
BeanDefinition beanDefinition = getBeanDefinition(beanName);
|
||||
|
||||
if (beanDefinition instanceof RootBeanDefinition rootBeanDefinition) {
|
||||
return rootBeanDefinition;
|
||||
}
|
||||
return RootBeanDefinition.class.cast(val);
|
||||
|
||||
throw new IllegalStateException(String.format("%s is not a root bean", beanName));
|
||||
}
|
||||
|
||||
default boolean isFactoryBean(String beanName) {
|
||||
/**
|
||||
* Determines whether a bean identified by the given, required {@link String name} is a
|
||||
* {@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
|
||||
* {@link org.springframework.beans.factory.FactoryBean}.
|
||||
*/
|
||||
default boolean isFactoryBean(@NonNull String beanName) {
|
||||
return getBeanFactory().isFactoryBean(beanName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a Spring {@link org.springframework.transaction.TransactionManager} is present.
|
||||
*
|
||||
* @return {@literal true} if a Spring {@link org.springframework.transaction.TransactionManager} is present.
|
||||
*/
|
||||
default boolean isTransactionManagerPresent() {
|
||||
|
||||
return resolveType("org.springframework.transaction.TransactionManager") //
|
||||
.map(it -> !ObjectUtils.isEmpty(getBeanFactory().getBeanNamesForType(it))) //
|
||||
.orElse(false);
|
||||
return resolveType("org.springframework.transaction.TransactionManager")
|
||||
.filter(it -> !ObjectUtils.isEmpty(getBeanFactory().getBeanNamesForType(it)))
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
default void ifTypePresent(String typeName, Consumer<Class<?>> action) {
|
||||
/**
|
||||
* Determines whether the given, required {@link String type name} is declared on the application classpath
|
||||
* 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}.
|
||||
* @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(String)
|
||||
*/
|
||||
default void ifTypePresent(@NonNull String typeName, @NonNull Consumer<Class<?>> action) {
|
||||
resolveType(typeName).ifPresent(action);
|
||||
}
|
||||
|
||||
default void ifTransactionManagerPresent(Consumer<String[]> beanNamesConsumer) {
|
||||
/**
|
||||
* Runs the given {@link Consumer action} on any {@link org.springframework.transaction.TransactionManager} beans
|
||||
* defined in the application context.
|
||||
*
|
||||
* @param beanNamesConsumer {@link Consumer} defining the action to perform on
|
||||
* the {@link org.springframework.transaction.TransactionManager} beans if present; must not be {@literal null}.
|
||||
* @see java.util.function.Consumer
|
||||
*/
|
||||
default void ifTransactionManagerPresent(@NonNull Consumer<String[]> beanNamesConsumer) {
|
||||
|
||||
ifTypePresent("org.springframework.transaction.TransactionManager", txMgrType -> {
|
||||
String[] txMgrBeanNames = getBeanFactory().getBeanNamesForType(txMgrType);
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
* 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.lang.annotation.Annotation;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.aot.generator.CodeContribution;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
|
||||
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.support.RepositoryFactoryBeanSupport;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* {@link AotContributingBeanPostProcessor} taking care of data repositories.
|
||||
* <p>
|
||||
* Post processes {@link RepositoryFactoryBeanSupport repository factory beans} to provide generic type information to
|
||||
* AOT tooling to allow deriving target type from the {@link org.springframework.beans.factory.config.BeanDefinition
|
||||
* bean definition}. If generic types to not match, due to customization of the factory bean by the user, at least the
|
||||
* target repository type is provided via the {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE}.
|
||||
* </p>
|
||||
* <p>
|
||||
* Via {@link #contribute(AotRepositoryContext, CodeContribution)} stores can provide custom logic for contributing
|
||||
* additional (eg. reflection) configuration. By default reflection configuration will be added for types reachable from
|
||||
* the repository declaration and query methods as well as all used {@link Annotation annotations} from the
|
||||
* {@literal org.springframework.data} namespace.
|
||||
* </p>
|
||||
* The post processor is typically configured via {@link RepositoryConfigurationExtension#getAotPostProcessor()} and
|
||||
* gets added by the {@link org.springframework.data.repository.config.RepositoryConfigurationDelegate}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.0
|
||||
*/
|
||||
public class AotContributingRepositoryBeanPostProcessor implements AotContributingBeanPostProcessor, BeanFactoryAware {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(AotContributingBeanPostProcessor.class);
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
private Map<String, RepositoryMetadata<?>> configMap;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public RepositoryBeanContribution contribute(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
|
||||
|
||||
if (ObjectUtils.isEmpty(configMap) || !configMap.containsKey(beanName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
RepositoryMetadata<?> metadata = configMap.get(beanName);
|
||||
|
||||
Set<Class<? extends Annotation>> identifyingAnnotations = Collections.emptySet();
|
||||
if (metadata.getConfigurationSource() instanceof RepositoryConfigurationExtensionSupport ces) {
|
||||
identifyingAnnotations = new LinkedHashSet<>(ces.getIdentifyingAnnotations());
|
||||
}
|
||||
|
||||
RepositoryInformation repositoryInformation = RepositoryBeanDefinitionReader.readRepositoryInformation(metadata,
|
||||
beanFactory);
|
||||
|
||||
DefaultRepositoryContext ctx = new DefaultRepositoryContext();
|
||||
ctx.setAotContext(() -> beanFactory);
|
||||
ctx.setBeanName(beanName);
|
||||
ctx.setBasePackages(metadata.getBasePackages().toSet());
|
||||
ctx.setRepositoryInformation(repositoryInformation);
|
||||
ctx.setIdentifyingAnnotations(identifyingAnnotations);
|
||||
|
||||
/*
|
||||
* Help the AOT processing render the FactoryBean<T> type correctly that is used to tell the outcome of the FB.
|
||||
* We just need to set the target repo type of the RepositoryFactoryBeanSupport while keeping the actual ID and DomainType set to object.
|
||||
* If the generics do not match we do not try to resolve and remap them, but rather set the ObjectType attribute.
|
||||
*/
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Enhancing repository factory bean definition %s.", beanName));
|
||||
}
|
||||
|
||||
ResolvableType resolvedFactoryBean = ResolvableType.forClass(
|
||||
ctx.resolveType(metadata.getRepositoryFactoryBeanClassName()).orElse(RepositoryFactoryBeanSupport.class));
|
||||
if (resolvedFactoryBean.getGenerics().length == 3) {
|
||||
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(
|
||||
ctx.resolveType(metadata.getRepositoryFactoryBeanClassName()).orElse(RepositoryFactoryBeanSupport.class),
|
||||
repositoryInformation.getRepositoryInterface(), Object.class, Object.class));
|
||||
} else {
|
||||
beanDefinition.setTargetType(resolvedFactoryBean);
|
||||
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, repositoryInformation.getRepositoryInterface());
|
||||
}
|
||||
|
||||
return new RepositoryBeanContribution(ctx).setModuleContribution(this::contribute);
|
||||
}
|
||||
|
||||
protected void contribute(AotRepositoryContext ctx, CodeContribution contribution) {
|
||||
|
||||
ctx.getResolvedTypes() //
|
||||
.stream() //
|
||||
.filter(it -> !isJavaOrPrimitiveType(it)) //
|
||||
.forEach(it -> contributeType(it, contribution));
|
||||
|
||||
ctx.getResolvedAnnotations().stream() //
|
||||
.filter(AotContributingRepositoryBeanPostProcessor::isSpringDataManagedAnnotation) //
|
||||
.map(MergedAnnotation::getType) //
|
||||
.forEach(it -> contributeType(it, contribution));
|
||||
}
|
||||
|
||||
protected static boolean isSpringDataManagedAnnotation(MergedAnnotation<?> annotation) {
|
||||
|
||||
if (isInDataNamespace(annotation.getType())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return annotation.getMetaTypes().stream().anyMatch(AotContributingRepositoryBeanPostProcessor::isInDataNamespace);
|
||||
}
|
||||
|
||||
private static boolean isInDataNamespace(Class<?> type) {
|
||||
return type.getPackage().getName().startsWith(TypeContributor.DATA_NAMESPACE);
|
||||
}
|
||||
|
||||
private static boolean isJavaOrPrimitiveType(Class<?> type) {
|
||||
if (TypeUtils.type(type).isPartOf("java") || type.isPrimitive() || ClassUtils.isPrimitiveArray(type)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void contributeType(Class<?> type, CodeContribution contribution) {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Contributing type information for %s.", type));
|
||||
}
|
||||
|
||||
TypeContributor.contribute(type, it -> true, contribution);
|
||||
}
|
||||
|
||||
public Predicate<Class<?>> typeFilter() { // like only document ones.
|
||||
return it -> true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
|
||||
if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
|
||||
throw new IllegalArgumentException(
|
||||
"AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
|
||||
}
|
||||
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
|
||||
}
|
||||
|
||||
public Map<String, RepositoryMetadata<?>> getConfigMap() {
|
||||
return configMap;
|
||||
}
|
||||
|
||||
public void setConfigMap(Map<String, RepositoryMetadata<?>> configMap) {
|
||||
this.configMap = configMap;
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.aot.generator.CodeContribution;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor;
|
||||
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.data.ManagedTypes;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link AotContributingBeanPostProcessor} handling {@link #getModulePrefix() prefixed} {@link ManagedTypes} instances.
|
||||
* This allows to register store specific handling of discovered types.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.0
|
||||
*/
|
||||
public class AotManagedTypesPostProcessor implements AotContributingBeanPostProcessor, BeanFactoryAware {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(AotManagedTypesPostProcessor.class);
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
@Nullable private String modulePrefix;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BeanInstantiationContribution contribute(RootBeanDefinition beanDefinition, Class<?> beanType,
|
||||
String beanName) {
|
||||
|
||||
if (!ClassUtils.isAssignable(ManagedTypes.class, beanType) || !matchesPrefix(beanName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return contribute(AotContext.context(beanFactory), beanFactory.getBean(beanName, ManagedTypes.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to provide a customized flavor of {@link BeanInstantiationContribution}. By overriding this method calls to
|
||||
* {@link #contributeType(ResolvableType, CodeContribution)} might no longer be issued.
|
||||
*
|
||||
* @param aotContext never {@literal null}.
|
||||
* @param managedTypes never {@literal null}.
|
||||
* @return new instance of {@link AotManagedTypesPostProcessor} or {@literal null} if nothing to do.
|
||||
*/
|
||||
@Nullable
|
||||
protected BeanInstantiationContribution contribute(AotContext aotContext, ManagedTypes managedTypes) {
|
||||
return new ManagedTypesContribution(aotContext, managedTypes, this::contributeType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to contribute configuration for a given {@literal type}.
|
||||
*
|
||||
* @param type never {@literal null}.
|
||||
* @param contribution never {@literal null}.
|
||||
*/
|
||||
protected void contributeType(ResolvableType type, CodeContribution contribution) {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Contributing type information for %s.", type.getType()));
|
||||
}
|
||||
|
||||
TypeContributor.contribute(type.toClass(), Collections.singleton(TypeContributor.DATA_NAMESPACE), contribution);
|
||||
TypeUtils.resolveUsedAnnotations(type.toClass()).forEach(annotation -> TypeContributor
|
||||
.contribute(annotation.getType(), Collections.singleton(TypeContributor.DATA_NAMESPACE), contribution));
|
||||
}
|
||||
|
||||
protected boolean matchesPrefix(String beanName) {
|
||||
return StringUtils.startsWithIgnoreCase(beanName, getModulePrefix());
|
||||
}
|
||||
|
||||
public String getModulePrefix() {
|
||||
return modulePrefix;
|
||||
}
|
||||
|
||||
public void setModulePrefix(@Nullable String modulePrefix) {
|
||||
this.modulePrefix = modulePrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
static class ManagedTypesContribution implements BeanInstantiationContribution {
|
||||
|
||||
private AotContext aotContext;
|
||||
private ManagedTypes managedTypes;
|
||||
private BiConsumer<ResolvableType, CodeContribution> contributionAction;
|
||||
|
||||
public ManagedTypesContribution(AotContext aotContext, ManagedTypes managedTypes,
|
||||
BiConsumer<ResolvableType, CodeContribution> contributionAction) {
|
||||
|
||||
this.aotContext = aotContext;
|
||||
this.managedTypes = managedTypes;
|
||||
this.contributionAction = contributionAction;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(CodeContribution contribution) {
|
||||
|
||||
List<Class<?>> types = new ArrayList<>(100);
|
||||
managedTypes.forEach(types::add);
|
||||
if (types.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TypeCollector.inspect(types).forEach(type -> {
|
||||
contributionAction.accept(type, contribution);
|
||||
});
|
||||
}
|
||||
|
||||
public AotContext getAotContext() {
|
||||
return aotContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,37 +22,45 @@ import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
|
||||
/**
|
||||
* {@link AotContext} specific to Spring Data {@link org.springframework.data.repository.Repository} infrastructure.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author John Blum
|
||||
* @see AotContext
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface AotRepositoryContext extends AotContext {
|
||||
|
||||
/**
|
||||
* @return the bean name of the repository / factory bean
|
||||
* @return the {@link String bean name} of the repository / factory bean.
|
||||
*/
|
||||
String getBeanName();
|
||||
|
||||
/**
|
||||
* @return base packages to look for repositories.
|
||||
* @return a {@link Set} of {@link String base packages} to search for repositories.
|
||||
*/
|
||||
Set<String> getBasePackages();
|
||||
|
||||
/**
|
||||
* @return metadata about the repository itself
|
||||
*/
|
||||
RepositoryInformation getRepositoryInformation();
|
||||
|
||||
/**
|
||||
* @return the {@link Annotation} types used to identify domain types.
|
||||
*/
|
||||
Set<Class<? extends Annotation>> getIdentifyingAnnotations();
|
||||
|
||||
/**
|
||||
* @return all types reachable from the repository.
|
||||
* @return {@link RepositoryInformation metadata} about the repository itself.
|
||||
* @see org.springframework.data.repository.core.RepositoryInformation
|
||||
*/
|
||||
RepositoryInformation getRepositoryInformation();
|
||||
|
||||
/**
|
||||
* @return all {@link MergedAnnotation annotations} reachable from the repository.
|
||||
* @see org.springframework.core.annotation.MergedAnnotation
|
||||
*/
|
||||
Set<MergedAnnotation<Annotation>> getResolvedAnnotations();
|
||||
|
||||
/**
|
||||
* @return all {@link Class types} reachable from the repository.
|
||||
*/
|
||||
Set<Class<?>> getResolvedTypes();
|
||||
|
||||
/**
|
||||
* @return all annotations reachable from the repository.
|
||||
*/
|
||||
Set<MergedAnnotation<Annotation>> getResolvedAnnotations();
|
||||
}
|
||||
|
||||
@@ -25,12 +25,14 @@ import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.RepositoryInformationSupport;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.RepositoryFragment;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* {@link RepositoryInformation} based on {@link RepositoryMetadata} collected at build time.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see org.springframework.data.repository.core.RepositoryInformation
|
||||
* @see org.springframework.data.repository.core.RepositoryMetadata
|
||||
* @since 3.0
|
||||
*/
|
||||
class AotRepositoryInformation extends RepositoryInformationSupport implements RepositoryInformation {
|
||||
@@ -45,32 +47,30 @@ class AotRepositoryInformation extends RepositoryInformationSupport implements R
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCustomMethod(Method method) {
|
||||
|
||||
public boolean isCustomMethod(@NonNull Method method) {
|
||||
// TODO:
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBaseClassMethod(Method method) {
|
||||
public boolean isBaseClassMethod(@NonNull Method method) {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Method getTargetClassMethod(Method method) {
|
||||
|
||||
public Method getTargetClassMethod(@NonNull Method method) {
|
||||
// TODO
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @return configured repository fragments.
|
||||
* @since 3.0
|
||||
*/
|
||||
@Nullable
|
||||
@NonNull
|
||||
public Set<RepositoryFragment<?>> getFragments() {
|
||||
return new LinkedHashSet<>(fragments.get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,53 +30,23 @@ import org.springframework.data.util.Lazy;
|
||||
* Default implementation of {@link AotRepositoryContext}
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author John Blum
|
||||
* @see AotRepositoryContext
|
||||
* @since 3.0
|
||||
*/
|
||||
class DefaultRepositoryContext implements AotRepositoryContext {
|
||||
class DefaultAotRepositoryContext implements AotRepositoryContext {
|
||||
|
||||
private AotContext aotContext;
|
||||
|
||||
private String beanName;
|
||||
private java.util.Set<String> basePackages;
|
||||
private final Lazy<Set<MergedAnnotation<Annotation>>> resolvedAnnotations = Lazy.of(this::discoverAnnotations);
|
||||
private final Lazy<Set<Class<?>>> managedTypes = Lazy.of(this::discoverTypes);
|
||||
|
||||
private RepositoryInformation repositoryInformation;
|
||||
|
||||
private Set<String> basePackages;
|
||||
private Set<Class<? extends Annotation>> identifyingAnnotations;
|
||||
private Lazy<Set<MergedAnnotation<Annotation>>> resolvedAnnotations = Lazy.of(this::discoverAnnotations);
|
||||
private Lazy<Set<Class<?>>> managedTypes = Lazy.of(this::discoverTypes);
|
||||
|
||||
@Override
|
||||
public ConfigurableListableBeanFactory getBeanFactory() {
|
||||
return aotContext.getBeanFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBeanName() {
|
||||
return beanName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getBasePackages() {
|
||||
return basePackages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositoryInformation getRepositoryInformation() {
|
||||
return repositoryInformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<? extends Annotation>> getIdentifyingAnnotations() {
|
||||
return identifyingAnnotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<?>> getResolvedTypes() {
|
||||
return managedTypes.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<MergedAnnotation<Annotation>> getResolvedAnnotations() {
|
||||
return resolvedAnnotations.get();
|
||||
}
|
||||
private String beanName;
|
||||
|
||||
public AotContext getAotContext() {
|
||||
return aotContext;
|
||||
@@ -86,28 +56,65 @@ class DefaultRepositoryContext implements AotRepositoryContext {
|
||||
this.aotContext = aotContext;
|
||||
}
|
||||
|
||||
public void setBeanName(String beanName) {
|
||||
this.beanName = beanName;
|
||||
@Override
|
||||
public ConfigurableListableBeanFactory getBeanFactory() {
|
||||
return getAotContext().getBeanFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getBasePackages() {
|
||||
return basePackages;
|
||||
}
|
||||
|
||||
public void setBasePackages(Set<String> basePackages) {
|
||||
this.basePackages = basePackages;
|
||||
}
|
||||
|
||||
public void setRepositoryInformation(RepositoryInformation repositoryInformation) {
|
||||
this.repositoryInformation = repositoryInformation;
|
||||
@Override
|
||||
public String getBeanName() {
|
||||
return beanName;
|
||||
}
|
||||
|
||||
public void setBeanName(String beanName) {
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<? extends Annotation>> getIdentifyingAnnotations() {
|
||||
return identifyingAnnotations;
|
||||
}
|
||||
|
||||
public void setIdentifyingAnnotations(Set<Class<? extends Annotation>> identifyingAnnotations) {
|
||||
this.identifyingAnnotations = identifyingAnnotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositoryInformation getRepositoryInformation() {
|
||||
return repositoryInformation;
|
||||
}
|
||||
|
||||
public void setRepositoryInformation(RepositoryInformation repositoryInformation) {
|
||||
this.repositoryInformation = repositoryInformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<MergedAnnotation<Annotation>> getResolvedAnnotations() {
|
||||
return resolvedAnnotations.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<?>> getResolvedTypes() {
|
||||
return managedTypes.get();
|
||||
}
|
||||
|
||||
protected Set<MergedAnnotation<Annotation>> discoverAnnotations() {
|
||||
|
||||
Set<MergedAnnotation<Annotation>> annotations = new LinkedHashSet<>(getResolvedTypes().stream().flatMap(type -> {
|
||||
return TypeUtils.resolveUsedAnnotations(type).stream();
|
||||
}).collect(Collectors.toSet()));
|
||||
Set<MergedAnnotation<Annotation>> annotations = getResolvedTypes().stream()
|
||||
.flatMap(type -> TypeUtils.resolveUsedAnnotations(type).stream())
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
|
||||
annotations.addAll(TypeUtils.resolveUsedAnnotations(repositoryInformation.getRepositoryInterface()));
|
||||
|
||||
return annotations;
|
||||
}
|
||||
|
||||
@@ -123,7 +130,7 @@ class DefaultRepositoryContext implements AotRepositoryContext {
|
||||
|
||||
Scanner typeScanner = aotContext.getTypeScanner().scanForTypesAnnotatedWith(getIdentifyingAnnotations());
|
||||
Set<Class<?>> classes = typeScanner.inPackages(getBasePackages());
|
||||
TypeCollector.inspect(classes).list().stream().forEach(types::add);
|
||||
types.addAll(TypeCollector.inspect(classes).list());
|
||||
}
|
||||
|
||||
// context.get
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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.List;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.data.ManagedTypes;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* {@link BeanRegistrationAotContribution} used to contribute a {@link ManagedTypes} registration.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.beans.factory.aot.BeanRegistrationAotContribution
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public class ManagedTypesRegistrationAotContribution implements BeanRegistrationAotContribution {
|
||||
|
||||
private final AotContext aotContext;
|
||||
private final ManagedTypes managedTypes;
|
||||
private final BiConsumer<ResolvableType, GenerationContext> contributionAction;
|
||||
|
||||
public ManagedTypesRegistrationAotContribution(AotContext aotContext, ManagedTypes managedTypes,
|
||||
@NonNull BiConsumer<ResolvableType, GenerationContext> contributionAction) {
|
||||
|
||||
this.aotContext = aotContext;
|
||||
this.managedTypes = managedTypes;
|
||||
this.contributionAction = contributionAction;
|
||||
}
|
||||
|
||||
protected AotContext getAotContext() {
|
||||
return this.aotContext;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected ManagedTypes getManagedTypes() {
|
||||
return ManagedTypes.nullSafeManagedTypes(this.managedTypes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(@NonNull GenerationContext generationContext,
|
||||
@NonNull BeanRegistrationCode beanRegistrationCode) {
|
||||
|
||||
List<Class<?>> types = getManagedTypes().toList();
|
||||
|
||||
if (!types.isEmpty()) {
|
||||
TypeCollector.inspect(types).forEach(type -> contributionAction.accept(type, generationContext));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.data.ManagedTypes;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link BeanRegistrationAotProcessor} handling {@link #getModulePrefix() module prefixed} {@link ManagedTypes}
|
||||
* instances. This AOT processor allows store specific handling of discovered types to be registered.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author John Blum
|
||||
* @see org.springframework.beans.factory.BeanFactoryAware
|
||||
* @see org.springframework.beans.factory.aot.BeanRegistrationAotProcessor
|
||||
* @since 3.0
|
||||
*/
|
||||
public class ManagedTypesRegistrationAotProcessor implements BeanRegistrationAotProcessor, BeanFactoryAware {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
@Nullable
|
||||
private String modulePrefix;
|
||||
|
||||
@Override
|
||||
public BeanRegistrationAotContribution processAheadOfTime(@NonNull RegisteredBean registeredBean) {
|
||||
return contribute(registeredBean.getMergedBeanDefinition(), registeredBean.getBeanClass(), registeredBean.getBeanName());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unused")
|
||||
protected BeanRegistrationAotContribution contribute(@NonNull RootBeanDefinition beanDefinition,
|
||||
@NonNull Class<?> beanType, @NonNull String beanName) {
|
||||
|
||||
return isMatch(beanType, beanName)
|
||||
? contribute(AotContext.from(beanFactory), beanFactory.getBean(beanName, ManagedTypes.class))
|
||||
: null;
|
||||
}
|
||||
|
||||
protected boolean isMatch(@Nullable Class<?> beanType, @Nullable String beanName) {
|
||||
return matchesByType(beanType) && matchesPrefix(beanName);
|
||||
}
|
||||
|
||||
protected boolean matchesByType(@Nullable Class<?> beanType) {
|
||||
return beanType != null && ClassUtils.isAssignable(ManagedTypes.class, beanType);
|
||||
}
|
||||
|
||||
protected boolean matchesPrefix(@Nullable String beanName) {
|
||||
return StringUtils.startsWithIgnoreCase(beanName, getModulePrefix());
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to provide a customized flavor of {@link BeanRegistrationAotContribution}. By overriding this method
|
||||
* calls to {@link #contributeType(ResolvableType, GenerationContext)} might no longer be issued.
|
||||
*
|
||||
* @param aotContext never {@literal null}.
|
||||
* @param managedTypes never {@literal null}.
|
||||
* @return new instance of {@link ManagedTypesRegistrationAotProcessor} or {@literal null} if nothing to do.
|
||||
*/
|
||||
@Nullable
|
||||
protected BeanRegistrationAotContribution contribute(AotContext aotContext, ManagedTypes managedTypes) {
|
||||
return new ManagedTypesRegistrationAotContribution(aotContext, managedTypes, this::contributeType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to contribute configuration for a given {@literal type}.
|
||||
*
|
||||
* @param type never {@literal null}.
|
||||
* @param generationContext never {@literal null}.
|
||||
*/
|
||||
protected void contributeType(ResolvableType type, GenerationContext generationContext) {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Contributing type information for [%s]", type.getType()));
|
||||
}
|
||||
|
||||
Set<String> annotationNamespaces = Collections.singleton(TypeContributor.DATA_NAMESPACE);
|
||||
|
||||
TypeContributor.contribute(type.toClass(), annotationNamespaces, generationContext);
|
||||
|
||||
TypeUtils.resolveUsedAnnotations(type.toClass()).forEach(annotation ->
|
||||
TypeContributor.contribute(annotation.getType(), annotationNamespaces, generationContext));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
public void setModulePrefix(@Nullable String modulePrefix) {
|
||||
this.modulePrefix = modulePrefix;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getModulePrefix() {
|
||||
return this.modulePrefix;
|
||||
}
|
||||
}
|
||||
47
src/main/java/org/springframework/data/aot/Predicates.java
Normal file
47
src/main/java/org/springframework/data/aot/Predicates.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Abstract utility class containing common, reusable {@link Predicate Predicates}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.util.function.Predicate
|
||||
* @since 3.0
|
||||
*/
|
||||
// TODO: Consider moving to the org.springframework.data.util package.
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class Predicates {
|
||||
|
||||
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_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_NATIVE = member -> Modifier.isNative(member.getModifiers());
|
||||
public static final Predicate<Member> IS_PRIVATE = member -> Modifier.isPrivate(member.getModifiers());
|
||||
public static final Predicate<Member> IS_PROTECTED = member -> Modifier.isProtected(member.getModifiers());
|
||||
public static final Predicate<Member> IS_PUBLIC = member -> Modifier.isPublic(member.getModifiers());
|
||||
public static final Predicate<Member> IS_SYNTHETIC = Member::isSynthetic;
|
||||
|
||||
public static final Predicate<Method> IS_BRIDGE_METHOD = Method::isBridge;
|
||||
|
||||
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
/*
|
||||
* 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.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.aop.SpringProxy;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aot.generator.CodeContribution;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
|
||||
import org.springframework.core.DecoratingProxy;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.projection.EntityProjectionIntrospector.ProjectionPredicate;
|
||||
import org.springframework.data.projection.TargetAware;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.support.RepositoryFragment;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* The {@link BeanInstantiationContribution} for a specific data repository.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.0
|
||||
*/
|
||||
public class RepositoryBeanContribution implements BeanInstantiationContribution {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(RepositoryBeanContribution.class);
|
||||
|
||||
private final AotRepositoryContext context;
|
||||
private final RepositoryInformation repositoryInformation;
|
||||
private BiConsumer<AotRepositoryContext, CodeContribution> moduleContribution;
|
||||
|
||||
public RepositoryBeanContribution(AotRepositoryContext context) {
|
||||
|
||||
this.context = context;
|
||||
this.repositoryInformation = context.getRepositoryInformation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(CodeContribution contribution) {
|
||||
|
||||
writeRepositoryInfo(contribution);
|
||||
|
||||
if (moduleContribution != null) {
|
||||
moduleContribution.accept(context, contribution);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeRepositoryInfo(CodeContribution contribution) {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Contributing data repository information for %s.",
|
||||
repositoryInformation.getRepositoryInterface()));
|
||||
}
|
||||
|
||||
// TODO: is this the way?
|
||||
contribution.runtimeHints().reflection() //
|
||||
.registerType(repositoryInformation.getRepositoryInterface(), hint -> {
|
||||
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
|
||||
}) //
|
||||
.registerType(repositoryInformation.getRepositoryBaseClass(), hint -> {
|
||||
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
|
||||
}) //
|
||||
.registerType(repositoryInformation.getDomainType(), hint -> {
|
||||
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
|
||||
});
|
||||
|
||||
// fragments
|
||||
for (RepositoryFragment<?> fragment : getRepositoryInformation().getFragments()) {
|
||||
|
||||
contribution.runtimeHints().reflection() //
|
||||
.registerType(fragment.getSignatureContributor(), hint -> {
|
||||
|
||||
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
|
||||
if (!fragment.getSignatureContributor().isInterface()) {
|
||||
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// the surrounding proxy
|
||||
contribution.runtimeHints().proxies() // repository proxy
|
||||
.registerJdkProxy(repositoryInformation.getRepositoryInterface(), SpringProxy.class, Advised.class,
|
||||
DecoratingProxy.class);
|
||||
|
||||
context.ifTransactionManagerPresent(txMgrBeanNames -> {
|
||||
|
||||
contribution.runtimeHints().proxies() // transactional proxy
|
||||
.registerJdkProxy(transactionalRepositoryProxy());
|
||||
|
||||
if (AnnotationUtils.findAnnotation(repositoryInformation.getRepositoryInterface(), Component.class) != null) {
|
||||
|
||||
TypeReference[] source = transactionalRepositoryProxy();
|
||||
TypeReference[] txProxyForSerializableComponent = Arrays.copyOf(source, source.length + 1);
|
||||
txProxyForSerializableComponent[source.length] = TypeReference.of(Serializable.class);
|
||||
contribution.runtimeHints().proxies().registerJdkProxy(txProxyForSerializableComponent);
|
||||
}
|
||||
});
|
||||
|
||||
// reactive repo
|
||||
if (repositoryInformation.isReactiveRepository()) {
|
||||
// TODO: do we still need this and how to configure it?
|
||||
// registry.initialization().add(NativeInitializationEntry.ofBuildTimeType(configuration.getRepositoryInterface()));
|
||||
}
|
||||
|
||||
// Kotlin
|
||||
Optional<Class<?>> coroutineRepo = context
|
||||
.resolveType("org.springframework.data.repository.kotlin.CoroutineCrudRepository");
|
||||
if (coroutineRepo.isPresent()
|
||||
&& ClassUtils.isAssignable(coroutineRepo.get(), repositoryInformation.getRepositoryInterface())) {
|
||||
|
||||
contribution.runtimeHints().reflection() //
|
||||
.registerTypes(
|
||||
Arrays.asList(TypeReference.of("org.springframework.data.repository.kotlin.CoroutineCrudRepository"),
|
||||
TypeReference.of(Repository.class), TypeReference.of(Iterable.class),
|
||||
TypeReference.of("kotlinx.coroutines.flow.Flow"), TypeReference.of("kotlin.collections.Iterable"),
|
||||
TypeReference.of("kotlin.Unit"), TypeReference.of("kotlin.Long"), TypeReference.of("kotlin.Boolean")),
|
||||
hint -> {
|
||||
hint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS);
|
||||
});
|
||||
}
|
||||
|
||||
// repository methods
|
||||
repositoryInformation.getQueryMethods().map(repositoryInformation::getReturnedDomainClass)
|
||||
.filter(Class::isInterface).forEach(type -> {
|
||||
if (ProjectionPredicate.typeHierarchy().test(type, repositoryInformation.getDomainType())) {
|
||||
contributeProjection(type, contribution);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private TypeReference[] transactionalRepositoryProxy() {
|
||||
|
||||
return new TypeReference[] { TypeReference.of(repositoryInformation.getRepositoryInterface()),
|
||||
TypeReference.of(Repository.class),
|
||||
TypeReference.of("org.springframework.transaction.interceptor.TransactionalProxy"),
|
||||
TypeReference.of("org.springframework.aop.framework.Advised"), TypeReference.of(DecoratingProxy.class) };
|
||||
}
|
||||
|
||||
protected void contributeProjection(Class<?> type, CodeContribution contribution) {
|
||||
|
||||
contribution.runtimeHints().proxies().registerJdkProxy(type, TargetAware.class, SpringProxy.class,
|
||||
DecoratingProxy.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for module specific contributions.
|
||||
*
|
||||
* @param moduleContribution can be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public RepositoryBeanContribution setModuleContribution(
|
||||
@Nullable BiConsumer<AotRepositoryContext, CodeContribution> moduleContribution) {
|
||||
|
||||
this.moduleContribution = moduleContribution;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RepositoryInformation getRepositoryInformation() {
|
||||
return repositoryInformation;
|
||||
}
|
||||
}
|
||||
@@ -31,42 +31,53 @@ import org.springframework.data.util.Lazy;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Reader that allows to extract {@link RepositoryInformation} from metadata.
|
||||
* Reader used to extract {@link RepositoryInformation} from {@link RepositoryMetadata}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.0
|
||||
* @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
|
||||
*/
|
||||
class RepositoryBeanDefinitionReader {
|
||||
|
||||
static RepositoryInformation readRepositoryInformation(RepositoryMetadata metadata,
|
||||
static RepositoryInformation readRepositoryInformation(RepositoryMetadata<?> metadata,
|
||||
ConfigurableListableBeanFactory beanFactory) {
|
||||
|
||||
return new AotRepositoryInformation(metadataSupplier(metadata, beanFactory),
|
||||
repositoryBaseClass(metadata, beanFactory), fragments(metadata, beanFactory));
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private static Supplier<Collection<RepositoryFragment<?>>> fragments(RepositoryMetadata metadata,
|
||||
ConfigurableListableBeanFactory beanFactory) {
|
||||
return Lazy
|
||||
.of(() -> (Collection<RepositoryFragment<?>>) metadata.getFragmentConfiguration().stream().flatMap(it -> {
|
||||
RepositoryFragmentConfiguration fragmentConfiguration = (RepositoryFragmentConfiguration) it;
|
||||
|
||||
List<RepositoryFragment> fragments = new ArrayList<>(2);
|
||||
if (fragmentConfiguration.getClassName() != null) {
|
||||
fragments.add(RepositoryFragment.implemented(forName(fragmentConfiguration.getClassName(), beanFactory)));
|
||||
}
|
||||
if (fragmentConfiguration.getInterfaceName() != null) {
|
||||
fragments
|
||||
.add(RepositoryFragment.structural(forName(fragmentConfiguration.getInterfaceName(), beanFactory)));
|
||||
}
|
||||
return Lazy.of(() -> (Collection<RepositoryFragment<?>>) metadata.getFragmentConfiguration().stream()
|
||||
.flatMap(it -> {
|
||||
|
||||
return fragments.stream();
|
||||
}).collect(Collectors.toList()));
|
||||
RepositoryFragmentConfiguration fragmentConfiguration = (RepositoryFragmentConfiguration) it;
|
||||
List<RepositoryFragment> fragments = new ArrayList<>(2);
|
||||
|
||||
if (fragmentConfiguration.getClassName() != null) {
|
||||
fragments.add(RepositoryFragment.implemented(forName(fragmentConfiguration.getClassName(), beanFactory)));
|
||||
}
|
||||
if (fragmentConfiguration.getInterfaceName() != null) {
|
||||
fragments.add(RepositoryFragment.structural(forName(fragmentConfiguration.getInterfaceName(), beanFactory)));
|
||||
}
|
||||
|
||||
return fragments.stream();
|
||||
})
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private static Supplier<Class<?>> repositoryBaseClass(RepositoryMetadata metadata,
|
||||
ConfigurableListableBeanFactory beanFactory) {
|
||||
return Lazy.of(() -> (Class<?>) metadata.getRepositoryBaseClassName().map(it -> forName(it.toString(), beanFactory))
|
||||
|
||||
return Lazy.of(() ->
|
||||
(Class<?>) metadata.getRepositoryBaseClassName().map(it -> forName(it.toString(), beanFactory))
|
||||
.orElseGet(() -> {
|
||||
// TODO: retrieve the default without loading the actual RepositoryBeanFactory
|
||||
return Object.class;
|
||||
@@ -74,15 +85,16 @@ class RepositoryBeanDefinitionReader {
|
||||
}
|
||||
|
||||
static Supplier<org.springframework.data.repository.core.RepositoryMetadata> metadataSupplier(
|
||||
RepositoryMetadata metadata, ConfigurableListableBeanFactory beanFactory) {
|
||||
RepositoryMetadata<?> metadata, ConfigurableListableBeanFactory beanFactory) {
|
||||
|
||||
return Lazy.of(() -> new DefaultRepositoryMetadata(forName(metadata.getRepositoryInterface(), beanFactory)));
|
||||
}
|
||||
|
||||
static Class<?> forName(String name, ConfigurableListableBeanFactory beanFactory) {
|
||||
try {
|
||||
return ClassUtils.forName(name, beanFactory.getBeanClassLoader());
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ClassNotFoundException cause) {
|
||||
throw new TypeNotPresentException(name, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,441 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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.io.Serializable;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.aop.SpringProxy;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.DecoratingProxy;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.data.projection.EntityProjectionIntrospector;
|
||||
import org.springframework.data.projection.TargetAware;
|
||||
import org.springframework.data.repository.Repository;
|
||||
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.support.RepositoryFactoryBeanSupport;
|
||||
import org.springframework.data.repository.core.support.RepositoryFragment;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link BeanRegistrationAotContribution} used to contribute repository registrations.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.aot.generate.GenerationContext
|
||||
* @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 {
|
||||
|
||||
private static final String KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME =
|
||||
"org.springframework.data.repository.kotlin.CoroutineCrudRepository";
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link RepositoryRegistrationAotContribution} initialized with
|
||||
* the given, required {@link RepositoryRegistrationAotProcessor} from which this contribution was created.
|
||||
*
|
||||
* @param repositoryRegistrationAotProcessor reference back to the {@link RepositoryRegistrationAotProcessor} from which this
|
||||
* contribution was created.
|
||||
* @return a new instance of {@link RepositoryRegistrationAotContribution}.
|
||||
* @throws IllegalArgumentException if the {@link RepositoryRegistrationAotProcessor} is {@literal null}.
|
||||
* @see org.springframework.data.aot.RepositoryRegistrationAotProcessor
|
||||
*/
|
||||
public static RepositoryRegistrationAotContribution fromProcessor(
|
||||
@NonNull RepositoryRegistrationAotProcessor repositoryRegistrationAotProcessor) {
|
||||
|
||||
return new RepositoryRegistrationAotContribution(repositoryRegistrationAotProcessor);
|
||||
}
|
||||
|
||||
private AotRepositoryContext repositoryContext;
|
||||
|
||||
private BiConsumer<AotRepositoryContext, GenerationContext> moduleContribution;
|
||||
|
||||
@NonNull
|
||||
private final RepositoryRegistrationAotProcessor repositoryRegistrationAotProcessor;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of the {@link RepositoryRegistrationAotContribution} initialized with the given,
|
||||
* required {@link RepositoryRegistrationAotProcessor} from which this contribution was created.
|
||||
*
|
||||
* @param repositoryRegistrationAotProcessor reference back to the {@link RepositoryRegistrationAotProcessor} from which this
|
||||
* contribution was created.
|
||||
* @throws IllegalArgumentException if the {@link RepositoryRegistrationAotProcessor} is {@literal null}.
|
||||
* @see org.springframework.data.aot.RepositoryRegistrationAotProcessor
|
||||
*/
|
||||
protected RepositoryRegistrationAotContribution(
|
||||
@NonNull RepositoryRegistrationAotProcessor repositoryRegistrationAotProcessor) {
|
||||
|
||||
Assert.notNull(repositoryRegistrationAotProcessor,
|
||||
"RepositoryRegistrationAotProcessor must not be null");
|
||||
|
||||
this.repositoryRegistrationAotProcessor = repositoryRegistrationAotProcessor;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected ConfigurableListableBeanFactory getBeanFactory() {
|
||||
return getRepositoryRegistrationAotProcessor().getBeanFactory();
|
||||
}
|
||||
|
||||
protected Optional<BiConsumer<AotRepositoryContext, GenerationContext>> getModuleContribution() {
|
||||
return Optional.ofNullable(this.moduleContribution);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected AotRepositoryContext getRepositoryContext() {
|
||||
|
||||
Assert.state(this.repositoryContext != null,
|
||||
"The AOT RepositoryContext was not properly initialized; did you call the forBean(:RegisteredBean) method");
|
||||
|
||||
return this.repositoryContext;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected RepositoryRegistrationAotProcessor getRepositoryRegistrationAotProcessor() {
|
||||
return this.repositoryRegistrationAotProcessor;
|
||||
}
|
||||
|
||||
public RepositoryInformation getRepositoryInformation() {
|
||||
return getRepositoryContext().getRepositoryInformation();
|
||||
}
|
||||
|
||||
private void logTrace(String message, Object... arguments) {
|
||||
getRepositoryRegistrationAotProcessor().logTrace(message, arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link RepositoryRegistrationAotContribution} for given, required {@link RegisteredBean}
|
||||
* representing the {@link Repository} registered in the bean registry.
|
||||
*
|
||||
* @param repositoryBean {@link RegisteredBean} for the {@link Repository}; must not be {@literal null}.
|
||||
* @return a {@link RepositoryRegistrationAotContribution} to contribute AOT metadata and code
|
||||
* for the {@link Repository} {@link RegisteredBean}.
|
||||
* @throws IllegalArgumentException if the {@link RegisteredBean} is {@literal null}.
|
||||
* @see org.springframework.beans.factory.support.RegisteredBean
|
||||
*/
|
||||
public RepositoryRegistrationAotContribution forBean(@NonNull RegisteredBean repositoryBean) {
|
||||
|
||||
Assert.notNull(repositoryBean, "The RegisteredBean for the repository must not be null");
|
||||
|
||||
RepositoryMetadata<?> repositoryMetadata = getRepositoryRegistrationAotProcessor().getRepositoryMetadata(repositoryBean);
|
||||
|
||||
this.repositoryContext = buildAotRepositoryContext(repositoryBean, repositoryMetadata);
|
||||
|
||||
enhanceRepositoryBeanDefinition(repositoryBean, repositoryMetadata, this.repositoryContext);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
protected DefaultAotRepositoryContext buildAotRepositoryContext(@NonNull RegisteredBean bean,
|
||||
@NonNull RepositoryMetadata<?> repositoryMetadata) {
|
||||
|
||||
RepositoryInformation repositoryInformation = resolveRepositoryInformation(repositoryMetadata);
|
||||
|
||||
DefaultAotRepositoryContext repositoryContext = new DefaultAotRepositoryContext();
|
||||
|
||||
repositoryContext.setAotContext(this::getBeanFactory);
|
||||
repositoryContext.setBeanName(bean.getBeanName());
|
||||
repositoryContext.setBasePackages(repositoryMetadata.getBasePackages().toSet());
|
||||
repositoryContext.setIdentifyingAnnotations(resolveIdentifyingAnnotations());
|
||||
repositoryContext.setRepositoryInformation(repositoryInformation);
|
||||
|
||||
return repositoryContext;
|
||||
}
|
||||
|
||||
private Set<Class<? extends Annotation>> resolveIdentifyingAnnotations() {
|
||||
|
||||
Set<Class<? extends Annotation>> identifyingAnnotations = Collections.emptySet();
|
||||
|
||||
try {
|
||||
// TODO: Getting all beans of type RepositoryConfigurationExtensionSupport will have the effect that
|
||||
// if the user is currently operating in multi-store mode, then all identifying annotations from
|
||||
// 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
|
||||
// with AOT, ATM?
|
||||
Map<String, RepositoryConfigurationExtensionSupport> repositoryConfigurationExtensionBeans =
|
||||
getBeanFactory().getBeansOfType(RepositoryConfigurationExtensionSupport.class);
|
||||
|
||||
repositoryConfigurationExtensionBeans.values().stream()
|
||||
.map(RepositoryConfigurationExtensionSupport::getIdentifyingAnnotations)
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toCollection(() -> identifyingAnnotations));
|
||||
|
||||
} catch (Throwable ignore) {
|
||||
// Possible BeansException because no bean exists of type RepositoryConfigurationExtension,
|
||||
// which included non-Singletons and occurred during eager initialization.
|
||||
}
|
||||
|
||||
return identifyingAnnotations;
|
||||
}
|
||||
|
||||
private RepositoryInformation resolveRepositoryInformation(RepositoryMetadata<?> repositoryMetadata) {
|
||||
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}.
|
||||
*
|
||||
* We just need to set the target {@link Repository} {@link Class type} of the {@link RepositoryFactoryBeanSupport}
|
||||
* while keeping the actual ID and DomainType set to {@link Object}. If the generic type signature does not match,
|
||||
* then we do not try to resolve and remap the types, but rather set the {@literal factoryBeanObjectType} attribute
|
||||
* on the {@link RootBeanDefinition}.
|
||||
*/
|
||||
protected void enhanceRepositoryBeanDefinition(@NonNull RegisteredBean repositoryBean,
|
||||
@NonNull RepositoryMetadata<?> repositoryMetadata, @NonNull AotRepositoryContext repositoryContext) {
|
||||
|
||||
logTrace(String.format("Enhancing repository factory bean definition [%s]", repositoryBean.getBeanName()));
|
||||
|
||||
Class<?> repositoryFactoryBeanType = repositoryContext
|
||||
.resolveType(repositoryMetadata.getRepositoryFactoryBeanClassName())
|
||||
.orElse(RepositoryFactoryBeanSupport.class);
|
||||
|
||||
ResolvableType resolvedRepositoryFactoryBeanType = ResolvableType.forClass(repositoryFactoryBeanType);
|
||||
|
||||
RootBeanDefinition repositoryBeanDefinition = repositoryBean.getMergedBeanDefinition();
|
||||
|
||||
if (isRepositoryWithTypeParameters(resolvedRepositoryFactoryBeanType)) {
|
||||
repositoryBeanDefinition.setTargetType(ResolvableType.forClassWithGenerics(repositoryFactoryBeanType,
|
||||
repositoryContext.getRepositoryInformation().getRepositoryInterface(), Object.class, Object.class));
|
||||
} else {
|
||||
repositoryBeanDefinition.setTargetType(resolvedRepositoryFactoryBeanType);
|
||||
repositoryBeanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE,
|
||||
repositoryContext.getRepositoryInformation().getRepositoryInterface());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRepositoryWithTypeParameters(ResolvableType type) {
|
||||
return type != null && type.getGenerics().length == 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link BiConsumer Callback} for data module specific contributions.
|
||||
*
|
||||
* @param moduleContribution {@link BiConsumer} used by data modules to submit contributions; can be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
@NonNull
|
||||
@SuppressWarnings("unused")
|
||||
public RepositoryRegistrationAotContribution withModuleContribution(
|
||||
@Nullable BiConsumer<AotRepositoryContext, GenerationContext> moduleContribution) {
|
||||
this.moduleContribution = moduleContribution;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(@NonNull GenerationContext generationContext,
|
||||
@NonNull BeanRegistrationCode beanRegistrationCode) {
|
||||
|
||||
contributeRepositoryInfo(this.repositoryContext, generationContext);
|
||||
getModuleContribution().ifPresent(it -> it.accept(getRepositoryContext(), generationContext));
|
||||
}
|
||||
|
||||
private void contributeRepositoryInfo(@NonNull AotRepositoryContext repositoryContext,
|
||||
@NonNull GenerationContext contribution) {
|
||||
|
||||
RepositoryInformation repositoryInformation = getRepositoryInformation();
|
||||
|
||||
logTrace("Contributing repository information for [%s]",
|
||||
repositoryInformation.getRepositoryInterface());
|
||||
|
||||
// TODO: is this the way?
|
||||
contribution.getRuntimeHints().reflection()
|
||||
.registerType(repositoryInformation.getRepositoryInterface(), hint ->
|
||||
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS))
|
||||
.registerType(repositoryInformation.getRepositoryBaseClass(), hint ->
|
||||
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS))
|
||||
.registerType(repositoryInformation.getDomainType(), hint ->
|
||||
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS));
|
||||
|
||||
// Repository Fragments
|
||||
for (RepositoryFragment<?> fragment : getRepositoryInformation().getFragments()) {
|
||||
|
||||
Class<?> repositoryFragmentType = fragment.getSignatureContributor();
|
||||
|
||||
contribution.getRuntimeHints().reflection().registerType(repositoryFragmentType, hint -> {
|
||||
|
||||
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
|
||||
|
||||
if (!repositoryFragmentType.isInterface()) {
|
||||
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Repository Proxy
|
||||
contribution.getRuntimeHints().proxies().registerJdkProxy(repositoryInformation.getRepositoryInterface(),
|
||||
SpringProxy.class, Advised.class, DecoratingProxy.class);
|
||||
|
||||
// Transactional Repository Proxy
|
||||
repositoryContext.ifTransactionManagerPresent(transactionManagerBeanNames -> {
|
||||
|
||||
// TODO: Is the following double JDK Proxy registration above necessary or would a single JDK Proxy
|
||||
// registration suffice?
|
||||
// In other words, simply having a single JDK Proxy registration either with or without
|
||||
// the additional Serializable TypeReference?
|
||||
// NOTE: Using a single JDK Proxy registration causes the
|
||||
// simpleRepositoryWithTxManagerNoKotlinNoReactiveButComponent() test case method to fail.
|
||||
List<TypeReference> transactionalRepositoryProxyTypeReferences =
|
||||
transactionalRepositoryProxyTypeReferences(repositoryInformation);
|
||||
|
||||
contribution.getRuntimeHints().proxies()
|
||||
.registerJdkProxy(transactionalRepositoryProxyTypeReferences.toArray(new TypeReference[0]));
|
||||
|
||||
if (isComponentAnnotatedRepository(repositoryInformation)) {
|
||||
transactionalRepositoryProxyTypeReferences.add(TypeReference.of(Serializable.class));
|
||||
contribution.getRuntimeHints().proxies()
|
||||
.registerJdkProxy(transactionalRepositoryProxyTypeReferences.toArray(new TypeReference[0]));
|
||||
}
|
||||
});
|
||||
|
||||
// Reactive Repositories
|
||||
if (repositoryInformation.isReactiveRepository()) {
|
||||
// TODO: do we still need this and how to configure it?
|
||||
// registry.initialization().add(NativeInitializationEntry.ofBuildTimeType(configuration.getRepositoryInterface()));
|
||||
}
|
||||
|
||||
// Kotlin
|
||||
if (isKotlinCoroutineRepository(repositoryContext, repositoryInformation)) {
|
||||
contribution.getRuntimeHints().reflection().registerTypes(kotlinRepositoryReflectionTypeReferences(),
|
||||
hint -> hint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
|
||||
}
|
||||
|
||||
// Repository query methods
|
||||
repositoryInformation.getQueryMethods()
|
||||
.map(repositoryInformation::getReturnedDomainClass)
|
||||
.filter(Class::isInterface)
|
||||
.forEach(type -> {
|
||||
if (EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy()
|
||||
.test(type, repositoryInformation.getDomainType())) {
|
||||
contributeProjection(type, contribution);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isComponentAnnotatedRepository(RepositoryInformation repositoryInformation) {
|
||||
return AnnotationUtils.findAnnotation(repositoryInformation.getRepositoryInterface(), Component.class) != null;
|
||||
}
|
||||
|
||||
private boolean isKotlinCoroutineRepository(AotRepositoryContext repositoryContext,
|
||||
RepositoryInformation repositoryInformation) {
|
||||
|
||||
return repositoryContext.resolveType(KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME)
|
||||
.filter(it -> ClassUtils.isAssignable(it, repositoryInformation.getRepositoryInterface()))
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
private List<TypeReference> kotlinRepositoryReflectionTypeReferences() {
|
||||
|
||||
return new ArrayList<>(Arrays.asList(
|
||||
TypeReference.of("org.springframework.data.repository.kotlin.CoroutineCrudRepository"),
|
||||
TypeReference.of(Repository.class),
|
||||
TypeReference.of(Iterable.class),
|
||||
TypeReference.of("kotlinx.coroutines.flow.Flow"),
|
||||
TypeReference.of("kotlin.collections.Iterable"),
|
||||
TypeReference.of("kotlin.Unit"),
|
||||
TypeReference.of("kotlin.Long"),
|
||||
TypeReference.of("kotlin.Boolean")
|
||||
));
|
||||
}
|
||||
|
||||
private List<TypeReference> transactionalRepositoryProxyTypeReferences(RepositoryInformation repositoryInformation) {
|
||||
|
||||
return new ArrayList<>(Arrays.asList(
|
||||
TypeReference.of(repositoryInformation.getRepositoryInterface()),
|
||||
TypeReference.of(Repository.class),
|
||||
TypeReference.of("org.springframework.transaction.interceptor.TransactionalProxy"),
|
||||
TypeReference.of("org.springframework.aop.framework.Advised"),
|
||||
TypeReference.of(DecoratingProxy.class)
|
||||
));
|
||||
}
|
||||
|
||||
private void contributeProjection(Class<?> type, GenerationContext generationContext) {
|
||||
|
||||
generationContext.getRuntimeHints().proxies()
|
||||
.registerJdkProxy(type, TargetAware.class, SpringProxy.class, DecoratingProxy.class);
|
||||
}
|
||||
|
||||
protected void contribute(AotRepositoryContext repositoryContext, GenerationContext generationContext) {
|
||||
|
||||
repositoryContext.getResolvedTypes().stream()
|
||||
.filter(it -> !isJavaOrPrimitiveType(it))
|
||||
.forEach(it -> contributeType(it, generationContext));
|
||||
|
||||
repositoryContext.getResolvedAnnotations().stream()
|
||||
.filter(this::isSpringDataManagedAnnotation)
|
||||
.map(MergedAnnotation::getType)
|
||||
.forEach(it -> contributeType(it, generationContext));
|
||||
}
|
||||
|
||||
private boolean isJavaOrPrimitiveType(Class<?> type) {
|
||||
|
||||
return TypeUtils.type(type).isPartOf("java")
|
||||
|| type.isPrimitive()
|
||||
|| ClassUtils.isPrimitiveArray(type);
|
||||
}
|
||||
|
||||
private boolean isSpringDataManagedAnnotation(MergedAnnotation<?> annotation) {
|
||||
|
||||
return annotation != null
|
||||
&& (isInSpringDataNamespace(annotation.getType()) || annotation.getMetaTypes().stream()
|
||||
.anyMatch(this::isInSpringDataNamespace));
|
||||
}
|
||||
|
||||
private boolean isInSpringDataNamespace(Class<?> type) {
|
||||
return type != null && type.getPackage().getName().startsWith(TypeContributor.DATA_NAMESPACE);
|
||||
}
|
||||
|
||||
protected void contributeType(Class<?> type, GenerationContext generationContext) {
|
||||
|
||||
logTrace("Contributing type information for [%s]", type);
|
||||
TypeContributor.contribute(type, it -> true, generationContext);
|
||||
}
|
||||
|
||||
// TODO What was this meant to be used for? Was this type filter maybe meant to be used in
|
||||
// the TypeContributor.contribute(:Class, :Predicate :GenerationContext) method
|
||||
// used in the contributeType(..) method above?
|
||||
public Predicate<Class<?>> typeFilter() { // like only document ones. // TODO: As in MongoDB?
|
||||
return it -> true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.lang.annotation.Annotation;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
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.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link BeanRegistrationAotProcessor} responsible processing and providing AOT configuration for repositories.
|
||||
* <p>
|
||||
* Processes {@link RepositoryFactoryBeanSupport repository factory beans} to provide generic type information to
|
||||
* the AOT tooling to allow deriving target type from the {@link RootBeanDefinition bean definition}. If generic types
|
||||
* do not match due to customization of the factory bean by the user, at least the target repository type is provided
|
||||
* via the {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE}.
|
||||
* </p>
|
||||
* <p>
|
||||
* With {@link RepositoryRegistrationAotContribution#contribute(AotRepositoryContext, GenerationContext)}, stores
|
||||
* can provide custom logic for contributing additional (eg. reflection) configuration. By default, reflection
|
||||
* configuration will be added for types reachable from the repository declaration and query methods as well as
|
||||
* all used {@link Annotation annotations} from the {@literal org.springframework.data} namespace.
|
||||
* </p>
|
||||
* The processor is typically configured via {@link RepositoryConfigurationExtension#getRepositoryAotProcessor()}
|
||||
* and gets added by the {@link org.springframework.data.repository.config.RepositoryConfigurationDelegate}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author John Blum
|
||||
* @see org.springframework.beans.factory.BeanFactoryAware
|
||||
* @see org.springframework.beans.factory.aot.BeanRegistrationAotProcessor
|
||||
* @since 3.0.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotProcessor, BeanFactoryAware {
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private Map<String, RepositoryMetadata<?>> configMap;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BeanRegistrationAotContribution processAheadOfTime(@NonNull RegisteredBean bean) {
|
||||
|
||||
return isRepositoryBean(bean)
|
||||
? newRepositoryRegistrationAotContribution(bean)
|
||||
: null;
|
||||
}
|
||||
|
||||
private boolean isRepositoryBean(RegisteredBean bean) {
|
||||
return bean != null && getConfigMap().containsKey(bean.getBeanName());
|
||||
}
|
||||
|
||||
protected RepositoryRegistrationAotContribution newRepositoryRegistrationAotContribution(
|
||||
@NonNull RegisteredBean repositoryBean) {
|
||||
|
||||
RepositoryRegistrationAotContribution contribution =
|
||||
RepositoryRegistrationAotContribution.fromProcessor(this).forBean(repositoryBean);
|
||||
|
||||
return contribution.withModuleContribution(contribution::contribute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
|
||||
|
||||
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory,
|
||||
() -> "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
|
||||
|
||||
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected ConfigurableListableBeanFactory getBeanFactory() {
|
||||
return this.beanFactory;
|
||||
}
|
||||
|
||||
public void setConfigMap(@Nullable Map<String, RepositoryMetadata<?>> configMap) {
|
||||
this.configMap = configMap;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Map<String, RepositoryMetadata<?>> getConfigMap() {
|
||||
return nullSafeMap(this.configMap);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private <K, V> Map<K, V> nullSafeMap(@Nullable Map<K, V> map) {
|
||||
return map != null ? map : Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected RepositoryMetadata<?> getRepositoryMetadata(@NonNull RegisteredBean bean) {
|
||||
return getConfigMap().get(nullSafeBeanName(bean));
|
||||
}
|
||||
|
||||
private String nullSafeBeanName(@Nullable RegisteredBean bean) {
|
||||
|
||||
String beanName = bean != null ? bean.getBeanName() : null;
|
||||
|
||||
return StringUtils.hasText(beanName) ? beanName : "";
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Log getLogger() {
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
private void logAt(Predicate<Log> logLevelPredicate, BiConsumer<Log, String> logOperation,
|
||||
String message, Object... arguments) {
|
||||
|
||||
Log logger = getLogger();
|
||||
|
||||
if (logLevelPredicate.test(logger)) {
|
||||
logOperation.accept(logger, String.format(message, arguments));
|
||||
}
|
||||
}
|
||||
|
||||
protected void logDebug(String message, Object... arguments) {
|
||||
logAt(Log::isDebugEnabled, Log::debug, message, arguments);
|
||||
}
|
||||
|
||||
protected void logTrace(String message, Object... arguments) {
|
||||
logAt(Log::isTraceEnabled, Log::trace, message, arguments);
|
||||
}
|
||||
}
|
||||
@@ -19,49 +19,67 @@ import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
|
||||
import org.springframework.beans.factory.generator.AotContributingBeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.generator.BeanFactoryContribution;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.data.ManagedTypes;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link AotContributingBeanFactoryPostProcessor} implementation to capture common data infrastructure concerns.
|
||||
* {@link BeanFactoryInitializationAotProcessor} implementation used to encapsulate common data infrastructure concerns
|
||||
* and preprocess the {@link ConfigurableListableBeanFactory} ahead of the AOT compilation in order to prepare
|
||||
* the Spring Data {@link BeanDefinition BeanDefinitions} for AOT processing.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author John Blum
|
||||
* @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory
|
||||
* @see org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution
|
||||
* @see org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor
|
||||
* @since 3.0
|
||||
*/
|
||||
public class AotDataComponentsBeanFactoryPostProcessor implements AotContributingBeanFactoryPostProcessor {
|
||||
public class SpringDataBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(AotContributingBeanFactoryPostProcessor.class);
|
||||
private static final Log logger = LogFactory.getLog(BeanFactoryInitializationAotProcessor.class);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BeanFactoryContribution contribute(ConfigurableListableBeanFactory beanFactory) {
|
||||
postProcessManagedTypes(beanFactory);
|
||||
public BeanFactoryInitializationAotContribution processAheadOfTime(
|
||||
@NonNull ConfigurableListableBeanFactory beanFactory) {
|
||||
|
||||
processManagedTypes(beanFactory);
|
||||
return null;
|
||||
}
|
||||
|
||||
private void postProcessManagedTypes(ConfigurableListableBeanFactory beanFactory) {
|
||||
private void processManagedTypes(@NonNull ConfigurableListableBeanFactory beanFactory) {
|
||||
|
||||
if (beanFactory instanceof BeanDefinitionRegistry registry) {
|
||||
for (String beanName : beanFactory.getBeanNamesForType(ManagedTypes.class)) {
|
||||
|
||||
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
|
||||
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 (logger.isDebugEnabled()) {
|
||||
logger.info(String.format("Replacing ManagedType bean definition %s.", beanName));
|
||||
}
|
||||
|
||||
BeanDefinition beanDefinitionReplacement = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(ManagedTypes.class)
|
||||
.setFactoryMethod("of")
|
||||
.addConstructorArgValue(supplier.get())
|
||||
.getBeanDefinition();
|
||||
|
||||
registry.removeBeanDefinition(beanName);
|
||||
registry.registerBeanDefinition(beanName, BeanDefinitionBuilder.rootBeanDefinition(ManagedTypes.class)
|
||||
.setFactoryMethod("of").addConstructorArgValue(supplier.get()).getBeanDefinition());
|
||||
registry.registerBeanDefinition(beanName, beanDefinitionReplacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ package org.springframework.data.aot;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -37,57 +37,64 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Sebastien Deleuze
|
||||
* @author John Blum
|
||||
*/
|
||||
public class TypeCollector {
|
||||
|
||||
private static Log logger = LogFactory.getLog(TypeCollector.class);
|
||||
private static final Log logger = LogFactory.getLog(TypeCollector.class);
|
||||
|
||||
static final Set<String> EXCLUDED_DOMAINS = new HashSet<>(Arrays.asList("java", "sun.", "jdk.", "reactor.",
|
||||
"kotlinx.", "kotlin.", "org.springframework.core.", "org.springframework.boot."));
|
||||
|
||||
private Predicate<Class<?>> excludedDomainsFilter = (type) -> {
|
||||
return EXCLUDED_DOMAINS.stream().noneMatch(type.getPackageName()::startsWith);
|
||||
private final Predicate<Class<?>> excludedDomainsFilter = type -> {
|
||||
String packageName = type.getPackageName();
|
||||
return EXCLUDED_DOMAINS.stream().noneMatch(packageName::startsWith);
|
||||
};
|
||||
|
||||
Predicate<Class<?>> typeFilter = excludedDomainsFilter;
|
||||
private Predicate<Class<?>> typeFilter = excludedDomainsFilter;
|
||||
|
||||
private final Predicate<Method> methodFilter = (method) -> {
|
||||
if (method.getName().startsWith("$$_hibernate")) {
|
||||
return false;
|
||||
}
|
||||
if (method.getDeclaringClass().getPackageName().startsWith("java.") || method.getDeclaringClass().isEnum()
|
||||
|| EXCLUDED_DOMAINS.stream().anyMatch(it -> method.getDeclaringClass().getPackageName().startsWith(it))) {
|
||||
return false;
|
||||
}
|
||||
if (method.isBridge() || method.isSynthetic()) {
|
||||
return false;
|
||||
}
|
||||
return (!Modifier.isNative(method.getModifiers()) && !Modifier.isPrivate(method.getModifiers())
|
||||
&& !Modifier.isProtected(method.getModifiers())) || !method.getDeclaringClass().equals(Object.class);
|
||||
private final Predicate<Method> methodFilter = method -> {
|
||||
|
||||
Predicate<Method> excludedDomainsPredicate = methodToTest ->
|
||||
excludedDomainsFilter.test(methodToTest.getDeclaringClass());
|
||||
|
||||
Predicate<Method> excludedMethodsPredicate = Predicates.IS_BRIDGE_METHOD
|
||||
.or(Predicates.IS_OBJECT_MEMBER)
|
||||
.or(Predicates.IS_HIBERNATE_MEMBER)
|
||||
.or(Predicates.IS_ENUM_MEMBER)
|
||||
.or(Predicates.IS_NATIVE)
|
||||
.or(Predicates.IS_PRIVATE)
|
||||
.or(Predicates.IS_PROTECTED)
|
||||
.or(Predicates.IS_SYNTHETIC)
|
||||
.or(excludedDomainsPredicate.negate());
|
||||
|
||||
return !excludedMethodsPredicate.test(method);
|
||||
};
|
||||
|
||||
private Predicate<Field> fieldFilter = (field) -> {
|
||||
if (field.isSynthetic() | field.getName().startsWith("$$_hibernate")) {
|
||||
return false;
|
||||
}
|
||||
if (field.getDeclaringClass().getPackageName().startsWith("java.")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
private Predicate<Field> fieldFilter = field -> {
|
||||
|
||||
Predicate<Member> excludedFieldPredicate = Predicates.IS_HIBERNATE_MEMBER
|
||||
.or(Predicates.IS_SYNTHETIC)
|
||||
.or(Predicates.IS_JAVA);
|
||||
|
||||
return !excludedFieldPredicate.test(field);
|
||||
};
|
||||
|
||||
public TypeCollector filterFields(Predicate<Field> filter) {
|
||||
@NonNull
|
||||
public TypeCollector filterFields(@NonNull Predicate<Field> filter) {
|
||||
this.fieldFilter = filter.and(filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TypeCollector filterTypes(Predicate<Class<?>> filter) {
|
||||
@NonNull
|
||||
public TypeCollector filterTypes(@NonNull Predicate<Class<?>> filter) {
|
||||
this.typeFilter = this.typeFilter.and(filter);
|
||||
return this;
|
||||
}
|
||||
@@ -98,10 +105,12 @@ public class TypeCollector {
|
||||
* @param types the types to inspect
|
||||
* @return a type model collector for the type
|
||||
*/
|
||||
@NonNull
|
||||
public static ReachableTypes inspect(Class<?>... types) {
|
||||
return inspect(Arrays.asList(types));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ReachableTypes inspect(Collection<Class<?>> types) {
|
||||
return new ReachableTypes(new TypeCollector(), types);
|
||||
}
|
||||
@@ -168,8 +177,8 @@ public class TypeCollector {
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
logger.warn(ex);
|
||||
} catch (Exception cause) {
|
||||
logger.warn(cause);
|
||||
}
|
||||
return new HashSet<>(discoveredTypes);
|
||||
}
|
||||
@@ -191,11 +200,11 @@ public class TypeCollector {
|
||||
|
||||
public static class ReachableTypes {
|
||||
|
||||
private TypeCollector typeCollector;
|
||||
private final Iterable<Class<?>> roots;
|
||||
private final Lazy<List<Class<?>>> reachableTypes = Lazy.of(this::collect);
|
||||
private final TypeCollector typeCollector;
|
||||
|
||||
public ReachableTypes(TypeCollector typeCollector, Iterable<Class<?>> roots) {
|
||||
public ReachableTypes(@NonNull TypeCollector typeCollector, @NonNull Iterable<Class<?>> roots) {
|
||||
|
||||
this.typeCollector = typeCollector;
|
||||
this.roots = roots;
|
||||
@@ -220,24 +229,24 @@ public class TypeCollector {
|
||||
|
||||
private final Map<String, ResolvableType> mutableCache = new LinkedHashMap<>();
|
||||
|
||||
public void add(ResolvableType resolvableType) {
|
||||
public void add(@NonNull ResolvableType resolvableType) {
|
||||
mutableCache.put(resolvableType.toString(), resolvableType);
|
||||
}
|
||||
|
||||
public boolean contains(ResolvableType key) {
|
||||
return mutableCache.containsKey(key.toString());
|
||||
public void clear() {
|
||||
mutableCache.clear();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return mutableCache.size();
|
||||
public boolean contains(@NonNull ResolvableType key) {
|
||||
return mutableCache.containsKey(key.toString());
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mutableCache.isEmpty();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mutableCache.clear();
|
||||
public int size() {
|
||||
return mutableCache.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.aot.generator.CodeContribution;
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.SynthesizedAnnotation;
|
||||
@@ -39,7 +39,7 @@ class TypeContributor {
|
||||
* @param type
|
||||
* @param contribution
|
||||
*/
|
||||
static void contribute(Class<?> type, CodeContribution contribution) {
|
||||
static void contribute(Class<?> type, GenerationContext contribution) {
|
||||
contribute(type, Collections.emptySet(), contribution);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,8 @@ class TypeContributor {
|
||||
* @param filter
|
||||
* @param contribution
|
||||
*/
|
||||
static void contribute(Class<?> type, Predicate<Class<? extends Annotation>> filter, CodeContribution contribution) {
|
||||
@SuppressWarnings("unchecked")
|
||||
static void contribute(Class<?> type, Predicate<Class<? extends Annotation>> filter, GenerationContext contribution) {
|
||||
|
||||
if (type.isPrimitive()) {
|
||||
return;
|
||||
@@ -58,27 +59,24 @@ class TypeContributor {
|
||||
|
||||
if (type.isAnnotation() && filter.test((Class<? extends Annotation>) type)) {
|
||||
|
||||
contribution.runtimeHints().reflection().registerType(type, hint -> {
|
||||
hint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS);
|
||||
});
|
||||
contribution.getRuntimeHints().reflection().registerType(type, hint ->
|
||||
hint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
|
||||
|
||||
// TODO: do we need this if meta annotated with SD annotation?
|
||||
if (type.getPackage().getName().startsWith(DATA_NAMESPACE)) {
|
||||
contribution.runtimeHints().proxies().registerJdkProxy(type, SynthesizedAnnotation.class);
|
||||
contribution.getRuntimeHints().proxies().registerJdkProxy(type, SynthesizedAnnotation.class);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type.isInterface()) {
|
||||
contribution.runtimeHints().reflection().registerType(type, hint -> {
|
||||
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
|
||||
});
|
||||
contribution.getRuntimeHints().reflection().registerType(type, hint ->
|
||||
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
|
||||
return;
|
||||
}
|
||||
|
||||
contribution.runtimeHints().reflection().registerType(type, hint -> {
|
||||
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
|
||||
});
|
||||
contribution.getRuntimeHints().reflection().registerType(type, hint ->
|
||||
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,7 +87,7 @@ class TypeContributor {
|
||||
* @param annotationNamespaces
|
||||
* @param contribution
|
||||
*/
|
||||
static void contribute(Class<?> type, Set<String> annotationNamespaces, CodeContribution contribution) {
|
||||
static void contribute(Class<?> type, Set<String> annotationNamespaces, GenerationContext contribution) {
|
||||
contribute(type, it -> isPartOfOrMetaAnnotatedWith(it, annotationNamespaces), contribution);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.aot;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@@ -26,37 +27,89 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Scanner used to scan for {@link Class types} that will be added to the AOT processing infrastructure.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author John Blum
|
||||
*/
|
||||
public class TypeScanner { // TODO: replace this with AnnotatedTypeScanner maybe?
|
||||
// TODO: Replace this with AnnotatedTypeScanner, maybe?
|
||||
public class TypeScanner {
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
public TypeScanner(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
static TypeScanner scanner(ClassLoader classLoader) {
|
||||
/**
|
||||
* Factory method used to construct a new {@link TypeScanner} initialized with the given {@link ClassLoader}
|
||||
* used to resolve scanned {@link Class type}.
|
||||
*
|
||||
* @param classLoader {@link ClassLoader} used to resolve scanned {@link Class types}.
|
||||
* @return a new {@link TypeScanner}.
|
||||
*/
|
||||
@NonNull
|
||||
static TypeScanner scanner(@Nullable ClassLoader classLoader) {
|
||||
return new TypeScanner(classLoader);
|
||||
}
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link TypeScanner} initialized with the given {@link ClassLoader}
|
||||
* used to resolve scanned {@link Class types}.
|
||||
*
|
||||
* @param classLoader {@link ClassLoader} used to resolve scanned {@link Class types}.
|
||||
*/
|
||||
public TypeScanner(@Nullable ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans for {@link Class types} potentially annotated with any of the given {@link Annotation} types.
|
||||
*
|
||||
* @param annotations array of {@link Annotation} types used to filter the scanned {@link Class types};
|
||||
* must not be {@literal null}.
|
||||
* @return new instance of {@link Scanner}.
|
||||
* @see #scanForTypesAnnotatedWith(Collection)
|
||||
* @see Scanner
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Scanner scanForTypesAnnotatedWith(Class<? extends Annotation>... annotations) {
|
||||
return scanForTypesAnnotatedWith(Arrays.asList(annotations));
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans for {@link Class types} potentially annotated with any of the given {@link Annotation} types.
|
||||
*
|
||||
* @param annotations {@link Collection} of {@link Annotation} types used to filter the scanned {@link Class types}.
|
||||
* @return new instance of {@link Scanner}.
|
||||
* @see #scanForTypesAnnotatedWith(Class[])
|
||||
* @see Scanner
|
||||
*/
|
||||
public Scanner scanForTypesAnnotatedWith(Collection<Class<? extends Annotation>> annotations) {
|
||||
return new ScannerImpl().includeTypesAnnotatedWith(annotations);
|
||||
}
|
||||
|
||||
public interface Scanner {
|
||||
|
||||
/**
|
||||
* Collects the {@link String names} of packages to scan.
|
||||
*
|
||||
* @param packageNames array of {@link String package names} ot scan; must not be {@literal null}.
|
||||
* @return the resolved, scanned {@link Class types}.
|
||||
* @see #inPackages(Collection)
|
||||
*/
|
||||
default Set<Class<?>> inPackages(String... packageNames) {
|
||||
return inPackages(Arrays.asList(packageNames));
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the {@link String names} of packages to scan.
|
||||
*
|
||||
* @param packageNames {@link Collection} of {@link String package names} ot scan.
|
||||
* @return the resolved, scanned {@link Class types}.
|
||||
* @see #inPackages(String...)
|
||||
*/
|
||||
Set<Class<?>> inPackages(Collection<String> packageNames);
|
||||
}
|
||||
|
||||
@@ -64,7 +117,7 @@ public class TypeScanner { // TODO: replace this with AnnotatedTypeScanner maybe
|
||||
|
||||
ClassPathScanningCandidateComponentProvider componentProvider;
|
||||
|
||||
public ScannerImpl() {
|
||||
ScannerImpl() {
|
||||
|
||||
componentProvider = new ClassPathScanningCandidateComponentProvider(false);
|
||||
componentProvider.setEnvironment(new StandardEnvironment());
|
||||
@@ -72,7 +125,11 @@ public class TypeScanner { // TODO: replace this with AnnotatedTypeScanner maybe
|
||||
}
|
||||
|
||||
ScannerImpl includeTypesAnnotatedWith(Collection<Class<? extends Annotation>> annotations) {
|
||||
annotations.stream().map(AnnotationTypeFilter::new).forEach(componentProvider::addIncludeFilter);
|
||||
|
||||
nullSafeCollection(annotations).stream()
|
||||
.map(AnnotationTypeFilter::new)
|
||||
.forEach(componentProvider::addIncludeFilter);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -81,27 +138,29 @@ public class TypeScanner { // TODO: replace this with AnnotatedTypeScanner maybe
|
||||
|
||||
Set<Class<?>> types = new LinkedHashSet<>();
|
||||
|
||||
packageNames.forEach(pkg -> {
|
||||
componentProvider.findCandidateComponents(pkg).forEach(it -> {
|
||||
resolveType(it.getBeanClassName()).ifPresent(types::add);
|
||||
});
|
||||
});
|
||||
nullSafeCollection(packageNames).forEach(pkg ->
|
||||
componentProvider.findCandidateComponents(pkg).forEach(it ->
|
||||
resolveType(it.getBeanClassName()).ifPresent(types::add)));
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private <T> Collection<T> nullSafeCollection(@Nullable Collection<T> collection) {
|
||||
return collection != null ? collection : Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Class<?>> resolveType(String typeName) {
|
||||
|
||||
if (!ClassUtils.isPresent(typeName, classLoader)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
try {
|
||||
return Optional.of(ClassUtils.forName(typeName, classLoader));
|
||||
} catch (ClassNotFoundException e) {
|
||||
// just do nothing
|
||||
if (ClassUtils.isPresent(typeName, classLoader)) {
|
||||
try {
|
||||
return Optional.of(ClassUtils.forName(typeName, classLoader));
|
||||
} catch (ClassNotFoundException ignore) {
|
||||
// just do nothing
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.util.ObjectUtils;
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
// TODO: Consider moving to the org.springframework.data.util package.
|
||||
public class TypeUtils {
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.util.stream.Collectors;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
||||
@@ -48,11 +49,11 @@ import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.core.metrics.ApplicationStartup;
|
||||
import org.springframework.core.metrics.StartupStep;
|
||||
import org.springframework.data.ManagedTypes;
|
||||
import org.springframework.data.aot.AotDataComponentsBeanFactoryPostProcessor;
|
||||
import org.springframework.data.aot.TypeScanner;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationDelegate.LazyRepositoryInjectionPointResolver.ManagedTypesBean;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StopWatch;
|
||||
@@ -67,6 +68,7 @@ import org.springframework.util.StopWatch;
|
||||
* @author Jens Schauder
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @author John Blum
|
||||
*/
|
||||
public class RepositoryConfigurationDelegate {
|
||||
|
||||
@@ -114,7 +116,8 @@ public class RepositoryConfigurationDelegate {
|
||||
*
|
||||
* @param environment can be {@literal null}.
|
||||
* @param resourceLoader can be {@literal null}.
|
||||
* @return
|
||||
* @return the given {@link Environment} if not {@literal null}, a configured {@link Environment},
|
||||
* or a default {@link Environment}.
|
||||
*/
|
||||
private static Environment defaultEnvironment(@Nullable Environment environment,
|
||||
@Nullable ResourceLoader resourceLoader) {
|
||||
@@ -128,11 +131,13 @@ public class RepositoryConfigurationDelegate {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the found repositories in the given {@link BeanDefinitionRegistry}.
|
||||
* Registers the discovered repositories in the given {@link BeanDefinitionRegistry}.
|
||||
*
|
||||
* @param registry
|
||||
* @param extension
|
||||
* @param registry {@link BeanDefinitionRegistry} in which to register the repository bean.
|
||||
* @param extension {@link RepositoryConfigurationExtension} for the module.
|
||||
* @return {@link BeanComponentDefinition}s for all repository bean definitions found.
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension
|
||||
* @see org.springframework.beans.factory.support.BeanDefinitionRegistry
|
||||
*/
|
||||
public List<BeanComponentDefinition> registerRepositoriesIn(BeanDefinitionRegistry registry,
|
||||
RepositoryConfigurationExtension extension) {
|
||||
@@ -146,9 +151,6 @@ public class RepositoryConfigurationDelegate {
|
||||
|
||||
RepositoryBeanDefinitionBuilder builder = new RepositoryBeanDefinitionBuilder(registry, extension,
|
||||
configurationSource, resourceLoader, environment);
|
||||
List<BeanComponentDefinition> definitions = new ArrayList<>();
|
||||
|
||||
StopWatch watch = new StopWatch();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(LogMessage.format("Scanning for %s repositories in packages %s.", //
|
||||
@@ -156,19 +158,22 @@ public class RepositoryConfigurationDelegate {
|
||||
configurationSource.getBasePackages().stream().collect(Collectors.joining(", "))));
|
||||
}
|
||||
|
||||
StopWatch watch = new StopWatch();
|
||||
ApplicationStartup startup = getStartup(registry);
|
||||
StartupStep repoScan = startup.start("spring.data.repository.scanning");
|
||||
|
||||
repoScan.tag("dataModule", extension.getModuleName());
|
||||
repoScan.tag("basePackages",
|
||||
() -> configurationSource.getBasePackages().stream().collect(Collectors.joining(", ")));
|
||||
|
||||
watch.start();
|
||||
|
||||
Collection<RepositoryConfiguration<RepositoryConfigurationSource>> configurations = extension
|
||||
.getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode);
|
||||
|
||||
List<BeanComponentDefinition> definitions = new ArrayList<>();
|
||||
|
||||
Map<String, RepositoryConfiguration<?>> configurationsByRepositoryName = new HashMap<>(configurations.size());
|
||||
Map<String, RepositoryMetadata<?>> metadataMap = new HashMap<>(configurations.size());
|
||||
Map<String, RepositoryMetadata<?>> metadataByRepositoryBeanName = new HashMap<>(configurations.size());
|
||||
|
||||
for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : configurations) {
|
||||
|
||||
@@ -185,6 +190,8 @@ public class RepositoryConfigurationDelegate {
|
||||
}
|
||||
|
||||
AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
|
||||
|
||||
beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, configuration.getRepositoryInterface());
|
||||
beanDefinition.setResourceDescription(configuration.getResourceDescription());
|
||||
|
||||
String beanName = configurationSource.generateBeanName(beanDefinition);
|
||||
@@ -194,9 +201,7 @@ public class RepositoryConfigurationDelegate {
|
||||
configuration.getRepositoryInterface(), configuration.getRepositoryFactoryBeanClassName()));
|
||||
}
|
||||
|
||||
metadataMap.put(beanName, builder.buildMetadata(configuration));
|
||||
|
||||
beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, configuration.getRepositoryInterface());
|
||||
metadataByRepositoryBeanName.put(beanName, builder.buildMetadata(configuration));
|
||||
registry.registerBeanDefinition(beanName, beanDefinition);
|
||||
definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
|
||||
}
|
||||
@@ -204,68 +209,62 @@ public class RepositoryConfigurationDelegate {
|
||||
potentiallyLazifyRepositories(configurationsByRepositoryName, registry, configurationSource.getBootstrapMode());
|
||||
|
||||
watch.stop();
|
||||
|
||||
repoScan.tag("repository.count", Integer.toString(configurations.size()));
|
||||
repoScan.end();
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info(
|
||||
LogMessage.format("Finished Spring Data repository scanning in %s ms. Found %s %s repository interfaces.", //
|
||||
watch.getLastTaskTimeMillis(), configurations.size(), extension.getModuleName()));
|
||||
logger.info(LogMessage.format("Finished Spring Data repository scanning in %s ms. Found %s %s repository interfaces.",
|
||||
watch.getLastTaskTimeMillis(), configurations.size(), extension.getModuleName()));
|
||||
}
|
||||
|
||||
// TODO: AOT Processing -> guard this one with a flag so it's not always present
|
||||
registerAotComponents(registry, extension, metadataMap);
|
||||
// TODO: With regard to AOT Processing, perhaps we need to be smart and detect whether "core" AOT components are
|
||||
// (or rather configuration is) present on the classpath to enable Spring Data AOT component registration.
|
||||
registerAotComponents(registry, extension, metadataByRepositoryBeanName);
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
private void registerAotComponents(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension,
|
||||
Map<String, RepositoryMetadata<?>> metadataMap) {
|
||||
Map<String, RepositoryMetadata<?>> metadataByRepositoryBeanName) {
|
||||
|
||||
{ // overall general data bean factory postprocessor - TODO: move this to spring factories!!!
|
||||
if (!registry.isBeanNameInUse(AotDataComponentsBeanFactoryPostProcessor.class.getName())) {
|
||||
registry.registerBeanDefinition(AotDataComponentsBeanFactoryPostProcessor.class.getName(), BeanDefinitionBuilder
|
||||
.rootBeanDefinition(AotDataComponentsBeanFactoryPostProcessor.class).getBeanDefinition());
|
||||
// Managed types lookup if possible
|
||||
if (extension instanceof RepositoryConfigurationExtensionSupport extensionSupport) {
|
||||
|
||||
String targetManagedTypesBeanName = String.format("%s.managed-types", extension.getModulePrefix());
|
||||
|
||||
if (!registry.isBeanNameInUse(targetManagedTypesBeanName)) {
|
||||
|
||||
// this needs to be lazy or we'd resolve types to early maybe
|
||||
Supplier<Set<Class<?>>> args = () -> {
|
||||
|
||||
Set<String> packages = metadataByRepositoryBeanName.values().stream()
|
||||
.flatMap(it -> it.getBasePackages().stream())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
return new TypeScanner(resourceLoader.getClassLoader())
|
||||
.scanForTypesAnnotatedWith(extensionSupport.getIdentifyingAnnotations())
|
||||
.inPackages(packages);
|
||||
};
|
||||
|
||||
registry.registerBeanDefinition(targetManagedTypesBeanName, BeanDefinitionBuilder
|
||||
.rootBeanDefinition(ManagedTypesBean.class).addConstructorArgValue(args).getBeanDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
{ // Managed types lookup if possible
|
||||
if (extension instanceof RepositoryConfigurationExtensionSupport configExtensionSupport) {
|
||||
// module-specific repository aot processor
|
||||
String repositoryAotProcessorBeanName = String.format("data-%s.repository-aot-processor" /* might be duplicate */,
|
||||
extension.getModulePrefix());
|
||||
|
||||
String targetManagedTypesBeanName = String.format("%s.managed-types", extension.getModulePrefix());
|
||||
if (!registry.isBeanNameInUse(targetManagedTypesBeanName)) {
|
||||
if (!registry.isBeanNameInUse(repositoryAotProcessorBeanName)) {
|
||||
|
||||
// this needs to be lazy or we'd resolve types to early maybe
|
||||
Supplier<Set<Class<?>>> args = new Supplier<Set<Class<?>>>() {
|
||||
BeanDefinitionBuilder repositoryAotProcessor = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(extension.getRepositoryAotProcessor())
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
|
||||
@Override
|
||||
public Set<Class<?>> get() {
|
||||
repositoryAotProcessor.addPropertyValue("configMap", metadataByRepositoryBeanName);
|
||||
|
||||
Set<String> packages = metadataMap.values().stream().flatMap(it -> it.getBasePackages().stream())
|
||||
.collect(Collectors.toSet());
|
||||
return new TypeScanner(resourceLoader.getClassLoader())
|
||||
.scanForTypesAnnotatedWith(configExtensionSupport.getIdentifyingAnnotations()).inPackages(packages);
|
||||
}
|
||||
};
|
||||
|
||||
registry.registerBeanDefinition(targetManagedTypesBeanName, BeanDefinitionBuilder
|
||||
.rootBeanDefinition(ManagedTypesBean.class).addConstructorArgValue(args).getBeanDefinition());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // module specific repository post processor
|
||||
String aotRepoPostProcessorBeanName = String.format("data-%s.repository-post-processor" /* might be duplicate */,
|
||||
extension.getModulePrefix());
|
||||
|
||||
if (!registry.isBeanNameInUse(aotRepoPostProcessorBeanName)) {
|
||||
|
||||
BeanDefinitionBuilder aotRepoPostProcessor = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(extension.getAotPostProcessor());
|
||||
aotRepoPostProcessor.addPropertyValue("configMap", metadataMap);
|
||||
registry.registerBeanDefinition(aotRepoPostProcessorBeanName, aotRepoPostProcessor.getBeanDefinition());
|
||||
}
|
||||
registry.registerBeanDefinition(repositoryAotProcessorBeanName, repositoryAotProcessor.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +279,7 @@ public class RepositoryConfigurationDelegate {
|
||||
private static void potentiallyLazifyRepositories(Map<String, RepositoryConfiguration<?>> configurations,
|
||||
BeanDefinitionRegistry registry, BootstrapMode mode) {
|
||||
|
||||
if (!DefaultListableBeanFactory.class.isInstance(registry) || mode.equals(BootstrapMode.DEFAULT)) {
|
||||
if (!DefaultListableBeanFactory.class.isInstance(registry) || BootstrapMode.DEFAULT.equals(mode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -315,7 +314,8 @@ public class RepositoryConfigurationDelegate {
|
||||
* than a single type is considered a multi-store configuration scenario which will trigger stricter repository
|
||||
* scanning.
|
||||
*
|
||||
* @return
|
||||
* @return {@literal true} if multiple data store repository implementations are present in the application.
|
||||
* This typically means an Spring application is using more than 1 type of data store.
|
||||
*/
|
||||
private boolean multipleStoresDetected() {
|
||||
|
||||
@@ -360,11 +360,12 @@ public class RepositoryConfigurationDelegate {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link LazyRepositoryInjectionPointResolver} that will have its configurations augmented with the
|
||||
* given ones.
|
||||
* Returns a new {@link LazyRepositoryInjectionPointResolver} that will have its configurations augmented with
|
||||
* the given ones.
|
||||
*
|
||||
* @param configurations must not be {@literal null}.
|
||||
* @return
|
||||
* @return a new {@link LazyRepositoryInjectionPointResolver} that will have its configurations augmented with
|
||||
* the given ones.
|
||||
*/
|
||||
LazyRepositoryInjectionPointResolver withAdditionalConfigurations(
|
||||
Map<String, RepositoryConfiguration<?>> configurations) {
|
||||
@@ -398,14 +399,14 @@ public class RepositoryConfigurationDelegate {
|
||||
|
||||
static class ManagedTypesBean implements ManagedTypes {
|
||||
|
||||
private Lazy<Set<Class<?>>> types;
|
||||
private final Lazy<Set<Class<?>>> types;
|
||||
|
||||
public ManagedTypesBean(Supplier<Set<Class<?>>> types) {
|
||||
this.types = Lazy.of(types);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<Class<?>> action) {
|
||||
public void forEach(@NonNull Consumer<Class<?>> action) {
|
||||
types.get().forEach(action);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,14 @@ package org.springframework.data.repository.config;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.data.aot.AotContributingRepositoryBeanPostProcessor;
|
||||
import org.springframework.data.aot.RepositoryRegistrationAotProcessor;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -32,6 +34,7 @@ import org.springframework.util.StringUtils;
|
||||
* @see RepositoryConfigurationExtensionSupport
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author John Blum
|
||||
*/
|
||||
public interface RepositoryConfigurationExtension {
|
||||
|
||||
@@ -57,13 +60,34 @@ public interface RepositoryConfigurationExtension {
|
||||
return StringUtils.capitalize(getModulePrefix());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link String prefix} identifying the module.
|
||||
*
|
||||
* @return a {@link String prefix} identifying the module.
|
||||
* @see #getModuleName()
|
||||
*/
|
||||
String getModulePrefix();
|
||||
|
||||
/**
|
||||
* Returns the {@link BeanRegistrationAotProcessor} type responsible for contributing AOT/native configuration
|
||||
* required by the Spring Data Repository infrastructure components at native runtime.
|
||||
*
|
||||
* @return the {@link BeanRegistrationAotProcessor} type responsible for contributing AOT/native configuration.
|
||||
* Defaults to {@link RepositoryRegistrationAotProcessor}. Must not be {@literal null}.
|
||||
* @see org.springframework.beans.factory.aot.BeanRegistrationAotProcessor
|
||||
* @since 3.0
|
||||
*/
|
||||
@NonNull
|
||||
default Class<? extends BeanRegistrationAotProcessor> getRepositoryAotProcessor() {
|
||||
return RepositoryRegistrationAotProcessor.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link RepositoryConfiguration}s obtained through the given {@link RepositoryConfigurationSource}.
|
||||
*
|
||||
* @param configSource
|
||||
* @param loader
|
||||
* @param configSource {@link RepositoryConfigurationSource} encapsulating the source (XML, Annotation) of
|
||||
* the repository configuration.
|
||||
* @param loader {@link ResourceLoader} used to load resources.
|
||||
* @param strictMatchesOnly whether to return strict repository matches only. Handing in {@literal true} will cause
|
||||
* the repository interfaces and domain types handled to be checked whether they are managed by the current
|
||||
* store.
|
||||
@@ -81,28 +105,27 @@ public interface RepositoryConfigurationExtension {
|
||||
String getDefaultNamedQueryLocation();
|
||||
|
||||
/**
|
||||
* Returns the name of the repository factory class to be used.
|
||||
* Returns the {@link String name} of the repository factory class to be used.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
String getRepositoryFactoryBeanClassName();
|
||||
|
||||
/**
|
||||
* @return the {@link AotContributingBeanPostProcessor} type responsible for contributing AOT/native configuration.
|
||||
* Defaults to {@link AotContributingRepositoryBeanPostProcessor}. Must not be {@literal null}
|
||||
* @since 3.0
|
||||
* Returns the default location of the Spring Data named queries.
|
||||
*
|
||||
* @return must not be {@literal null} or empty.
|
||||
*/
|
||||
default Class<? extends AotContributingBeanPostProcessor> getAotPostProcessor() {
|
||||
return AotContributingRepositoryBeanPostProcessor.class;
|
||||
}
|
||||
String getDefaultNamedQueryLocation();
|
||||
|
||||
/**
|
||||
* Callback to register additional bean definitions for a {@literal repositories} root node. This usually includes
|
||||
* beans you have to set up once independently of the number of repositories to be created. Will be called before any
|
||||
* repositories bean definitions have been registered.
|
||||
*
|
||||
* @param registry
|
||||
* @param configurationSource
|
||||
* @param registry {@link BeanDefinitionRegistry} containing bean definitions.
|
||||
* @param configurationSource {@link RepositoryConfigurationSource} encapsulating the source (e.g. XML, Annotation)
|
||||
* of the repository configuration.
|
||||
*/
|
||||
void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource configurationSource);
|
||||
|
||||
@@ -130,4 +153,5 @@ public interface RepositoryConfigurationExtension {
|
||||
* @param config will never be {@literal null}.
|
||||
*/
|
||||
void postProcess(BeanDefinitionBuilder builder, XmlRepositoryConfigurationSource config);
|
||||
|
||||
}
|
||||
|
||||
@@ -27,19 +27,13 @@ import org.springframework.data.util.Streamable;
|
||||
public interface RepositoryInformation extends RepositoryMetadata {
|
||||
|
||||
/**
|
||||
* Returns the base class to be used to create the proxy backing instance.
|
||||
* Returns whether the given method is logically a base class method. This also includes methods (re)declared in the
|
||||
* repository interface that match the signatures of the base implementation.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
Class<?> getRepositoryBaseClass();
|
||||
|
||||
/**
|
||||
* Returns if the configured repository interface has custom methods, that might have to be delegated to a custom
|
||||
* implementation. This is used to verify repository configuration.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean hasCustomMethod();
|
||||
boolean isBaseClassMethod(Method method);
|
||||
|
||||
/**
|
||||
* Returns whether the given method is a custom repository method.
|
||||
@@ -57,15 +51,6 @@ public interface RepositoryInformation extends RepositoryMetadata {
|
||||
*/
|
||||
boolean isQueryMethod(Method method);
|
||||
|
||||
/**
|
||||
* Returns whether the given method is logically a base class method. This also includes methods (re)declared in the
|
||||
* repository interface that match the signatures of the base implementation.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
boolean isBaseClassMethod(Method method);
|
||||
|
||||
/**
|
||||
* Returns all methods considered to be query methods.
|
||||
*
|
||||
@@ -73,6 +58,13 @@ public interface RepositoryInformation extends RepositoryMetadata {
|
||||
*/
|
||||
Streamable<Method> getQueryMethods();
|
||||
|
||||
/**
|
||||
* Returns the base class to be used to create the proxy backing instance.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?> getRepositoryBaseClass();
|
||||
|
||||
/**
|
||||
* Returns the target class method that is backing the given method. This can be necessary if a repository interface
|
||||
* redeclares a method of the core repository interface (e.g. for transaction behavior customization). Returns the
|
||||
@@ -84,6 +76,16 @@ public interface RepositoryInformation extends RepositoryMetadata {
|
||||
*/
|
||||
Method getTargetClassMethod(Method method);
|
||||
|
||||
/**
|
||||
* Returns if the configured repository interface has custom methods, that might have to be delegated to a custom
|
||||
* implementation. This is used to verify repository configuration.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
default boolean hasCustomMethod() {
|
||||
return getQueryMethods().stream().anyMatch(this::isCustomMethod);
|
||||
}
|
||||
|
||||
default boolean hasQueryMethods() {
|
||||
return getQueryMethods().iterator().hasNext();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user