感兴趣的话,可以看我另外一篇关于 Bean 的文章:【Java基础】Spring 中 Bean 的理解与使用
Bean 作为 Spring 框架面试中不可或缺的概念,其本质上是指代任何被 Spring 加载生成出来的对象。(本质上区别于 Java Bean,Java Bean 是对于 Java 类的一种规范定义。)Spring Bean 代表着 Spring 中最小的执行单位,其加载、作用域、生命周期的管理都由 Spring 操作。可见 Spring Bean 在整个 Spring 框架中的重要地位。
在了解 Spring 是如何管理 Bean 组件之前,咋们有必要了解为什么 Spring 需要设计出来这么一套机制。假设当前咋们是某个大家族里的公子转世,天天过着衣来伸手饭来张口的生活。在你的家里有一位无微不至的大管家,无论你需要什么,只要跟管家说一下,他就能给你找来。
有一天,你突然饿了,于是你对着管家吩咐道:“本少爷想吃帝王蟹。”。管家听到命令后,吭哧吭哧的给你搞来了。至于管家到底是抓来的、还是买来的,作为少爷的你自然是不关注的。
与此相类似的,如果把程序员想象成少爷,那么 SpringBoot 就是我们忠诚的管家先生。当我们需要用容器内的对象时,只需要“告诉” Spring,Spring 就能自动帮我们加载,我们则无需考虑这个 Bean 到底是如何加载的、什么时候回收等细节逻辑。我们只需要使用即可。由此一来,降低了使用门槛,也减少了对于细节的一些管理。
@Component // 默认为单例
public class MyBean {
// 代码...
}
-------------------------------------
@Component
@Scope("prototype")
public class MyPrototypeBean {
// 代码...
}
@Component
public class MyService {
@Autowired
private MyBean myBean;
// 使用myBean的代码...
}
总的来说,Bean 是 Spring 框架中被实例化、管理和维护的对象。通过在类上使用 ‘@Component’ 注解或其派生注解,将一个类声明为 Bean,并将其交给 Spring 容器处理。Spring 容器负责实例化、管理和维护 Bean 的生命周期和依赖关系。通过依赖注入和自动装配,应用程序可以方便的使用和管理 Bean。
在了解了 Spring 设计 Bean 的目的以后,我们就可以来了解下在 Spring 中,我们是如何告诉 Spring,我们需要一个 Bean 的了。以下面的 MyBean 类为例子,我们来一步步介绍 Spring 是如何管理、加载 bean 的。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyBean {
Integer filedA;
Integer fieldB;
}
开门见山的说,Spring 对于 Bean 的装配有三种方式:xml 装配、Java 显式配置和自动装配。
xml装配就不介绍了,因为是比较老的装配方式了
经常在第三方项目中,如果我们想要注入一个容器,那么往往需要通过注解 @Configuration + @Bean 的方式进行实现。如下所示:
@Configuration
public class MyBeanConfiguration {
@Bean(name = "myBean")
public MyBean initMyBean() {
return new MyBean();
}
}
-------------------------------------------------
@SpringBootApplication
@ComponentScan(basePackages = {"com.example.demo.*", "com.alibaba"}) // 需指明路径。
public class emptyDemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(emptyDemoApplication.class, args);
Object myBean = context.getBean("myBean");
System.out.println(myBean); // MyBean(filedA=null, fieldB=null)
}
}
需要注意的点是,Spring 默认是不会开启第三方的 bean 扫描的(这个取决于下面一种的自动装配机制。),如果需要对第三方的包进行扫描,那么需要采用 @ComponentScan 注解进行显式的指明。
自动装配机制是 SpringBoot 的一大亮点之一,其主要依赖于 @SpringBootApplication 下的 @EnableAutoConfiguration 注解(该注解在 @SpringBootApplication 注解里面,看源码可以看到)实现。简单来说,就是在该注解指定的目录下,通过使用 @Component 及其衍生注解如 @Service、@Repository 等,Spring 就会默认将对应对象注册到容器中。具体例子如下:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class MyBean {
Integer filedA;
Integer fieldB;
}
-------------------------------------------------
@SpringBootApplication
@ComponentScan(basePackages = {"com.example.demo.*", "com.alibaba"}) // 需指明路径。
public class emptyDemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(emptyDemoApplication.class, args);
Object myBean = context.getBean("myBean");
System.out.println(myBean); // MyBean(filedA=null, fieldB=null)
}
}
自动装配的方案,遵循了“约定大于配置”的设计理念,通过约定俗成来极大减少了程序员开发的成本。在通常情况下,Spring 只会默认扫描当前类路径下的组件,不会扫描其它第三方包组件。可以通过上文的 @ComponentScan 来扩充扫描的范围。
在了解了 Bean 的设计目的及其装配注入的方式后,咋们有必要对 Bean 的整个生命周期做一个了解。但是在了解具体的生命周期之前,我们需要了解一个概念,即容器的作用域。作用域大致有以下五种:
作用域 | 含义 |
singleton(默认) | 将单个 bean 定义限定为每个 Spring IoC 容器的单个对象实例。 |
prototype | 将单个 bean 定义限定为任意数量的对象实例 |
request | 每次用户请求时,只生成一个 Bean 对象 |
session | 每次 Http 会话建立到终止时,只能够生成一个对应的 Bean 实例 |
application | 每次应用启动到终止,只维持一个对应的 Bean 实例对象 |
|
每次 webSocket 从建立链接到断开链接,只存在一个对应的 Bean 实例对象 |
从含义的解释上来看,作用域主要是解决 Bean 的作用范围的。以 singleton 和 prototype 来说,singleton 在创建之后,springboot 会保证整个上下文环境中都只存在一个该类型的 bean。而如果是 prototype 情况,那么每次 springboot 发生加载的时候,都会新创建一个 bean 进行注入。
相似的,request、session则是在每次用户请求、每次会话建立都新创建 bean 进行注入。通过指定作用域,我们就可以判断出当前这个 Bean 对象的大致生命周期和作用范围。
从主观上来考虑,一个 Bean 在容器中管理,大概需要以下这么几步:
而这上述几步,其实也就对应着 Bean 生命周期:
实例化和初始化的区别:实例化是创建对象的过程,而初始化是为对象设置属性、注入依赖以及调用特定方法来使其准备好执行操作的过程。在 Spring 框架中,实例化和初始化都是由容器来管理的,可以通过配置文件或者注解来指定 Bean 的创建和初始化过程。
同时,为了方便拓展,Spring 也在特定的生命周期前后提供了接口以供拓展实现,最重要的两个实现接口就是如下两个:
InstantiationAwareBeanPostProcessor 主要在 Bean 实例化、属性赋值的时候提供了拓展接口;
而 BeanPostProcessor 则主要在 Bean 初始化前后提供拓展接口。我们熟知的 @PostConstruct 注解,就是通过实现了 BeanPostProcessor 接口,来实现的后处理机制。
总体来说,Spring 中 bean 的基本生命流程主要如下所示: