前言

前面我们讲诉了将Bean正确地装配到IoC容器,却未讲诉IoC如何装配和销毁Bean。本篇文章主要讲诉一下Bean的生命周期和作用域。


一、生命周期

  • Bean 的生命周期的过程, 它大致分为Bean定义、Bean 的初始化、 Bean 的生存期和 Bean 的销毁4个部分。 其中 Bean 定义过程大致如下:

    • Spring 通过我们的配置,如@ComponentScan 定义的扫描路径去找到带有@Component 的类,
      这个过程就是一个资源定位的过程。
    • 一旦找到了资源,那么它就开始解析,并且将定义的信息保存起来。注意,此时还没有初始
      化Bean,也就没有Bean 的实例,它有的仅仅是Bean 的定义
    • 然后就会把Bean 定义发布到 Spring IoC 容器中。 此时, IoC 容器也只有Bean 的定义,还是
      没有Bean 的实例生成
  • 完成了这3 步只是一个资源定位并将Bean 的定义发布到IoC容器的过程,还没有Bean实例的生成,更没有完成依赖注入。在默认的情况下, Spring会继续去完成Bean 的实例化和依赖注入,这样从IoC 容器中就可以得到一个依赖注入完成的Bean。 但是,有些Bean会受到变化因素的影响,这时我们倒希望是取出 Bean 的时候完成初始化和依赖注入换句话说就是让那些 Bean 只是将定义发布到IoC 容器而不做实例化和依赖注入, 当我们取出来的时候才做初始化和依赖注入等操作。

Spring Bean的初始化过程:
在这里插入图片描述
ComponentScan 中还有一个配置项 lazyI nit,只可以配置 Boolean 值,且默认值为 false,也就是默认不进行延迟初始化,因此在默认的情况下Spring会对Bean进行实例化和依赖注入对应的属性值

引入例子:人类(Person)有时候利用一些动物(Animal)去完成一些事情,比方说狗(Dog)是用来看门的,猫(Cat)是用来抓老鼠的.。

代码如下:

//定义人类接口
public interface Person {
    void service();

    void setAnimal(Animal animal);
}
//定义动物接口
public interface Animal {
    void user();
}
//定义狗
@Component
public class Dog implements Animal {
    @Override
    public void user() {
        System.out.println("狗【" + Dog.class.getSimpleName() + "】是用来看门的");
    }
}
//定义年轻人
@Component
public class YoungPerson implements Person {
    @Autowired
    private Animal animal = null;

    @Override
    public void service() {
        this.animal.user();
    }

    @Override
    public void setAnimal(Animal animal) {
        this.animal = animal;
    }
}
//定义猫
@Component
public class Cat implements Animal{
    @Override
    public void user() {
        System.out.println("猫【" + Cat.class.getSimpleName() + "】是抓老鼠的");
    }
}

//定义配置类
@Configuration
@ComponentScan("com.dragon.restart")//所有的包和类都在restart下
public class AppConfig {
}

此时没有配置lazyInit的情况进行断点测试如下:
在这里插入图片描述
可以看到在断点处,我们并没有获取Bean 的实例,而日志就已经打出了,可见它是在SpringIoC容器初
始化时就执行了实例化和依赖注入。为了改变这个情况,我们在配置类AppConfig的@ComponentScan
中加入lazylnit 配置,如下面的代码:

@Configuration
@ComponentScan(value = "com.dragon.restart",lazyInit = true)
public class AppConfig {
}

在这里插入图片描述
在这里插入图片描述

就可以发现在断点处“延迟依赖注入”这行并不会出现在日志中,只有运行过断点处才会出现这行日志,这是因为我们把它修改为了延迟初始化, Spring并不会在发布Bean定义后马上为我们完成实例化和依赖注入。

如果仅仅是实例化和依赖注入还是比较简单的,还不能完成进行自定义的要求。 为了完成依赖注入的功能, Spring 在完成依赖注入之后,还提供了一系列的接口和配置来完成Bean初始化的过程,让我们学习这个过程。 Spring在完成依赖注入后,还会进行如下图所示流程来完成它的生命周期:
在这里插入图片描述

图中描述的是整个IoC容器初始化Bean 的流程,作为开发者,需要注意这些流程。除此之外,还需要注意以下两点:

  • 这些接口和方法是针对什么而言的。 对于上图, 在没有注释的情况下的流程节点都是针对单个Bean 而言的,但是BeanPostProcessor 是针对所有 Bean 而言的,这是我们需要注意的地方。
  • 即使你定义了 ApplicationContextAware 接口,但是有时候并不会调用,这要根据你的 IoC 容器来决定。 我们知道, Spring IoC 容器最低的要求是实现 BeanFactory 接口,而不是实现ApplicationContext 接口 。 对于那些没有实现 ApplicationContext 接口的容器,在生命周期对应的ApplicationContextAware 定义的方法也是不会被调用的,只有实现了 ApplicationContext 接口的容器,才会在生命周期调用 ApplicationContextAware 所定义的 setApplicationContext方法。

现在改造一下YoungPerson类:

@Component
public class YoungPerson implements Person, BeanNameAware , BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {
    private Animal animal = null;

    @Override
    public void service() {
        this.animal.user();
    }
    @Autowired
    @Qualifier("dog")
    @Override
    public void setAnimal(Animal animal) {
        System.out.println("延迟依赖注入");
        this.animal = animal;
    }

    @Override
    public void setBeanName(String name) {
        System.out.println ("【" + this.getClass().getSimpleName() + "】调用BeanNameAware的setBeanName");
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println ("【" + this.getClass().getSimpleName() + "】调用BeanFactoryAware的setBeanFactory");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println ("【" + this.getClass().getSimpleName() + "】调用DisposableBean方法");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println ("【" + this.getClass().getSimpleName() + "】调用InitializingBean方法的afterPropertiesSet方法");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println ("【" + this.getClass().getSimpleName() + "】调用ApplicationContextAware方法的setApplicationContext方法");
    }
    @PostConstruct
    public void init () {
        System.out.println("【" + this.getClass().getSimpleName() + "】注解@PostConstruct定义的自定义初始化方法");
    }
    @PreDestroy
    public void destroyl () {
        System.out.println("【" + this.getClass().getSimpleName() + "】注解@PreDestroy定义的自定义销毁方法");
    }
}

这样,这个 B巳an 就实现了生命周期中单个 Bean 可以实现的所有接口, 并且通过注解@PostConstruct 定义了初始化方法,通过注解@PreDestroy 定义了销毁方法。 为了测试 Bean 的后置处理器, 这里创建一个类BeanPostProcessorExampIe,如下:

/**
 * @Version: 1.0.0
 * @Author: Dragon_王
 * @ClassName: BeanPostProcessorExample
 * @Description: TODO描述
 * @Date: 2024/1/20 23:34
 */
public class BeanPostProcessorExample implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeanPostProcessor调用"+
                "postProcessBeforeinitialization方法,参数【"+
                bean.getClass().getSimpleName()+"】【"+beanName+"】");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeanPostProcessor调用"+
                "postProcessAfterinitialization方法,参数【"+
                bean.getClass().getSimpleName()+"】【"+beanName+"】");
        return bean;
    }
}

注意,这个Bean后置处理器将对所有的Bean有效,运行测试如下:
测试类:

AnnotationConfigApplicationContext ctx =new AnnotationConfigApplicationContext(AppConfig.class) ;
		ctx.close();
2024-01-20T23:43:23.135+08:00  INFO 748 --- [           main] c.d.restart.RestartApplicationTests      : Starting RestartApplicationTests using Java 19 with PID 748 (started by ThundeRobot in E:\IDEA_projects\restart)
2024-01-20T23:43:23.136+08:00  INFO 748 --- [           main] c.d.restart.RestartApplicationTests      : No active profile set, falling back to 1 default profile: "default"
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【RestartApplication$$SpringCGLIB$$0】【restartApplication】
BeanPostProcessor调用postProcessAfterinitialization方法,参数【RestartApplication$$SpringCGLIB$$0】【restartApplication】
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【AppConfig$$SpringCGLIB$$0】【appConfig】
BeanPostProcessor调用postProcessAfterinitialization方法,参数【AppConfig$$SpringCGLIB$$0】【appConfig】
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【Cat】【cat】
BeanPostProcessor调用postProcessAfterinitialization方法,参数【Cat】【cat】
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【Dog】【dog】
BeanPostProcessor调用postProcessAfterinitialization方法,参数【Dog】【dog】
延迟依赖注入
【YoungPerson】调用BeanNameAware的setBeanName
【YoungPerson】调用BeanFactoryAware的setBeanFactory
【YoungPerson】调用ApplicationContextAware方法的setApplicationContext方法
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【YoungPerson】【youngPerson】
【YoungPerson】注解@PostConstruct定义的自定义初始化方法
【YoungPerson】调用InitializingBean方法的afterPropertiesSet方法
BeanPostProcessor调用postProcessAfterinitialization方法,参数【YoungPerson】【youngPerson】
BeanPostProcessor 调用 postProcessBeforeinitialization 方法,参数 【Cat】【cat】
 BeanPostProcessor 调用 postProcessAfterinitialization 方法, 参数 【Cat】【cat】
 2024-01-20T23:43:24.044+08:00  INFO 748 --- [main] c.d.restart.RestartApplicationTests      : Started RestartApplicationTests in 1.142 seconds (process running for 1.772)YoungPerson】注解@PreDestroy定义的自定义销毁方法
【YoungPerson】调用DisposableBean方法

从日志可以看出,对于Bean后置处理器(BeanPostProcessor)而言, 它对所有的 Bean 都起作用,而其他的接口则是对于单个Bean起作用。我们还可以注意到BussinessPerson执行的流程是上图所画出的流程。有时候Bean 的定义可能使用的是第三方的类,此时可以使用注解@Bean来配置自定义初始化和销毁方法,如下所示:

@Bean(InitMethod =”Init”, destroyMethod = ”destroy” )

二、作用域

在介绍IoC 容器最顶级接口 BeanFactory 的时候, 可以看到 isSingleton 和 isPrototype 两个方法。其中,isSingleton 方法如果返回 true,则 Bean 在 loC 容器中以单例存在,这也是 Spring IoC 容器的默认值;如果 isPrototype 方法返回 true,则当我们每次获取 Bean 的时候, IoC 容器都会创建一个新的 Bean,这显然存在很大的不同,这便是Spring Bean 的作用域的问题。在一般的容器中, Bean都会存在单例(Singleton)和原型(Prototype)两种作用域, Java EE 广泛地使用在互联网中,而在 Web容器中, 则存在页面(page)、请求(request)、会话 (session)和应用(application) 4 种作用域对于页面(page),是针对 JSP 当前页面的作用域,所以 Spring是无法支持的。为了满足各类的作用域,在Spring 的作用域中就存在如表所示的几种类型。

作用域类型使用范围作用域描述
singleton所有Spring 应用默认值, loC 容器只存在单例
prototype所有Spring 应用每当从IoC 容器中取出一个 Bean,则创建一个新的Bean
sessionSpring Web 应用HTTP 会话
applicationSpring Web 应用Web 工程生命周期
requestSpring Web 应用Web 工程单次请求 (request)
globalSessionSpring Web 应用在一个全局的HTTPSession 中, 一个 Bean 定义对应一个实例。 实践中基本不使用
  • 前四个最常用
  • 对于application作用域,完全可以使用单例来替代。

下面我们探讨单例 (Singleton)和原型(prototype)的区别

首先定义一个类

@Component 
//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 
public class ScopeBean { }

这是一个简单的类, 可以看到这里声明作用域的代码已经被注释掉了, 这样就是启用默认的作用域,实际就是单例。为了证明作用域的存在,我们进行一下测试:

AnnotationConfigApplicationContext ctx
				=new AnnotationConfigApplicationContext (AppConfig.class);
		ScopeBean scopeBeanl = ctx.getBean (ScopeBean.class);
		ScopeBean scopeBean2 = ctx.getBean (ScopeBean .class);
		System.out.println (scopeBeanl == scopeBean2) ;

