【Spring】(四)Bean 的作用域和生命周期

文章目录

  • 前言
  • 一、Bean 的作用域
    • 1.1 被修改的 Bean 案例
    • 1.2 作用域的定义
    • 1.3 Bean 的六种作用域
    • 1.4 Bean 作用域的设置
  • 二、Spring 的执行流程 和 Bean 的生命周期
    • 2.1 Spring 的执行流程
    • 2.2 Bean 的生命周期
    • 2.3 Bean 生命周期的演示


前言

Bean 是 Spring 框架中的一个核心概念,它是指由 Spring 容器管理的对象实例。在使用 Spring 进行开发时,我们通常会定义各种各样的 Bean,用于承载应用程序的不同功能和组件。然而,很多开发者可能只关注了 Bean 的定义和使用方式,而忽略了 Bean 的作用域和生命周期,这两者对于一个应用程序的性能、稳定性和可维护性都至关重要。

在本文中,我将深入讨论 Bean 的作用域和生命周期,并解释它们对于 Spring 应用程序的影响。我会尽量用简单明了的语言来阐述这些概念,以便读者能够轻松理解和应用到自己的开发实践中。

一、Bean 的作用域

初次看到 Bean 的作用域这个名词,可能会令我们一头雾水,但是没关系,接下来的一个简单案例将会告诉我们什么是 Bean 的作用域。

1.1 被修改的 Bean 案例

现在有一个公共的 Bean对象,可以供 A 和 B 两个用户使用,但是 A 在使用这个 Bean 对象的时候却悄悄的对这个 Bean 的数据进行了修改,那么会不会导致 B 用户在使用这个 Bean 对象的时候发生预期之外的结果呢?

创建一个 UserBean 的类,它的作用是通过 @Bean 注解的方式将 User 对象存储到 Spring 容器中,其中 User 包含一个 idname 属性:

@Component
public class UserBeans {@Beanpublic User getUser(){User user = new User();user.setId(123);user.setName("张三");return user;}
}

然后创建一个 UserController1 类,通过 @Controller 注解将其存储到 Spring 容器中,然后使用属性注解获取 Spring 容器中的 Bean 对象。另外创建一个 printUser 方法,里面创建一个临时引用 myUser 指向 Bean 对象,然后对这个 Bean 的数据进行修改:

@Controller
public class UserController1 {@Autowiredprivate User user;public void printUser(){System.out.println("user: " +  user);User myUser = user;myUser.setName("李四");System.out.println("myUser: " + myUser);System.out.println("user: " +  user);}
}

另外再创建一个 UserController2,同样使用 @Controller 注解将其存储到 Spring 容器中,然后使用属性注解获取 Spring 容器中的 Bean 对象。另外创建一个 printUser 方法,只打印获取到的 Bean 对象:

@Controller
public class UserController2 {@Resourceprivate User user;public void printUser(){System.out.println("UserController2: user -> " +  user);}
}

在启动类中的 main 方法分别通过 ApplicationContext 获取 UserController1UserController2,然后分别执行其中的方法,观察修改 Bean 对象后产生的影响。

public static void main(String[] args) {ApplicationContext context= new ClassPathXmlApplicationContext("spring-config.xml");UserController1 userController1= context.getBean("userController1", UserController1.class);UserController2 userController2= context.getBean("userController2", UserController2.class);userController1.printUser();System.out.println("=========");userController2.printUser();
}

执行的结果如下:

从运行的结果来看,当 UserController1 修改了 Bean 在数据,此时通过 UserController2 获取的 Bean 的数据也被修改了,那么就说明一个 Bean 在 Spring 中的储存只有一份,并且是单例模式的。因此 Spring 容器中的 Bean 的默认作用域就是单例模式(singleton)的

1.2 作用域的定义

作用域(Scope)是在编程中用于描述变量或标识符在程序中可访问的范围。换句话说,它规定了变量在哪些部分可以被引用和使用。作用域是一个重要的概念,因为它可以帮助程序员避免命名冲突和理解变量在代码中的生命周期

