您现在的位置是:首页 >技术杂谈 >Spring(Bean 作用域和生命周期)网站首页技术杂谈

Spring(Bean 作用域和生命周期)

wwwllsuper 2024-06-30 00:01:02
简介Spring(Bean 作用域和生命周期)
从前⾯的课程我们可以看出 Spring 是⽤来读取和存储 Bean,因此在 Spring 中 Bean 是最核⼼的操作资源,所以接下来我们深⼊学习⼀下 Bean 对象。

1. 案例1: Bean作用域的问题

现在有一个公共的 Bean,通过给 A 用户 和 B 用户使用, 然后在使用的过程中 A 偷偷的修改了公共 Bean 的数据, 导致 B 在使用时发生了预期之外的逻辑错误

(1)公共 Bean(我们赋予它为张三所作)

/**
 * 作者:张三
 */
@Controller
public class UserBeans {
    @Bean
    public User user1(){
        User user = new User();
        user.setId(1);
        user.setName("张三");
        user.setPassword("123");
        return user;
    }
}

(2)李四去使用的时候对其公共对象进行了改名操作

/**
 * 作者:李四
 */
@Controller
public class UserController {
    @Autowired
    private User user1;

    public void getUser(){
        System.out.println("User1:" + user1);
        User user = user1;
        user.setName("李四");
        System.out.println("User:" + user);
    }

}

(3)王五在李四修改了公共对象后继续使用

/**
 * 作者:王五
 */
@Controller
public class UserAdviceController {
    @Resource
    private User user1;

    public void getUser(){
        System.out.println("王五 | User1:" + user1);
    }

}

(4)打印结果,观察起获得的公共 Bean 的值

public class APP {
    public static void main(String[] args) {
        //1.获取上下文对象
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");

        //2.获取 Bean
        UserController userController = applicationContext.getBean("userController",UserController.class);
        userController.getUser();

        UserAdviceController userAdviceController = applicationContext.getBean("userAdviceController",UserAdviceController.class);
        userAdviceController.getUser();
    }
}

得到两个张三一个李四才是我们理想的结果

这里王五我们预期是要拿到公共对象(张三)的,但是现在拿到的是李四为什么呢?

1.1 原因分析

存在这个问题的原因就是 Bean 默认情况下是 单例模式 (singleton), 也就是所有类使用的都是同一个对象, 使用同一个对象,那就是没有过多的生命周期的创建和销毁,从而可以提高性能,所以在 Spring 中 Bean 的作用域也是 单例模式的

也就是说李四拿到了张三这个对象之后,将其姓名修改为李四,也就是把全局的单例对象也改成了李四了,这个时候后面的人去拿时拿到的就是李四了不再是张三了

2. 作用域定义

限定程序中变量的可⽤范围叫做作⽤域,或者说在源代码中定义变量的某个区域就叫做作⽤域。

Bean 的作⽤域是指 Bean 在 Spring 整个框架中的某种⾏为模式,⽐如 singleton 单例作⽤域,就
表示 Bean 在整个 Spring 中只有⼀份,它是全局共享的,那么当其他⼈修改了这个值之后,那么另⼀个⼈读取到的就是被修改的值。

2.1 Bean的五种作用域

Spring 容器在初始化一个 Bean 的实例时,会同时指定该实例的作用域,Spring中有 6 种作用域

  • singleton: 单例模式 (默认,每次请求取的是同一个对象)

  • prototype: 原型模式 (多例模式,每次请求创建一个新的对象)

  • request: 请求作用域 (Spring MVC,每次请求创建一个对象)

  • session: 会话作用域 (Spring MVC,在一个会话中共享一个对象)

  • application: 全局作用域 (Spring MVC,所有人共用一个)

  • websocket:HTTP WebSocket 作⽤域
其中前两种是 spring 核⼼作⽤域,⽽后 4 种是 spring mvc 中的作⽤域

2.2 设置作用域

@Scope 标签既可以修饰⽅法也可以修饰类,@Scope 有两种设置⽅式:
1. 直接设置值:@Scope("prototype")
2. 使⽤枚举设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

在这里我们设置为原型模式每次请求创建一个新的对象 