在这里插入图片描述
从测试的结果来看,显然scopeBeanl 和 scopeBean2 这两个变量都指向了同一的实例,所以在IoC容器中, 只有一个ScopeBean 的实例。 然后取消代码中作用域代码的注释,进行同样的测试, 则可以看到scopeBeanl == scopeBean2 返回的将是 false,而不再是 true, 那是因为我们将Bean 的作用域修改为了 prototype,这样就能让IoC 容器在每次获取Bean 时,都新建一个Bean的实例返回给调用者。
在这里插入图片描述
这里的 ConfigurableBeanFactory 只能提供单例 ( SCOPE_ SINGLETON )和原型 ( SCOPE_PROTOTYPE)两种作用域供选择, 如果是在 SpringMVC环境中,还可以使用 WebApplicationContext去定义其他作用域, 如请求(SCOPE REQUEST)、 会话 (SCOPE_SESSION) 和应用 (SCOPE_APPLICATION)。 例如,下面的代码就是定义请求作用域:

@Component 
@Scope(WebApplicationContext.SCOPE_REQUEST) 
public class ScopeBean { }

总结

以上就是Bean生命周期和作用域的讲解。

相关文章

使用Go Validator在Go应用中有效验证数据

Go Validator是一个开源的包,为Go结构体提供强大且易于使用的数据验证功能。该库允许开发者为其数据结构定义自定义验证规则,并确保传入的数据满足指定的条件。Go Validator支持内置验证器、自定义验证器,甚至允许您链式多个验证规则以满足更复杂的数据验证需求。如果内置验证器无法满足您的需求,您可以通过定义自己的验证函数来创建自定义验证器。这个功能允许您实现特定于应用程序需求的验证逻辑。

【Vue3】使用ref与reactive创建响应式对象

先来简单介绍一下ref,它可以定义响应式的变量let xxx = ref(初始值)。**返回值:**一个RefImpl的实例对象,简称ref对象或refref对象的value属性是响应式的。JSxxx.value,但模板中不需要.value,直接使用即可。对于let name = ref('张三')来说,name不是响应式的,name.value是响应式的。下面我们看一看上图红框中代表的意思是,我们哪里需要响应就在哪个里面导入上述代码即可。

如何设置页面恢复运行事件触发回调

由于 Android 原生的 resume 和 pause 事件不能区分是压后台导致还是页面切换导致,所以 pageResume 和 pagePause 事件是通过 JSAPI 调用记录回调的,仅适用于同一个 session 内 Window 之间的互相切换。当一个 WebView 界面重新回到栈顶时,例如从后台被唤起、锁屏界面恢复、从下个页面回退,会触发页面恢复运行(resume)事件。如果这个界面是通过 popWindow 或 popTo 到达,且传递了 data 参数,则此页可以获取到这些参数。

日常遇到Maven出现依赖版本/缓存问题通用思路。

如果怀疑是本地仓库中缓存的依赖有问题,可以手动删除本地仓库(默认位置在用户的.m2/repository目录下),但这是一个较为极端的做法,因为这会删除所有项目的所有本地依赖,之后Maven将不得不重新下载这些依赖。针对于这样的问题 首先我们的第一思路 就是怀疑到是缓存的问题,那么我在这里去描述一下 我们遇到这类通用类的问题如何解决。检查项目的pom.xml文件,确认依赖声明正确无误,没有冲突的版本号或不正确的依赖范围。版本问题导致的,但是我确认过了一下的一些操作 依然没有解决我的问题。

什么是tomcat?tomcat是干什么用的?

Tomcat是一个开源的、轻量级的应用服务器,是Apache软件基金会的一个项目。它实现了Java Servlet、JavaServer Pages(JSP)和Java Expression Language(EL)等Java技术,用于支持在Java平台上运行的动态Web应用程序。AJP是用于Apache服务器与Tomcat之间进行通信的协议,通常用于将动态生成的内容传递给Apache服务器进行处理。它能够运行Servlet和JSP,提供了一个环境,使得开发者能够构建和运行基于Java的Web应用。