在 Spring 框架中,作用域(Scope)就是用来定义 Bean 对象的生命周期和可见性规则作用域决定了在不同的上下文环境中,Spring 容器如何管理和提供 Bean 对象。例如,在定义一个 Bean 的时候,我们可以指定其作用域,从而决定它在应用程序中的行为表现。

1.3 Bean 的六种作用域

Spring 容器在初始化一个 Bean 的实例的时候,同时会指定该实例的作用域,如果我们不修改要指定的作用域,Spring 就会默认指定一个默认的作用域。以下是 Spring 中的六种作用域,其中最后四种是基于 Spring MVC 生效的,因此本文先不讨论。

  1. 单例作用域(singleton):这是 Spring 容器默认的作用域。在整个应用程序的生命周期中,只会创建一个该类型的 Bean 实例,并且所有对该 Bean 的引用都会执行同一个对象。这种作用域适用于哪些无状态、线程安全的 Bean 对象,例如工具类、配置类等。

  2. 原型作用域(prototype):每次请求 Bean 对象的时候,Spring 容器都会创建一个新的 Bean 实例,因此在不同的请求中,得到的是不同的对象实例。原型作用域适用于那先状态较多,需要频繁创建和销毁的对象,比如某些与会话相关的 Bean。

  3. 请求作用域(request):请求作用域是在 Web 应用中常用的作用域。它表示每次 HTTP 请求都会创建一个新的 Bean 实例,该实例仅在当前请求的处理过程中有效。当请求结束后,该 Bean 会被销毁。这样的作用域通常用于存储和处理与单个请求相关的数据。

  4. 会话作用域(session):会话作用域是在 Web 应用中基于用户会话的作用域。每个 HTTP 会话(Session)对应一个 Bean 实例,该实例在整个会话的生命周期内有效。这种作用域适用于需要在整个会话期间保持状态的对象,比如用户登录信息等。

  5. 全局作用域(application):全局作用域是指在整个 Web 应用程序的生命周期内只创建一个 Bean 实例。该作用域的 Bean 在整个应用中可见,适用于那些在整个应用中需要共享的状态信息。

  6. HTTP WebSocket 作用域(websocket): HTTP WebSocket 作用域是基于 WebSocket 连接的作用域。每个 WebSocket 连接对应一个 Bean 实例,该实例在 WebSocket 连接的整个生命周期内有效。

单例作用域(singleton)和全局作用域(application)之间的区别:

单例作用域和全局作用域都只创建一个 Bean 对象,那么它们之间有什么区别呢?

1. 定义位置:

  • 单例作用域是 Spring Core 的一部分,在整个 Spring IoC 容器中生效。它适用于任何类型的 Spring 应用,不仅限于 Web 应用。
  • 全局作用域是 Spring Web 的一部分,在 Servlet 容器中生效。它是专门为 Web 应用设计的,通过 Spring Web 库提供支持。

2. 作用范围:

  • 单例作用域只保证在 Spring IoC 容器中,每个 Bean 只会有一个实例。在整个应用程序中,不同的 Spring IoC 容器可能会有不同的实例。
  • 全局作用域确保在整个 Web 应用程序中,每个 Bean 只会有一个实例。无论是在同一个 Servlet 容器中还是不同的 Servlet 容器中,都只有一个实例。

3. 应用场景:

  • 单例作用域适用于那些无状态、线程安全的 Bean,例如工具类、配置类等。由于单例 Bean 在整个应用中只有一个实例,因此在性能和资源利用方面有一定的优势。
  • 全局作用域适用于那些在整个 Web 应用中需要共享状态信息的 Bean,比如全局的配置对象、全局缓存等。通过全局作用域,我们可以确保这些对象在整个应用中只有一个实例,而不受多个 Servlet 容器的影响。

4. 管理容器:

  • 单例作用域的管理是 Spring IoC 容器负责的,它由 Spring Core 提供的 IoC 容器管理,与 Web 应用无关。
  • 全局作用域的管理需要由 Spring Web 库提供的 Servlet 容器来管理,它与 Web 应用的生命周期相关。

总结来说,单例作用域适用于整个 Spring IoC 容器,保证每个 Bean 在容器中只有一个实例;而全局作用域适用于整个 Web 应用,保证每个 Bean 在整个应用中只有一个实例。选择合适的作用域取决于具体的应用需求和设计考虑。

