Reintroduce component index support for Jakarta annotations

Spring Framework 6.0 GA introduced a regression in the component index
support for Jakarta annotations such as @Named and @ManagedBean.

Prior to this commit, @Named and @ManagedBean components were
registered in the component index at build time; however, component
scanning failed to find those component at run time.

This commit updates ClassPathScanningCandidateComponentProvider so that
`jakarta.*` annotation types are once again supported for component
scanning via the component index at run time.

Closes gh-29641
This commit is contained in:
Sam Brannen 2022-12-06 16:18:13 -05:00
parent e124e802a3
commit f4bc9ffb98
7 changed files with 196 additions and 81 deletions

View File

@ -79,6 +79,7 @@ import org.springframework.util.ClassUtils;
* @author Ramnivas Laddad * @author Ramnivas Laddad
* @author Chris Beams * @author Chris Beams
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Sam Brannen
* @since 2.5 * @since 2.5
* @see org.springframework.core.type.classreading.MetadataReaderFactory * @see org.springframework.core.type.classreading.MetadataReaderFactory
* @see org.springframework.core.type.AnnotationMetadata * @see org.springframework.core.type.AnnotationMetadata
@ -343,7 +344,7 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
if (filter instanceof AnnotationTypeFilter annotationTypeFilter) { if (filter instanceof AnnotationTypeFilter annotationTypeFilter) {
Class<? extends Annotation> annotationType = annotationTypeFilter.getAnnotationType(); Class<? extends Annotation> annotationType = annotationTypeFilter.getAnnotationType();
return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotationType) || return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotationType) ||
annotationType.getName().startsWith("javax.")); annotationType.getName().startsWith("jakarta."));
} }
if (filter instanceof AssignableTypeFilter assignableTypeFilter) { if (filter instanceof AssignableTypeFilter assignableTypeFilter) {
Class<?> target = assignableTypeFilter.getTargetType(); Class<?> target = assignableTypeFilter.getTargetType();

View File

@ -0,0 +1,24 @@
/*
* Copyright 2002-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 example.indexed;
/**
* @author Sam Brannen
*/
@jakarta.annotation.ManagedBean
public class IndexedJakartaManagedBeanComponent {
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2002-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 example.indexed;
/**
* @author Sam Brannen
*/
@jakarta.inject.Named("myIndexedJakartaNamedComponent")
public class IndexedJakartaNamedComponent {
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2002-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 example.scannable;
/**
* @author Sam Brannen
*/
@jakarta.annotation.ManagedBean
public class JakartaManagedBeanComponent {
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2002-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 example.scannable;
/**
* @author Sam Brannen
*/
@jakarta.inject.Named("myJakartaNamedComponent")
public class JakartaNamedComponent {
}

View File

@ -18,10 +18,17 @@ package org.springframework.context.annotation;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Stream;
import example.gh24375.AnnotatedComponent; import example.gh24375.AnnotatedComponent;
import example.indexed.IndexedJakartaManagedBeanComponent;
import example.indexed.IndexedJakartaNamedComponent;
import example.profilescan.DevComponent; import example.profilescan.DevComponent;
import example.profilescan.ProfileAnnotatedComponent; import example.profilescan.ProfileAnnotatedComponent;
import example.profilescan.ProfileMetaAnnotatedComponent; import example.profilescan.ProfileMetaAnnotatedComponent;
@ -31,6 +38,8 @@ import example.scannable.DefaultNamedComponent;
import example.scannable.FooDao; import example.scannable.FooDao;
import example.scannable.FooService; import example.scannable.FooService;
import example.scannable.FooServiceImpl; import example.scannable.FooServiceImpl;
import example.scannable.JakartaManagedBeanComponent;
import example.scannable.JakartaNamedComponent;
import example.scannable.MessageBean; import example.scannable.MessageBean;
import example.scannable.NamedComponent; import example.scannable.NamedComponent;
import example.scannable.NamedStubDao; import example.scannable.NamedStubDao;
@ -58,10 +67,13 @@ import org.springframework.stereotype.Service;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Integration tests for {@link ClassPathScanningCandidateComponentProvider}.
*
* @author Mark Fisher * @author Mark Fisher
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Chris Beams * @author Chris Beams
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Sam Brannen
*/ */
class ClassPathScanningCandidateComponentProviderTests { class ClassPathScanningCandidateComponentProviderTests {
@ -79,27 +91,53 @@ class ClassPathScanningCandidateComponentProviderTests {
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);
provider.setResourceLoader(new DefaultResourceLoader( provider.setResourceLoader(new DefaultResourceLoader(
CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader()))); CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader())));
testDefault(provider); testDefault(provider, true, false);
} }
@Test @Test
void defaultsWithIndex() { void defaultsWithIndex() {
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);
provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER)); provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER));
testDefault(provider); testDefault(provider, "example", true, true);
} }
private void testDefault(ClassPathScanningCandidateComponentProvider provider) { private static final Set<Class<?>> springComponents = Set.of(
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); DefaultNamedComponent.class,
assertThat(containsBeanClass(candidates, DefaultNamedComponent.class)).isTrue(); NamedComponent.class,
assertThat(containsBeanClass(candidates, NamedComponent.class)).isTrue(); FooServiceImpl.class,
assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isTrue(); StubFooDao.class,
assertThat(containsBeanClass(candidates, StubFooDao.class)).isTrue(); NamedStubDao.class,
assertThat(containsBeanClass(candidates, NamedStubDao.class)).isTrue(); ServiceInvocationCounter.class,
assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); BarComponent.class
assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue(); );
assertThat(candidates).hasSize(7);
assertBeanDefinitionType(candidates); private static final Set<Class<?>> scannedJakartaComponents = Set.of(
JakartaNamedComponent.class,
JakartaManagedBeanComponent.class
);
private static final Set<Class<?>> indexedJakartaComponents = Set.of(
IndexedJakartaNamedComponent.class,
IndexedJakartaManagedBeanComponent.class
);
private void testDefault(ClassPathScanningCandidateComponentProvider provider, boolean includeScannedJakartaComponents, boolean includeIndexedJakartaComponents) {
testDefault(provider, TEST_BASE_PACKAGE, includeScannedJakartaComponents, includeIndexedJakartaComponents);
}
private void testDefault(ClassPathScanningCandidateComponentProvider provider, String basePackage, boolean includeScannedJakartaComponents, boolean includeIndexedJakartaComponents) {
Set<Class<?>> expectedTypes = new HashSet<>(springComponents);
if (includeScannedJakartaComponents) {
expectedTypes.addAll(scannedJakartaComponents);
}
if (includeIndexedJakartaComponents) {
expectedTypes.addAll(indexedJakartaComponents);
}
Set<BeanDefinition> candidates = provider.findCandidateComponents(basePackage);
assertScannedBeanDefinitions(candidates);
assertBeanTypes(candidates, expectedTypes);
} }
@Test @Test
@ -119,9 +157,8 @@ class ClassPathScanningCandidateComponentProviderTests {
private void testAntStyle(ClassPathScanningCandidateComponentProvider provider) { private void testAntStyle(ClassPathScanningCandidateComponentProvider provider) {
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE + ".**.sub"); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE + ".**.sub");
assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue(); assertScannedBeanDefinitions(candidates);
assertThat(candidates).hasSize(1); assertBeanTypes(candidates, BarComponent.class);
assertBeanDefinitionType(candidates);
} }
@Test @Test
@ -148,7 +185,7 @@ class ClassPathScanningCandidateComponentProviderTests {
provider.addIncludeFilter(new AnnotationTypeFilter(Component.class)); provider.addIncludeFilter(new AnnotationTypeFilter(Component.class));
provider.resetFilters(true); provider.resetFilters(true);
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
assertBeanDefinitionType(candidates); assertScannedBeanDefinitions(candidates);
} }
@Test @Test
@ -168,7 +205,7 @@ class ClassPathScanningCandidateComponentProviderTests {
private void testCustomAnnotationTypeIncludeFilter(ClassPathScanningCandidateComponentProvider provider) { private void testCustomAnnotationTypeIncludeFilter(ClassPathScanningCandidateComponentProvider provider) {
provider.addIncludeFilter(new AnnotationTypeFilter(Component.class)); provider.addIncludeFilter(new AnnotationTypeFilter(Component.class));
testDefault(provider); testDefault(provider, false, false);
} }
@Test @Test
@ -189,12 +226,9 @@ class ClassPathScanningCandidateComponentProviderTests {
private void testCustomAssignableTypeIncludeFilter(ClassPathScanningCandidateComponentProvider provider) { private void testCustomAssignableTypeIncludeFilter(ClassPathScanningCandidateComponentProvider provider) {
provider.addIncludeFilter(new AssignableTypeFilter(FooService.class)); provider.addIncludeFilter(new AssignableTypeFilter(FooService.class));
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
assertScannedBeanDefinitions(candidates);
// Interfaces/Abstract class are filtered out automatically. // Interfaces/Abstract class are filtered out automatically.
assertThat(containsBeanClass(candidates, AutowiredQualifierFooService.class)).isTrue(); assertBeanTypes(candidates, AutowiredQualifierFooService.class, FooServiceImpl.class, ScopedProxyTestBean.class);
assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isTrue();
assertThat(containsBeanClass(candidates, ScopedProxyTestBean.class)).isTrue();
assertThat(candidates).hasSize(3);
assertBeanDefinitionType(candidates);
} }
@Test @Test
@ -217,24 +251,20 @@ class ClassPathScanningCandidateComponentProviderTests {
provider.addExcludeFilter(new AnnotationTypeFilter(Service.class)); provider.addExcludeFilter(new AnnotationTypeFilter(Service.class));
provider.addExcludeFilter(new AnnotationTypeFilter(Repository.class)); provider.addExcludeFilter(new AnnotationTypeFilter(Repository.class));
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
assertThat(containsBeanClass(candidates, NamedComponent.class)).isTrue(); assertScannedBeanDefinitions(candidates);
assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); assertBeanTypes(candidates, NamedComponent.class, ServiceInvocationCounter.class, BarComponent.class);
assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue();
assertThat(candidates).hasSize(3);
assertBeanDefinitionType(candidates);
} }
@Test @Test
void customSupportIncludeFilterWithNonIndexedTypeUseScan() { void customSupportIncludeFilterWithNonIndexedTypeUseScan() {
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER)); provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER));
// This annotation type is not directly annotated with Indexed so we can use // This annotation type is not directly annotated with @Indexed so we can use
// the index to find candidates // the index to find candidates.
provider.addIncludeFilter(new AnnotationTypeFilter(CustomStereotype.class)); provider.addIncludeFilter(new AnnotationTypeFilter(CustomStereotype.class));
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
assertThat(containsBeanClass(candidates, DefaultNamedComponent.class)).isTrue(); assertScannedBeanDefinitions(candidates);
assertThat(candidates).hasSize(1); assertBeanTypes(candidates, DefaultNamedComponent.class);
assertBeanDefinitionType(candidates);
} }
@Test @Test
@ -243,9 +273,8 @@ class ClassPathScanningCandidateComponentProviderTests {
provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER)); provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER));
provider.addIncludeFilter(new AssignableTypeFilter(FooDao.class)); provider.addIncludeFilter(new AssignableTypeFilter(FooDao.class));
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
assertThat(containsBeanClass(candidates, StubFooDao.class)).isTrue(); assertScannedBeanDefinitions(candidates);
assertThat(candidates).hasSize(1); assertBeanTypes(candidates, StubFooDao.class);
assertBeanDefinitionType(candidates);
} }
@Test @Test
@ -267,12 +296,9 @@ class ClassPathScanningCandidateComponentProviderTests {
private void testExclude(ClassPathScanningCandidateComponentProvider provider) { private void testExclude(ClassPathScanningCandidateComponentProvider provider) {
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isTrue(); assertScannedBeanDefinitions(candidates);
assertThat(containsBeanClass(candidates, StubFooDao.class)).isTrue(); assertBeanTypes(candidates, FooServiceImpl.class, StubFooDao.class, ServiceInvocationCounter.class,
assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); BarComponent.class, JakartaManagedBeanComponent.class);
assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue();
assertThat(candidates).hasSize(4);
assertBeanDefinitionType(candidates);
} }
@Test @Test
@ -290,13 +316,7 @@ class ClassPathScanningCandidateComponentProviderTests {
provider.addExcludeFilter(new AnnotationTypeFilter(Service.class)); provider.addExcludeFilter(new AnnotationTypeFilter(Service.class));
provider.addExcludeFilter(new AnnotationTypeFilter(Controller.class)); provider.addExcludeFilter(new AnnotationTypeFilter(Controller.class));
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
assertThat(candidates).hasSize(3); assertBeanTypes(candidates, NamedComponent.class, ServiceInvocationCounter.class, BarComponent.class);
assertThat(containsBeanClass(candidates, NamedComponent.class)).isTrue();
assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue();
assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue();
assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isFalse();
assertThat(containsBeanClass(candidates, StubFooDao.class)).isFalse();
assertThat(containsBeanClass(candidates, NamedStubDao.class)).isFalse();
} }
@Test @Test
@ -304,8 +324,7 @@ class ClassPathScanningCandidateComponentProviderTests {
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AnnotationTypeFilter(Aspect.class)); provider.addIncludeFilter(new AnnotationTypeFilter(Aspect.class));
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
assertThat(candidates).hasSize(1); assertBeanTypes(candidates, ServiceInvocationCounter.class);
assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue();
} }
@Test @Test
@ -313,8 +332,7 @@ class ClassPathScanningCandidateComponentProviderTests {
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AssignableTypeFilter(FooDao.class)); provider.addIncludeFilter(new AssignableTypeFilter(FooDao.class));
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
assertThat(candidates).hasSize(1); assertBeanTypes(candidates, StubFooDao.class);
assertThat(containsBeanClass(candidates, StubFooDao.class)).isTrue();
} }
@Test @Test
@ -322,8 +340,7 @@ class ClassPathScanningCandidateComponentProviderTests {
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AssignableTypeFilter(MessageBean.class)); provider.addIncludeFilter(new AssignableTypeFilter(MessageBean.class));
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
assertThat(candidates).hasSize(1); assertBeanTypes(candidates, MessageBean.class);
assertThat(containsBeanClass(candidates, MessageBean.class)).isTrue();
} }
@Test @Test
@ -332,11 +349,8 @@ class ClassPathScanningCandidateComponentProviderTests {
provider.addIncludeFilter(new AnnotationTypeFilter(Component.class)); provider.addIncludeFilter(new AnnotationTypeFilter(Component.class));
provider.addIncludeFilter(new AssignableTypeFilter(FooServiceImpl.class)); provider.addIncludeFilter(new AssignableTypeFilter(FooServiceImpl.class));
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
assertThat(candidates).hasSize(7); assertBeanTypes(candidates, NamedComponent.class, ServiceInvocationCounter.class, FooServiceImpl.class,
assertThat(containsBeanClass(candidates, NamedComponent.class)).isTrue(); BarComponent.class, DefaultNamedComponent.class, NamedStubDao.class, StubFooDao.class);
assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue();
assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isTrue();
assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue();
} }
@Test @Test
@ -346,18 +360,15 @@ class ClassPathScanningCandidateComponentProviderTests {
provider.addIncludeFilter(new AssignableTypeFilter(FooServiceImpl.class)); provider.addIncludeFilter(new AssignableTypeFilter(FooServiceImpl.class));
provider.addExcludeFilter(new AssignableTypeFilter(FooService.class)); provider.addExcludeFilter(new AssignableTypeFilter(FooService.class));
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
assertThat(candidates).hasSize(6); assertBeanTypes(candidates, NamedComponent.class, ServiceInvocationCounter.class, BarComponent.class,
assertThat(containsBeanClass(candidates, NamedComponent.class)).isTrue(); DefaultNamedComponent.class, NamedStubDao.class, StubFooDao.class);
assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue();
assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue();
assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isFalse();
} }
@Test @Test
void withNullEnvironment() { void withNullEnvironment() {
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_PROFILE_PACKAGE); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_PROFILE_PACKAGE);
assertThat(containsBeanClass(candidates, ProfileAnnotatedComponent.class)).isFalse(); assertThat(candidates).isEmpty();
} }
@Test @Test
@ -367,7 +378,7 @@ class ClassPathScanningCandidateComponentProviderTests {
env.setActiveProfiles("other"); env.setActiveProfiles("other");
provider.setEnvironment(env); provider.setEnvironment(env);
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_PROFILE_PACKAGE); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_PROFILE_PACKAGE);
assertThat(containsBeanClass(candidates, ProfileAnnotatedComponent.class)).isFalse(); assertThat(candidates).isEmpty();
} }
@Test @Test
@ -377,7 +388,7 @@ class ClassPathScanningCandidateComponentProviderTests {
env.setActiveProfiles(ProfileAnnotatedComponent.PROFILE_NAME); env.setActiveProfiles(ProfileAnnotatedComponent.PROFILE_NAME);
provider.setEnvironment(env); provider.setEnvironment(env);
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_PROFILE_PACKAGE); Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_PROFILE_PACKAGE);
assertThat(containsBeanClass(candidates, ProfileAnnotatedComponent.class)).isTrue(); assertBeanTypes(candidates, ProfileAnnotatedComponent.class);
} }
@Test @Test
@ -515,19 +526,22 @@ class ClassPathScanningCandidateComponentProviderTests {
} }
private boolean containsBeanClass(Set<BeanDefinition> candidates, Class<?> beanClass) { private static void assertBeanTypes(Set<BeanDefinition> candidates, Class<?>... expectedTypes) {
for (BeanDefinition candidate : candidates) { assertBeanTypes(candidates, Arrays.stream(expectedTypes));
if (beanClass.getName().equals(candidate.getBeanClassName())) {
return true;
}
}
return false;
} }
private void assertBeanDefinitionType(Set<BeanDefinition> candidates) { private static void assertBeanTypes(Set<BeanDefinition> candidates, Collection<Class<?>> expectedTypes) {
candidates.forEach(c -> assertBeanTypes(candidates, expectedTypes.stream());
assertThat(c).isInstanceOf(ScannedGenericBeanDefinition.class) }
);
private static void assertBeanTypes(Set<BeanDefinition> candidates, Stream<Class<?>> expectedTypes) {
List<String> actualTypeNames = candidates.stream().map(BeanDefinition::getBeanClassName).distinct().sorted().toList();
List<String> expectedTypeNames = expectedTypes.map(Class::getName).distinct().sorted().toList();
assertThat(actualTypeNames).containsExactlyElementsOf(expectedTypeNames);
}
private static void assertScannedBeanDefinitions(Set<BeanDefinition> candidates) {
candidates.forEach(type -> assertThat(type).isInstanceOf(ScannedGenericBeanDefinition.class));
} }

View File

@ -8,3 +8,7 @@ example.scannable.StubFooDao=org.springframework.stereotype.Component
example.scannable.NamedStubDao=org.springframework.stereotype.Component example.scannable.NamedStubDao=org.springframework.stereotype.Component
example.scannable.ServiceInvocationCounter=org.springframework.stereotype.Component example.scannable.ServiceInvocationCounter=org.springframework.stereotype.Component
example.scannable.sub.BarComponent=org.springframework.stereotype.Component example.scannable.sub.BarComponent=org.springframework.stereotype.Component
example.scannable.JakartaManagedBeanComponent=jakarta.annotation.ManagedBean
example.scannable.JakartaNamedComponent=jakarta.inject.Named
example.indexed.IndexedJakartaManagedBeanComponent=jakarta.annotation.ManagedBean
example.indexed.IndexedJakartaNamedComponent=jakarta.inject.Named