C# winfrom中excel文件导入导出

在C#交流群里,看到很多小伙伴在excel数据导入导出到C#界面上存在疑惑,所以今天专门做了这个主题,希望大家有所收获!环境:win10+vs2017界面:主要以演示为主,所以没有做优化,然后主界面上添加两个按钮,分别命名为ExportExcel和ImportExcel,添加两个dataGridView,分别是dataGridView1和dataGridView2然后在窗体加载程序中给dataGr...

Java 与 JavaScript 的区别与联系

Java 和 JavaScript 两种编程语言在软件开发中扮演着重要的角色。尽管它们都以“Java”命名,但实际上它们是完全不同的语言,各有其独特的特点和用途。本文将深入探讨 Java 和 JavaScript 的区别与联系,帮助大家更好地理解它们在编程世界中的作用。

C语言中的作用域与生命周期

但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性,只能在自己所在的源文件内部使用了,其他源文件,即使声明了,也是无法正常使用的。结论:static修饰局部变量改变了变量的生命周期,生命周期改变的本质是改变了变量的存储类型,本来一个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。extern 是用来声明外部符号的,如果一个全局的符号在A文件中定义的,在B文件中想使用,就可以使用extern进行声明,然后使用。全局变量的生命周期是:整个程序的生命周期。

Python和Java的区别(不断更新)

运行效率:一般来说,Java的运行效率要高于Python,这主要是因为Java是编译型语言,其代码在执行前会进行预编译,而Python是解释型语言,边解释边执行。而Python没有类似的强大虚拟机,但它的核心是可以很方便地使用C语言函数或C++库,这使得Python可以轻松地与底层硬件进行交互。**类型系统:**Java是一种静态类型语言,所有变量需要先声明(类型)才能使用,且类型在编译时就已经确定。总的来说,Python和Java各有其优势和特点,选择哪种语言取决于具体的项目需求、开发环境以及个人偏好。

服务器与电脑的区别?

服务器是指一种专门提供计算和存储资源、运行特定软件服务的物理或虚拟计算机。服务器主要用于接受和处理来自客户端(如个人电脑、手机等)的请求,并向客户端提供所需的服务或数据。服务器在网络环境中扮演着中心节点的角色,负责存储和管理数据、提供网络服务、处理计算任务等。

windows下ngnix自启动(借助工具winSw)

在windows下安装nginx后,不想每次都手动启动。本文记录下windows下ngnix自启动(借助工具winSw)的操作流程提示:以下是本篇文章正文内容,下面案例可供参考本文记录下windows下ngnix自启动(借助工具winSw)的操作流程。

synchronized 和 Lock 有什么区别?synchronized 和 ReentrantLock 区别是什么?说一下 atomic 的原理?

例如,AtomicInteger 的 incrementAndGet() 方法就是通过 CAS 操作实现的,它首先尝试原子地将共享变量加 1,如果操作成功,则返回新的值,否则重试直到操作成功为止。CAS 操作的原理是,当 V 的值等于 A 时,将 V 的值更新为 B,否则什么也不做。synchronized 和 Lock 都是 Java 中用于实现线程同步的关键字/类库,它们都能够提供对共享资源的安全访问和防止数据竞争的功能,但是在实现方式、特性、适用场景等方面存在一些差异。

SpringBoot security 安全认证(二)——登录拦截器

本节内容:实现登录拦截器,除了登录接口之外所有接口访问都要携带Token,并且对Token合法性进行验证,实现登录状态的保持。核心内容:1、要实现登录拦截器,从Request请求中获取token,从缓存中获取Token并验证登录是否过期,若验证通过则放行;2、实现对拦截器配置,SpringBoot 安全模块使用HttpSecurity 来完成请求安全管理。

Kafka常见生产问题详解