1.4 Bean 作用域的设置

在 Spring 中,设置 Bean 作用域可以通过两种方法:XML 配置和注解配置

1. XML 配置
在 XML 配置文件中的 <bean>标签中,可以使用 scope 属性来设置 Bean 的作用域。例如,对于原型作用域的 Bean,可以这样配置:

<bean id="myBean" class="com.spring.demo.MyBean" scope="prototype"><!-- Bean 的属性配置 -->
</bean>

其中,scope指定的是作用域的名称,如:prototype、singleton。

2. 注解配置

使用注解配置 Bean 的作用域更加的简洁。在 Spring 中,可以使用 @Scope 注解来指定 Bean 的作用域,例如对刚才的 UserBeans 类指定原型作用域:

@Component
public class UserBeans {@Bean(name = {"user1"})// @Scope("prototype") // 原型模式@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 使用全局变量设置public User getUser(){User user = new User();user.setId(123);user.setName("张三");return user;}
}

此时,@Scope中的参数可以说作用域的名称,如:prototype;也可以通过ConfigurableBeanFactory类来指定。ConfigurableBeanFactory类的源码:


可以发现ConfigurableBeanFactory类也是对作用域名的封装。

当作用域设置为原型模型的时候,再次运行刚才修改 Bean 内容的代码:

此时就能够发现,两个 UserController 获取到的都是一个全新的 Bean 对象,即使一个修改也不会对另一个造成影响。

二、Spring 的执行流程 和 Bean 的生命周期

2.1 Spring 的执行流程

Spring 执行流程:

  1. 启动 Spring 容器:

    • Spring 容器在启动时会读取配置文件或者扫描注解来获取 Bean 的定义信息。
  2. 实例化 Bean(分配内存空间,从无到有):

    • Spring 容器根据配置信息或者注解的定义,实例化 Bean 对象。实例化过程可能会涉及构造函数的调用和依赖对象的创建。
  3. Bean 注册到 Spring 容器(存操作):

    • 实例化后的 Bean 被注册到 Spring 容器中,容器将其纳入管理范围,并为每个 Bean 分配一个唯一的标识符(通常是 Bean 的名称或者 ID)。
  4. Bean 初始化(初始化操作):

    • 如果 Bean 的定义中配置了初始化方法(例如使用 init-method 属性或者 @PostConstruct 注解),Spring 容器会在实例化之后调用该初始化方法,用于执行一些初始化逻辑。
  5. 将 Bean 装配到需要的类中(取操作):

    • 当其他类需要使用某个 Bean 时,Spring 容器会根据依赖注入的配置,将对应的 Bean 自动注入到需要的类中。这样,其他类就可以直接使用该 Bean 的实例,而不需要关心 Bean 对象的创建和管理过程。
  6. 使用 Bean:

    • 现在,Bean 已经被装配到需要的类中,可以在其他类中直接使用它了。
  7. Bean 销毁(销毁操作):

    • 如果 Bean 的定义中配置了销毁方法(例如使用 destroy-method 属性或者 @PreDestroy 注解),Spring 容器会在容器关闭时调用该销毁方法,用于执行一些清理操作。

需要注意的是,Bean 的生命周期在 Spring 容器的控制下,开发者无需手动管理 Bean 的创建和销毁过程,这就是 Spring IoC(控制反转)的核心思想。通过依赖注入,我们能够更专注于业务逻辑的实现,而不需要关心对象的创建和管理。

2.2 Bean 的生命周期

