Spring(11) Bean的生命周期

目录

    • 一、简介
    • 二、Bean的流程
      • 1.BeanDefinition
      • 2.Bean 的生命周期
    • 三、代码验证
      • 1.User 实体类
      • 2.MyBeanPostProcessor 后置处理器
      • 3.SpringConfig 扫描包配置
      • 4.UserTest 测试类
      • 5.测试结果
      • 6.模拟AOP增强

一、简介

首先,为什么要学习 Spring 中 Bean 的生命周期呢?

虽然不了解 Bean 的生命周期,并不影响日常工作中的开发。但是如果我们了解了 Bean 的生命周期,可以帮助我们更好地掌握 Spring 框架,并且能够让我们更好地去理解 Spring 容器是如何管理和创建 Bean 示例的。如果以后遇到 Bean 的相关问题(比如 Spring 的循环依赖问题),就可以方便我们去调试和解决这些问题。同时也让我们可以编写出更健壮、灵活、易维护的应用程序。所以说,理解 Bean 的生命周期对掌握 Spring 框架来说是至关重要的。

这里我们主要分两部分去说明:

  1. 先从整体上去介绍 Bean 创建的整个流程 -> Bean的流程;
  2. 然后再用代码的方式去验证流程 -> 代码验证。

二、Bean的流程

1.BeanDefinition

BeanDefinition:Bean 的定义信息。Spring 容器在进行实例化时,会将 xml 配置中 <bean> 的信息(注解也是如此)封装成一个 BeanDefinition 对象,Spring 根据 BeanDefinition 来创建 Bean 对象,里面有很多用来描述 Bean 的属性。

有了 BeanDefinition 之后,再去创建 Bean 对象的时候,就可以通过反射去创建。

<bean id="userDao" class="com.demo.dao.impl.UserDaoImpl" lazy-init="true"/>
<bean id="userService" class="com.demo.service.UserServiceImpl" scope="singleton">
	<property name="userDao" ref="userDao"></property>
</bean>
  • beanClassName:bean 的类名。
  • initMethodName:初始化方法名称。
  • propertyValues:bean 的属性值。
  • scope:作用域。
  • lazyInit:延迟初始化。

以上这些属性都被封装到 BeanDefinition 中备用。那具体什么时候用呢?这就是下面要说的 Bean 的生命周期。

2.Bean 的生命周期

目前我们已经有了 BeanDefinition 这个对象了,那第一步就是要去创建对象。

在这里插入图片描述

  • 第1步,调用 Bean 的构造函数。 来去实例化当前 Bean 的对象。

  • 第2步,依赖注入。 像是一些 @Autowired 或者 @Value 标明的属性,这些都是在依赖注入中完成。

  • 第3步,Aware接口。 比较常见的有三个,它们都是以 Aware 结尾的接口,如果 Bean 实现了这些接口,就要重写里面的方法:

    BeanNameAware:在初始化过程中可以获取到 Bean 的名称。

    BeanFactoryAware:在初始化过程中可以获取到 Bean 工厂。

    ApplicationContextAware:在初始化过程中可以获取到应用上下文。

    以上这些内容都是为了方便对 Bean 进行扩展。

  • 第4步,BeanPostProcessor#before。 BeanPostProcessor 是 Bean 的后置处理器,用来增强 Bean 的功能的,在初始化方法调用之前(before)进行回调。这个 BeanPostProcessor 后置处理器在 Bean 的生命中其中占有非常重要的作用,需要重点记忆。

  • 第5步,调用初始化方法。 里面有两部分:

    InitializingBean:这是一个接口类,如果当前 Bean 实现了这个接口的话,就要重写里面的方法,这一步就是来执行重写之后的方法。

    自定义init方法:比如在 Bean 中的某一个方法使用了注解 @PostConstruct,这里就会去执行标明了注解的方法。其实自定义init方法也是从 BeanDefinition 中读取到的信息。

  • 第6步,BeanPostProcessor#after。 还是在 Bean 的后置处理器中执行,但是是在初始化方法之后(after)执行。在 Spring 中对 Bean 进行增强的话,都是用到这个初始化方法之后执行的后置处理器。其实在 Spring 内部就使用到了很多的后置处理器,比较典型的就是:当一个类被曾倩了,使用到了 AOP,那这个类通常都是使用后置处理器(BeanPostProcessor#after)来增强的。我们知道 AOP 的底层使用的是动态代理,有两种:JDK动态代理CGLIB动态代理

    到这一步之后,基本上这个对象就创建完成了,现在就可以从 Spring 容器中去获取和使用这个 Bean 对象了。

    注意:依赖注入 开始到 BeanPostProcessor#after 完成之后,都是针对当前的 Bean 进行初始化赋值,当然也做了一些增强。这里需要注意,Spring 中 Bean 的创建是一步一步完成的,也就是说 Bean 的创建和初始化赋值是分开的。调用构造函数就是创建 Bean 对象,但是这里创建好后是一个空对象,里面没有值,下面就是初始化赋值的过程。

  • 第6步,销毁Bean。 当 Spring 容器关闭之后,这个 Bean 对象就要执行销毁的操作了。比较典型的是,如果在某个方法上使用了 @PreDestroy 这个注解,那这个方法就是一个销毁的方法,Spring 容器关闭的时候就会执行这个销毁的方法。

