Bean的作用域和生命周期

1. Bean 的作用域案例

package Beans.Component;

import Model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class UserComponent {
    @Bean
    public User getUser(){
        User user=new User();
        user.setUserId(0);
        user.setUserName("张三");
        return user;
    }
}

package Beans.Controller;

import Model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserControllerA {
    @Autowired
    private User user;
    public User getUser(){
        System.out.println("Bean原name:" + user.getUserName());
        // 修改公共 Bean
        user.setUserName("王五");
        return user;
    }
}

A使用完之后修改了 User 的 name 属性

我们来看看后续用户使用 User 的情况

package Beans.Controller;

import Model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class BeanScopeControllerB {
    @Autowired
    private User user;

    public User getUser() {
        return user;
    }
}

import Beans.Controller.UserControllerA;
import Beans.Controller.UserControllerB;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        UserControllerA A = context.getBean(UserControllerA.class);
        System.out.println("修改之前:" + A.getUser());

        UserControllerB B = context.getBean(UserControllerB.class);
        System.out.println("修改之后:" + B.getUser());
    }
}

// 运行结果
Bean原name:张三
修改之前:User{userId=0, userName='王五'}
修改之后:User{userId=0, userName='王五'}

发现 A 的修改作用到了 B 上

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4GItMSpf-1651447791413)(/Users/cxf/Desktop/MarkDown/images/作用域的修改.png)]

原因分析

以上的原因是因为 Bean 的单例模式导致的,单例模式中可以很大程度提升性能,因此 Spring 的 Bean 作用域默认也是 Singleton。所以中间只要被修改,后续的都会受到影响。

作用域定义

限定代码中变量的作用范围「定义某个变量的区域就是作用域」

Bean 的单例模式修改之后会影响后续的代码使用,从作用域的角度来看会发现 Bean 的单例模式作用域是在整个Spring框架下的

2. Bean 的六种作用域

2.1 singleton「单例作用域」

运用:在普通的 Spring 项目中

描述:该作用域下的 Bean 对象在 IOC 容器中只有一份「即ApplicationContext.getBean( )获取或者@Autowired装配都是同一个 Bean」

场景:通常 无状态 的 Bean 使用该作用域。无状态:Bean 对象的属性状态不需要更新

备注:Spring 默认选择该作用域

package Beans.Component;

import Model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
public class UserComponent {
    @Scope("singleton")
    @Bean(name = {"getUser", "gU"})
    public User getUser(){
        User user=new User();
        user.setUserId(0);
        user.setUserName("张三");
        return user;
    }
}

// 运行结果
Bean原name:张三
修改之前:User{userId=0, userName='王五'}
修改之后:User{userId=0, userName='王五'}

2.2 prototype「原型作用域」

运用:在普通的 Spring 项目中

描述:每次对该作用于下的 Bean 请求都会创建新的实例「多例模式」:即ApplicationContext.getBean( )获取或者@Autowired装配都是一个新的对象实例

场景:通常 有状态 的 Bean 使用该作用域

package Beans.Component;

import Model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
public class UserComponent {
    @Scope("prototype")
    @Bean(name = {"getUser", "gU"})
    public User getUser(){
        User user=new User();
        user.setUserId(0);
        user.setUserName("张三");
        return user;
    }
}
// 运行结果
Bean原name:张三
修改之前:User{userId=0, userName='王五'}
修改之后:User{userId=0, userName='张三'}

补充一个额外的注解

package Beans.Component;

import Model.User;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
public class UserComponent {
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Bean(name = {"getUser", "gU"})
    public User getUser(){
        User user=new User();
        user.setUserId(0);
        user.setUserName("张三");
        return user;
    }
}

2.3 request「请求作用域」

运用:限定在 SpringMvc 项目中

描述:每次 HTTP 请求都会创建新的 Bean 实例,类似于 prototype

场景:一次 HTTP 请求和响应的共享 Bean

2.4 session「会话作用域」

运用:限定在 SpringMvc 项目中

描述:在一个 HTTP Session 中定一个 Bean 实例

场景:用户回话的共享 Bean,比如:记录一用户的登录信息

2.5 application「全局作用域,了解即可」

运用:限定在 SpringMvc 项目中

描述:在一个 HTTP ServleContext 中,定一个 Bean 实例

场景:Web 应用的上下文信息,比如:记录一个应用的共享信息

2.6 websocket「HTTP WebSocket 作用域,了解即可」

运用:限定在 SpringWebSocket 项目中

