diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index 47bbb0326cb..72fce80ce4c 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -83,6 +83,7 @@ dependencies { api("jakarta.xml.bind:jakarta.xml.bind-api:3.0.1") api("javax.annotation:javax.annotation-api:1.3.2") api("javax.cache:cache-api:1.1.1") + api("javax.inject:javax.inject:1") api("javax.money:money-api:1.1") api("jaxen:jaxen:1.2.0") api("junit:junit:4.13.2") diff --git a/spring-context/spring-context.gradle b/spring-context/spring-context.gradle index 8c520617513..4d09e07664b 100644 --- a/spring-context/spring-context.gradle +++ b/spring-context/spring-context.gradle @@ -21,6 +21,7 @@ dependencies { optional("jakarta.interceptor:jakarta.interceptor-api") optional("jakarta.validation:jakarta.validation-api") optional("javax.annotation:javax.annotation-api") + optional("javax.inject:javax.inject") optional("javax.money:money-api") optional("org.apache.groovy:groovy") optional("org.apache-extras.beanshell:bsh") diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java index 3b335be1754..d3508028993 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java @@ -41,8 +41,10 @@ import org.springframework.util.StringUtils; * themselves annotated with {@code @Component}. * *

Also supports Jakarta EE's {@link jakarta.annotation.ManagedBean} and - * JSR-330's {@link jakarta.inject.Named} annotations, if available. Note that - * Spring component annotations always override such standard annotations. + * JSR-330's {@link jakarta.inject.Named} annotations (as well as their pre-Jakarta + * {@code javax.annotation.ManagedBean} and {@code javax.inject.Named} equivalents), + * if available. Note that Spring component annotations always override such + * standard annotations. * *

If the annotation's value doesn't indicate a bean name, an appropriate * name will be built based on the short name of the class (with the first @@ -53,6 +55,7 @@ import org.springframework.util.StringUtils; * * @author Juergen Hoeller * @author Mark Fisher + * @author Sam Brannen * @since 2.5 * @see org.springframework.stereotype.Component#value() * @see org.springframework.stereotype.Repository#value() @@ -134,7 +137,9 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator { boolean isStereotype = annotationType.equals(COMPONENT_ANNOTATION_CLASSNAME) || metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME) || annotationType.equals("jakarta.annotation.ManagedBean") || - annotationType.equals("jakarta.inject.Named"); + annotationType.equals("javax.annotation.ManagedBean") || + annotationType.equals("jakarta.inject.Named") || + annotationType.equals("javax.inject.Named"); return (isStereotype && attributes != null && attributes.containsKey("value")); } 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 3c56610c9da..1490284a4e0 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 @@ -62,11 +62,13 @@ import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** - * A component provider that provides candidate components from a base package. Can - * use {@link CandidateComponentsIndex the index} if it is available of scans the - * classpath otherwise. Candidate components are identified by applying exclude and - * include filters. {@link AnnotationTypeFilter}, {@link AssignableTypeFilter} include - * filters on an annotation/superclass that are annotated with {@link Indexed} are + * A component provider that scans for candidate components starting from a + * specified base package. Can use the {@linkplain CandidateComponentsIndex component + * index}, if it is available, and scans the classpath otherwise. + * + *

Candidate components are identified by applying exclude and include filters. + * {@link AnnotationTypeFilter} and {@link AssignableTypeFilter} include filters + * for an annotation/target-type that is annotated with {@link Indexed} are * supported: if any other include filter is specified, the index is ignored and * classpath scanning is used instead. * @@ -201,8 +203,9 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC * {@link Repository @Repository}, {@link Service @Service}, and * {@link Controller @Controller} stereotype annotations. *

Also supports Jakarta EE's {@link jakarta.annotation.ManagedBean} and - * JSR-330's {@link jakarta.inject.Named} annotations, if available. - * + * JSR-330's {@link jakarta.inject.Named} annotations (as well as their + * pre-Jakarta {@code javax.annotation.ManagedBean} and {@code javax.inject.Named} + * equivalents), if available. */ @SuppressWarnings("unchecked") protected void registerDefaultFilters() { @@ -216,11 +219,27 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC catch (ClassNotFoundException ex) { // JSR-250 1.1 API (as included in Jakarta EE) not available - simply skip. } + try { + this.includeFilters.add(new AnnotationTypeFilter( + ((Class) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false)); + logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); + } + catch (ClassNotFoundException ex) { + // JSR-250 1.1 API not available - simply skip. + } try { this.includeFilters.add(new AnnotationTypeFilter( ((Class) ClassUtils.forName("jakarta.inject.Named", cl)), false)); logger.trace("JSR-330 'jakarta.inject.Named' annotation found and supported for component scanning"); } + catch (ClassNotFoundException ex) { + // JSR-330 API (as included in Jakarta EE) not available - simply skip. + } + try { + this.includeFilters.add(new AnnotationTypeFilter( + ((Class) ClassUtils.forName("javax.inject.Named", cl)), false)); + logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning"); + } catch (ClassNotFoundException ex) { // JSR-330 API not available - simply skip. } @@ -306,7 +325,7 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC /** - * Scan the class path for candidate components. + * Scan the component index or class path for candidate components. * @param basePackage the package to check for annotated classes * @return a corresponding Set of autodetected bean definitions */ @@ -320,7 +339,7 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC } /** - * Determine if the index can be used by this instance. + * Determine if the component index can be used by this instance. * @return {@code true} if the index is available and the configuration of this * instance is supported by it, {@code false} otherwise * @since 5.0 @@ -345,7 +364,8 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC if (filter instanceof AnnotationTypeFilter annotationTypeFilter) { Class annotationType = annotationTypeFilter.getAnnotationType(); return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotationType) || - annotationType.getName().startsWith("jakarta.")); + annotationType.getName().startsWith("jakarta.") || + annotationType.getName().startsWith("javax.")); } if (filter instanceof AssignableTypeFilter assignableTypeFilter) { Class target = assignableTypeFilter.getTargetType(); diff --git a/spring-context/src/test/java/example/indexed/IndexedJavaxManagedBeanComponent.java b/spring-context/src/test/java/example/indexed/IndexedJavaxManagedBeanComponent.java new file mode 100644 index 00000000000..b563b4d3797 --- /dev/null +++ b/spring-context/src/test/java/example/indexed/IndexedJavaxManagedBeanComponent.java @@ -0,0 +1,24 @@ +/* + * Copyright 2002-2023 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 + */ +@javax.annotation.ManagedBean +public class IndexedJavaxManagedBeanComponent { +} diff --git a/spring-context/src/test/java/example/indexed/IndexedJavaxNamedComponent.java b/spring-context/src/test/java/example/indexed/IndexedJavaxNamedComponent.java new file mode 100644 index 00000000000..581be8a6f97 --- /dev/null +++ b/spring-context/src/test/java/example/indexed/IndexedJavaxNamedComponent.java @@ -0,0 +1,24 @@ +/* + * Copyright 2002-2023 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 + */ +@javax.inject.Named("myIndexedJavaxNamedComponent") +public class IndexedJavaxNamedComponent { +} diff --git a/spring-context/src/test/java/example/scannable/JavaxManagedBeanComponent.java b/spring-context/src/test/java/example/scannable/JavaxManagedBeanComponent.java new file mode 100644 index 00000000000..b3029035d87 --- /dev/null +++ b/spring-context/src/test/java/example/scannable/JavaxManagedBeanComponent.java @@ -0,0 +1,24 @@ +/* + * Copyright 2002-2023 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 + */ +@javax.annotation.ManagedBean("myJavaxManagedBeanComponent") +public class JavaxManagedBeanComponent { +} diff --git a/spring-context/src/test/java/example/scannable/JavaxNamedComponent.java b/spring-context/src/test/java/example/scannable/JavaxNamedComponent.java new file mode 100644 index 00000000000..a0fe78e7429 --- /dev/null +++ b/spring-context/src/test/java/example/scannable/JavaxNamedComponent.java @@ -0,0 +1,24 @@ +/* + * Copyright 2002-2023 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 + */ +@javax.inject.Named("myJavaxNamedComponent") +public class JavaxNamedComponent { +} diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationBeanNameGeneratorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationBeanNameGeneratorTests.java index ae5a58a286e..bded664c3f9 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationBeanNameGeneratorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationBeanNameGeneratorTests.java @@ -24,6 +24,8 @@ import java.lang.annotation.Target; import example.scannable.DefaultNamedComponent; import example.scannable.JakartaManagedBeanComponent; import example.scannable.JakartaNamedComponent; +import example.scannable.JavaxManagedBeanComponent; +import example.scannable.JavaxNamedComponent; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; @@ -90,11 +92,21 @@ class AnnotationBeanNameGeneratorTests { assertGeneratedName(JakartaNamedComponent.class, "myJakartaNamedComponent"); } + @Test + void generateBeanNameWithJavaxNamedComponent() { + assertGeneratedName(JavaxNamedComponent.class, "myJavaxNamedComponent"); + } + @Test void generateBeanNameWithJakartaManagedBeanComponent() { assertGeneratedName(JakartaManagedBeanComponent.class, "myJakartaManagedBeanComponent"); } + @Test + void generateBeanNameWithJavaxManagedBeanComponent() { + assertGeneratedName(JavaxManagedBeanComponent.class, "myJavaxManagedBeanComponent"); + } + @Test void generateBeanNameWithCustomStereotypeComponent() { assertGeneratedName(DefaultNamedComponent.class, "thoreau"); 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 9c6ac57b4e8..f7880f4910d 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 @@ -29,6 +29,8 @@ import java.util.stream.Stream; import example.gh24375.AnnotatedComponent; import example.indexed.IndexedJakartaManagedBeanComponent; import example.indexed.IndexedJakartaNamedComponent; +import example.indexed.IndexedJavaxManagedBeanComponent; +import example.indexed.IndexedJavaxNamedComponent; import example.profilescan.DevComponent; import example.profilescan.ProfileAnnotatedComponent; import example.profilescan.ProfileMetaAnnotatedComponent; @@ -40,6 +42,8 @@ import example.scannable.FooService; import example.scannable.FooServiceImpl; import example.scannable.JakartaManagedBeanComponent; import example.scannable.JakartaNamedComponent; +import example.scannable.JavaxManagedBeanComponent; +import example.scannable.JavaxNamedComponent; import example.scannable.MessageBean; import example.scannable.NamedComponent; import example.scannable.NamedStubDao; @@ -100,9 +104,16 @@ class ClassPathScanningCandidateComponentProviderTests { JakartaManagedBeanComponent.class ); - private static final Set> indexedJakartaComponents = Set.of( + private static final Set> scannedJavaxComponents = Set.of( + JavaxNamedComponent.class, + JavaxManagedBeanComponent.class + ); + + private static final Set> indexedComponents = Set.of( IndexedJakartaNamedComponent.class, - IndexedJakartaManagedBeanComponent.class + IndexedJakartaManagedBeanComponent.class, + IndexedJavaxNamedComponent.class, + IndexedJavaxManagedBeanComponent.class ); @@ -111,25 +122,28 @@ class ClassPathScanningCandidateComponentProviderTests { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); provider.setResourceLoader(new DefaultResourceLoader( CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader()))); - testDefault(provider, TEST_BASE_PACKAGE, true, false); + testDefault(provider, TEST_BASE_PACKAGE, true, true, false); } @Test void defaultsWithIndex() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER)); - testDefault(provider, "example", true, true); + testDefault(provider, "example", true, true, true); } private void testDefault(ClassPathScanningCandidateComponentProvider provider, String basePackage, - boolean includeScannedJakartaComponents, boolean includeIndexedJakartaComponents) { + boolean includeScannedJakartaComponents, boolean includeScannedJavaxComponents, boolean includeIndexedComponents) { Set> expectedTypes = new HashSet<>(springComponents); if (includeScannedJakartaComponents) { expectedTypes.addAll(scannedJakartaComponents); } - if (includeIndexedJakartaComponents) { - expectedTypes.addAll(indexedJakartaComponents); + if (includeScannedJavaxComponents) { + expectedTypes.addAll(scannedJavaxComponents); + } + if (includeIndexedComponents) { + expectedTypes.addAll(indexedComponents); } Set candidates = provider.findCandidateComponents(basePackage); @@ -202,7 +216,7 @@ class ClassPathScanningCandidateComponentProviderTests { private void testCustomAnnotationTypeIncludeFilter(ClassPathScanningCandidateComponentProvider provider) { provider.addIncludeFilter(new AnnotationTypeFilter(Component.class)); - testDefault(provider, TEST_BASE_PACKAGE, false, false); + testDefault(provider, TEST_BASE_PACKAGE, false, false, false); } @Test @@ -295,7 +309,7 @@ class ClassPathScanningCandidateComponentProviderTests { Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); assertScannedBeanDefinitions(candidates); assertBeanTypes(candidates, FooServiceImpl.class, StubFooDao.class, ServiceInvocationCounter.class, - BarComponent.class, JakartaManagedBeanComponent.class); + BarComponent.class, JakartaManagedBeanComponent.class, JavaxManagedBeanComponent.class); } @Test diff --git a/spring-context/src/test/resources/example/scannable/spring.components b/spring-context/src/test/resources/example/scannable/spring.components index a859835ba72..3fdf592b196 100644 --- a/spring-context/src/test/resources/example/scannable/spring.components +++ b/spring-context/src/test/resources/example/scannable/spring.components @@ -10,5 +10,9 @@ example.scannable.ServiceInvocationCounter=org.springframework.stereotype.Compon example.scannable.sub.BarComponent=org.springframework.stereotype.Component example.scannable.JakartaManagedBeanComponent=jakarta.annotation.ManagedBean example.scannable.JakartaNamedComponent=jakarta.inject.Named +example.scannable.JavaxManagedBeanComponent=javax.annotation.ManagedBean +example.scannable.JavaxNamedComponent=javax.inject.Named example.indexed.IndexedJakartaManagedBeanComponent=jakarta.annotation.ManagedBean example.indexed.IndexedJakartaNamedComponent=jakarta.inject.Named +example.indexed.IndexedJavaxManagedBeanComponent=javax.annotation.ManagedBean +example.indexed.IndexedJavaxNamedComponent=javax.inject.Named