Bean 的生命周期是指一个 Bean 实例从被创建到被销毁的整个过程。在 Spring 容器中,Bean 的生命周期主要包含以下阶段:

  1. 实例化:这个阶段 Spring 容器会根据配置信息和注解创建 Bean 实例。这是 “从无到有” 的过程,即分配内存空间并调用构造方法来创建 Bean 实例。

  2. 设置属性(Bean 的注入和装配):在实例化后,Spring 容器会根据配置文件或注解,将属性值注入到 Bean 实例中。这是依赖注入(Dependency Injection)的过程,通过属性或构造方法来设置 Bean 的属性值。

  3. Bean 初始化:在属性赋值完成后,Spring 容器会执行以下步骤来初始化 Bean:

    • 各种通知:如果 Bean 实现了相应的 Aware 接口,Spring 会通过回调方式通知 Bean 相应的状态,例如 BeanNameAwareBeanFactoryAwareApplicationContextAware 等。
    • 初始化前置方法:执行 BeanPostProcessor 初始化前置方法,如 postProcessBeforeInitialization
    • 初始化方法(XML方式和注解方式):如果 Bean 定义了初始化方法(@PostConstruct 注解或实现了 InitializingBean 接口),Spring 容器会在设置属性后调用该方法进行进一步的初始化。
    • 初始化后置方法:如果 Bean 定义了初始化后置方法,如 BeanPostProcessor 接口实现类的 postProcessAfterInitialization 方法,Spring 容器会在 Bean 初始化完成后调用该方法。
  4. 使用 Bean:在初始化完成后,Bean 实例就可以被应用程序使用了。它会被注入到其他类中,或者通过 Spring 容器获取并调用它的方法。

  5. 销毁 Bean 对象:在容器关闭或者程序结束时,Spring 容器会执行以下步骤来销毁 Bean:

    • 销毁前置方法:如果 Bean 定义了销毁前置方法(destroy-method),Spring 容器会在 Bean 销毁前调用该方法。
    • 销毁方法(XML方式和注解方式):如果 Bean 定义了销毁方法(@PreDestroy 注解或实现了 DisposableBean 接口),Spring 容器会在 Bean 销毁时调用该方法进行清理操作。

2.3 Bean 生命周期的演示

下面的代码演示了一个完整的 Bean 生命周期过程,并展示了 Spring 中 Bean 的各个阶段的回调方法。让我们来看一下 Bean 生命周期的执行流程:


@Component
public class BeanComponent implements BeanNameAware, BeanPostProcessor {@Overridepublic void setBeanName(String s) {System.out.println("执行了通知,Bean name -> " + s);}// xml 的初始化方法public void myInit(){System.out.println("XML 方式初始化");}@PostConstructpublic void doPostConstruct(){System.out.println("注解 的初始化方法");}public void sayHi(){System.out.println("do sayHi()");}@PreDestroypublic void preDestroy(){System.out.println("do PreDestroy");}// 前置方法@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("do postProcessBeforeInitialization");return bean;}// 后置方法@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("do postProcessAfterInitialization");return bean;}
}

首先需要在 spring-config.xml配置文件中增加以下内容:

<!-- XML 配置方式 --><bean id="beanComponent"class="com.spring.demo.component.BeanComponent" scope="prototype" init-method="myInit" ></bean>

模拟 Bean 的生命周期:

  1. 实例化 Bean:

    • Spring 容器读取配置文件或扫描注解,发现了 BeanComponent 的定义,并实例化了该 Bean。此时,还没有调用 Bean 的构造方法。
  2. 设置属性(Bean 的注入和装配):

    • 如果有需要,Spring 容器会将相应的属性注入到 BeanComponent 实例中。
  3. Bean 初始化:

    • 执行各种通知:因为 BeanComponent 实现了 BeanNameAware 接口,所以 setBeanName 方法会被调用,打印输出 执行了通知,Bean name -> beanComponent
    • 初始化前置方法:如果是 XML 配置方式,myInit 方法会被调用,打印输出 XML 方式初始化
    • 初始化方法(@PostConstruct 注解):doPostConstruct 方法会被调用,打印输出 注解 的初始化方法
    • 初始化后置方法:postProcessAfterInitialization 方法会被调用,打印输出 do postProcessAfterInitialization
  4. 使用 Bean:

    • 在初始化完成后,BeanComponent 实例可以被应用程序使用,例如调用 sayHi() 方法,打印输出 do sayHi()
  5. 销毁 Bean 对象:

    • 在容器关闭或程序结束时,preDestroy 方法会被调用,打印输出 do PreDestroy

需要注意的是,在上述示例中,使用了 BeanPostProcessor 接口来实现前置和后置方法。这个接口提供了在 Bean 初始化前后对 Bean 进行额外处理的能力。在实际应用中,可以通过实现 BeanPostProcessor 接口来自定义一些特定的处理逻辑,例如 AOP 的代理生成。