以上就是 Bean 的生命周期,里面的步骤还是挺多的。为了方便加深印象,我们可以用代码的方式去验证一下。

三、代码验证

1.User 实体类

User.java

继承了 BeanNameAwareBeanFactoryAwareApplicationContextAwareInitializingBean 接口。

package com.demo.lifecycle;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;

@Component
public class User implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean {

    public User() {
        System.out.println("User类的构造方法被调用了...");
    }

    /**
     * 姓名
     */
    private String name;

    @Value("张三")
    public void setName(String name) {
        System.out.println("setName方法被调用了...");
        this.name = name;
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("BeanNameAware接口的setBeanName方法被调用了,bean的名字是:" + name);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("BeanFactoryAware接口的setBeanFactory方法被调用了...");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("ApplicationContextAware接口的setApplicationContext方法被调用了...");
    }

    @Override
    public void afterPropertiesSet() {
        System.out.println("InitializingBean接口的afterPropertiesSet方法被调用了...");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("PreDestroy注解的destroy方法被调用了...");
    }
}

2.MyBeanPostProcessor 后置处理器

MyBeanPostProcessor.java

定义了 #before#after 方法的增强实现。

package com.demo.lifecycle;

import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Objects;

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("BeanPostProcessor接口的postProcessBeforeInitialization方法被调用了 -> user对象初始化方法前开始增强....");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (Objects.equals(beanName, "user")) {
            System.out.println("BeanPostProcessor接口的postProcessAfterInitialization方法被调用了 -> user对象初始化方法后开始增强....");
            // cglib代理对象
            /*Enhancer enhancer = new Enhancer();
            // 设置需要增强的类
            enhancer.setSuperclass(bean.getClass());
            // 执行回调方法,增强方法
            enhancer.setCallback(new InvocationHandler() {
                @Override
                public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                    // 执行目标方法
                    return method.invoke(bean, objects);
                }
            });
            // 创建代理对象
            return enhancer.create();*/
        }
        return bean;
    }
}

3.SpringConfig 扫描包配置

SpringConfig.java

配置了扫描包位置,只扫描 User 实体类和 MyBeanPostProcessor 后置处理器。

package com.demo.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.demo.lifecycle")
public class SpringConfig {
}

4.UserTest 测试类

UserTest.java

main 方法执行,使用 SpringConfig 配置创建上下文,并从容器中获取 user 的 Bean 对象。

package com.demo.test;

import com.demo.config.SpringConfig;
import com.demo.lifecycle.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class UserTest {

    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        User user = ctx.getBean(User.class);
        System.out.println(user);
    }
}

5.测试结果

由于我使用的是 SpringBoot 项目进行的测试,会多打印一些日志,其中不带时间前缀的是我们手动 println 的日志。

Connected to the target VM, address: '127.0.0.1:61642', transport: 'socket'
20:03:56.949 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@9660f4e
20:03:56.985 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
20:03:57.100 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [D:\IdeaProjects\SpringBootExamples\springboot-demo\target\classes\com\demo\lifecycle\MyBeanPostProcessor.class]
20:03:57.105 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [D:\IdeaProjects\SpringBootExamples\springboot-demo\target\classes\com\demo\lifecycle\User.class]
20:03:57.266 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
20:03:57.270 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
20:03:57.272 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
20:03:57.275 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
20:03:57.281 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'myBeanPostProcessor'
20:03:57.304 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'springConfig'
BeanPostProcessor接口的postProcessBeforeInitialization方法被调用了 -> user对象初始化方法前开始增强....
20:03:57.305 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'user'
User类的构造方法被调用了...
setName方法被调用了...
BeanNameAware接口的setBeanName方法被调用了,bean的名字是:user
BeanFactoryAware接口的setBeanFactory方法被调用了...
ApplicationContextAware接口的setApplicationContext方法被调用了...
BeanPostProcessor接口的postProcessBeforeInitialization方法被调用了 -> user对象初始化方法前开始增强....
InitializingBean接口的afterPropertiesSet方法被调用了...
BeanPostProcessor接口的postProcessAfterInitialization方法被调用了 -> user对象初始化方法后开始增强....
User类的构造方法被调用了...
com.demo.lifecycle.User@2235eaab
Disconnected from the target VM, address: '127.0.0.1:61642', transport: 'socket'

Process finished with exit code 0

可以看到日志是按照我们梳理的 Bean 的生命周期顺序打印的,验证完毕。

6.模拟AOP增强

MyBeanPostProcessor.javapostProcessAfterInitialization() 方法中注释了 AOP 底层 CGLIB 动态代理的演示代码的,可以在 UserTestmain 方法中获取到 user 的 Bean 对象后,将鼠标放到 user 上看下,打开和关闭 CGLIB 代理的注释生成的 user Bean 会不同:

关闭 CGLIB 代理:

在这里插入图片描述

打开 CGLIB 代理:

在这里插入图片描述

整理完毕,完结撒花~ 🌻





参考地址:

1.框架篇-05-Spring-bean的生命周期,https://www.bilibili.com/video/BV1yT411H7YK/?p=39&spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=cf16f2e5f7c4612add10f9f9268a2c8a

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【网络工程】网络流量分析工具 Wireshark

文章目录 第一章&#xff1a;WireShark介绍第二章&#xff1a;WireShark应用第三章&#xff1a;Wireshark 实战 第一章&#xff1a;WireShark介绍 Wireshark (前身 Ethereal)&#xff1a;它是一个强大的网络封包分析软件工具 ! 此工具使用WinPCAP作为接口&#xff0c;直接与网卡…

TiDB Serverless 正式商用,全托管的云服务带来数据管理和应用程序开发的全新体验

八 年 前 &#xff0c;我们构建了 TiDB&#xff0c;一个开源分布式关系型数据库。 我们的目标是重新定义开发者和企业处理数据的方式&#xff0c;满足不断增长的可扩展性、灵活性和性能需求。 从那时起&#xff0c;PingCAP 便致力于为开发者和企业提供快速、灵活和规模化的数据…

线性代数 | 机器学习数学基础

前言 线性代数&#xff08;linear algebra&#xff09;是关于向量空间和线性映射的一个数学分支。它包括对线、面和子空间的研究&#xff0c;同时也涉及到所有的向量空间的一般性质。 本文主要介绍机器学习中所用到的线性代数核心基础概念&#xff0c;供读者学习阶段查漏补缺…

Autosar通信入门系列05-聊聊一帧Can/CanFD报文发送时间?

本文框架 1. 概述2. 一帧CAN报文发送时间计算3. 一帧CanFD报文的传输时间计算3.1 标准CAN与CANFD两者间的区别3.2 CANFD报文传输时间计算 1. 概述 本篇我们一起看下一帧Can报文发送需要多长时间&#xff0c;下述文章里我们会首先计算下Can分别对应的字节数&#xff0c;再根据传…

Linux 下设置开机自启动的方法

文章目录 事先准备对于普通的 Linux对于 RedHat Enterprise Linux 9 笔者的运行环境&#xff1a; 设置成功过的 Linux&#xff1a; RedHat Enterprise Linux 9 x86_64 CentOS 8 x86_64 事先准备 进行这个教程之前&#xff0c;必须要先安装好一个 Linux 操作系统。这个 Linux…

C 语言高级3--函数指针回调函数,预处理,动态库的封装

