JavaEE【Spring】:Bean 作⽤域和⽣命周期

一、Bean 的作用域问题

1、案例

先定义一个 Cat 类:

public class Cat {
    private int id;
    private String name;
    private int age;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

定义一个公共的 Bean:

@Component
public class CatBeans {
    @Bean
    public Cat cat() {
        Cat cat = new Cat();
        cat.setId(1);
        cat.setName("喵喵喵");
        cat.setAge(8);
        return cat;
    }
}

小明 在使用时,修改了 Bean:

@Controller
public class ScopeController {
    @Autowired
    private Cat cat;

    public void doScope() {
        System.out.println("Do scope controller.");
        System.out.println("原数据:" + cat.toString());
        // 修改
        Cat cat2 = new Cat();
        cat2.setName("喵喵");
        System.out.println("修改之后的数据: " + cat2.toString());
    }
}

我们之后如果还要用这个 Bean:

@Controller
public class ScopeController2 {
    @Resource
    private Cat cat;

    public void doSCope(){
        System.out.println("Do scope controller 2.");
        System.out.println(cat.toString());
    }
}

测试:

public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下文对象
        ApplicationContext context =
                new ClassPathXmlApplicationContext("spring-config.xml");
                
        ScopeController scopeController =
                context.getBean("scopeController", ScopeController.class);
        scopeController.doScope();
        System.out.println();
        
        ScopeController2 scopeController2 =
                context.getBean("scopeController2", ScopeController2.class);
        scopeController2.doSCope();
        System.out.println();
    }
}

运行结果为:
在这里插入图片描述

2、分析

Spring 中 Bean 默认情况下是单例模式,即 cat 和 cat1 都指向了同一块区域,我们无论是修改 cat 还是 cat1 都会对这个 Bean 造成修改。

二、作用域定义

Bean 在 Spring 整个框架中的某种⾏为模式,⽐如 singleton 单例作⽤域,就表示 Bean 在整个 Spring 中只有⼀份。