描述:再以一个 HTTP WebSocket 的生命周期中,保存了一个 Map 结构的头信息,将用来包裹客户端信息头。第一次初始化后,直到 WebSocket 结束都是同一个 Bean

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

  • singleton 是 Spring Core 的作用域;application 是 Spring Web 中的作用域
  • singleton 作用域 IOC 容器,而 application 作用域 Servlet 作用域

3. Bean 的原理分析

3.1 Bean 执行流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y2FzqnsC-1651447791415)(/Users/cxf/Desktop/MarkDown/images/Bean的执行流程.png)]

Bean执行流程「Spring执行流程」:启动 Spring 容器 -> 实例化 Bean「分配内存,从无到有」 -> Bean 注册到 Spring 中「存」 -> 将 Bean 装配到需要的类中「取」

3.2 Bean 生命周期

了解了 Servlet 的生命周期:每个 Servlet 都是从 Tomcat 实例化出来的对象 init() 自动调用「诞生」 -> 根据每个 HTTP 请求来进行对应的响应「应用」-> 最后结束运行的时候会调用 **destory()**自动销毁

那么 Bean 对像的生命周期又是什么呢?可以分为 5 部分

  1. 实例化 Bean「为 Bena 分配内存」
  2. 设置属性「Bean的依赖注入和装配」
  3. Bean 初始化
    • 实现了各种 Aware 通知的方法,如 BeanNameAware,BeanFaactoryAware,ApplicationContextAware 的接口方法
    • 执行 BeanPostProcessor 初始化前置方法
    • 执行 @PostConstruct 初始化方法,依赖注入操作之后被执行
    • 执行自己制定的 init-method 方法「如果有指d定方法」
    • 执行 BeanPostProcessor 初始化后置方法
  4. 使用 Bean
  5. 销毁 Bean

销毁容器的各种方法,如 @PreDestory,DIsposableBean 接口方法,destory-method

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kVD1OABB-1651447791416)(/Users/cxf/Desktop/MarkDown/images/Bean生命周期.png)]

package Beans.Component;

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

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

@Component
public class BeanLife implements BeanNameAware {
    @Override
    public void setBeanName(String s) {
        System.out.println("执行:BeanNameAware 方法" + s);
    }

    /**
     * 初始化方法1
     */
    @PostConstruct
    public void postConstruct1(){
        System.out.println("执行:PostConstruct");
    }

    /**
     * 初始化方法2
     */
    @PostConstruct
    public void init2(){
        System.out.println("执行:init-method");
    }

    /**
     * 销毁方法1
     */
    @PreDestroy
    public void preDestroy(){
        System.out.println("执行:preDestroy");
    }

    /**
     * 销毁方法2
     */
    public void myDestroy(){
        System.out.println("执行:destroy-method");
    }
}

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="Beans"></content:component-scan>
    <!--  Bean的初始化和销毁  -->
    <beans>
        <bean id="beanLife" class="Beans.Component.BeanLife" init-method="init2" destroy-method="myDestroy"></bean>
    </beans>
</beans>
import Beans.Component.BeanLife;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
        BeanLife beanLife=context.getBean(BeanLife.class);
        System.out.println("使用Bean");
        System.out.println("销毁Bean");
        context.destroy();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nq79aUOr-1651447791418)(/Users/cxf/Desktop/MarkDown/images/代码解析.png)]

这里边一个是 xml的产物,一个是 @注解的产物。描述的都是一个事情,只是形式不一样罢了。

  • 初始化: PostConstruct 是通过注解的形式执行调用,init-method 是通过配置 spring-config.xml 执行调用的「注意方法名要关联才可以被调用到」
  • 销毁: PreDestroy 是通过注解的形式执行调用,destroy-method 是通过配置 spring-config.xml 执行调用的「注意方法名要关联才可以被调用到」

3.3 实例化 VS 初始化

实例化是步骤2「先分配内存,在分配属性」,初始化「给属性赋值」是步骤3

这俩操作能不能颠倒过来呢?

那之前的代码例子来说

@Service
public class UserService {
    public UserService() {
        System.out.println("
                User Service");
    }

    public void sayHi() {
        System.out.println("User Service SayHi.");
    }
}

@Controller
public class UserController {
    @Resource
    private UserService userService;

    @PostConstruct
    public void postConstruct() {
        userService.sayHi();
        System.out.println(" User Controller ");
    }
}

如果先执行了初始化,则代码15行中的设置属性还没有生成,肯定会出现异常情况。因此必须先设置完属性在初始化

Spring 的加载过程是 先外后内:先把外部的东西准备好,在加载内部的变量

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值