1、概览 {#1概览}
本文将带你全面了解 Spring @Component
注解及相关领域。
2、Spring ApplicationContext {#2spring-applicationcontext}
在了解 @Component
之前,首先需要了解一下 Spring ApplicationContext
。
Spring ApplicationContext
是 Spring 保存对象实例的地方,Spring 已确定这些实例将被自动管理和分发。这些实例被称为 Bean。
Spring 的一些主要功能包括 Bean 管理和依赖注入。
利用控制反转(Inversion of Control),Spring 可以从应用中收集 Bean 实例,并在适当的时候使用它们。可以在 Spring 中定义 Bean 依赖,而无需处理这些对象的设置和实例化。
使用 @Autowired
等注解将 Spring 管理的 Bean 注入应用的能力是在 Spring 中创建功能强大且可扩展代码的驱动力。
那么,如何让 Spring 来管理的 Bean 呢?可以利用 Spring 的自动 Bean 检测功能,通过在类中使用元注解(Stereotype Annotation)来实现。
3、@Component
{#3component}
@Component
是一个注解,它允许 Spring 自动检测自定义 Bean。
换句话说,无需编写任何明确的代码,Spring 就能做到:
- 扫描应用,查找注解为
@Component
的类 - 将它们实例化,并注入任何指定的依赖
- 在需要的地方注入
不过,大多数时候应该使用更专业的元(Stereotype)注解来实现这一功能。
3.1、Spring 元注解 {#31spring-元注解}
Spring 提供了一些专门的元注解:@Controller
、@Service
和 @Repository
。它们都提供了与 @Component
相同的功能。
它们的作用都是一样的,因为它们都是由 @Component
作为元注解组成的注解。它们就像 @Component
别名,在 Spring 自动检测或依赖注入之外有专门的用途和意义。
理论上,如果愿意,可以只使用 @Component
来满足我们对 Bean 自动检测的需求。反过来,也可以编写使用 @Component
的专用注解。
不过,Spring 的其他领域会专门使用 Spring 的专用注解来提供额外的自动化优势。因此,应该在大多数情况下坚持使用预定义的专用注解。
假设在 Spring Boot 项目中拥有上述每种情况的示例:
@Controller
public class ControllerExample {
}
@Service
public class ServiceExample {
}
@Repository
public class RepositoryExample {
}
@Component
public class ComponentExample {
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface CustomComponent {
}
@CustomComponent
public class CustomComponentExample {
}
可以编写一个测试,证明 Spring 能自动检测到每个 Bean 并将其添加到 ApplicationContext
中:
@SpringBootTest
@ExtendWith(SpringExtension.class)
public class ComponentUnitTest {
@Autowired
private ApplicationContext applicationContext;
@Test
public void givenInScopeComponents_whenSearchingInApplicationContext_thenFindThem() {
assertNotNull(applicationContext.getBean(ControllerExample.class));
assertNotNull(applicationContext.getBean(ServiceExample.class));
assertNotNull(applicationContext.getBean(RepositoryExample.class));
assertNotNull(applicationContext.getBean(ComponentExample.class));
assertNotNull(applicationContext.getBean(CustomComponentExample.class));
}
}
3.2、@ComponentScan
{#32componentscan}
@Component
只是一个普通的注解。该注解的作用是将 Bean 与其他对象(如 Domain 对象)区分开来。
Spring 使用 @ComponentScan
注解将它们收集到 ApplicationContext
中。
Spring Boot 中的 @SpringBootApplication
注解就是一个包含了 @ComponentScan
的注解。只要 @SpringBootApplication
类位于项目根目录,它就会默认扫描应用中定义的每个 @Component
。
如果 @SpringBootApplication
类不在项目根目录下,或者想扫描外部资源,可以显式配置 @ComponentScan
来查找指定的任何包,只要它存在于 classpath
上。
定义一个超出扫描范围的 @Component
Bean:
package com.baeldung.component.scannedscope;
@Component
public class ScannedScopeExample {
}
接着,通过 @ComponentScan
注解指定其所在的包:
package com.baeldung.component.inscope;
@SpringBootApplication
@ComponentScan({"com.baeldung.component.inscope", "com.baeldung.component.scannedscope"})
public class ComponentApplication {
//public static void main(String[] args) {...}
}
最后,可以测试一下它是别成功扫描加载到:
@Test
public void givenScannedScopeComponent_whenSearchingInApplicationContext_thenFindIt() {
assertNotNull(applicationContext.getBean(ScannedScopeExample.class));
}
当要扫描项目中的外部依赖时,这种情况更有可能发生。
3.3、@Component
的限制 {#33component-的限制}
在某些情况下,无法使用 @Component
。但仍希望指定对象成为 Spring 管理的 Bean。
在项目外的包中定义一个使用 @Component
注解的对象:
package com.baeldung.component.outsidescope;
@Component
public class OutsideScopeExample {
}
下面的测试可以证明 ApplicationContext
不包含外部包中定义的组件:
@Test
public void givenOutsideScopeComponent_whenSearchingInApplicationContext_thenFail() {
assertThrows(NoSuchBeanDefinitionException.class, () -> applicationContext.getBean(OutsideScopeExample.class));
}
另外,因为它来自第三方,可能无法编辑源码,无法添加 @Component
注解。又或者,我们想根据运行环境有条件地使用一种 Bean 实现,而不是另一种。自动检测通常就足够了,但当它无法满足需求时,可以使用 @Bean
注解。
4、@Component
和 @Bean
{#4component-和-bean}
@Bean
也是 Spring 用于在运行时收集 Bean 的注解,但它不是用在类级别上的。相反,使用 @Bean 注解方法,以便 Spring 可以将方法的结果存储为 Spring Bean。
首先,创建一个没有注解的 POJO:
public class BeanExample {
}
在 @Configuration
注解的配置类中,可以创建一个生成 Bean 的方法:
@Bean
public BeanExample beanExample() {
return new BeanExample();
}
BeanExample
可能代表一个本地类,也可能是一个外部类。这并不重要,只需要返回它的一个实例即可。
然后,编写一个测试,验证 Spring 是否加载了 Bean:
@Test
public void givenBeanComponents_whenSearchingInApplicationContext_thenFindThem() {
assertNotNull(applicationContext.getBean(BeanExample.class));
}
由于 @Component
和 @Bean
之间的差异,应该注意一些重要的影响。
@Component
是一个类级别的注解,而@Bean
是方法级别的注解,因此只有在类的源代码可编辑时,才可以使用@Component
作为选项。@Bean
始终可以使用,但它的语法更加冗长。@Component
与 Spring 的自动检测兼容,但@Bean
需要手动实例化类。- 使用
@Bean
可以将 Bean 的实例化与其类定义分离。因此,可以使用它将第三方类转化为 Spring Bean。这也意味着可以添加逻辑来决定要使用哪个实例作为 Bean。
5、总结 {#5总结}
本文介绍了 Spring @Component
注解和其他相关主题。
各种 Spring 元注解,只是 @Component
的专门版本。
@Component
注解的组件依需要被 @ComponentScan
扫描才能被实例化为 Spring Bean。
如果不能编辑源码,没法给组件类添加 @Component
注解,那么可以自定义配置类,在方法上使用 @Bean
注解手动地初始化组件类为 Spring Bean。
Ref:https://www.baeldung.com/spring-component-annotation