Spring 中 Bean 的作用域与生命周期

383 阅读5分钟

1. Bean 的作用域

1.1 什么是 Bean 的作用域?

(您可能需要知道如何用注解存取对象👉)Spring 中用注解来存储和注入 Bean 对象 - 掘金 (juejin.cn)

  Bean 的作用域是指 BeanSpring 整个框架中的某种行为模式,Bean的作用域用于定义 在IoC容器中创建的Bean实例的生命周期范围。

  案例:我们创建如下的类

image.png

User: @Getter@Setter@ToStringLombok 插件以及依赖提供,我们就不用写getsettoSpring方法了(怎么配置,后文介绍)。

@Getter
@Setter
@ToString
public class User {
    private int id;
    private String name;
}

UserBeans:

@Component
public class UserBeans {

    //创建一个Bean,并把它放入Spring容器中。
    @Bean
    public User user(){
        User user = new User();
        user.setId(1);
        user.setName("小明");
        return user;
    }
}

UserController:

@Controller
public class UserController {

    @Autowired
    private User user;//注入User,此时 User 是“小明”

    public void print(){
        System.out.println("user--> " + user);
        //修改 myUser
        User myUser = user;
        myUser.setName("张三");
        System.out.println("myUser--> " + myUser);
        System.out.println("user--> " + user);
    }
}

UserController2:

@Controller
public class UserController2 {
    @Autowired
    private User user;//注入

    public void print(){
        System.out.println("user--> "+user);//目的:想得到“小明”
    }
}

Main:

public class Main {
    public static void main(String[] args) {
        //获取Spring容器
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");

        UserController userController = context.getBean("userController",UserController.class);
        userController.print();

        System.out.println("------------------------------------------------------------------------");

        UserController2 userController2 = context.getBean("userController2", UserController2.class);
        userController2.print();
    }
}

结果:

image.png

  可以看到,User在全局下是同一个对象,换言之,此处的BeanSpring容器中只有一份。在任何一个地方修改此处的Bean,它的值就会改变;但是我们预期的结果是,公共 Bean 可以在各自的类中被修改,但不能影响到其他类,显然上面的代码违背了我们的预期,这就是作用域的体现。

1.1.1 Lombok 的配置

  第一步:将下面的代码赋值到pom.xml文件中

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.26</version>
    <scope>provided</scope>
</dependency>
image.png

  第二步:在插件商店中下载“Lombok”

image.png

  第三步:加注解

注解名称作用
@Getter自动生成 getter 方法
@Setter自动生成 setter 方法
@ToString自动生成 toString() 方法
@EqualsAndHashCode自动生成 equals()hashCode() 方法
@NoArgsConstructor自动生成无参构造函数
@AllArgsConstructor自动生成包含所有参数的构造函数
@RequiredArgsConstructor自动生成包含必需参数的构造函数
@Data自动生成 toString()equals()hashCode()gettersetter 方法