比如,在原有Topic下,可以调整Producer的分区策略,让Producer将后续的消息更多的发送到新增的Partition里,这样可以让各个Partition上的消息能够趋于平衡。思路是可行的,但是重试的次数,发送消息的数量等都是需要考虑的问题。PageCache缓存中的消息是断电即丢失的。因为如果业务逻辑异步进行,而消费者已经同步提交了Offset,那么如果业务逻辑执行过程中出现了异常,失败了,那么Broker端已经接收到了消费者的应答,后续就不会再重新推送消息,这样就造成了业务层面的消息丢失。

深入理解 Java 变量类型、声明及应用

Java 变量 变量是用于存储数据值的容器。在 Java 中,有不同类型的变量,例如: String - 存储文本,例如 "你好"。字符串值用双引号引起来。 int - 存储整数(全数字),没有小数,例如 123 或 -123。 float - 存储浮点数,带有小数,例如 19.

Zookeeper分布式队列实战

ZooKeeper实现队列步骤1.创建队列根节点:在Zookeeper中创建一个持久节点,用作队列的根节点。所有队列元素的节点将放在这个根节点下。2.实现入队操作:当需要将一个元素添加到队列时,可以在队列的根节点下创建一个临时有序节点。节点的数据可以包含队列元素的信息。3.实现出队操作:当需要从队列中取出一个元素时,先获取根节点下的所有子节点。再找到具有最小序号的子节点,获取该节点的数据,删除该节点,然后返回节点的数据。

SpringMVC校验注解不生效

用来实现参数校验功能。Spring使用hibernate-validator作为它的默认实现,我们只需要进行一些简单的注解声明,就可以达到参数校验的功能。但是在实际使用场景中,经常会出现校验没生效的问题。

Web 安全之点击劫持(Clickjacking)攻击详解

点击劫持(Clickjacking)攻击,又称为界面伪装攻击,是一种利用视觉欺骗手段进行攻击的方式。攻击者通过技术手段欺骗用户点击本没有打算点击的位置,当用户在被攻击者攻击的页面上进行操作时,实际点击结果被劫持,从而被攻击者利用。这种攻击方式利用了用户对网站的信任,通过覆盖层(通常是透明的iframe)覆盖在另一个网页之上,使受害者无法察觉。

为什么Java中的String类被设计为final类?

String类作为Java中不可或缺的类之一,被设计成final类带来了不可变性、安全性、可靠性和性能优势。不可变的特性使得String对象在多线程环境下安全共享,提高了应用程序的并发性和性能。此外,String类的设计还符合Java类库的一致性和规范,确保了整个语言的稳定性和可靠性。因此,String类被设计成final类是出于多方面的考虑,以提供最佳的使用体验和编程效率。

bat脚本打开多个黑窗口并执行不同的命令

在使用java -jar运行jar包之前,需要先启动redis,而redis的安装目录与jar包不在同一目录下,所以每次启动项目的时候都需要来回的切换目录。现写了一个bat脚本,用来一键启动redis和jar包。start cmd /k "cd /d redis安装目录 && redis-server redis.windows.conf"其中,cmd /k命令是不关闭黑窗口的命令,timeout /T 3表示等待3秒,/NOBREAK表示键盘输入不会中断等待。

鸿蒙(ArkUI)开发:实现二级联动

列表的二级联动(Cascading List)是指根据一个列表(一级列表)的选择结果,来更新另一个列表(二级列表)的选项。这种联动可以使用户根据实际需求,快速定位到想要的选项,提高交互体验。例如,短视频中拍摄风格的选择、照片编辑时的场景的选择,本文即为大家介绍如何开发二级联动。

Vue和React的区别 | | React函数式写法和类写法的区别

React 更多的是一个库而不是框架,它更专注于视图层的管理,通过社区和第三方库来进行补充和扩展。类式组件: 类式组件是 ES6 中引入的 class 类的一种用法,它继承自 React.Component,拥有完整的生命周期和内部状态管理能力。它是无状态的,没有生命周期和内部状态。而在 React 中,我们使用 JSX 语法,它是一种 JavaScript 的扩展语法,可以在 JavaScript 中直接编写类似 HTML 的结构。在 React 中,我们可以定义组件的两种方式,即函数式组件和类式组件。
返回
顶部