From f4bc9ffb98456b67cd4d51046bfab56cbfbccde5 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 6 Dec 2022 16:18:13 -0500 Subject: [PATCH] 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 --- ...athScanningCandidateComponentProvider.java | 3 +- .../IndexedJakartaManagedBeanComponent.java | 24 +++ .../indexed/IndexedJakartaNamedComponent.java | 24 +++ .../JakartaManagedBeanComponent.java | 24 +++ .../scannable/JakartaNamedComponent.java | 24 +++ ...anningCandidateComponentProviderTests.java | 172 ++++++++++-------- .../example/scannable/spring.components | 6 +- 7 files changed, 196 insertions(+), 81 deletions(-) create mode 100644 spring-context/src/test/java/example/indexed/IndexedJakartaManagedBeanComponent.java create mode 100644 spring-context/src/test/java/example/indexed/IndexedJakartaNamedComponent.java create mode 100644 spring-context/src/test/java/example/scannable/JakartaManagedBeanComponent.java create mode 100644 spring-context/src/test/java/example/scannable/JakartaNamedComponent.java diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index 232c53234c66..7c717714c75a 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -79,6 +79,7 @@ * @author Ramnivas Laddad * @author Chris Beams * @author Stephane Nicoll + * @author Sam Brannen * @since 2.5 * @see org.springframework.core.type.classreading.MetadataReaderFactory * @see org.springframework.core.type.AnnotationMetadata @@ -343,7 +344,7 @@ private boolean indexSupportsIncludeFilter(TypeFilter filter) { if (filter instanceof AnnotationTypeFilter annotationTypeFilter) { Class annotationType = annotationTypeFilter.getAnnotationType(); return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotationType) || - annotationType.getName().startsWith("javax.")); + annotationType.getName().startsWith("jakarta.")); } if (filter instanceof AssignableTypeFilter assignableTypeFilter) { Class target = assignableTypeFilter.getTargetType(); diff --git a/spring-context/src/test/java/example/indexed/IndexedJakartaManagedBeanComponent.java b/spring-context/src/test/java/example/indexed/IndexedJakartaManagedBeanComponent.java new file mode 100644 index 000000000000..ed640a7a73da --- /dev/null +++ b/spring-context/src/test/java/example/indexed/IndexedJakartaManagedBeanComponent.java @@ -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 { +} diff --git a/spring-context/src/test/java/example/indexed/IndexedJakartaNamedComponent.java b/spring-context/src/test/java/example/indexed/IndexedJakartaNamedComponent.java new file mode 100644 index 000000000000..a2b1ed2042e0 --- /dev/null +++ b/spring-context/src/test/java/example/indexed/IndexedJakartaNamedComponent.java @@ -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 { +} diff --git a/spring-context/src/test/java/example/scannable/JakartaManagedBeanComponent.java b/spring-context/src/test/java/example/scannable/JakartaManagedBeanComponent.java new file mode 100644 index 000000000000..ffeb6c29e0a6 --- /dev/null +++ b/spring-context/src/test/java/example/scannable/JakartaManagedBeanComponent.java @@ -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 { +} diff --git a/spring-context/src/test/java/example/scannable/JakartaNamedComponent.java b/spring-context/src/test/java/example/scannable/JakartaNamedComponent.java new file mode 100644 index 000000000000..64165df69c34 --- /dev/null +++ b/spring-context/src/test/java/example/scannable/JakartaNamedComponent.java @@ -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 { +} diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java index 1f13a2771323..a5e2d44ee210 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java @@ -18,10 +18,17 @@ import java.lang.annotation.Retention; 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.regex.Pattern; +import java.util.stream.Stream; import example.gh24375.AnnotatedComponent; +import example.indexed.IndexedJakartaManagedBeanComponent; +import example.indexed.IndexedJakartaNamedComponent; import example.profilescan.DevComponent; import example.profilescan.ProfileAnnotatedComponent; import example.profilescan.ProfileMetaAnnotatedComponent; @@ -31,6 +38,8 @@ import example.scannable.FooDao; import example.scannable.FooService; import example.scannable.FooServiceImpl; +import example.scannable.JakartaManagedBeanComponent; +import example.scannable.JakartaNamedComponent; import example.scannable.MessageBean; import example.scannable.NamedComponent; import example.scannable.NamedStubDao; @@ -58,10 +67,13 @@ import static org.assertj.core.api.Assertions.assertThat; /** + * Integration tests for {@link ClassPathScanningCandidateComponentProvider}. + * * @author Mark Fisher * @author Juergen Hoeller * @author Chris Beams * @author Stephane Nicoll + * @author Sam Brannen */ class ClassPathScanningCandidateComponentProviderTests { @@ -79,27 +91,53 @@ void defaultsWithScan() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); provider.setResourceLoader(new DefaultResourceLoader( CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader()))); - testDefault(provider); + testDefault(provider, true, false); } @Test void defaultsWithIndex() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER)); - testDefault(provider); + testDefault(provider, "example", true, true); } - private void testDefault(ClassPathScanningCandidateComponentProvider provider) { - Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(containsBeanClass(candidates, DefaultNamedComponent.class)).isTrue(); - assertThat(containsBeanClass(candidates, NamedComponent.class)).isTrue(); - assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isTrue(); - assertThat(containsBeanClass(candidates, StubFooDao.class)).isTrue(); - assertThat(containsBeanClass(candidates, NamedStubDao.class)).isTrue(); - assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); - assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue(); - assertThat(candidates).hasSize(7); - assertBeanDefinitionType(candidates); + private static final Set> springComponents = Set.of( + DefaultNamedComponent.class, + NamedComponent.class, + FooServiceImpl.class, + StubFooDao.class, + NamedStubDao.class, + ServiceInvocationCounter.class, + BarComponent.class + ); + + private static final Set> scannedJakartaComponents = Set.of( + JakartaNamedComponent.class, + JakartaManagedBeanComponent.class + ); + + private static final Set> 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> expectedTypes = new HashSet<>(springComponents); + if (includeScannedJakartaComponents) { + expectedTypes.addAll(scannedJakartaComponents); + } + if (includeIndexedJakartaComponents) { + expectedTypes.addAll(indexedJakartaComponents); + } + + Set candidates = provider.findCandidateComponents(basePackage); + assertScannedBeanDefinitions(candidates); + assertBeanTypes(candidates, expectedTypes); } @Test @@ -119,9 +157,8 @@ void antStylePackageWithIndex() { private void testAntStyle(ClassPathScanningCandidateComponentProvider provider) { Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE + ".**.sub"); - assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue(); - assertThat(candidates).hasSize(1); - assertBeanDefinitionType(candidates); + assertScannedBeanDefinitions(candidates); + assertBeanTypes(candidates, BarComponent.class); } @Test @@ -148,7 +185,7 @@ void customFiltersFollowedByResetUseIndex() { provider.addIncludeFilter(new AnnotationTypeFilter(Component.class)); provider.resetFilters(true); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertBeanDefinitionType(candidates); + assertScannedBeanDefinitions(candidates); } @Test @@ -168,7 +205,7 @@ void customAnnotationTypeIncludeFilterWithIndex() { private void testCustomAnnotationTypeIncludeFilter(ClassPathScanningCandidateComponentProvider provider) { provider.addIncludeFilter(new AnnotationTypeFilter(Component.class)); - testDefault(provider); + testDefault(provider, false, false); } @Test @@ -189,12 +226,9 @@ void customAssignableTypeIncludeFilterWithIndex() { private void testCustomAssignableTypeIncludeFilter(ClassPathScanningCandidateComponentProvider provider) { provider.addIncludeFilter(new AssignableTypeFilter(FooService.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); + assertScannedBeanDefinitions(candidates); // Interfaces/Abstract class are filtered out automatically. - assertThat(containsBeanClass(candidates, AutowiredQualifierFooService.class)).isTrue(); - assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isTrue(); - assertThat(containsBeanClass(candidates, ScopedProxyTestBean.class)).isTrue(); - assertThat(candidates).hasSize(3); - assertBeanDefinitionType(candidates); + assertBeanTypes(candidates, AutowiredQualifierFooService.class, FooServiceImpl.class, ScopedProxyTestBean.class); } @Test @@ -217,24 +251,20 @@ private void testCustomSupportedIncludeAndExcludeFilter(ClassPathScanningCandida provider.addExcludeFilter(new AnnotationTypeFilter(Service.class)); provider.addExcludeFilter(new AnnotationTypeFilter(Repository.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(containsBeanClass(candidates, NamedComponent.class)).isTrue(); - assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); - assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue(); - assertThat(candidates).hasSize(3); - assertBeanDefinitionType(candidates); + assertScannedBeanDefinitions(candidates); + assertBeanTypes(candidates, NamedComponent.class, ServiceInvocationCounter.class, BarComponent.class); } @Test void customSupportIncludeFilterWithNonIndexedTypeUseScan() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER)); - // This annotation type is not directly annotated with Indexed so we can use - // the index to find candidates + // This annotation type is not directly annotated with @Indexed so we can use + // the index to find candidates. provider.addIncludeFilter(new AnnotationTypeFilter(CustomStereotype.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(containsBeanClass(candidates, DefaultNamedComponent.class)).isTrue(); - assertThat(candidates).hasSize(1); - assertBeanDefinitionType(candidates); + assertScannedBeanDefinitions(candidates); + assertBeanTypes(candidates, DefaultNamedComponent.class); } @Test @@ -243,9 +273,8 @@ void customNotSupportedIncludeFilterUseScan() { provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER)); provider.addIncludeFilter(new AssignableTypeFilter(FooDao.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(containsBeanClass(candidates, StubFooDao.class)).isTrue(); - assertThat(candidates).hasSize(1); - assertBeanDefinitionType(candidates); + assertScannedBeanDefinitions(candidates); + assertBeanTypes(candidates, StubFooDao.class); } @Test @@ -267,12 +296,9 @@ void excludeFilterWithIndex() { private void testExclude(ClassPathScanningCandidateComponentProvider provider) { Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isTrue(); - assertThat(containsBeanClass(candidates, StubFooDao.class)).isTrue(); - assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); - assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue(); - assertThat(candidates).hasSize(4); - assertBeanDefinitionType(candidates); + assertScannedBeanDefinitions(candidates); + assertBeanTypes(candidates, FooServiceImpl.class, StubFooDao.class, ServiceInvocationCounter.class, + BarComponent.class, JakartaManagedBeanComponent.class); } @Test @@ -290,13 +316,7 @@ void withComponentAnnotationOnly() { provider.addExcludeFilter(new AnnotationTypeFilter(Service.class)); provider.addExcludeFilter(new AnnotationTypeFilter(Controller.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(candidates).hasSize(3); - 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(); + assertBeanTypes(candidates, NamedComponent.class, ServiceInvocationCounter.class, BarComponent.class); } @Test @@ -304,8 +324,7 @@ void withAspectAnnotationOnly() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); provider.addIncludeFilter(new AnnotationTypeFilter(Aspect.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(candidates).hasSize(1); - assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); + assertBeanTypes(candidates, ServiceInvocationCounter.class); } @Test @@ -313,8 +332,7 @@ void withInterfaceType() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); provider.addIncludeFilter(new AssignableTypeFilter(FooDao.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(candidates).hasSize(1); - assertThat(containsBeanClass(candidates, StubFooDao.class)).isTrue(); + assertBeanTypes(candidates, StubFooDao.class); } @Test @@ -322,8 +340,7 @@ void withClassType() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); provider.addIncludeFilter(new AssignableTypeFilter(MessageBean.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(candidates).hasSize(1); - assertThat(containsBeanClass(candidates, MessageBean.class)).isTrue(); + assertBeanTypes(candidates, MessageBean.class); } @Test @@ -332,11 +349,8 @@ void withMultipleMatchingFilters() { provider.addIncludeFilter(new AnnotationTypeFilter(Component.class)); provider.addIncludeFilter(new AssignableTypeFilter(FooServiceImpl.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(candidates).hasSize(7); - assertThat(containsBeanClass(candidates, NamedComponent.class)).isTrue(); - assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); - assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isTrue(); - assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue(); + assertBeanTypes(candidates, NamedComponent.class, ServiceInvocationCounter.class, FooServiceImpl.class, + BarComponent.class, DefaultNamedComponent.class, NamedStubDao.class, StubFooDao.class); } @Test @@ -346,18 +360,15 @@ void excludeTakesPrecedence() { provider.addIncludeFilter(new AssignableTypeFilter(FooServiceImpl.class)); provider.addExcludeFilter(new AssignableTypeFilter(FooService.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(candidates).hasSize(6); - assertThat(containsBeanClass(candidates, NamedComponent.class)).isTrue(); - assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); - assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue(); - assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isFalse(); + assertBeanTypes(candidates, NamedComponent.class, ServiceInvocationCounter.class, BarComponent.class, + DefaultNamedComponent.class, NamedStubDao.class, StubFooDao.class); } @Test void withNullEnvironment() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); Set candidates = provider.findCandidateComponents(TEST_PROFILE_PACKAGE); - assertThat(containsBeanClass(candidates, ProfileAnnotatedComponent.class)).isFalse(); + assertThat(candidates).isEmpty(); } @Test @@ -367,7 +378,7 @@ void withInactiveProfile() { env.setActiveProfiles("other"); provider.setEnvironment(env); Set candidates = provider.findCandidateComponents(TEST_PROFILE_PACKAGE); - assertThat(containsBeanClass(candidates, ProfileAnnotatedComponent.class)).isFalse(); + assertThat(candidates).isEmpty(); } @Test @@ -377,7 +388,7 @@ void withActiveProfile() { env.setActiveProfiles(ProfileAnnotatedComponent.PROFILE_NAME); provider.setEnvironment(env); Set candidates = provider.findCandidateComponents(TEST_PROFILE_PACKAGE); - assertThat(containsBeanClass(candidates, ProfileAnnotatedComponent.class)).isTrue(); + assertBeanTypes(candidates, ProfileAnnotatedComponent.class); } @Test @@ -515,19 +526,22 @@ void componentScanningFindsComponentsAnnotatedWithAnnotationsContainingNestedAnn } - private boolean containsBeanClass(Set candidates, Class beanClass) { - for (BeanDefinition candidate : candidates) { - if (beanClass.getName().equals(candidate.getBeanClassName())) { - return true; - } - } - return false; + private static void assertBeanTypes(Set candidates, Class... expectedTypes) { + assertBeanTypes(candidates, Arrays.stream(expectedTypes)); } - private void assertBeanDefinitionType(Set candidates) { - candidates.forEach(c -> - assertThat(c).isInstanceOf(ScannedGenericBeanDefinition.class) - ); + private static void assertBeanTypes(Set candidates, Collection> expectedTypes) { + assertBeanTypes(candidates, expectedTypes.stream()); + } + + private static void assertBeanTypes(Set candidates, Stream> expectedTypes) { + List actualTypeNames = candidates.stream().map(BeanDefinition::getBeanClassName).distinct().sorted().toList(); + List expectedTypeNames = expectedTypes.map(Class::getName).distinct().sorted().toList(); + assertThat(actualTypeNames).containsExactlyElementsOf(expectedTypeNames); + } + + private static void assertScannedBeanDefinitions(Set candidates) { + candidates.forEach(type -> assertThat(type).isInstanceOf(ScannedGenericBeanDefinition.class)); } diff --git a/spring-context/src/test/resources/example/scannable/spring.components b/spring-context/src/test/resources/example/scannable/spring.components index 9e7303d2517b..a859835ba72e 100644 --- a/spring-context/src/test/resources/example/scannable/spring.components +++ b/spring-context/src/test/resources/example/scannable/spring.components @@ -7,4 +7,8 @@ example.scannable.ScopedProxyTestBean=example.scannable.FooService example.scannable.StubFooDao=org.springframework.stereotype.Component example.scannable.NamedStubDao=org.springframework.stereotype.Component example.scannable.ServiceInvocationCounter=org.springframework.stereotype.Component -example.scannable.sub.BarComponent=org.springframework.stereotype.Component \ No newline at end of file +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