目录
- 1 什么是组件扫描
- 2 何时使用组件扫描
- 3 扫描整个包`basePackages`与 includeFilters
- 4 Spring boot 的 Bean 生命周期
- 4.1 生命周期
- 4.2 Bean 生命周期
- 4.3 周期各个阶段
首先,我想先为你介绍一下“Spring”,这是一个开放源代码的设计模式解决方案和轻量级的反转控制(IoC)和面向切面(AOP)的容器框架。在这个框架中,有一个重要的概念叫做“组件扫描”。
1 什么是组件扫描
为了进行依赖注入,Spring 创建了一个所谓的应用程序上下文。在启动期间,Spring 实例化对象并将它们添加到应用程序上下文中。应用程序上下文中的对象称为“Spring beans”或“组件”。Spring 解决了 Spring bean 之间的依赖关系,并将 Spring bean 注入到其他 Spring bean 的字段或构造函数中。
那么,什么是组件扫描呢?组件扫描就是Spring框架用来发现并自动注册你的应用程序中的bean的方式。这些bean可以是任何用@Component,@Controller,@Service,@Repository或者其他注解标记的类。Spring框架会扫描这些标记,并在应用程序的上下文中创建和管理这些bean。
1. 启动 Spring 应用
|
2. 查找所有的 `@Configuration` 类
|
3. 找到 `ExplicitScan` 类
|
4. 发现 `@ComponentScan` 注解,读取 `basePackages` 属性
|
5. 从 `io.reflectoring.vehicles` 包开始,扫描这个包和它的所有子包
|
6. 对每个找到的类,检查是否存在 `@Component`、`@Service`、`@Repository`、`@Controller`等 Spring 注解
|
7. 对于带有这些注解的类,将它们注册为 Spring Beans
在类路径中搜索应该对应用程序上下文有贡献的类的过程称为组件扫描。
@Component
这是一个通用的构造型注释,用于指示该类是 Spring 管理的组件。其他刻板印象是@Component
.
@Controller
这表明带注释的类是一个 Spring 管理的控制器,它提供带注释的方法@RequestMapping
来响应 Web 请求。
Spring 4.0 引入了@RestController
结合了@Controller
和 的注释@ResponseBody
,使得创建返回 JSON 对象-的 RESTful 服务变得容易。
@Service
我们可以将@Service
构造型用于包含业务逻辑的类或来自服务层的类。
@Repository
我们可以使用@Repository
负责提供对数据库实体的访问的 DAO 类的构造型。
如果我们使用 Spring Data 来管理数据库操作,那么我们应该使用 Spring Data Repository 接口而不是构建我们自己的@Repository
-annotated 类。
2 何时使用组件扫描
组件扫描的实现是通过@ComponentScan注解来实现的。如果你的应用是一个Spring Boot应用,那么,包含Spring Boot应用类的包以及其下的所有包都会被一个隐式的组件扫描所覆盖。这是因为Spring Boot的@SpringBootApplication
注解包含了@Configuration
, @ComponentScan
, 和@EnableAutoConfiguration
三个注解。
默认情况下,@ComponentScan
注解会扫描当前包以及其所有子包的组件。所以,如果你的应用程序的包结构没有变化,你不需要显式地进行组件扫描。注意,如果你在默认的包中指定了带有@Configuration
注解的类,Spring会扫描类路径中所有JAR文件的所有类。这种情况不是很推荐的,如果项目jar包的数量很巨大那么注解会扫描的时间会很长,导致启动的时间会很慢。
我们利用@ComponentScan
和@Configuration
注解引导 Spring 扫描带有各种构造型注解的类。注解@ComponentScan
有多个属性,这些属性可以根据我们的需求进行调整以获得预期的扫描行为。
我们将使用 的ApplicationContext
方法getBeanDefinitionNames()
来检查已成功扫描并添加到应用程序上下文的 beans 列表。
@Component
class BeanViewer {
private final Logger LOG = LoggerFactory.getLogger(getClass());
@EventListener
public void showBeansRegistered(ApplicationReadyEvent event) {
String[] beanNames = event.getApplicationContext()
.getBeanDefinitionNames();
for(String beanName: beanNames) {
LOG.info("{}", beanName);
}
}
}
以上BeanViewer
将打印在应用程序上下文中注册的所有 beans。这将帮助我们检查我们的组件是否正确加载。
例子1 同一个包下:
让我们用一个实例来解释这个过程。假设我们有一个应用程序,其结构如下:
|- com.example.demo (main package)
|- DemoApplication.java (Spring Boot Application class)
|- UserService.java (@Service annotated class)
|- BeanViewer.java
当我们启动这个 Spring Boot 应用时,UserService
类会被自动扫描并作为一个 Spring bean 注册到应用程序上下文中,因为它位于 DemoApplication.java
同一个包或其子包下。
例子2 不在同包下:
现在,假设我们有另一个包,它并不在 DemoApplication.java
的包或子包下:
|- com.example.demo
|- DemoApplication.java
|- UserService.java
|- com.example.utils
|- UtilityService.java (@Service annotated class)
在这种情况下,UtilityService
类不会被默认的组件扫描机制捕捉到。
为了让 Spring 扫描并注册这个类,我们需要在 DemoApplication.java
类或任何其他的配置类上添加 @ComponentScan
注解,并指定 com.example.utils
作为需要扫描的包。
@SpringBootApplication
@ComponentScan(basePackages = "com.example.utils")
public class DemoApplication {
// ...
}
当然,@ComponentScan
注解也支持更多的定制,@ComponentScan
注解提供了更多的定制化选项让我们看一下它可以用来修改其行为的注释的属性:
-
basePackages
:获取应扫描组件的包名称列表。 -
basePackageClasses
:获取应扫描其包的类的列表。 -
includeFilters
:使我们能够指定应扫描哪些类型的组件。 -
excludeFilters
: 这是相反的includeFilters
。我们可以在扫描时根据条件指定条件来忽略某些组件。 -
useDefaultFilters
:如果为真,它会启用自动检测带有任何构造型注释的类。如果为 false,则将包含属于includeFilters
和定义的过滤条件的组件。excludeFilters
在控制组件扫描时,需要对此类配置项有深入的理解,这样可以帮助我们创建出优雅和高效的 Spring 应用。
3 扫描整个包basePackages
与 includeFilters
在了解了如何使用Spring的@ComponentScan
注解,我们可以开始深入探索其高级选项,例如定制包扫描以及包含和排除过滤器。
首先,我们创建一个ExplicitScan
类,并将其放在主应用程序包io.reflectoring.componentscan
中。这样,该类就会被Spring的默认组件扫描捕获。然后,我们在该类中使用@ComponentScan
注解,并通过basePackages
属性指定另一个包io.reflectoring.vehicles
作为扫描目标:
package io.reflectoring.componentscan;
@Configuration
@ComponentScan(basePackages= "io.reflectoring.vehicles")
public class ExplicitScan {
}
运行这个应用程序,我们可以看到vehicles
包中的所有组件都被成功地注册到了应用程序的上下文中。其中,BeanViewer
类的日志输出就是这个结果的直接证明。
includeFilters
那么,如果我们只希望包含某个特定类型的组件呢?比如,我们只希望包含扩展了Zht
类的组件。这时,我们就可以使用includeFilters
属性并配合FilterType.ASSIGNABLE_TYPE
过滤器来实现这个需求:
@Configuration
@ComponentScan(basePackages= "io.reflectoring.vehicles",
includeFilters=
@ComponentScan.Filter(
type=FilterType.ASSIGNABLE_TYPE,
classes=Zht.class),
useDefaultFilters=false)
public class ExplicitScan {
}
在这个例子中,我们修改了ExplicitScan
类以包含扩展了Zht
类的组件,同时设置useDefaultFilters
为false
以禁用默认的过滤器。运行这个应用程序,只有继承Zht
类的组件会被扫描到,因为它们都扩展了Zht
类。
其他可用的过滤器类型是:
-
ANNOTATION
:仅匹配具有特定构造型注释的类。 -
ASPECTJ
: 使用 AspectJ 类型模式表达式匹配类 -
ASSIGNABLE_TYPE
:匹配扩展或实现此类或接口的类。 -
REGEX
:使用包名称的正则表达式匹配类。
在上面的例子中,我们修改了我们的ExplicitScan
类以includeFilters
包含扩展的组件Car.class
,我们正在改变useDefaultFilters = false
以便只应用我们的特定过滤器。
excludeFilters
另一方面,我们也可以使用excludeFilters
属性来排除一些我们不希望被组件扫描捕获的类。同样,我们需要配合FilterType.ASSIGNABLE_TYPE
过滤器来实现这个需求:
@Configuration
@ComponentScan(basePackages= "io.reflectoring.vehicles",
excludeFilters=
@ComponentScan.Filter(
type=FilterType.ASSIGNABLE_TYPE,
classes=Car.class))
public class ExplicitScan {
}
在这个例子中,我们没有设置useDefaultFilters
为false
,所以默认的过滤器仍然会被应用。运行这个应用程序,继承Zht
类的组件会被排除在了组件扫描之外。
最后,我想强调一下,@ComponentScan
注解是一个强大的工具,但也需要谨慎使用。否则,它可能会让你的应用程序的组成规则变得混乱。为了保持清晰和明确的规则,一个好的做法是在一个显式导入的@Configuration
类中使用@ComponentScan
注解,并且仅仅自动扫描这个类所在的包。这样,我们就可以更好地管理和控制我们的应用程序上下文了
4 Spring boot 的 Bean 生命周期
现在开始我们称,在创建、编排和销毁ApplicationContext
方面受 Spring 控制的每个对象都称为 Spring Bean。
通过上面的介绍我们知道定义 Spring bean 的最常见方法是使用注解@Component
:
@Component
class MySpringBean {
...
}
如果启用了 Spring boot的组件扫描,则会将一个对象MySpringBean
添加到应用程序上下文中。
另一种方法创建Spring bean的方法式在@Configuration注解类中创建一个@Bean注解方法来返回MySpringBean类的对象,让这个类对象变为spring bean。
@Configuration
class MySpringConfiguration {
@Bean
public MySpringBean mySpringBean() {
return new MySpringBean();
}
}
4.1 生命周期
当我们看到 Spring bean 的生命周期时,生命周期时分为对象实例化到销毁的许多阶段。为了方便大家理解,我们将它们分为创建和销毁阶段。
Bean 创建阶段
- **实例化:**这是 bean 的一切开始的地方。Spring 实例化 bean 对象就像我们手动创建 Java 对象实例一样。
- **填充属性:**实例化对象后,Spring 扫描实现接口的 bean
Aware
并开始设置相关属性。 - 预初始化: Spring
BeanPostProcessor
在此阶段开始运行。这些postProcessBeforeInitialization()
方法完成了他们的工作。此外,@PostConstruct
带注释的方法会在它们之后运行。 - AfterPropertiesSet: Spring 执行
afterPropertiesSet()
实现InitializingBean
. - 自定义初始化:
initMethod
Spring 触发我们在注释属性中定义的初始化方法@Bean
。 - 初始化后: Spring 的
BeanPostProcessor
s 第二次起作用。此阶段触发postProcessAfterInitialization()
方法。
Bean 销毁阶段
- Pre-Destroy: Spring
@PreDestroy
在这个阶段触发带注解的方法。 - 销毁: Spring 执行实现
destroy()
的方法DisposableBean
。 - 自定义销毁:
destroyMethod
我们可以使用注释中的属性定义自定义销毁挂钩@Bean
,Spring 在最后阶段运行它们。
Spring Bean生命周期
1. 实例化 Bean
|
2. 填充属性
|
3. 预初始化 - `postProcessBeforeInitialization()` 方法
|
4. 执行 `@PostConstruct` 注解的方法
|
5. 调用 `afterPropertiesSet()` 方法(如果Bean实现了`InitializingBean`接口)
|
6. 执行自定义初始化方法(如果在 `@Bean` 注解中定义了 `initMethod` 属性)
|
7. 初始化后 - `postProcessAfterInitialization()` 方法
|
V
(在这里, Bean处于完全初始化和可用的状态, 直到它被销毁)
|
8. 调用 `@PreDestroy` 注解的方法
|
9. 执行 `destroy()` 方法(如果Bean实现了`DisposableBean`接口)
|
10. 执行自定义销毁方法(如果在 `@Bean` 注解中定义了 `destroyMethod` 属性)
|
V
(在这里, Bean被完全销毁, 并从Spring应用上下文中移除)
1 启动接口
我们可以实现 Spring 的InitializingBean
接口中的afterPropertiesSet()
方法启动Spring bean 生命周期。
@Component
class MySpringBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
}
}
2 销毁接口
同样的,我们可以实现DisposableBean
接口的destroy()
方法销毁阶段的方法。
@Component
class MySpringBean implements DisposableBean {
@Override
public void destroy() {
//...
}
}
3 JSR-250 注释启动与销毁
我们可以使用Spring JSR-250标准中的@PostConstruct和
@PreDestroy方法,将它们挂钩到预初始化和销毁阶段。
@Component
class MySpringBean {
@PostConstruct
public void postConstruct() {
}
@PreDestroy
public void preDestroy() {
}
}
4 使用@Bean
注释的属性
此外,我们在创建 Spring bean 时,可以在 @Bean 配置中设置注解的initMethod
属性和destroyMethod属性来设置初始化启动和销毁。
@Configuration
class MySpringConfiguration {
@Bean(initMethod = "onInitialize", destroyMethod = "onDestroy")
public MySpringBean mySpringBean() {
return new MySpringBean();
}
}
我们应该注意,如果我们的 bean 中有一个名为close()
or的公共方法shutdown()
,那么默认情况下它会自动触发销毁回调。
@Component
class MySpringBean {
public void close() {
}
}
如果我们不希望这种默认销毁行为,我们可以通过设置禁用destroyMethod=""来关闭它。
@Configuration
class MySpringConfiguration {
@Bean(destroyMethod = "")
public MySpringBean mySpringBean() {
return new MySpringBean();
}
}
5 BeanPostProcessor 设置
我们可以利用该BeanPostProcessor
接口在 Spring bean 初始化之前或之后运行任何自定义操作,来实现启动和销毁bean。
class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
}
6 使用Aware
接口
进入生命周期的另一种方法是使用接口Aware
来实现启动与销毁。
@Component
class MySpringBean implements BeanNameAware, ApplicationContextAware {
@Override
public void setBeanName(String name) {
//...
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
//...
}
}
4.2 Bean 生命周期
当我们需要调整我们的软件以满足新的需求时,寻找最佳实践以维持我们代码库的长期可维护性是至关重要的。在 Spring 框架中,大多数情况下,挂钩到 bean 生命周期是扩展我们应用程序的好方法。让我们深入了解一下这个过程。
1 BeanNameAware 接口
一种常见的情况是在运行时获取 bean 的属性,如 bean 的名称。例如,当我们需要在日志中记录 bean 的创建:
@Component
class NamedSpringBean implements BeanNameAware {
Logger logger = LoggerFactory.getLogger(NamedSpringBean.class);
public void setBeanName(String name) {
System.out.println(name + "获得bean名字");
}
}
在这个例子中,我们通过实现 BeanNameAware
接口,能够在 Spring 创建 bean 时获取其名称,从而可以在日志中记录它。
2 动态改变bean实例
我们可能需要以编程方式定义 Spring bean。这在我们需要动态地创建和更改 bean 实例的情况下特别有用。例如,我们可以创建一个 IpToLocationService
,这个服务可以动态地更新 IpDatabaseRepository
以便它始终使用最新版本的数据库:
@Service
class IpToLocationService implements BeanFactoryAware {
DefaultListableBeanFactory listableBeanFactory;
IpDatabaseRepository ipDatabaseRepository;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
listableBeanFactory = (DefaultListableBeanFactory) beanFactory;
updateIpDatabase();
}
public void updateIpDatabase(){
}
}
在这个例子中,我们通过实现 BeanFactoryAware
接口来获取 BeanFactory
实例,此外我们在方法中updateIpDatabase()
获取实例后立即调用我们的方法。因此,我们可以在 Spring 上下文启动时创建 bean 的第一个实例。BeanFactory``setBeanFactory()``IpDatabaseRepository
另一种情况是从 Spring 上下文之外访问ApplicationContext
or实例。BeanFactory
例如,我们可能希望将 注入BeanFactory
到非 Spring 类中,以便能够访问该类中的 Spring bean 或配置。
class AutowireCapableJobFactory
extends SpringBeanJobFactory implements ApplicationContextAware {
private AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(final ApplicationContext context) {
beanFactory = context.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle)
throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
在此示例中,我们使用ApplicationContextAware
接口来访问 bean 工厂,并使用 bean 工厂自动装配Job
最初不受 Spring 管理的 bean 中的依赖项。
此外,也可以用 Spring - Jersey集成,Jersey也是常用的一种方法。
@Configuration
class JerseyConfig extends ResourceConfig {
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void registerResources() {
applicationContext.getBeansWithAnnotation(Path.class).values()
.forEach(this::register);
}
}
通过将 Jersey 标记ResourceConfig
为 Spring @Configuration
,我们注入ApplicationContext
并查找所有由 Jersey 注释的 bean @Path
,以便在应用程序启动时轻松注册它们。
4.3 周期各个阶段
下面大家可以看到Spring bean生命周期中各个阶段的执行顺序。
class MySpringBean implements BeanNameAware, ApplicationContextAware,
InitializingBean, DisposableBean {
private String message;
public void sendMessage(String message) {
this.message = message;
}
public String getMessage() {
return this.message;
}
@Override
public void setBeanName(String name) {
System.out.println("--- setBeanName executed ---");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
System.out.println("--- setApplicationContext executed ---");
}
@PostConstruct
public void postConstruct() {
System.out.println("--- @PostConstruct executed ---");
}
@Override
public void afterPropertiesSet() {
System.out.println("--- afterPropertiesSet executed ---");
}
public void initMethod() {
System.out.println("--- init-method executed ---");
}
@PreDestroy
public void preDestroy() {
System.out.println("--- @PreDestroy executed ---");
}
@Override
public void destroy() throws Exception {
System.out.println("--- destroy executed ---");
}
public void destroyMethod() {
System.out.println("--- destroy-method executed ---");
}
}
此外,我们创建了一个BeanPostProcessor
挂钩到初始化之前和之后的阶段:
class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof MySpringBean) {
System.out.println("--- postProcessBeforeInitialization executed ---");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof MySpringBean) {
System.out.println("--- postProcessAfterInitialization executed ---");
}
return bean;
}
}
接下来,我们编写一个 Spring 配置来定义我们的 bean:
@Configuration
class MySpringConfiguration {
@Bean
public MyBeanPostProcessor myBeanPostProcessor(){
return new MyBeanPostProcessor();
}
@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
public MySpringBean mySpringBean(){
return new MySpringBean();
}
}
最后,我们编写一个@SpringBootTest
来运行我们的 Spring 上下文:
@SpringBootTest
class BeanLifecycleApplicationTests {
@Autowired
public MySpringBean mySpringBean;
@Test
public void testMySpringBeanLifecycle() {
String message = "Hello World";
mySpringBean.sendMessage(message);
assertThat(mySpringBean.getMessage()).isEqualTo(message);
}
}
因此,我们的测试方法记录了生命周期阶段之间的执行顺序:
--- setBeanName executed ---
--- setApplicationContext executed ---
--- postProcessBeforeInitialization executed ---
--- @PostConstruct executed ---
--- afterPropertiesSet executed ---
--- init-method executed ---
--- postProcessAfterInitialization executed ---
...
--- @PreDestroy executed ---
--- destroy executed ---
--- destroy-method executed ---
在本文中,我们了解了 bean 生命周期阶段是什么、为什么以及我们如何挂接到 Spring 中的生命周期阶段。Spring 在 bean 生命周期中有许多阶段以及许多接收回调的方法。我们可以像在BeanPostProcessor
。尽管每个方法都有其用途,但我们应该注意使用 Spring 接口将我们的代码耦合到 Spring Framework。
另一方面,@PostConstruct
注释@PreDestroy
是 Java API 的一部分。因此,我们认为它们是接收生命周期回调的更好替代方案,因为它们甚至可以将我们的组件与 Spring 分离。