1.2 Bean 的 6 种作用域

  1. Singleton:单例作用域(默认),整个应用中只有一个 Bean 实例,每次请求时都返回同一个实例。

  2. Prototype:原型作用域,每次请求时都会创建一个新的 Bean 实例,即每次使用该 Bean 时,Spring 都会创建一个新的实例。

  3. Request:请求作用域,每次 HTTP 请求都会创建一个新的 Bean 实例,该作用域仅适用于 WebApplicationContext 环境。

  4. Session:会话作用域,每个 HTTP Session 都会创建一个新的 Bean 实例,该作用域仅适用于 WebApplicationContext 环境。

  5. Application:应用作用域,整个应用程序中只有一个 Bean 实例,该作用域仅适用于 ServletContext 环境。

  6. WebSocket:WebSocket 作用域,每个 WebSocket 连接都会创建一个新的 Bean 实例,该作用域仅适用于 WebApplicationContext 环境。

  需要注意的是,RequestSessionWebSocket 这三种作用域只适用于 WebApplicationContext(Spring MVC) 环境,即只能在 Web 应用程序中使用。而 SingletonPrototype``` 和 ``Application 三种作用域适用于所有环境。

(后 4 种状态是 Spring MVC 中的值,在普通的 Spring 项⽬中只有前两种)

1.3 自定义作用域

  还是用上面的案例,自定义作用域要用到@Scope注解,我们要在 Bean 存入容器的位置设置:

@Component
public class UserBeans {
    
    //@Scope("prototye") 也可以这么写。
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)//设置为 Prototype 模式
    @Bean
    public User user(){
        User user = new User();
        user.setId(1);
        user.setName("小明");
        return user;
    }
}

结果:

image.png

现在已经成功了。

2. Bean 的生命周期

2.1 Spring 的执行流程

  这里仅了解大概的流程:

Bean的作用域.drawio.png

启动 Spring 容器 -> 实例化 Bean -> Bean 注册到 Spring 中 -> 将 Bean 装配到需要的类中(注入)。

2.2 Bean 的生命周期

  在Spring框架中,一个Bean的生命周期可以分为以下几个阶段:

  1. 实例化 Bean:当Spring容器加载配置文件并初始化时,它会根据Bean定义创建Bean的实例。
  2. 属性赋值:容器会将Bean的属性注入到相应的属性中,这些属性可能是基本类型、集合或其他Bean。
  3. Bean 初始化
    • 各种通知:实现了各种 Aware 通知的⽅法,如 BeanNameAware、BeanFactoryAware、 ApplicationContextAware 的接口⽅法;
    • 执行初始化前置方法;
    • 执行初始化方法;
      • 注解方式:@PostConstruct
      • xml方式:init-method 方法
    • 执行初始化后置方法;
  4. 使用 Bean
  5. 销毁 Bean:如果 Bean 实现了 DisposableBean 接口,容器将调用它的 destroy() 方法;或者 Bean 可以定义一个自定义销毁方法(xml方式),使用 destroy-method 属性指定该方法;或者用 @PreDestroy 注解的方法。

  前置方法和后置方法通常是指实现了 BeanPostProcessor 接口的类中的方法。具体来说,BeanPostProcessor接口定义了两个方法postProcessBeforeInitialization()postProcessAfterInitialization()。前者在Bean初始化之前调用,后者在Bean初始化之后调用。

2.3 通过案例来看生命周期

  我们创建如下的类。

image.png

spring-config.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"
       xmlns:content="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 加上这一行,因为要用到注解 -->
    <content:component-scan base-package="com.spring.demo.component"></content:component-scan>
    <!--  这里用了 init-method="init" 来指定初始化方法 -->
    <bean id="beanComponent" class="com.spring.demo.component.BeanComponent" init-method="init"></bean>
</beans>

BeanComponent

public class BeanComponent implements BeanNameAware {
    //通知方法
    @Override
    public void setBeanName(String s) {
        System.out.println("执行了通知 BeanName: " + s);
    }

    //初始化方法(用xml的方式)
    public void init(){
        System.out.println("用xml的方式->初始化方法");
    }

    //用注解的方式初始化方法
    @PostConstruct
    public void doPostConstruct(){
        System.out.println("用注解的方式->初始化方法");
    }

    public void hi(){
        System.out.println("执行了hi()->Hello!");
    }

    //销毁 用注解的方式
    @PreDestroy
    public void doPreDestroy(){
        System.out.println("销毁:doPreDestroy");
    }
}

Main

public class Main {
    public static void main(String[] args) {
        //为什么是 ClassPathXmlApplicationContext? 因为它提供了destroy方法。
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        BeanComponent beanComponent = context.getBean("beanComponent",BeanComponent.class);
        //调用hi方法
        beanComponent.hi();
        //销毁 Bean 对象
        context.destroy();
    }
}

结果:

image.png

(ps:如有错误,请在评论区指出,谢谢🌹)