• Bean 的作用域和生命周期


    Bean 的作用域

    在使用 Bean 的时候,一个公共的 Bean,交给 A用户 和 B用户 来使用,如果 A用户 偷偷修改了 Bean 的数据,那么 B用户 拿到的数据和预期的就不一样了。

    案例

    先在 Spring 当中存储一个 User 对象:

    @Component
    public class Users {
        @Bean
        public User user1() {
            User user = new User();
            user.setId(1);
            user.setName("张三");
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    A用户 拿到对象,并作出修改:

    @Component
    public class BeanScope1 {
        @Autowired
        private User user1;
        public User getUser() {
            User user = user1;
            user.setName("李四");
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    把张三修改成了李四,再获取对象进行输出的时候:

    @Component
    public class BeanScope1 {
        @Autowired
        private User user1;
        public User getUser() {
            User user = user1;
            user.setName("李四");
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行结果如下:
    在这里插入图片描述
    产生这样的原因是因为 Bean 在 Spring 中,默认情况下是单例状态,也就是所有人的使用都是同一个对像。这样可以很好的节约资源,避免资源的浪费。

    作用域就是:Bean 在 Spring 中只有一份,它是全局共享的,那么当其他人修改了这个值之后,另外一个人读取到的就是被修改的值。

    Bean 的六种作用域

    1. singleton:单例作用域(默认)在这种模式下的 Bean 在 IoC 容器里面,只存在一个实例。
    2. prototype:原型作用域(多例模式)每次对作用域下的 Bean 请求,都会创建新的实例,然后再去获取 Bean。
    3. request:请求作用域(Spring MVC)每次 Http 请求会创建新的 Bean 实例,类似于 prototype。
    4. session:会话作用域(Spring MVC)在一个 http session 中,定义一个 Bean 实例。记录用户的登录信息。
    5. application:全局作用域(Spring MVC)更多人使用的时候,就用 application。也是单例模式,应用在 Web 应用的上下文信息。
    6. websocket:Http WebSocket 作用域(Spring WebSocket)在 WebSocket 会话中使用。

    设置作用域的时候,只需要通过 @Scope 注解就可以了

    1. 直接设置值:@Scope(“prototype”)
    2. 使用枚举设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

    默认情况下的单例作用域

    public class Test {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
            BeanScope1 beanScope1 = context.getBean(BeanScope1.class);
            User user1 = beanScope1.getUser();
            System.out.println("BeanScope1:" + user1);
    
            BeanScope2 beanScope2 = context.getBean(BeanScope2.class);
            User user2 = beanScope2.getUser();
            System.out.println("BeanScope2:" + user2);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    可以发现 User 对象全被改了,防止被改的话,就在存 User 对象之前,给它设置 @Scope

    Bean 的多例模式

    也就是为了防止出现像上面这种情况,在使用的时候已经被别人修改。

    直接通过 prototype

    @Component
    public class Users {
        @Bean(name = "user1")
        @Scope("prototype")
        public User user1() {
            User user = new User();
            user.setId(1);
            user.setName("张三");
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    代码如下:

    public class Test {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
            BeanScope1 beanScope1 = context.getBean(BeanScope1.class);
            User user1 = beanScope1.getUser();
            System.out.println("BeanScope1:" + user1);
    
            BeanScope2 beanScope2 = context.getBean(BeanScope2.class);
            User user2 = beanScope2.getUser();
            System.out.println("BeanScope2:" + user2);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    运行结果如下:
    在这里插入图片描述
    就是通过 prototype 对对象设置,每次一个请求就生成一个对象。

    通过 ConfigurableBeanFactory

    也就是设置 @Scope 的 ConfigurableBeanFactory.SCOPE_PROTOTYPE 来保证多例模式

    @Component
    public class Users {
        @Bean(name = "user1")
        @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
        public User user1() {
            User user = new User();
            user.setId(1);
            user.setName("张三");
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    运行结果如下:
    在这里插入图片描述
    仍然可以保证多例模式。

    Spring 的执行流程

    1. Spring 项目在运行的时候,会先启动容器,也就是我们在 main 方法里面写了这样的代码之后:

      在这里插入图片描述
      在这里启动容器之后,就要加载配置文件:beans.xml 了。

    2. 然后接下来就是根据配置完成 Bean 的初始化:

      在这里插入图片描述
      会通过这里的扫描路径,来存入 Bean 。将指定路径种带有五大类注解的普通类存入 Spring 当中,还有类里面带有方法注解的方法,其返回对象也存入 Spring 当中。

    3. 注册 Bean 对象到容器中,只有在包扫描的路径上的类,且使用 Spring 的注解才可以被注册到容器当中:
      在这里插入图片描述

    4. 装配 Bean 属性,也就是把 Bean 注册到其他类当中:
      在这里插入图片描述

    总的来说:先去启动容器,加载 xml 配置文件。然后,扫描五大类注解,随后,将具有五大类注解的类,存入 Spring 当中。如果 存入的过程中,存在属性的注入,就先执行属性的注入。然后,再继续执行 类 的 实例化。实例化之后,将其存入Spring 中。

    Bean 的生命周期

    生命周期就是⼀个对象从诞⽣到销毁的整个⽣命过程,我们把这个过程就叫做⼀个对象的⽣命周期。Bean 的生命周期有以下几步:

    1. 实例化 Bean(为 Bean 分配内存空间)
    2. 设置属性(Bean 注入和装配)
    3. Bean 初始化
      a)执行各种通知(各种Aware)如:BeanNameAware,BeanFactoryAware,ApplicationContextAware 的接口方法。
      b)执行 BeanPostProcessor 初始化前置方法。
      c)执行 @PostConstruct 初始化方法,依赖注入操作之后被执行。
      d)执行自己指定的 init-method 方法(如果有指定的话
      e)执行 BeanPostProcessor 初始化后置方法。
    4. 使用 Bean
    5. 销毁 Bean,通过方法来销毁容器:如 @PreDestroy、DisposableBean、destroy-method

    执行流程图
    在这里插入图片描述

    生命周期代码演示

    代码如下:

    import org.springframework.beans.factory.BeanNameAware;
    import org.springframework.stereotype.Component;
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    @Component
    public class BeanLifeComponent implements BeanNameAware {
        @PostConstruct
        public void postConstruct() {
            System.out.println("执⾏ PostConstruct()");
        }
        public void init() {
            System.out.println("执⾏ BeanLifeComponent init-method");
        }
        @PreDestroy
        public void preDestroy() {
            System.out.println("执⾏:preDestroy()");
        }
        public void setBeanName(String s) {
            System.out.println("执⾏了 setBeanName ⽅法:" + s);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    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.component">
    </content:component-scan>
    <beans>
    <bean id="beanLifeComponent"
    class="com.component.BeanLifeComponent" init-method="init"></bean>
    </beans>
    </beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    启动类:

    import com.controller.BeanLife;
    import 
    org.springframework.context.support.ClassPathXmlApplicationContext;
    public class BeanLifeTest {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext context =
                    new ClassPathXmlApplicationContext("spring-config.xml");
            BeanLife life = context.getBean(BeanLife.class);
            System.out.println("执⾏ main ⽅法");
    // 执⾏销毁⽅法
            context.destroy();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    运行结果如下:
    在这里插入图片描述
    造成两次通知,是因为 BeanLifeComponent 有注解,原先代码又配置了扫描路径,所以还会加载一次。

    现设置属性,再初始化,是为了防止还没有注入的时候,就初始化,然后空指针异常。

  • 相关阅读:
    如何学习创建和使用 Java 归档(JAR)文件
    spring事务里面开启线程插入,报错了是否会回滚?
    按键精灵中的日志、分辨率、找色逻辑、线程
    骑砍2霸主MOD开发(8)-action_sets.xml骨骼动画
    ThinkPHP v6.0.13 存在反序列化漏洞
    HFSS中激励方式学习笔记(总)
    理解JavaScript事件循环机制
    springCloud-Nacos注册中心的搭建
    华为数通方向HCIP-DataCom H12-831题库(单选题:161-180)
    一文带你了解【抽象类和接口】
  • 原文地址:https://blog.csdn.net/sjp151/article/details/126787421