diff --git a/src/main/java/org/springframework/data/ManagedTypes.java b/src/main/java/org/springframework/data/ManagedTypes.java deleted file mode 100644 index 447d2d458..000000000 --- a/src/main/java/org/springframework/data/ManagedTypes.java +++ /dev/null @@ -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> 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> 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>> dataProvider) { - - return new ManagedTypes() { - - final Lazy>> lazyProvider = Lazy.of(dataProvider); - - @Override - public void forEach(@NonNull Consumer> 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> 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> toList() { - - List> list = new ArrayList<>(100); - forEach(list::add); - return list; - } -} diff --git a/src/main/java/org/springframework/data/aot/AotContext.java b/src/main/java/org/springframework/data/aot/AotContext.java index 55fafea07..48edc6fdf 100644 --- a/src/main/java/org/springframework/data/aot/AotContext.java +++ b/src/main/java/org/springframework/data/aot/AotContext.java @@ -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> scanPackageForTypes(@NonNull Collection> identifyingAnnotations, Collection packageNames) { - return getTypeScanner().scanForTypesAnnotatedWith(identifyingAnnotations).inPackages(packageNames); + return getTypeScanner().scanPackages(packageNames).forTypesAnnotatedWith(identifyingAnnotations).collectAsSet(); } /** diff --git a/src/main/java/org/springframework/data/aot/DataRuntimeHints.java b/src/main/java/org/springframework/data/aot/DataRuntimeHints.java deleted file mode 100644 index 9b784f19c..000000000 --- a/src/main/java/org/springframework/data/aot/DataRuntimeHints.java +++ /dev/null @@ -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)); - } -} diff --git a/src/main/java/org/springframework/data/aot/DefaultAotRepositoryContext.java b/src/main/java/org/springframework/data/aot/DefaultAotRepositoryContext.java index 42ffe8073..13bd945eb 100644 --- a/src/main/java/org/springframework/data/aot/DefaultAotRepositoryContext.java +++ b/src/main/java/org/springframework/data/aot/DefaultAotRepositoryContext.java @@ -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> classes = typeScanner.inPackages(getBasePackages()); + Set> classes = aotContext.getTypeScanner().scanPackages(getBasePackages()).forTypesAnnotatedWith(getIdentifyingAnnotations()).collectAsSet(); types.addAll(TypeCollector.inspect(classes).list()); } - // context.get return types; } } diff --git a/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotProcessor.java b/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java similarity index 63% rename from src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotProcessor.java rename to src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java index 86aa8f9b2..018fc6117 100644 --- a/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotProcessor.java +++ b/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java @@ -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; } } diff --git a/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java b/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java index 06d9cada2..c8785f9a4 100644 --- a/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java +++ b/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java @@ -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 diff --git a/src/main/java/org/springframework/data/aot/Predicates.java b/src/main/java/org/springframework/data/aot/Predicates.java index b3e737c4b..868afb625 100644 --- a/src/main/java/org/springframework/data/aot/Predicates.java +++ b/src/main/java/org/springframework/data/aot/Predicates.java @@ -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, diff --git a/src/main/java/org/springframework/data/aot/PublicMethodReflectiveProcessor.java b/src/main/java/org/springframework/data/aot/PublicMethodReflectiveProcessor.java index 0388610a5..a66a30df7 100644 --- a/src/main/java/org/springframework/data/aot/PublicMethodReflectiveProcessor.java +++ b/src/main/java/org/springframework/data/aot/PublicMethodReflectiveProcessor.java @@ -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)); - } } diff --git a/src/main/java/org/springframework/data/aot/RepositoryRegistrationAotContribution.java b/src/main/java/org/springframework/data/aot/RepositoryRegistrationAotContribution.java index 61ab47041..6d6d7691d 100644 --- a/src/main/java/org/springframework/data/aot/RepositoryRegistrationAotContribution.java +++ b/src/main/java/org/springframework/data/aot/RepositoryRegistrationAotContribution.java @@ -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 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, diff --git a/src/main/java/org/springframework/data/aot/SpringDataBeanFactoryInitializationAotProcessor.java b/src/main/java/org/springframework/data/aot/SpringDataBeanFactoryInitializationAotProcessor.java index 151db5484..9e5cba659 100644 --- a/src/main/java/org/springframework/data/aot/SpringDataBeanFactoryInitializationAotProcessor.java +++ b/src/main/java/org/springframework/data/aot/SpringDataBeanFactoryInitializationAotProcessor.java @@ -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); diff --git a/src/main/java/org/springframework/data/aot/TypeScanner.java b/src/main/java/org/springframework/data/aot/TypeScanner.java deleted file mode 100644 index 158d136ff..000000000 --- a/src/main/java/org/springframework/data/aot/TypeScanner.java +++ /dev/null @@ -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... 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> 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> 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> inPackages(Collection packageNames); - } - - class ScannerImpl implements Scanner { - - ClassPathScanningCandidateComponentProvider componentProvider; - - ScannerImpl() { - - componentProvider = new ClassPathScanningCandidateComponentProvider(false); - componentProvider.setEnvironment(new StandardEnvironment()); - componentProvider.setResourceLoader(new DefaultResourceLoader(classLoader)); - } - - ScannerImpl includeTypesAnnotatedWith(Collection> annotations) { - - nullSafeCollection(annotations).stream() - .map(AnnotationTypeFilter::new) - .forEach(componentProvider::addIncludeFilter); - - return this; - } - - @Override - public Set> inPackages(Collection packageNames) { - - Set> types = new LinkedHashSet<>(); - - nullSafeCollection(packageNames).forEach(pkg -> - componentProvider.findCandidateComponents(pkg).forEach(it -> - resolveType(it.getBeanClassName()).ifPresent(types::add))); - - return types; - } - - @NonNull - private Collection nullSafeCollection(@Nullable Collection collection) { - return collection != null ? collection : Collections.emptySet(); - } - } - - private Optional> 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(); - } -} diff --git a/src/main/java/org/springframework/data/aot/hint/AuditingHints.java b/src/main/java/org/springframework/data/aot/hint/AuditingHints.java new file mode 100644 index 000000000..36852ce4b --- /dev/null +++ b/src/main/java/org/springframework/data/aot/hint/AuditingHints.java @@ -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)); + } +} diff --git a/src/main/java/org/springframework/data/aot/hint/RepositoryRuntimeHints.java b/src/main/java/org/springframework/data/aot/hint/RepositoryRuntimeHints.java new file mode 100644 index 000000000..02372cbfc --- /dev/null +++ b/src/main/java/org/springframework/data/aot/hint/RepositoryRuntimeHints.java @@ -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.
+ * 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")); + } +} diff --git a/src/main/java/org/springframework/data/repository/config/NamedQueriesBeanDefinitionBuilder.java b/src/main/java/org/springframework/data/repository/config/NamedQueriesBeanDefinitionBuilder.java index f80311f3a..975ff1d98 100644 --- a/src/main/java/org/springframework/data/repository/config/NamedQueriesBeanDefinitionBuilder.java +++ b/src/main/java/org/springframework/data/repository/config/NamedQueriesBeanDefinitionBuilder.java @@ -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; } diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java index 3d1908dbb..bc1606eaa 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java @@ -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); diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java index 6b9a7a2cb..8fe79d2f2 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java @@ -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> 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>> args = () -> { - - Set 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>> types; - - public ManagedTypesBean(Supplier>> types) { - this.types = Lazy.of(types); - } - - @Override - public void forEach(@NonNull Consumer> action) { - types.get().forEach(action); - } - } } diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java index 37942bde2..8eb17010a 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java @@ -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); - } diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java index 7d8169c1d..adad5c6a6 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java @@ -59,6 +59,11 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit private boolean noMultiStoreSupport = false; + @Override + public String getModuleName() { + return StringUtils.capitalize(getModulePrefix()); + } + public Collection> 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> getIdentifyingAnnotations() { + protected Collection> getIdentifyingAnnotations() { return Collections.emptySet(); } diff --git a/src/main/java/org/springframework/data/util/AnnotatedTypeScanner.java b/src/main/java/org/springframework/data/util/AnnotatedTypeScanner.java index 5f8b95531..6afe40829 100644 --- a/src/main/java/org/springframework/data/util/AnnotatedTypeScanner.java +++ b/src/main/java/org/springframework/data/util/AnnotatedTypeScanner.java @@ -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 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... annotationTypes) { + this(considerInterfaces, Arrays.asList(annotationTypes)); + } + + /** + * @param considerInterfaces + * @param annotationTypes + * @since 3.0 + */ + public AnnotatedTypeScanner(boolean considerInterfaces, Collection> 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 classNotFoundAction) { + this.classNotFoundAction = classNotFoundAction; + } + public Set> findTypes(String... basePackages) { return findTypes(Arrays.asList(basePackages)); } - public Set> findTypes(Iterable basePackages) { + Set> findTypes(Iterable basePackages, Collection filters) { ClassPathScanningCandidateComponentProvider provider = new InterfaceAwareScanner(considerInterfaces); @@ -95,9 +116,7 @@ public class AnnotatedTypeScanner implements ResourceLoaderAware, EnvironmentAwa provider.setEnvironment(environment); } - for (Class annotationType : annotationTypess) { - provider.addIncludeFilter(new AnnotationTypeFilter(annotationType, true, considerInterfaces)); - } + filters.forEach(provider::addIncludeFilter); Set> 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> findTypes(Iterable 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. diff --git a/src/main/java/org/springframework/data/util/DelegatingTypeScanner.java b/src/main/java/org/springframework/data/util/DelegatingTypeScanner.java new file mode 100644 index 000000000..6860a4b3b --- /dev/null +++ b/src/main/java/org/springframework/data/util/DelegatingTypeScanner.java @@ -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 packageNames; + private Collection includeFilters; + + private Consumer classNotFoundAction; + + private Lazy>> 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 packageNames, + Collection includeFilters, Consumer 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 packageNames) { + return new DelegatingTypeScanner(environment, resourceLoader, packageNames, includeFilters, classNotFoundAction); + } + + @Override + public TypeScanner forTypesAnnotatedWith(Collection> annotations) { + + return new DelegatingTypeScanner(environment, resourceLoader, packageNames, + annotations.stream().map(DelegatingTypeScanner::annotationFilter).collect(Collectors.toSet()), + classNotFoundAction); + } + + @Override + public TypeScanner onClassNotFound(Consumer action) { + return new DelegatingTypeScanner(environment, resourceLoader, packageNames, includeFilters, action); + } + + @Override + public Set> collectAsSet() { + return scanResult.get(); + } + + @Override + public void forEach(Consumer> action) { + collectAsSet().forEach(action); + } + + private Set> 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 annotation) { + return new AnnotationTypeFilter(annotation, true, false); + } +} diff --git a/src/main/java/org/springframework/data/util/TypeScanner.java b/src/main/java/org/springframework/data/util/TypeScanner.java new file mode 100644 index 000000000..5a005e13c --- /dev/null +++ b/src/main/java/org/springframework/data/util/TypeScanner.java @@ -0,0 +1,139 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.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 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... 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> annotations); + + /** + * Define what happens in the case of a {@link ClassNotFoundException}. + * + * @param action must not be {@literal null}. + * @return + */ + TypeScanner onClassNotFound(Consumer action); + + /** + * Obtain the scan result. + * + * @return never {@literal null}. + */ + default Set> collectAsSet() { + + LinkedHashSet> 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> action); +} diff --git a/src/main/resources/META-INF/spring/aot.factories b/src/main/resources/META-INF/spring/aot.factories index 1d7fea66b..208d901e6 100644 --- a/src/main/resources/META-INF/spring/aot.factories +++ b/src/main/resources/META-INF/spring/aot.factories @@ -1,4 +1,4 @@ org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\ org.springframework.data.aot.SpringDataBeanFactoryInitializationAotProcessor org.springframework.aot.hint.RuntimeHintsRegistrar=\ - org.springframework.data.aot.DataRuntimeHints + org.springframework.data.aot.hint.RepositoryRuntimeHints diff --git a/src/test/java/org/springframework/data/aot/CodeContributionAssert.java b/src/test/java/org/springframework/data/aot/CodeContributionAssert.java index b9a67c980..072b1f4fb 100644 --- a/src/test/java/org/springframework/data/aot/CodeContributionAssert.java +++ b/src/test/java/org/springframework/data/aot/CodeContributionAssert.java @@ -25,6 +25,8 @@ import org.assertj.core.api.AbstractAssert; import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.hint.ClassProxyHint; import org.springframework.aot.hint.JdkProxyHint; +import org.springframework.aot.hint.ProxyHintsPredicates; +import org.springframework.aot.hint.RuntimeHintsPredicates; /** * AssertJ {@link AbstractAssert Assertion} for code contributions originating from @@ -45,9 +47,9 @@ public class CodeContributionAssert extends AbstractAssert... types) { for (Class type : types) { - assertThat(this.actual.getRuntimeHints().reflection().getTypeHint(type)) + assertThat(this.actual.getRuntimeHints()) .describedAs("No reflection entry found for [%s]", type) - .isNotNull(); + .matches(RuntimeHintsPredicates.reflection().onType(type)); } return this; @@ -56,9 +58,9 @@ public class CodeContributionAssert extends AbstractAssert... types) { for (Class type : types) { - assertThat(this.actual.getRuntimeHints().reflection().getTypeHint(type)) + assertThat(this.actual.getRuntimeHints()) .describedAs("Reflection entry found for [%s]", type) - .isNull(); + .matches(RuntimeHintsPredicates.reflection().onType(type).negate()); } return this; diff --git a/src/test/java/org/springframework/data/aot/ManagedTypesRegistrationAotProcessorUnitTests.java b/src/test/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessorUnitTests.java similarity index 59% rename from src/test/java/org/springframework/data/aot/ManagedTypesRegistrationAotProcessorUnitTests.java rename to src/test/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessorUnitTests.java index 69a40be3d..b0c7d7d95 100644 --- a/src/test/java/org/springframework/data/aot/ManagedTypesRegistrationAotProcessorUnitTests.java +++ b/src/test/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessorUnitTests.java @@ -20,37 +20,46 @@ import static org.assertj.core.api.Assertions.*; import java.util.Collections; import java.util.function.Consumer; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import org.springframework.aot.generate.ClassNameGenerator; import org.springframework.aot.generate.DefaultGenerationContext; import org.springframework.aot.generate.InMemoryGeneratedFiles; import org.springframework.aot.hint.RuntimeHints; -import org.springframework.beans.factory.BeanFactory; +import org.springframework.aot.hint.RuntimeHintsPredicates; import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.data.ManagedTypes; +import org.springframework.data.domain.ManagedTypes; /** * @author Christoph Strobl */ -class ManagedTypesRegistrationAotProcessorUnitTests { +class ManagedTypesBeanRegistrationAotProcessorUnitTests { final RootBeanDefinition managedTypesDefinition = (RootBeanDefinition) BeanDefinitionBuilder - .rootBeanDefinition(ManagedTypes.class).setFactoryMethod("of") + .rootBeanDefinition(ManagedTypes.class).setFactoryMethod("fromIterable") .addConstructorArgValue(Collections.singleton(A.class)).getBeanDefinition(); final RootBeanDefinition myManagedTypesDefinition = (RootBeanDefinition) BeanDefinitionBuilder .rootBeanDefinition(MyManagedTypes.class).getBeanDefinition(); + DefaultListableBeanFactory beanFactory; + + @BeforeEach + void beforeEach() { + beanFactory = new DefaultListableBeanFactory(); + } + @Test // GH-2593 void processesBeanWithMatchingModulePrefix() { - BeanRegistrationAotContribution contribution = createPostProcessor("commons", bf -> { - bf.registerBeanDefinition("commons.managed-types", managedTypesDefinition); - }).contribute(managedTypesDefinition, ManagedTypes.class, "commons.managed-types"); + beanFactory.registerBeanDefinition("commons.managed-types", managedTypesDefinition); + + BeanRegistrationAotContribution contribution = createPostProcessor("commons") + .processAheadOfTime(RegisteredBean.of(beanFactory, "commons.managed-types")); assertThat(contribution).isNotNull(); } @@ -58,26 +67,27 @@ class ManagedTypesRegistrationAotProcessorUnitTests { @Test // GH-2593 void contributesReflectionForManagedTypes() { - BeanRegistrationAotContribution contribution = createPostProcessor("commons", bf -> { - bf.registerBeanDefinition("commons.managed-types", managedTypesDefinition); - }).contribute(managedTypesDefinition, ManagedTypes.class, "commons.managed-types"); + beanFactory.registerBeanDefinition("commons.managed-types", managedTypesDefinition); + + BeanRegistrationAotContribution contribution = createPostProcessor("commons") + .processAheadOfTime(RegisteredBean.of(beanFactory, "commons.managed-types")); DefaultGenerationContext generationContext = new DefaultGenerationContext(new ClassNameGenerator(), new InMemoryGeneratedFiles(), new RuntimeHints()); contribution.applyTo(generationContext, null); - new CodeContributionAssert(generationContext) // - .contributesReflectionFor(A.class) // - .doesNotContributeReflectionFor(B.class); + assertThat(generationContext.getRuntimeHints()).matches(RuntimeHintsPredicates.reflection().onType(A.class) + .and(RuntimeHintsPredicates.reflection().onType(B.class).negate())); } @Test // GH-2593 void processesMatchingSubtypeBean() { - BeanRegistrationAotContribution contribution = createPostProcessor("commons", bf -> { - bf.registerBeanDefinition("commons.managed-types", myManagedTypesDefinition); - }).contribute(myManagedTypesDefinition, MyManagedTypes.class, "commons.managed-types"); + beanFactory.registerBeanDefinition("commons.managed-types", myManagedTypesDefinition); + + BeanRegistrationAotContribution contribution = createPostProcessor("commons") + .processAheadOfTime(RegisteredBean.of(beanFactory, "commons.managed-types")); assertThat(contribution).isNotNull(); } @@ -85,9 +95,11 @@ class ManagedTypesRegistrationAotProcessorUnitTests { @Test // GH-2593 void ignoresBeanNotMatchingRequiredType() { - BeanRegistrationAotContribution contribution = createPostProcessor("commons", bf -> { - bf.registerBeanDefinition("commons.managed-types", managedTypesDefinition); - }).contribute(managedTypesDefinition, Object.class, "commons.managed-types"); + beanFactory.registerBeanDefinition("commons.managed-types", + BeanDefinitionBuilder.rootBeanDefinition(NotManagedTypes.class).getBeanDefinition()); + + BeanRegistrationAotContribution contribution = createPostProcessor("commons") + .processAheadOfTime(RegisteredBean.of(beanFactory, "commons.managed-types")); assertThat(contribution).isNull(); } @@ -95,40 +107,31 @@ class ManagedTypesRegistrationAotProcessorUnitTests { @Test // GH-2593 void ignoresBeanNotMatchingPrefix() { - BeanRegistrationAotContribution contribution = createPostProcessor("commons", bf -> { - bf.registerBeanDefinition("commons.managed-types", managedTypesDefinition); - }).contribute(managedTypesDefinition, ManagedTypes.class, "jpa.managed-types"); + beanFactory.registerBeanDefinition("jpa.managed-types", managedTypesDefinition); + + BeanRegistrationAotContribution contribution = createPostProcessor("commons") + .processAheadOfTime(RegisteredBean.of(beanFactory, "jpa.managed-types")); assertThat(contribution).isNull(); } - private ManagedTypesRegistrationAotProcessor createPostProcessor(String prefix, Consumer action) { - - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - action.accept(beanFactory); - - ManagedTypesRegistrationAotProcessor postProcessor = createPostProcessor(beanFactory); - postProcessor.setModulePrefix(prefix); + private ManagedTypesBeanRegistrationAotProcessor createPostProcessor(String moduleIdentifier) { + ManagedTypesBeanRegistrationAotProcessor postProcessor = new ManagedTypesBeanRegistrationAotProcessor(); + postProcessor.setModuleIdentifier(moduleIdentifier); return postProcessor; } - private ManagedTypesRegistrationAotProcessor createPostProcessor(BeanFactory beanFactory) { - - ManagedTypesRegistrationAotProcessor managedTypesRegistrationAotProcessor = new ManagedTypesRegistrationAotProcessor(); - managedTypesRegistrationAotProcessor.setBeanFactory(beanFactory); - return managedTypesRegistrationAotProcessor; - } - static class A {} static class B {} static class MyManagedTypes implements ManagedTypes { - @Override public void forEach(Consumer> action) { // just do nothing ¯\_(ツ)_/¯ } } + + static class NotManagedTypes {} } diff --git a/src/test/java/org/springframework/data/aot/RepositoryRegistrationAotProcessorIntegrationTests.java b/src/test/java/org/springframework/data/aot/RepositoryRegistrationAotProcessorIntegrationTests.java index 3562d9cbf..edf296c7e 100644 --- a/src/test/java/org/springframework/data/aot/RepositoryRegistrationAotProcessorIntegrationTests.java +++ b/src/test/java/org/springframework/data/aot/RepositoryRegistrationAotProcessorIntegrationTests.java @@ -73,7 +73,7 @@ public class RepositoryRegistrationAotProcessorIntegrationTests { .contributesReflectionFor(ConfigWithSimpleCrudRepository.Person.class) // repository domain type .contributesJdkProxy(ConfigWithSimpleCrudRepository.MyRepo.class, SpringProxy.class, Advised.class, DecoratingProxy.class) // - .doesNotContributeJdkProxy(ConfigWithSimpleCrudRepository.MyRepo.class, Repository.class, + .contributesJdkProxy(ConfigWithSimpleCrudRepository.MyRepo.class, Repository.class, TransactionalProxy.class, Advised.class, DecoratingProxy.class) .doesNotContributeJdkProxy(ConfigWithSimpleCrudRepository.MyRepo.class, Repository.class, TransactionalProxy.class, Advised.class, DecoratingProxy.class, Serializable.class); diff --git a/src/test/java/org/springframework/data/aot/SpringDataBeanFactoryInitializationAotProcessorUnitTests.java b/src/test/java/org/springframework/data/aot/SpringDataBeanFactoryInitializationAotProcessorUnitTests.java index 15a4219bf..8f7df10d7 100644 --- a/src/test/java/org/springframework/data/aot/SpringDataBeanFactoryInitializationAotProcessorUnitTests.java +++ b/src/test/java/org/springframework/data/aot/SpringDataBeanFactoryInitializationAotProcessorUnitTests.java @@ -15,12 +15,8 @@ */ package org.springframework.data.aot; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import java.util.Collections; import java.util.LinkedHashSet; @@ -31,7 +27,7 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.data.ManagedTypes; +import org.springframework.data.domain.ManagedTypes; /** * @author Christoph Strobl @@ -58,7 +54,7 @@ class SpringDataBeanFactoryInitializationAotProcessorUnitTests { BeanDefinition beanDefinition = beanFactory.getBeanDefinition("data.managed-types"); - assertThat(beanDefinition.getFactoryMethodName()).isEqualTo("of"); + assertThat(beanDefinition.getFactoryMethodName()).isEqualTo("fromIterable"); assertThat(beanDefinition.hasConstructorArgumentValues()).isTrue(); assertThat(beanDefinition.getConstructorArgumentValues().getArgumentValue(0, null).getValue()) .isEqualTo(Collections.singleton(DomainType.class)); diff --git a/src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportUnitTests.java b/src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportUnitTests.java index f7ee0e598..52c81e64e 100755 --- a/src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportUnitTests.java +++ b/src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportUnitTests.java @@ -18,6 +18,8 @@ package org.springframework.data.repository.config; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.lang.annotation.Annotation; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -31,6 +33,7 @@ import org.springframework.context.annotation.AnnotationBeanNameGenerator; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.FilterType; import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.StandardAnnotationMetadata; import org.springframework.data.mapping.Person; diff --git a/src/test/java/org/springframework/data/repository/query/ParameterUnitTests.java b/src/test/java/org/springframework/data/repository/query/ParameterUnitTests.java index 5d39c9ba8..f001fe876 100644 --- a/src/test/java/org/springframework/data/repository/query/ParameterUnitTests.java +++ b/src/test/java/org/springframework/data/repository/query/ParameterUnitTests.java @@ -31,7 +31,7 @@ import org.springframework.core.MethodParameter; * * @author Jens Schauder */ -class ParameterUnitTests { +class KParameterUnitTests { @Test // DATAJPA-1185 void classParameterWithSameTypeParameterAsReturnedListIsDynamicProjectionParameter() throws Exception { diff --git a/src/test/java/org/springframework/data/util/TypeScannerUnitTests.java b/src/test/java/org/springframework/data/util/TypeScannerUnitTests.java new file mode 100644 index 000000000..a1704fbd2 --- /dev/null +++ b/src/test/java/org/springframework/data/util/TypeScannerUnitTests.java @@ -0,0 +1,97 @@ +/* + * 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 static org.assertj.core.api.Assertions.*; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.springframework.data.classloadersupport.HidingClassLoader; + +/** + * @author Christoph Strobl + */ +class TypeScannerUnitTests { + + @Test // GH-2593 + void looksForTypesMatchingAnnotationFilter() { + + Set> result = TypeScanner.typeScanner(getClass().getClassLoader()) // + .scanPackages(getClass().getPackageName()) // + .forTypesAnnotatedWith(FindMe.class) // + .collectAsSet(); + + assertThat(result).containsExactlyInAnyOrder(AnnotatedWithFindMe.class, AnnotatedWithMetaFindMe.class); + } + + @Test // GH-2593 + void looksForAllTypesIfNoFilter() { + + Set> result = TypeScanner.typeScanner(getClass().getClassLoader()) // + .scanPackages(getClass().getPackageName()) // + .collectAsSet(); + + assertThat(result).contains(AnnotatedWithFindMe.class, AnnotatedWithMetaFindMe.class, WithoutAnnotations.class); + } + + @Test // GH-2593 + void ignoresClassesNotFound() { + + TypeScanner scanner = TypeScanner.typeScanner(HidingClassLoader.hideTypes(AnnotatedWithFindMe.class)) // + .scanPackages(getClass().getPackageName()) // + .forTypesAnnotatedWith(FindMe.class); + + scanner.collectAsSet(); // no exception + } + + @Test // GH-2593 + void raisesErrorClassesNotFoundIfConfigured() { + + TypeScanner scanner = TypeScanner.typeScanner(HidingClassLoader.hideTypes(AnnotatedWithFindMe.class)) // + .scanPackages(getClass().getPackageName()) // + .forTypesAnnotatedWith(FindMe.class).onClassNotFound(ex -> { + throw new IllegalStateException(ex); + }); + + assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> scanner.collectAsSet()); // no exception + } + + @Retention(RetentionPolicy.RUNTIME) + @interface FindMe { + } + + @FindMe + @Retention(RetentionPolicy.RUNTIME) + @interface MetaAnnotatedWithFindMe { + } + + @FindMe + private static class AnnotatedWithFindMe { + + } + + @MetaAnnotatedWithFindMe + private static class AnnotatedWithMetaFindMe { + + } + + private static class WithoutAnnotations { + + } +}