1、Bean 的 6 种作用域

Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作⽤域。Spring有 6 种作⽤域,
最后四种是基于 Spring MVC ⽣效的(主要学习前四种 作用域):

  • singleton:单例作⽤域(默认作用域),只有一个全局对象。
  • prototype:原型作⽤域(多例作⽤域)每次访问都会创建一个新的对象。
  • request:请求作⽤域(只能在 Spring MVC 中使用),每次 HTTP 请求都会创建一个对象。
  • session:回话作⽤域(只能在 Spring MVC 中使用),每次(session)会话使用一个 bean 对象。
  • application:全局作⽤域(只能在 Spring MVC 中使用),一个 http servlet context 中共享一个对象。
  • websocket:HTTP WebSocket 作⽤域(只能在 Web Socket 中使用

下面是官方的资料:

① singleton

  • 官⽅说明:(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
  • 描述:该作⽤域下的Bean在IoC容器中只存在⼀个实例:获取Bean(即通过 applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是同⼀ 个对象。
  • 场景:通常无状态的Bean使⽤该作⽤域。⽆状态表示Bean对象的属性状态不需要更新
  • 备注:Spring默认选择该作⽤域

② prototype

  • 官⽅说明:Scopes a single bean definition to any number of object instances.
  • 描述:每次对该作⽤域下的Bean的请求都会创建新的实例:获取Bean(即通过 applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是新的 对象实例。
  • 场景:通常有状态的Bean使⽤该作⽤域

③ request

  • 官⽅说明:Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
  • 描述:每次http请求会创建新的Bean实例,类似于prototype
  • 场景:⼀次http的请求和响应的共享Bean
  • 备注:限定SpringMVC中使⽤

④ session

  • 官⽅说明:Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
  • 描述:在⼀个http session中,定义⼀个Bean实例
  • 场景:⽤户回话的共享Bean, ⽐如:记录⼀个⽤户的登陆信息
  • 备注:限定SpringMVC中使⽤

⑤ application

  • 官⽅说明:Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
  • 描述:在⼀个http servlet Context中,定义⼀个Bean实例
  • 场景:Web应⽤的上下⽂信息,⽐如:记录⼀个应⽤的共享信息
  • 备注:限定SpringMVC中使⽤

⑥ websocket

  • 官⽅说明:Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.
  • 描述:在⼀个HTTP WebSocket的⽣命周期中,定义⼀个Bean实例
  • 场景:WebSocket的每次会话中,保存了⼀个Map结构的头信息,将⽤来包裹客户端消息头。第⼀次初始化后,直到WebSocket结束都是同⼀个Bean。
  • 备注:限定Spring WebSocket中使⽤

⑦ 单例作用域(singleton)和全局作用域(application)区别

  • singleton 是 Spring Core 的作⽤域;application 是 Spring Web 中的作⽤域;
  • singleton 作⽤于 IoC 的容器,⽽ application 作⽤于 Servlet 容器。

2、设置作用域

我们可以使用 @Scope 标签来声明 Bean 的作用域,代码如下:

@Component
public class MyComponent {
//    @Scope("prototype")
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Bean(name = "c1")
    public void doComponent1() {
        System.out.println("Do user component1.");
    }
}

使用 @Scope 标签声明 Bean 作用域的两种方式:

  1. 直接输入作用域:@Scope(“prototype”)
  2. 采用枚举的方式:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

注意

  • 在使用第一种方式的时候,要注意不能拼写错误;
  • 使用第二种方式就不容易写错(idea 自带提示).

3、案例修改

通过刚刚的学习,我们知道了:可以用 @Scope 标签来声明 Bean 作用域。我们之前也分析过,出现问题的原因,即 Spring 中默认为 单例作用域(singleton),那我们只需要将公共的 Bean 的作用域修改为 多例作用域(prototype)即可,修改如下:

@Component
public class CatBeans {
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Bean
    public Cat cat() {
        Cat cat = new Cat();
        cat.setId(1);
        cat.setName("喵喵喵");
        cat.setAge(8);
        return cat;
    }
}

此时我们再运行程序,结果为:
在这里插入图片描述

三、Bean 原理分析

1、Bean 执行流程

Bean 执⾏流程(Spring 执⾏流程):

  • 启动 Spring 容器
  • 实例化 Bean(分配内存空间,从⽆到有)
  • Bean 注册到 Spring 中(存操作)
  • 将 Bean 装配到需要的类中(取操作)
    在这里插入图片描述

2、Bean 生命周期

Bean 生命周期:

  1. 实例化(≠初始化):分配内存空间
  2. 设置属性:依赖注入 DI
  3. 初始化:
    1. 执行各种通知
    2. 初始化的前置方法:xml 文件里 定义的 init-method | @PostConstruct
    3. 初始化方法
    4. 初始化的后置方法
  4. 使用 Bean
  5. 销毁 Bean

① 实例化和初始化的区别

  • 实例化:是指创建一个对象的过程。这个过程中会在堆中开辟内存,将一些非静态的方法,变量存放在里面。在程序执行的过程中,可以创建多个对象,既多次实例化。每次实例化都会开辟一块新的内存。
  • 初始化:是完成程序执行前的准备工作。在这个阶段,静态的(变量,方法,代码块)会被执行。同时在会开辟一块存储空间用来存放静态的数据。初始化只在类加载的时候执行一次。

② 生命流程的“故事”

以买房为例:

  1. 买房(实例化)
  2. 装修(设置属性)
  3. 买家具(初始化)
  4. 入住(使用 Bean)
  5. 卖方(销毁 Bean)

③ 生命周期演示

//@Component
public class BeanLifeComponent implements BeanNameAware {

    public void setBeanName(String s) {
        System.out.println("执行了 Bean Name 通知:" +
                s);
    }

    /**
     * 方法名随意定义
     * xml中 init-method 指定的方法
     */
    public void initMethod(){
        System.out.println("执行了 init-method 方法");
    }

    /**
     * 方法名随意定义
     */
    @PostConstruct
    public void myPostConstruct(){
        System.out.println("执行了 PostConstruct 方法");
    }

    /**
     * 销毁前执行方法
     */
    @PreDestroy
    public void myPreDestroy(){
        System.out.println("执行了 PreDestroy 方法");
    }

    public void run(){
        System.out.println("执行了 run 方法");
    }
}

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.wzr"></content:component-scan>

    <bean id="beanlife" class="com.wzr.controller.BeanLifeComponent"
          init-method="initMethod"></bean>
</beans>

调用类:

public class App {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("spring-config.xml");
        BeanLifeComponent component =
                context.getBean("beanlife",BeanLifeComponent.class);
        component.run();
        context.destroy();
    }
}

在这里插入图片描述

④ 思考:为什么要先设置属性在进行初始化呢?

这就是因为,我们初始化过程中,有很多的方法,这些方法可能会调用某个属性,如果没有设置属性,就会报错(空指向异常)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WE-ubytt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值