Move runtime hints into another package

Original Pull Request: #2624
This commit is contained in:
Christoph Strobl
2022-06-10 09:29:38 +02:00
parent 51cda9993f
commit 4e23153816
29 changed files with 659 additions and 618 deletions

View File

@@ -1,152 +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;
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 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 {
/**
* 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());
}
/**
* 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;
}
/**
* 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;
}
/**
* 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() {
final Lazy<Iterable<? extends Class<?>>> lazyProvider = Lazy.of(dataProvider);
@Override
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;
}
}

View File

@@ -28,6 +28,7 @@ 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.data.util.TypeScanner;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -118,7 +119,7 @@ public interface AotContext {
*/
@NonNull
default TypeScanner getTypeScanner() {
return new TypeScanner(getClassLoader());
return TypeScanner.typeScanner(getClassLoader());
}
/**
@@ -129,14 +130,12 @@ public interface AotContext {
* 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);
return getTypeScanner().scanPackages(packageNames).forTypesAnnotatedWith(identifyingAnnotations).collectAsSet();
}
/**

View File

@@ -1,105 +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.Arrays;
import java.util.Properties;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.Advised;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.DecoratingProxy;
import org.springframework.core.io.InputStreamSource;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.repository.core.support.RepositoryFragmentsFactoryBean;
import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.lang.Nullable;
/**
* @author Christoph Strobl
* @since 3.0
*/
public class DataRuntimeHints implements RuntimeHintsRegistrar {
/*
NativeHint(trigger = MappingContext.class,
types = {
@TypeHint(types = {
RepositoryFactoryBeanSupport.class,
RepositoryFragmentsFactoryBean.class,
RepositoryFragment.class,
TransactionalRepositoryFactoryBeanSupport.class,
QueryByExampleExecutor.class,
MappingContext.class,
RepositoryMetadata.class,
RepositoryMetadata.class,
}),
@TypeHint(types = {ReadingConverter.class, WritingConverter.class}),
@TypeHint(types = {Properties.class, BeanFactory.class, InputStreamSource[].class}),
@TypeHint(types = Throwable.class, access = { TypeAccess.DECLARED_CONSTRUCTORS, TypeAccess.DECLARED_FIELDS}),
@TypeHint(typeNames = {
"org.springframework.data.projection.SpelEvaluatingMethodInterceptor$TargetWrapper",
}, access = { TypeAccess.DECLARED_CONSTRUCTORS, TypeAccess.DECLARED_METHODS, TypeAccess.PUBLIC_METHODS })
},
jdkProxies = @JdkProxyHint(typeNames = {
"org.springframework.data.annotation.QueryAnnotation",
"org.springframework.core.annotation.SynthesizedAnnotation" }
),
initialization = @InitializationHint(types = AbstractMappingContext.class, initTime = InitializationTime.BUILD)
)
*/
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
System.out.println("Spring data Runtime Hints");
hints.reflection()
.registerTypes(Arrays.asList(TypeReference.of(RepositoryFactoryBeanSupport.class),
TypeReference.of(RepositoryFragmentsFactoryBean.class), TypeReference.of(RepositoryFragment.class),
TypeReference.of(TransactionalRepositoryFactoryBeanSupport.class),
TypeReference.of(QueryByExampleExecutor.class), TypeReference.of(MappingContext.class),
TypeReference.of(RepositoryMetadata.class)), builder -> {
builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
});
hints.reflection().registerTypes(
Arrays.asList(TypeReference.of(Properties.class), TypeReference.of(BeanFactory.class),
TypeReference.of(InputStreamSource[].class)),
builder -> builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
hints.reflection().registerType(Throwable.class,
builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS));
hints.reflection().registerType(
TypeReference.of("org.springframework.data.projection.SpelEvaluatingMethodInterceptor$TargetWrapper"),
builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS));
hints.proxies().registerJdkProxy(TypeReference.of("org.springframework.data.annotation.QueryAnnotation"),
TypeReference.of("org.springframework.core.annotation.SynthesizedAnnotation"));
hints.proxies().registerJdkProxy(TypeReference.of(AuditorAware.class), TypeReference.of(SpringProxy.class), TypeReference.of(Advised.class), TypeReference.of(DecoratingProxy.class));
}
}

View File

@@ -22,7 +22,6 @@ import java.util.stream.Collectors;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.data.aot.TypeScanner.Scanner;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.util.Lazy;
@@ -128,12 +127,10 @@ class DefaultAotRepositoryContext implements AotRepositoryContext {
if (!getIdentifyingAnnotations().isEmpty()) {
Scanner typeScanner = aotContext.getTypeScanner().scanForTypesAnnotatedWith(getIdentifyingAnnotations());
Set<Class<?>> classes = typeScanner.inPackages(getBasePackages());
Set<Class<?>> classes = aotContext.getTypeScanner().scanPackages(getBasePackages()).forTypesAnnotatedWith(getIdentifyingAnnotations()).collectAsSet();
types.addAll(TypeCollector.inspect(classes).list());
}
// context.get
return types;
}
}

View File

@@ -20,25 +20,21 @@ 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.data.domain.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.
* {@link BeanRegistrationAotProcessor} handling {@link #getModuleIdentifier() module} {@link ManagedTypes} instances.
* This AOT processor allows store specific handling of discovered types to be registered.
*
* @author Christoph Strobl
* @author John Blum
@@ -46,28 +42,21 @@ import org.springframework.util.StringUtils;
* @see org.springframework.beans.factory.aot.BeanRegistrationAotProcessor
* @since 3.0
*/
public class ManagedTypesRegistrationAotProcessor implements BeanRegistrationAotProcessor, BeanFactoryAware {
public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
private final Log logger = LogFactory.getLog(getClass());
private BeanFactory beanFactory;
@Nullable
private String modulePrefix;
@Nullable private String moduleIdentifier;
@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) {
if (!isMatch(registeredBean.getBeanClass(), registeredBean.getBeanName())) {
return null;
}
return isMatch(beanType, beanName)
? contribute(AotContext.from(beanFactory), beanFactory.getBean(beanName, ManagedTypes.class))
: null;
BeanFactory beanFactory = registeredBean.getBeanFactory();
return contribute(AotContext.from(registeredBean.getBeanFactory()),
beanFactory.getBean(registeredBean.getBeanName(), ManagedTypes.class));
}
protected boolean isMatch(@Nullable Class<?> beanType, @Nullable String beanName) {
@@ -79,16 +68,16 @@ public class ManagedTypesRegistrationAotProcessor implements BeanRegistrationAot
}
protected boolean matchesPrefix(@Nullable String beanName) {
return StringUtils.startsWithIgnoreCase(beanName, getModulePrefix());
return StringUtils.startsWithIgnoreCase(beanName, getModuleIdentifier());
}
/**
* Hook to provide a customized flavor of {@link BeanRegistrationAotContribution}. By overriding this method
* calls to {@link #contributeType(ResolvableType, GenerationContext)} might no longer be issued.
* 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.
* @return new instance of {@link ManagedTypesBeanRegistrationAotProcessor} or {@literal null} if nothing to do.
*/
@Nullable
protected BeanRegistrationAotContribution contribute(AotContext aotContext, ManagedTypes managedTypes) {
@@ -111,21 +100,16 @@ public class ManagedTypesRegistrationAotProcessor implements BeanRegistrationAot
TypeContributor.contribute(type.toClass(), annotationNamespaces, generationContext);
TypeUtils.resolveUsedAnnotations(type.toClass()).forEach(annotation ->
TypeContributor.contribute(annotation.getType(), 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;
public void setModuleIdentifier(@Nullable String moduleIdentifier) {
this.moduleIdentifier = moduleIdentifier;
}
@Nullable
public String getModulePrefix() {
return this.modulePrefix;
public String getModuleIdentifier() {
return this.moduleIdentifier;
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2012-2018 the original author or authors.
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -22,7 +22,7 @@ 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.data.domain.ManagedTypes;
import org.springframework.lang.NonNull;
/**
@@ -52,7 +52,7 @@ public class ManagedTypesRegistrationAotContribution implements BeanRegistration
@NonNull
protected ManagedTypes getManagedTypes() {
return ManagedTypes.nullSafeManagedTypes(this.managedTypes);
return managedTypes == null ? ManagedTypes.empty() : managedTypes;
}
@Override

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2012-2018 the original author or authors.
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,

View File

@@ -26,8 +26,6 @@ public class PublicMethodReflectiveProcessor extends SimpleReflectiveProcessor {
@Override
protected void registerTypeHint(ReflectionHints hints, Class<?> type) {
System.out.println("invoking reflective hint annotation for : " + type);
hints.registerType(type, builder -> builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2012-2018 the original author or authors.
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -197,10 +197,10 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
Map<String, RepositoryConfigurationExtensionSupport> repositoryConfigurationExtensionBeans =
getBeanFactory().getBeansOfType(RepositoryConfigurationExtensionSupport.class);
repositoryConfigurationExtensionBeans.values().stream()
.map(RepositoryConfigurationExtensionSupport::getIdentifyingAnnotations)
.flatMap(Collection::stream)
.collect(Collectors.toCollection(() -> identifyingAnnotations));
// 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,

View File

@@ -15,11 +15,12 @@
*/
package org.springframework.data.aot;
import java.util.Arrays;
import java.util.Collections;
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;
@@ -27,14 +28,16 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.data.ManagedTypes;
import org.springframework.data.domain.ManagedTypes;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
/**
* {@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.
* 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
@@ -63,8 +66,11 @@ public class SpringDataBeanFactoryInitializationAotProcessor implements BeanFact
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
ValueHolder argumentValue = beanDefinition.getConstructorArgumentValues()
.getArgumentValue(0, null, null, null);
if(beanDefinition.getConstructorArgumentValues().isEmpty()) {
return;
}
ValueHolder argumentValue = beanDefinition.getConstructorArgumentValues().getArgumentValue(0, null, null, null);
if (argumentValue.getValue() instanceof Supplier supplier) {
@@ -72,11 +78,16 @@ public class SpringDataBeanFactoryInitializationAotProcessor implements BeanFact
logger.info(String.format("Replacing ManagedType bean definition %s.", beanName));
}
BeanDefinition beanDefinitionReplacement = BeanDefinitionBuilder
.rootBeanDefinition(ManagedTypes.class)
.setFactoryMethod("of")
.addConstructorArgValue(supplier.get())
.getBeanDefinition();
Object value = supplier.get();
if(ObjectUtils.isArray(value)) {
value = CollectionUtils.arrayToList(value);
}
if(!(value instanceof Iterable<?>)) {
value = Collections.singleton(value);
}
BeanDefinition beanDefinitionReplacement = BeanDefinitionBuilder.rootBeanDefinition(ManagedTypes.class)
.setFactoryMethod("fromIterable").addConstructorArgValue(value).getBeanDefinition();
registry.removeBeanDefinition(beanName);
registry.registerBeanDefinition(beanName, beanDefinitionReplacement);

View File

@@ -1,166 +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.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
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
*/
// TODO: Replace this with AnnotatedTypeScanner, maybe?
public class TypeScanner {
/**
* 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);
}
class ScannerImpl implements Scanner {
ClassPathScanningCandidateComponentProvider componentProvider;
ScannerImpl() {
componentProvider = new ClassPathScanningCandidateComponentProvider(false);
componentProvider.setEnvironment(new StandardEnvironment());
componentProvider.setResourceLoader(new DefaultResourceLoader(classLoader));
}
ScannerImpl includeTypesAnnotatedWith(Collection<Class<? extends Annotation>> annotations) {
nullSafeCollection(annotations).stream()
.map(AnnotationTypeFilter::new)
.forEach(componentProvider::addIncludeFilter);
return this;
}
@Override
public Set<Class<?>> inPackages(Collection<String> packageNames) {
Set<Class<?>> types = new LinkedHashSet<>();
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)) {
try {
return Optional.of(ClassUtils.forName(typeName, classLoader));
} catch (ClassNotFoundException ignore) {
// just do nothing
}
}
return Optional.empty();
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.hint;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.Advised;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.core.DecoratingProxy;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.domain.ReactiveAuditorAware;
import org.springframework.lang.Nullable;
/**
* @author Christoph Strobl
* @since 3.0
*/
public class AuditingHints {
/**
* {@link RuntimeHintsRegistrar} to be applied when auditing is enabled. Can be used along with
* {@link org.springframework.context.annotation.ImportRuntimeHints @ImportRuntimeHints} for conditional registration.
*
* @author Christoph Strobl
* @since 3.0
*/
public static class AuditingRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
registerSpringProxy(AuditorAware.class, hints);
}
}
/**
* {@link RuntimeHintsRegistrar} to be applied when reactive auditing is enabled. Can be used along with
* {@link org.springframework.context.annotation.ImportRuntimeHints @ImportRuntimeHints} for conditional registration.
*
* @author Christoph Strobl
* @since 3.0
*/
public static class ReactiveAuditingRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
registerSpringProxy(ReactiveAuditorAware.class, hints);
}
}
private static void registerSpringProxy(Class<?> type, RuntimeHints runtimeHints) {
runtimeHints.proxies().registerJdkProxy(TypeReference.of(type), TypeReference.of(SpringProxy.class),
TypeReference.of(Advised.class), TypeReference.of(DecoratingProxy.class));
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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.hint;
import java.util.Arrays;
import java.util.Properties;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.io.InputStreamSource;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.repository.core.support.RepositoryFragmentsFactoryBean;
import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.lang.Nullable;
/**
* {@link RuntimeHintsRegistrar} holding required hints to bootstrap data repositories. <br />
* Already registered via {@literal aot.factories}.
*
* @author Christoph Strobl
* @since 3.0
*/
public class RepositoryRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
// repository infrastructure
hints.reflection().registerTypes(Arrays.asList( //
TypeReference.of(RepositoryFactoryBeanSupport.class), //
TypeReference.of(RepositoryFragmentsFactoryBean.class), //
TypeReference.of(RepositoryFragment.class), //
TypeReference.of(TransactionalRepositoryFactoryBeanSupport.class), //
TypeReference.of(QueryByExampleExecutor.class), //
TypeReference.of(MappingContext.class), //
TypeReference.of(RepositoryMetadata.class) //
), builder -> {
builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
});
// named queries
hints.reflection().registerTypes(Arrays.asList( //
TypeReference.of(Properties.class), //
TypeReference.of(BeanFactory.class), //
TypeReference.of(InputStreamSource[].class) //
), builder -> builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
//
hints.reflection().registerType(Throwable.class,
builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS));
// SpEL support
hints.reflection().registerType(
TypeReference.of("org.springframework.data.projection.SpelEvaluatingMethodInterceptor$TargetWrapper"),
builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS));
// annotated queries
hints.proxies().registerJdkProxy( //
TypeReference.of("org.springframework.data.annotation.QueryAnnotation"), //
TypeReference.of("org.springframework.core.annotation.SynthesizedAnnotation"));
}
}

View File

@@ -42,7 +42,7 @@ public class NamedQueriesBeanDefinitionBuilder {
@SuppressWarnings("null")
public NamedQueriesBeanDefinitionBuilder(String defaultLocation) {
Assert.hasText(defaultLocation, "DefaultLocation must not be null nor empty!");
Assert.hasText(defaultLocation, "DefaultLocation must not be null nor empty");
this.defaultLocation = defaultLocation;
}
@@ -53,7 +53,7 @@ public class NamedQueriesBeanDefinitionBuilder {
*/
public void setLocations(String locations) {
Assert.hasText(locations, "Locations must not be null nor empty!");
Assert.hasText(locations, "Locations must not be null nor empty");
this.locations = locations;
}

View File

@@ -120,7 +120,7 @@ class RepositoryBeanDefinitionBuilder {
extension.getDefaultNamedQueryLocation());
configuration.getNamedQueriesLocation().ifPresent(definitionBuilder::setLocations);
String namedQueriesBeanName = BeanDefinitionReaderUtils.uniqueBeanName(extension.getModulePrefix() + ".named-queries", registry);
String namedQueriesBeanName = BeanDefinitionReaderUtils.uniqueBeanName(extension.getModuleIdentifier() + ".named-queries", registry);
BeanDefinition namedQueries = definitionBuilder.build(configuration.getSource());
registry.registerBeanDefinition(namedQueriesBeanName, namedQueries);

View File

@@ -22,12 +22,12 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aot.AotDetector;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
@@ -48,11 +48,8 @@ import org.springframework.core.io.support.SpringFactoriesLoader;
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.TypeScanner;
import org.springframework.data.util.TypeScanner;
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;
@@ -228,31 +225,9 @@ public class RepositoryConfigurationDelegate {
private void registerAotComponents(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension,
Map<String, RepositoryMetadata<?>> metadataByRepositoryBeanName) {
// 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());
}
}
// module-specific repository aot processor
String repositoryAotProcessorBeanName = String.format("data-%s.repository-aot-processor" /* might be duplicate */,
extension.getModulePrefix());
extension.getModuleIdentifier());
if (!registry.isBeanNameInUse(repositoryAotProcessorBeanName)) {
@@ -395,18 +370,4 @@ public class RepositoryConfigurationDelegate {
}
}
public static class ManagedTypesBean implements ManagedTypes {
private final Lazy<Set<Class<?>>> types;
public ManagedTypesBean(Supplier<Set<Class<?>>> types) {
this.types = Lazy.of(types);
}
@Override
public void forEach(@NonNull Consumer<Class<?>> action) {
types.get().forEach(action);
}
}
}

View File

@@ -56,17 +56,7 @@ public interface RepositoryConfigurationExtension {
*
* @return will never be {@literal null}.
*/
default String getModuleName() {
return StringUtils.capitalize(getModulePrefix());
}
/**
* Returns a {@link String prefix} identifying the module.
*
* @return a {@link String prefix} identifying the module.
* @see #getModuleName()
*/
String getModulePrefix();
String getModuleName();
/**
* Returns the {@link BeanRegistrationAotProcessor} type responsible for contributing AOT/native configuration
@@ -111,13 +101,6 @@ public interface RepositoryConfigurationExtension {
*/
String getRepositoryFactoryBeanClassName();
/**
* Returns the default location of the Spring Data named queries.
*
* @return must not be {@literal null} or empty.
*/
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
@@ -153,5 +136,4 @@ public interface RepositoryConfigurationExtension {
* @param config will never be {@literal null}.
*/
void postProcess(BeanDefinitionBuilder builder, XmlRepositoryConfigurationSource config);
}

View File

@@ -59,6 +59,11 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
private boolean noMultiStoreSupport = false;
@Override
public String getModuleName() {
return StringUtils.capitalize(getModulePrefix());
}
public <T extends RepositoryConfigurationSource> Collection<RepositoryConfiguration<T>> getRepositoryConfigurations(
T configSource, ResourceLoader loader) {
return getRepositoryConfigurations(configSource, loader, false);
@@ -127,7 +132,7 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
* @return
* @since 1.9
*/
public Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
protected Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
return Collections.emptySet();
}

View File

@@ -17,8 +17,11 @@ package org.springframework.data.util;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -28,6 +31,7 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
@@ -46,6 +50,10 @@ public class AnnotatedTypeScanner implements ResourceLoaderAware, EnvironmentAwa
private @Nullable ResourceLoader resourceLoader;
private @Nullable Environment environment;
private Consumer<ClassNotFoundException> classNotFoundAction = ex -> {
throw new IllegalStateException(ex);
};
/**
* Creates a new {@link AnnotatedTypeScanner} for the given annotation types.
*
@@ -64,9 +72,18 @@ public class AnnotatedTypeScanner implements ResourceLoaderAware, EnvironmentAwa
*/
@SafeVarargs
public AnnotatedTypeScanner(boolean considerInterfaces, Class<? extends Annotation>... annotationTypes) {
this(considerInterfaces, Arrays.asList(annotationTypes));
}
/**
* @param considerInterfaces
* @param annotationTypes
* @since 3.0
*/
public AnnotatedTypeScanner(boolean considerInterfaces, Collection<Class<? extends Annotation>> annotationTypes) {
this.annotationTypess = Arrays.asList(annotationTypes);
this.considerInterfaces = considerInterfaces;
this.annotationTypess = annotationTypes;
}
@Override
@@ -79,11 +96,15 @@ public class AnnotatedTypeScanner implements ResourceLoaderAware, EnvironmentAwa
this.environment = environment;
}
void setClassNotFoundAction(Consumer<ClassNotFoundException> classNotFoundAction) {
this.classNotFoundAction = classNotFoundAction;
}
public Set<Class<?>> findTypes(String... basePackages) {
return findTypes(Arrays.asList(basePackages));
}
public Set<Class<?>> findTypes(Iterable<String> basePackages) {
Set<Class<?>> findTypes(Iterable<String> basePackages, Collection<TypeFilter> filters) {
ClassPathScanningCandidateComponentProvider provider = new InterfaceAwareScanner(considerInterfaces);
@@ -95,9 +116,7 @@ public class AnnotatedTypeScanner implements ResourceLoaderAware, EnvironmentAwa
provider.setEnvironment(environment);
}
for (Class<? extends Annotation> annotationType : annotationTypess) {
provider.addIncludeFilter(new AnnotationTypeFilter(annotationType, true, considerInterfaces));
}
filters.forEach(provider::addIncludeFilter);
Set<Class<?>> types = new HashSet<>();
@@ -112,13 +131,13 @@ public class AnnotatedTypeScanner implements ResourceLoaderAware, EnvironmentAwa
if (beanClassName == null) {
throw new IllegalStateException(
String.format("Unable to obtain bean class name from bean definition %s", definition));
String.format("Unable to obtain bean class name from bean definition %s!", definition));
}
try {
types.add(ClassUtils.forName(beanClassName, classLoader));
} catch (ClassNotFoundException o_O) {
throw new IllegalStateException(o_O);
classNotFoundAction.accept(o_O);
}
}
}
@@ -126,6 +145,10 @@ public class AnnotatedTypeScanner implements ResourceLoaderAware, EnvironmentAwa
return types;
}
public Set<Class<?>> findTypes(Iterable<String> basePackages) {
return findTypes(basePackages, Streamable.of(annotationTypess).stream().map(annotation -> new AnnotationTypeFilter(annotation, true, considerInterfaces)).collect(Collectors.toSet()));
}
/**
* Custom extension of {@link ClassPathScanningCandidateComponentProvider} to make sure interfaces to not get dropped
* from scanning results.

View File

@@ -0,0 +1,112 @@
/*
* 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.util;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
/**
* A caching {@link TypeScanner} implementation delegating to {@link AnnotatedTypeScanner}.
*
* @author Christoph Strobl
* @since 3.0
*/
class DelegatingTypeScanner implements TypeScanner {
private final ResourceLoader resourceLoader;
private final Environment environment;
private Collection<String> packageNames;
private Collection<TypeFilter> includeFilters;
private Consumer<ClassNotFoundException> classNotFoundAction;
private Lazy<Set<Class<?>>> scanResult = Lazy.of(this::collect);
DelegatingTypeScanner(ResourceLoader resourceLoader) {
this(new StandardEnvironment(), resourceLoader);
}
DelegatingTypeScanner(Environment environment, ResourceLoader resourceLoader) {
this(environment, resourceLoader, Collections.emptyList(),
Collections.singleton((metadataReader, readerFactory) -> true), error -> {});
}
DelegatingTypeScanner(Environment environment, ResourceLoader resourceLoader, Collection<String> packageNames,
Collection<TypeFilter> includeFilters, Consumer<ClassNotFoundException> classNotFoundAction) {
this.environment = environment;
this.resourceLoader = resourceLoader;
this.packageNames = new ArrayList<>(packageNames);
this.includeFilters = new ArrayList<>(includeFilters);
this.classNotFoundAction = classNotFoundAction;
}
@Override
public TypeScanner scanPackages(Collection<String> packageNames) {
return new DelegatingTypeScanner(environment, resourceLoader, packageNames, includeFilters, classNotFoundAction);
}
@Override
public TypeScanner forTypesAnnotatedWith(Collection<Class<? extends Annotation>> annotations) {
return new DelegatingTypeScanner(environment, resourceLoader, packageNames,
annotations.stream().map(DelegatingTypeScanner::annotationFilter).collect(Collectors.toSet()),
classNotFoundAction);
}
@Override
public TypeScanner onClassNotFound(Consumer<ClassNotFoundException> action) {
return new DelegatingTypeScanner(environment, resourceLoader, packageNames, includeFilters, action);
}
@Override
public Set<Class<?>> collectAsSet() {
return scanResult.get();
}
@Override
public void forEach(Consumer<Class<?>> action) {
collectAsSet().forEach(action);
}
private Set<Class<?>> collect() {
AnnotatedTypeScanner annotatedTypeScanner = new AnnotatedTypeScanner(false, Collections.emptyList());
annotatedTypeScanner.setResourceLoader(resourceLoader);
annotatedTypeScanner.setEnvironment(environment);
if (classNotFoundAction != null) {
annotatedTypeScanner.setClassNotFoundAction(classNotFoundAction);
}
return annotatedTypeScanner.findTypes(packageNames, includeFilters);
}
private static AnnotationTypeFilter annotationFilter(Class<? extends Annotation> annotation) {
return new AnnotationTypeFilter(annotation, true, false);
}
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.util;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Consumer;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
/**
* A scanner that searches the classpath for matching types within given target packages.
*
* @author Christoph Strobl
* @since 3.0
*/
public interface TypeScanner {
/**
* Create a new {@link TypeScanner} using the given {@link ClassLoader}.
*
* @param classLoader must not be {@literal null}.
* @return new instance of {@link TypeScanner}.
*/
static TypeScanner typeScanner(ClassLoader classLoader) {
Assert.notNull(classLoader, "ClassLoader must not be null!");
return typeScanner(new DefaultResourceLoader(classLoader));
}
/**
* Create a new {@link TypeScanner} using the given {@link ResourceLoader}.
*
* @param resourceLoader must not be {@literal null}.
* @return new instance of {@link TypeScanner}.
*/
static TypeScanner typeScanner(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null!");
return new DelegatingTypeScanner(resourceLoader);
}
/**
* Create a new {@link TypeScanner} using the given {@link ApplicationContext}.
*
* @param context must not be {@literal null}.
* @return new instance of {@link TypeScanner}.
*/
static TypeScanner typeScanner(ApplicationContext context) {
Assert.notNull(context, "Context must not be null!");
return new DelegatingTypeScanner(context.getEnvironment(), context);
}
/**
* Collects the {@link String names} of packages to scan.
*
* @param packageNames array of {@link String package names} to scan; Must not be {@literal null}.
* @return new instance of {@link TypeScanner}.
* @see #scanPackages(Collection)
*/
default TypeScanner scanPackages(String... packageNames) {
return scanPackages(Arrays.asList(packageNames));
}
/**
* Collects the {@link String names} of packages to scan.
*
* @param packageNames {@link Collection} of {@link String package names} to scan.
* @return new instance of {@link TypeScanner}.
* @see #scanPackages(String...)
*/
TypeScanner scanPackages(Collection<String> packageNames);
/**
* Define annotations identifying types to include in the scan result.
*
* @param annotations must not be {@literal null}.
* @return new instance of {@link TypeScanner}.
* @see #forTypesAnnotatedWith(Collection)
*/
default TypeScanner forTypesAnnotatedWith(Class<? extends Annotation>... annotations) {
return forTypesAnnotatedWith(Arrays.asList(annotations));
}
/**
* Define annotations identifying types to include in the scan result.
*
* @param annotations must not be {@literal null}.
* @return new instance of {@link TypeScanner}.
*/
TypeScanner forTypesAnnotatedWith(Collection<Class<? extends Annotation>> annotations);
/**
* Define what happens in the case of a {@link ClassNotFoundException}.
*
* @param action must not be {@literal null}.
* @return
*/
TypeScanner onClassNotFound(Consumer<ClassNotFoundException> action);
/**
* Obtain the scan result.
*
* @return never {@literal null}.
*/
default Set<Class<?>> collectAsSet() {
LinkedHashSet<Class<?>> result = new LinkedHashSet<>();
forEach(result::add);
return result;
}
/**
* Performs the given action for each element found while scanning.
*
* @param action must not be {@literal null}.
*/
void forEach(Consumer<Class<?>> action);
}