Bean定义
Bean作为Spring框架面试中不可或缺的概念,其本质上是指代任何被Spring加载生成出来的对象。(本质上区别于Java Bean,Java Bean是对于Java类的一种规范定义。)Spring Bean代表着Spring中最小的执行单位,其加载、作用域、生命周期的管理都由Spring操作。可见Spring Bean在整个Spring框架中的重要地位。
设计目的
在了解Spring是如何管理Bean组件之前,咱们有必要了解为什么Spring需要设计出来这么一套机制。假设当前咱们是某个大家族里的公子转世,天天过着衣来伸手饭来张口的生活。在你的家里,有一位无微不至的大管家,无论你需要什么,只要跟管家说一下,他就能给你找来。
有一天,你突然饿了,于是你对着管家吩咐道:“本少爷想吃帝王蟹。”。管家听到命令后,吭哧吭哧的给你搞来了。至于管家到底是抓来的、还是买来的,作为少爷的你自然是不关注的。
与此相类似的,如果把程序员想像成少爷,那么SpringBoot就是我们忠诚的管家先生。当我们需要用容器内的对象时,只需要“告诉”Spring,Spring就能自动帮我们加载,我们则无需考虑这个Bean到底是如何加载的、什么时候回收等细节逻辑。我们只需要使用即可。由此一来,降低了使用门槛,也减少了对于细节的一些管理。
装配及注入
在了解了Spring设计Bean的目的以后,我们就可以来了解下在Spring中,我们是如何告诉Spring,我们需要一个Bean的了。以下面的MyBean类为例子,我们来一步步介绍Spring是如何管理、加载bean的。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyBean {
Integer filedA;
String fieldB;
}
开门见山的说,Spring对于Bean的装配有三种方式:xml装配、java显式配置和自动装配。
xml装配
对于xml装配来说,需要搭建一个Spring-bean.xml的文件,该文件中用于标注哪些是需要注入到spring中的类对象。如下是一个具体的实例。
xml装配配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myBean" class="com.example.demo.service.spring.MyBean">
<property name="filedA" value="11"/>
<property name="fieldB" value="xiaoaojun"/>
</bean>
</beans>
启动类:
@SneakyThrows
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-bean.xml");
System.out.println(context.getBean("myBean"));
}
Java装配
xml装配作为一种比较老的装配方式,随着Spring的升级已经逐渐被新的方式 - java装配的方式给替换掉了。经常在第三方项目中,如果我们想要注入一个容器,那么往往需要通过注解**@Configuration + @Bean**的方式进行实现。依旧是以上面的代码为例子,采用Java装配的逻辑如下所示:
@Configuration
public class MyBeanConfiguration {
@Bean(name = "myBean")
public MyBean initMyBean(){
return new MyBean();
}
}
@SpringBootApplication
@ComponentScan(basePackages = {"com.example.demo.*", "com.alibaba"}) // 需指明路径。
@Slf4j
public class DemoApplication {
@SneakyThrows
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
Object myBean = run.getBean("myBean");
System.out.println(myBean);
}
}
需要注意的点是,Spring默认是不会开启第三方的bean扫描的(这个取决于第三种方式中的自动装配机制。),如果需要对第三方的包进行扫描,那么需要采用@ComponentScan注解进行显式的指明。
自动装配
自动装配机制是SpringBoot的一大亮点之一,其主要依赖于@SpringBootApplication下的@EnableAutoConfiguration注解实现。简单来说,就是在该注解指定的目录下,通过使用@Component及其衍生注解如@Service、@Repository等,Spring就会默认将对应对象注册道容器中。具体例子如下:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class MyBean {
Integer filedA;
String fieldB;
}
@SpringBootApplication
@ComponentScan(basePackages = {"com.example.demo.*", "com.alibaba"}) // 需要显示指明路径。
@Slf4j
public class DemoApplication {
@SneakyThrows
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
Object myBean = run.getBean("myBean");
System.out.println(myBean);
}
}
自动装配的方案,遵循了“约定大于配置”的设计理念,通过约定俗成来极大减少了程序员开发的成本。在通常情况下,Spring只会默认扫描当前类路径下的组件,不会扫描其他第三方包组件。可以通过上文的@ComponentScan来扩充扫描的范围,当然也可以通过在类路径下修改META-INF/spring.factories文件,来指定对应的扫描路径。
生命周期
作用域
在了解了Bean的设计目的及其装配注入的方式后,咱们有必要对Bean的整个生命周期做一个了解。但是在了解具体的生命周期之前,我们需要了解一个概念,即容器的作用域。作用域大致有以下五种:
作用域 | 含义 |
---|---|
singleton(默认) | 将单个 bean 定义限定为每个 Spring IoC 容器的单个对象实例。 |
property | 将单个 bean 定义限定为任意数量的对象实例 |
request | 每次用户请求时,只生成一个Bean对象。 |
session | 每次Http会话建立到终止时,只能够生成一个对应的Bean实例。 |
application | 每次应用启动到终止,只维持一个对应的Bean实例对象。 |
websocket | 每次webSocket从建立链接到断开链接,只存在一个对应的Bean实例对象 |
从含义的解释上来看,作用域主要是解决Bean的作用范围的。以singleton和property来说,singleton在创建之后,springboot会保证整个上下文环境中都只存在一个该类型的bean。而如果是property情况,那么每次springboot发生加载的时候,都会新创建一个bean进行注入。
相似的,request、session则是在每次用户请求、每次会话建立都新创建bean进行注入。通过指定作用域,我们就可以判断出当前这个Bean对象的大致生命周期和作用范围。
Bean生命周期
todo?从主观上来考虑,一个Bean在容器中管理,大概需要以下这么几步:
1、调用构造方法,创建对应的Bean类。此时Bean类中的属性都是空的。
2、将Bean所依赖的一些数据,如待注入的容器等,填充到Bean对象中。
3、调用bean内的一些方法,如启动数据库链接等。同时将Bean填充到容器中存储起来,以方便应用程序获取使用。
4、如果当前不再使用该Bean对象,则调用销毁方法,将当前Bean销毁。
而这上述几步,其实也就对应着Bean生命周期:
- 实例化 Instantiation
- 属性赋值 Populate
- 初始化 Initialization(这里需要注意,初始化主要负责执行一些Bean的启动、链接方法,如连接数据库等。)
- 销毁 Destruction
同时,为了方便拓展,Spring也在特定的生命周期前后提供了接口以供拓展实现,最重要的两个实现接口就是如下两个:
- BeanPostProcessor
- InstantiationAwareBeanPostProcessor
InstantiationAwareBeanPostProcessor主要在Bean实例化、属性赋值的时候提供了拓展接口;
而BeanPostProcessor则主要在Bean初始化前后提供拓展接口。我们熟知的@PostConstruct注解,就是通过实现了BeanPostProcessor接口,来实现的后处理机制。
总体来说,Spring中bean的基本生命流程主要如下所示:
一些关键节点的代码如下所示:
实例化:org.springframework.beans.BeanWrapper#getWrappedInstance
属性填充:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
对象初始化:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition)
**对象销毁:**org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#destroySingleton
至此,一个Bean从加载到使用及销毁的流程,大体上就介绍完了。
总结
本文从Bean的定义、设计目的入手,介绍了SpringBoot中Bean机制的重要地位。同时通过Bean的装配注入机制、生命周期管理入手,剖析了SpringBoot是如何管理和处理Bean的。通过以上对Bean的介绍,相信我们可以在以后的代码开发中更加得心应手。
参考文献
spring官方文档
Spring Bean的生命周期
一文带你深入理解SpringBean生命周期之PostConstruct、PreDestroy详解