启动类的main方法:

    public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");BeanComponent beanComponent= context.getBean("beanComponent", BeanComponent.class);beanComponent.sayHi();}

执行结果:

这里发现执行了两次通知,第一次通常是因为在创建 Spring 上下文的时候会执行 Bean 的实例化;第二次执行通常是因为采用的是原型模式,当执行getBean的会创建一个新的 Bean 对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/22699.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

集成学习:机器学习模型如何“博采众长”

前置概念 偏差 指模型的预测值与真实值之间的差异&#xff0c;它反映了模型的拟合能力。 方差 指模型在不同的训练集上产生的预测结果的差异&#xff0c;它反映了模型的稳定性。 方差和偏差对预测结果所造成的影响 在机器学习中&#xff0c;我们通常希望模型的偏差和方差都…

C高级第三讲

1、思维导图 2、输入一个文件名&#xff0c;判断是否为shell脚本文件&#xff0c;如果是脚本文件&#xff0c;判断是否有可执行权限&#xff0c;如果有可执行权限&#xff0c;运行文件&#xff0c;如果没有可执行权限&#xff0c;给文件添加可执行权限。 #!/bin/bash read -p …

【性能测试】性能数据采集工具nmon安装使用及报告参数含义详解

目录 nmon nmon下载 解压安装 启动 数据采集配置 生成图形结果 nmon报告中的参数含义 资料获取方法 nmon nmon是一种在AIX与各种Linux操作系统上广泛使用的监控与分析工具&#xff0c;它能在系统运行过程中实时地捕捉系统资源的使用情况&#xff0c;并且能输出结果到文…

c语言——求n之内的素数和

//求n之内的素数和 //列如&#xff1a;2、3、5等 #include<stdio.h> #include<math.h> int main() {int i,j,k,n0;scanf("%d",&n);for(i2;i<n;i){k(int)sqrt(i);for(j2;j<k;j)if(i%j0)break;if(j>k){printf("%d,",i);n;if(n%50)p…

《每天5分钟玩转kubernetes》读书笔记

笔记 概念 Pod是脆弱的&#xff0c;但应用是健壮的。 kubelet运行在Cluster所有节点上&#xff0c;负责启动Pod和容器。kubeadm用于初始化Cluster。kubectl是k8s命令行工具。通过kubectl可以部署和管理应用&#xff0c;查看各种资源&#xff0c;创建、删除和更新各种组件。 …

Python(六十六)字典生成式

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

VX-API-Gateway开源网关技术的使用记录

VX-API-Gateway开源网关技术的使用记录 官网地址 https://mirren.gitee.io/vx-api-gateway-doc/ VX-API-Gateway(以下称为VX-API)是基于Vert.x (java)开发的 API网关, 是一个分布式、全异步、高性能、可扩展、轻量级的可视化配置的API网关服务官网下载程序zip包 访问 https:/…

【100天精通python】Day26:文件和IO操作_文件指针的定位与移动,序列化与反序列化

目录 专栏导读 1 文件的基本操作 1.1 参考 1.2 获取文件属性&#xff1a; 2 定位和移动文件指针 3 序列化和反序列化 3.1 序列化与反序列化概述 3.2JSON序列化与反序列化 JSON序列化&#xff1a; JSON反序列化&#xff1a; 3.3 pickle 序列化与反序列化 pickle 序列…

【前端】鼠标事件计算与圆心形成的角度

在业务需求中&#xff0c;常常出现一些我们无法完成的效果图&#xff0c;这时需要UI切图给我们&#xff0c;而切图后不可避免的一些点击事件无法方便的监听 如该图圆环&#xff0c;其实是一张单独的图片&#xff0c;这种情况下只能通过js判断用户点击、拖动的鼠标位置&#xf…

carla中lka实现(一)

前言&#xff1a; 对于之前项目中工作内容进行总结&#xff0c;使用Carla中的车辆进行lka算法调试&#xff0c;整体技术路线&#xff1a; ①在Carla中生成车辆&#xff0c;并在车辆上搭载camera&#xff0c;通过camera采集图像数据&#xff1b; ②使用图像处理lka算法&#…

