Spring框架学习 -- Bean的生命周期和作用域

简介: Spring框架学习 -- Bean的生命周期和作用域


前言

       我们可以类比一下普通变量的生命周期和作用域, 大多数变量的生命周期和作用域都被限定在了花括号内 {}, 除了这个大括号, 这个变量也就会被销毁, 与之对应的内存也会被JVM回收.

       对于类中的变量, 即使是出了{}, 也仍然能被访问到.

       前面的内容是如何存储和获取Bean对象, 因此spring中最核心的资源就是Bean对象, 那么Bean的作用域是怎么样的呢?

       下面我们通过一个案例来演示一下:

案例

       假设现在有一个公共的Bean对象提供给用户A和用户B使用, 然后在使用的图中,用户A 却悄咪咪的修改了公共的Bean资源, 导致B在使用的时候发生了预期之外的逻辑错误.

       下面是代码实现:

       首先我们实现Bean对象所属的类:

public class User {
    private int age;
    private String name;
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}

       创建Bean对象:

@Component
public class Users {
    @Bean
    public User getUser() {
        User user = new User();
        user.setAge(18);
        user.setName("如花");
        return user;
    }
}

       属性注入:

// controller1 相当于A
 
@Controller
public class Controller1 {
    @Autowired
    public User user;
 
    public void useUser() {
        User user1 = user;
        System.out.println("公共类原来的名字为:" + user1.getName());
        System.out.println("A 使用之前将其修改为 翠花");
        user1.setName("翠花");
        System.out.println("现在公共类的名字为:" + user1.getName());
    }
}
@Controller
public class Controller2 {
    @Autowired
    public User user;
 
    public void userUser() {
        User user1 = user;
        System.out.println("B 使用这个公共Bean类的名字为:" + user1.getName());
        System.out.println("但是B想念的是如花");
    }
 
}

       添加启动类:

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml");
        Controller1 controllerA = context.getBean("controller1", Controller1.class);
        controllerA.useUser();
        Controller2 controllerB = context.getBean("controller2", Controller2.class);
        controllerB.userUser();
    }
}

-- 输出:

案例分析

       上面问题的原因就是Bean默认情况下是单例状态的(singleton), 也就是所有人使用的都是同一个对象, 之前我们学过单例模式的时候就知道, 使用单例模式可以极大的提高性能, 但是同时也带来了新的安全问题.

       spring中Bean的作用域默认也是singleton的单例模式.

作用域的定义

       限定程序中变量的可用范围, 我们把这种行为叫做修改作用域, 也就是说这个可用范围对应于这个作用域, 或者说是在源代码中定义变量的某个区域就叫做作用域.

       而Bean的作用域是指在spring整个framework框架中的某个行为, 所以Bean通过不同的行为类型, 来决定定义域, 比如singleton单例作用域, 就表示这个Bean对象是在整个spring中只有一份, 他是全局共享的, 那么其他人修改这个值之后, 另外一个人读取到这个值他就是被修改的值.

       由Spring容器创建的Bean的生存周期被称为Bean的作用域, 默认情况下, 由spring容器创建的所有Bean对象都是Singleton作用域, 换句话说, 针对Bean定义只创建了一个Bean对象, 并有spring容器可以在整个应用程序生存期中使用该实例, 当不同的Bean与不同的层(控制层, 服务层 和 数据访问对象层) 相对应时, 可以使用Singleton作用域.

       spring支持的第二种作用域就是prototype, 类似于java代码中使用new操作来创建Bean对象, 每次需要在容器中或者通过Bean引用其他Bean定义中, 又或者通过显示Bean查询(getBean) 从应用程序中访问Prototype作用域时就会创建他们:

       注意这里的scope = "prototype"

 

Bean对象的6种作用域

        spring 容器在初始化一个Bean实例的时候, 同时会指定其作用域, spring有6 种作用域, 最后四种是基于spring MVC生效的:

  1. singleton : 单例作用域
  2. prototype : 原型作用域(多例作用域)
  3. request : 请求作用域
  4. session : 会话作用域
  5. application : 全局作用域
  6. websocket : HTTP WevSocket作用域

       注意后面四种是spring MVC中的值, 在普通的spring项目中, 只有目前两种

       下面介绍Spring中常用的两种作用域 >>

Singleton

  • 描述: 该作用域下的Bean在IOC容器中只存在一个实例, 通过ApplicationContext,getBean方法获取的Bean和通过@Autowired或者@Resource等注入的Bean都是同一个对象
  • 场景: 通过无状态的Bean使用该作用域, 无状态表示Bean对象的属性状态不需要更新
  • 备注: Spring默认选择该作用域

prototype

  • 描述: 每次该作用域下的Bean请求都会创建新的实例: 不管是通过spring上下文(ApplicationContext) 或者是通过属性注入Bean, 都是新的对象的实例
  • 场景: 通常有状态的Bean使用该作用域

设置作用域

        使用@Scope标签就可以用来声明Bean的作用域, 比如设置Bean的作用域, 代码如下:

@Component
public class Users {
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public User getUser() {
        User user = new User();
        user.setAge(18);
        user.setName("如花");
        return user;
    }
}

       @Scope标签即可以修饰方法, 也可以修饰类, @Scope有两种设置方法:

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

       或者是在xml配置文件中的Bean对象中添加scope属性:

延迟初始化

       默认情况下, spring容器在启动阶段创建Bean, 该过程被称为预先Bean初始化. 它的有点就是可以尽早发现配置错误. 例如在基于XML文件的配置中, Bean的定义的类属性出现了错别字, 或者引用了一个不可用的Bean定义等.

       但是从另外一方面, 如果存在大量的Bean定义, 或者一些特殊的Bean, 例如Hibernate SessionFactory 或者JPA EntityManagerFactory(这些Bena 的初始化会占用大量时间), 那么初始化可能会降低启动的速度, 一些Bean可能只在特定的场合中使用, 在其他情况下并不被需要, 在这种情况下的话, 预先初始化会导致不必要的内存消耗和性能消耗.

       spring还支持延迟Bean初始化, 如果开发人员将Bean的配置为延迟创建, 那么容器将会延迟Bean的创建, 直到被需要的时候才被创建, 通过从一个已经创建的另外一个Bean中的引用, 或者是显式的调用getBean来查找Bean, 都可以触发Bean的创建.

@Controller
public class Controller1 {
    @Autowired
    public User user;
 
    public void useUser() {
        User user1 = user;
        System.out.println("公共类原来的名字为:" + user1.getName());
        System.out.println("A 使用之前将其修改为 翠花");
        user1.setName("翠花");
        System.out.println("现在公共类的名字为:" + user1.getName());
    }
}

       例如上述代码, 如果这里的Controller1这个类中的属性user使用的是延迟初始化, 那么只有在使用的时候才会被创建, 例如执行到User1 = user的时候就会被创建和初始化.  

       在基于XML的配置中, 可以在 标签中使用lazy-init 的字段将Bean设置为延迟初始化.

       如果想在一个XML文件中将所有的Bean对象都定义为延迟初始化, 可以使用标签的default-lazy-init的字段.

        延迟Bean的创建的优点是加快了容器的启动时间, 并且占用了较少的内存, 但是另外一方面, 如果在元数据中存在Bean的配置错误, 在对方案进行测试之前, 将无法发现这些错误.

       将Bean定义为延迟初始化的时候需要格外注意, 如果其中一个依赖Bean被定义为预先初始化, 那么延迟定义将会没有任何作用, 预先Bean定义在启动期间被处理, 所以它也会触发处理延迟Bean的定义.

Spring的执行流程

spring的执行流程分为下面几点:

  1. 启动容器, 加载XML配置文件
  2. 根据配置完成Bean初始化:
  1. 添加扫描路径
  2. 识别扫描路径下的类注解, 识别Bean对象
  1. 注册Bean对象到spring容器中

    如果此Bean对象依赖于其他Bean对象的, 可以使用@Autowired和@Resource注解
  2. 装配Bean属性

总结下来就是:

->启动 Spring 容器

-> 实例化 Bean(分配内存空间,从⽆到有)

-> Bean 注册到 Spring 中(存操作)

-> 将 Bean 装配到需要的类中(取操作, getBean等)

Bean的生命周期

       所谓生命周期就是一个对象从诞生到销毁的整个生命过程, 我们把这个过程就叫做一个对象的生命周期.

       Bean的生命周期分为以下五部分:

  1. 实例化Bean, 为Bean分配内存空间
  2. 设置属性(Bean注入)
  3. Bean初始化
  1. 实现了各种Aware通知的方法, 如BeanNameAware, BeanFactoryAware,ApplicationContextAware
  2. 执行BeanPostProcessor初始化方法
  3. 执行@PostConstruct 初始化方法, 依赖注入后被执行
  4. 指定自己指定的init方法
  5. 执行BeanPostProcessor初始化后置方法
  1. 使用Bean
  2. 销毁Bean
  3. 销毁容器的各种⽅法,如 @PreDestroy、DisposableBean 接⼝⽅法、destroy-method

流程图如下:


目录
相关文章
|
21天前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
21天前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
21天前
|
XML Java 数据格式
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
Spring 第二节内容补充 关于Bean配置的更多内容和细节 万字详解!
119 18
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
|
9天前
|
XML Java 数据格式
spring复习02,xml配置管理bean
详细讲解了Spring框架中基于XML配置文件管理bean的各种方式,包括获取bean、依赖注入、特殊值处理、属性赋值、集合类型处理、p命名空间、bean作用域及生命周期和自动装配。
spring复习02,xml配置管理bean
|
12天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
557 7
|
9天前
|
XML Java 数据格式
spring复习03,注解配置管理bean
Spring框架中使用注解配置管理bean的方法,包括常用注解的标识组件、扫描组件、基于注解的自动装配以及使用注解后的注意事项,并提供了一个基于注解自动装配的完整示例。
spring复习03,注解配置管理bean
|
9天前
|
XML 前端开发 Java
控制spring框架注解介绍
控制spring框架注解介绍
|
9天前
|
存储 NoSQL Java
Spring Session框架
Spring Session 是一个用于在分布式环境中管理会话的框架,旨在解决传统基于 Servlet 容器的会话管理在集群和云环境中的局限性。它通过将用户会话数据存储在外部介质(如数据库或 Redis)中,实现了会话数据的跨服务器共享,提高了应用的可扩展性和性能。Spring Session 提供了无缝集成 Spring 框架的 API,支持会话过期策略、并发控制等功能,使开发者能够轻松实现高可用的会话管理。
Spring Session框架
|
17天前
|
Java 应用服务中间件 开发者
深入探索并实践Spring Boot框架
深入探索并实践Spring Boot框架
27 2
|
16天前
|
机器学习/深度学习 数据采集 JavaScript
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
ADR药品不良反应监测系统是一款智能化工具,用于监测和分析药品不良反应。该系统通过收集和分析病历、处方及实验室数据,快速识别潜在不良反应,提升用药安全性。系统采用Java开发,基于SpringBoot框架,前端使用Vue,具备数据采集、清洗、分析等功能模块,并能生成监测报告辅助医务人员决策。通过集成多种数据源并运用机器学习算法,系统可自动预警药品不良反应,有效减少药害事故,保障公众健康。
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
下一篇
无影云桌面