1. 直接设置值:@Scope("prototype")  

@Controller
public class UserBeans {
    @Scope("prototype")
    @Bean
    public User user1(){
        User user = new User();
        user.setId(1);
        user.setName("张三");
        user.setPassword("123");
        return user;
    }
}

2. 使⽤枚举设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

 3. Spring执行流程和 Bean 生命周期

3.1 Spring执行流程

1.启动spring容器(启动项目)

2.读取配置文件,初始化

   a)使用 xml 直接注册 bean

   b)配置 bean 根(扫描)路径

3.将 Bean 存储到 spring 中,通过类注释进行扫描和装配

4.将 Bean 从 spring 读取出来,装配到相应的类

Bean 执行流程(Spring 执行流程)︰

启动Spring容器->实例化Bean(分配内存空间,从无到有)->Bean注册到Spring 中(存操作)->将Bean装配到需要的类中(取操作)。

3.2 Bean 生命周期

Bean生命周期:

1.实例化(给 Bean 分配内存)

2.设置属性( Bean 注入和装配)

3.Bean 初始化

    a)执行各种通知

    b)初始化的前置工作

    c)进行初始化工作两种执行方式,一种是执行 @PostConstruct,另一种是执行 init-method

    d)初始化的后置工作

4.使用 Bean

5.销毁 Bean

注解 @PostConstruct  

  •     作用:如果在生成对象时要完成某些初始化操作,并且这些初始化操作又依赖于依赖注入,那么可以使用注解 @PostConstruct 加在要初始化的方法上,那么这个初始化方法就能够在依赖注入完成后自动被调用
  •     要求:修饰一个非静态的 void() 方法
  •     调用:@PostConstruct 修饰的方法会在服务器加载 Spring框架 的时候运行,并且只会被服务器执行一次,并且在构造函数之后执行,init() 方法之前执行
  •     该注解的方法在整个 Bean 初始化中执行的顺序:Constructor(构造方法)——》@Autowired(依赖注入)——》@PostConstruct(注解的方法)——》init()方法

注解 @PreDestroy

  •     要求:修饰一个非静态的 void() 方法
  •     调用:被 @PreDestroy 修饰的方法会在服务器卸载 Servlet 的时候运行,并且只会被服务器执行一次
  •     执行顺序:构造方法 ——》@PostConstruct ——》init() 方法 ——》destroy() 方法 ——》bean 销毁
     
package com.demo.component;

import org.springframework.beans.factory.BeanNameAware;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

/**
 * Created with IntelliJ IEDA.
 * Description:
 * User:86186
 * Date:2023-05-26
 * Time:23:09
 */
@Component
public class BeanLifeComponent implements BeanNameAware {

    @Override
    public void setBeanName(String s) {
        System.out.println("执行了通知方法");
    }

    @PostConstruct
    public void postConstruct(){
        System.out.println("执行了postConstruct");
    }

    public void Init(){
        System.out.println("执行了 init-method 方法");
    }

    @PreDestroy
    public void preDestroy(){
        System.out.println("执行销毁方法");
    }
}
<?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.demo"></content:component-scan>
    <bean id="myComponent" class="com.demo.component.BeanLifeComponent"
          init-method="Init"></bean>
</beans>
package com.demo;

import com.demo.component.BeanLifeComponent;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created with IntelliJ IEDA.
 * Description:
 * User:86186
 * Date:2023-05-26
 * Time:23:14
 */
public class App2 {
    public static void main(String[] args) {
        //获得上下文用这个方法ClassPathXmlApplicationContext而不用ApplicationContext是因为ClassPathXmlApplicationContext这个中有销毁方法
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");

        BeanLifeComponent beanLifeComponent = context.getBean("myComponent",BeanLifeComponent.class);
        System.out.println("使用 Bean");

        //销毁 Bean 直接销毁容器
        context.destroy();
    }
}

 这里执行两边的原因是:我们即使用了xml又使用了@Component

先设置属性再进行初始化,作用是

如果先初始化,就会出现空引用了,所以了为了防止空引用,就要先设置属性注入,然后再进行初始化 

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。