Docker-Compose编排与部署(lnmp实例)

第四阶段 时 间&#xff1a;2023年8月3日 参加人&#xff1a;全班人员 内 容&#xff1a; Docker-Compose编排与部署 目录 一、Docker Compose &#xff08;一&#xff09;概述 &#xff08;二&#xff09;Compose适用于所有环境&#xff1a; &#xff08;三&#xf…

Docker实战-操作Docker容器实战(二)

导语   上篇分享中,我们介绍了关于如何创建容器、如何启动容器、如何停止容器。这篇我们来分享一下如何操作容器。 如何进入容器 可以通过使用-d参数启动容器后会进入后台运行,用户无法查看容器中的信息,无法对容器中的信息进行操作。 这个时候如果我们需要进入容器对容器…

人脸识别场景下Faiss大规模向量检测性能测试评估分析

在前面的两篇博文中&#xff0c;主要是考虑基于之前以往的人脸识别项目经历结合最近使用到的faiss来构建更加高效的检索系统&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《基于facenetfaiss开发构建人脸识别系统》 Facenet算法的优点&#xff1a;高准确率&#…

HTTP隧道识别与防御:机器学习的解决方案

随着互联网的快速发展&#xff0c;HTTP代理爬虫已成为数据采集的重要工具。然而&#xff0c;随之而来的是恶意爬虫对网络安全和数据隐私的威胁。为了更好地保护网络环境和用户数据&#xff0c;我们进行了基于机器学习的HTTP代理爬虫识别与防御的研究。以增强对HTTP代理爬虫的识…

springboot+vue网红酒店客房预定系统的设计与实现_ui9bt

随着计算机技术发展&#xff0c;计算机系统的应用已延伸到社会的各个领域&#xff0c;大量基于网络的广泛应用给生活带来了十分的便利。所以把网红酒店预定管理与现在网络相结合&#xff0c;利用计算机搭建网红酒店预定系统&#xff0c;实现网红酒店预定的信息化。则对于进一步…

DBeaver安装+连接使用mysql

1、下载Dbeaver 官网&#xff1a;Download | DBeaver Community github&#xff1a;Releases dbeaver/dbeaver (github.com) 这里是在github下载的&#xff0c;下的是23.1.3版本 &#xff08;根据系统自己选择&#xff0c;这里下的是windows的版本&#xff09; 2、安装 3、…

单元测试之 - Review一个微服务的单元测试

这里以github上一个microservice的demo代码为例&#xff0c;来看看如何为一个完整的服务编写单元测试。具体代码如下所示&#xff0c;我们重点查看一下catalog和customer&#xff0c;order中的单元测试有哪些。 首先来看catalog服务的单元测试,这个服务下面主要编写了CatalogWe…

物联网|按键实验---学习I/O的输入及中断的编程|函数说明的格式|如何使用CMSIS的延时|读取通过外部中断实现按键捕获代码的实现及分析-学习笔记(14)

文章目录 通过外部中断实现按键捕获代码的实现及分析Tip1:函数说明的格式Tip2:如何使用CMSIS的延时GetTick函数原型stm32f407_intr_handle.c解析中断处理函数&#xff1a;void EXTI4_IRQHandler 调试流程软件模拟调试 两种代码的比较课后作业: 通过外部中断实现按键捕获代码的实…

5G网络在中国已经普及了,政策支持加大5G投入力度,这意味着什么呢?

5G网络是新型基础设施的重要组成部分&#xff0c;中国5G商用牌照已发放四年多&#xff0c;目前发展得怎样了&#xff1f;最近&#xff0c;官方公布了最新数据&#xff0c;截至7月底&#xff0c;中国5G移动电话用户已达7亿户&#xff0c;5G基站累计达到293.7万个&#xff0c;5G覆…

【perl】报错合集

perl报错合集 &#xff08;注&#xff1a;可能会不定时更新&#xff09; 1.Name “main::x” used only once: possible typo at … 1.Name "main::x" used only once: possible typo at ...给某个变量赋值但是从来没有用它&#xff0c;或者变量之只用一次但没有…