目录 1.函数指针和回调函数 1.1 函数指针 1.1.1 函数类型 1.1.2 函数指针(指向函数的指针) 1.1.3 函数指针数组 1.1.4 函数指针做函数参数(回调函数) 2.预处理 2.1 预处理的基本概念 2.2 文件包含指令(#include) 2.2.1 文件包含处理 2.2.2 #incude<>和#include&q…

基于Jenkins+Python+Ubuntu+Docker的接口/UI自动化测试环境部署详细过程

基于JenkinsPythonUbuntuDocker的接口/UI自动化测试环境部署详细过程 1 Jenkins是什么&#xff1f;2 Jenkins目标是什么&#xff1f;3 什么是CI/CD?3.1 CI持续集成3.2 CD持续部署3.3 CD持续交付 4 Ubuntu环境4.1 环境需求4.2 实现思路 5 Ubuntu下安装Docker6 安装Jenkins6.1 拉…

Linux Day06

目录 一、printf输出问题 二、复制进程fork 2.1进程 2.2 pid_t fork(void); 注意&#xff1a; 2.3逻辑地址和物理地址 2.4写时拷贝技术 一、printf输出问题 printf 函数并不会直接将数据输出到屏幕&#xff0c;而是先放到缓冲区中&#xff0c;只有一下三种情况满 足&a…

24考研数据结构-第五章:树与二叉树

目录 第五章&#xff1a;树5.1树的基本概念5.1.1树的定义5.1.2 基本术语5.1.3 树的性质 5.2二叉树的概念5.2.1 二叉树的定义与特性5.2.2 几种特殊的二叉树5.2.3 二叉树的性质5.2.4 完全二叉树的性质5.2.5 二叉树的存储结构1. 顺序存储重要的基本操作非完全二叉树2. 链式存储逆向…

【Paper】2020_网络化多智能体系统的事件触发一致性研究_徐勇

徐勇. 网络化多智能体系统的事件触发一致性研究[D].浙江大学,2020.DOI:10.27461/d.cnki.gzjdx.2020.001385. 文章目录 5 已知 DoS 攻击策略下多智能体系统的事件触发安全一致性分析5.1 引言5.2 数学模型与问题描述5.3 控制器和事件触发条件的设计5.5 数值仿真程序 Main.m程序 M…

使用FreeMarker导出word文档(支持导出图片)

今天跟大家分享一下工作中比较实用的导出word 带图片的功能。 对于在idea开发中我们需要引入以下依赖&#xff1a; 2.对于eclipse 开发我们需要进入对应的jar包 这个必须放在lib下&#xff0c;同样也需要在当前项目的环境是加入该依赖 需要在MEAT-INF加入 首先制定word 导出…

UE5 半透明覆层材质

文章目录 前言介绍示例1示例2示例3 前言 本文采用虚幻5.2.1版本演示&#xff0c;介绍半透明覆层材质&#xff08;覆层材质&#xff09;。 介绍 半透明覆层材质是 UE5.1 版本 更新的功能&#xff0c;使用半透明覆层材质&#xff0c;可以轻松的给物体表面附着一层材质。 在UE5…

Python3 处理PDF之PyMuPDF 入门

PyMuPDF 简介 PyMuPDF是一个用于处理PDF文件的Python库&#xff0c;它提供了丰富的功能来操作、分析和转换PDF文档。这个库的设计目标是提供一个简单易用的API,使得开发者能够轻松地在Python程序中实现PDF文件的各种操作。 PyMuPDF的主要特点如下&#xff1a; 跨平台兼容性&a…

Aspose.Imaging for Python via .NET Crack

Aspose.Imaging for Python via .NET Crack Aspose.Imaging for Python via.NET是一个提供高级图像处理功能的库。您可以使用此API轻松创建、加载、操作、转换或压缩图像。另外&#xff0c;Aspose.Imaging for Python通过.NET支持绘图和使用图形基元。图像导出和转换-API的核心…

SpringCloud(29):Nacos简介

1 什么是配置中心 1.1 什么是配置 应用程序在启动和运行的时候往往需要读取一些配置信息&#xff0c;配置基本上伴随着应用程序的整个生命周期&#xff0c;比如&#xff1a;数据库连接参数、启动参数等。 配置主要有以下几个特点&#xff1a; 配置是独立于程序的只读变量 …

编译LightGBM错误处理记录

在LightGBM源码中新建文件夹build&#xff0c;然后在build目录下运行命令 cmake -A x64 -DUSE_GPU1 -DBOOST_ROOTC:\local\boost_1_82_0 -DBOOST_LIBRARYDIRC:\local\boost_1_82_0\lib64-msvc-14.3 .. 报错 错误原因&#xff1a; miniconda3\Library\lib\cmake\中boost版本是…

状态模式(State)

状态模式是一种行为设计模式&#xff0c;允许一个对象在其内部状态改变时改变它的行为&#xff0c;使其看起来修改了自身所属的类。其别名为状态对象(Objects for States)。 State is a behavior design pattern that allows an object to change its behavior when its inter…

前端个人年度工作述职报告(二十篇)

前端个人年度工作述职报告篇1 尊敬的各位领导、各位同仁&#xff1a; 大家好!按照20__年度我公司就职人员工作评估的安排和要求&#xff0c;我认真剖析、总结了自己的工作情况&#xff0c;现将本人工作开展情况向各位领导、同仁做以汇报&#xff0c;有不妥之处&#xff0c;希…

【前瞻】视频技术的发展趋势讨论以及应用场景

视频技术的发展可以追溯到19世纪初期的早期实验。到20世纪初期&#xff0c;电视技术的发明和普及促进了视频技术的进一步发展。 1&#xff09;数字化&#xff1a;数字化技术的发明和发展使得视频技术更加先进。数字电视信号具有更高的清晰度和更大的带宽&#xff0c;可以更快地…

ospf减少LSA更新

实验及实验要求 一、思路 1.根据区域划分IP地址 2.使公网可通---写缺省 3.使R3成为MGRE中心站点&#xff0c;R5、R6、R7为分支站点 4.一个个去配置ospf区域和RIP区域&#xff0c;确保每个区域配置无误 5.区域0要更改OSPF在接口的工作类型为broadcast &#xff0c;并使R3为…