如何理解Spring中的Bean

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的介绍,相信我们可以在以后的代码开发中更加得心应手。

猜你喜欢

转载自blog.csdn.net/ww2651071028/article/details/130551976