Spring Bean生命周期

2023-05-16

1.概述

之前我们在总结Spring扩展点:后置处理器时谈到了Spring Bean的生命周期和其对Spring框架原理理解的重要性,所以接下来我们就来分析一下Bean生命周期的整体流程。首先Bean就是一些Java对象,只不过这些Bean不是我们主动new出来的,而是交个Spring IOC容器创建并管理的,因此Bean的生命周期受Spring IOC容器控制,Bean生命周期大致分为以下几个阶段:

  • Bean的实例化(Instantiation):Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的,是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化

  • Bean的属性赋值(Populate):Bean实例化之后还仅仅是个"半成品",还需要对Bean实例的属性进行填充,Bean的属性赋值就是指 Spring 容器根据BeanDefinition中属性配置的属性值注入到 Bean 对象中的过程。

  • Bean的初始化(Initialization):对Bean实例的属性进行填充完之后还需要执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段,并且Spring高频面试题Bean的循环引用问题也是在这个阶段体现的;

  • Bean的使用阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期,接下来Bean就可以被随心所欲地使用了。

  • Bean的销毁(Destruction):Bean 的销毁是指 Spring 容器在关闭时,执行一些清理操作的过程。在 Spring 容器中, Bean 的销毁方式有两种:销毁方法destroy-method和 DisposableBean 接口。

项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用

Github地址:https://github.com/plasticene/plasticene-boot-starter-parent

Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent

微信公众号Shepherd进阶笔记

2.Bean生命周期详解和使用案例

这里我先纠正一下在Spring扩展点后置处理器总结的描述:之前说BeanDefinitionRegistryPostProcessor,BeanFactoryPostProcessor,BeanPostProcessor这三个后置处理器的调用时机都在Spring Bean生命周期中是不严谨的,按照上面我们对Bean生命周期的阶段划分,只有BeanPostProcessor作用于Bean的生命周期中,而BeanDefinitionRegistryPostProcessor,BeanFactoryPostProcessor是针对BeanDefinition的,所以不属于Bean的生命周期中。

BeanPostProcessor在Bean生命周期的体现如下图所示:

Bean的生命周期和人的一生一样都会经历从出生到死亡,中间是一个漫长且复杂的过程,接下来我们就来整体分析一下Bean生命周期的核心流程和相关接口回调方法的调用时机,同时这里想强调一下Bean的生命周期也是面试的高频考点,对核心流程务必要掌握清楚,这里用一张流程图进行详述展示,是重点、重点、重点

根据上面的Bean生命周期核心流程做如下代码演示示例:

Bean定义:

@Data
@AllArgsConstructor
public class Boo implements InitializingBean, DisposableBean, BeanNameAware {
    private Long id;
    private String name;

    public Boo() {
        System.out.println("boo实例化构造方法执行了...");
    }

    @PostConstruct
    public void initMethod() {
        System.out.println("boo执行初始化init()方法了...");
    }

    @PreDestroy
    public void destroyMethod() {
        System.out.println("boo执行初始化destroy()方法了...");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("boo执行InitializingBean的afterPropertiesSet()方法了...");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("boo执行DisposableBean的destroy()方法了...");
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("boo执行BeanNameAware的setBeanName()方法了...");
    }
}

实现InstantiationAwareBeanPostProcessor:

@Component
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
    @Override
    public Object postProcessBeforeInstantiation(Class<?> BeanClass, String BeanName) throws BeansException {
        System.out.println("InstantiationAwareBeanPostProcessor的before()执行了...." + BeanName);
        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object Bean, String BeanName) throws BeansException {
        System.out.println("InstantiationAwareBeanPostProcessor的after()执行了...." + BeanName);
        return false;
    }
}

实现BeanPostProcessor:

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object Bean, String BeanName) throws BeansException {
        System.out.println("BeanPostProcessor的before()执行了...." + BeanName);
        return Bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object Bean, String BeanName) throws BeansException {
        System.out.println("BeanPostProcessor的after()执行了...."+ BeanName);
        return Bean;
    }
}

执行下面的配置类测试方法:

@ComponentScan(basePackages = {"com.shepherd.common.config"})
@Configuration
public class MyConfig {

    @Bean
    public Boo boo() {
        return new Boo();
    }

    public static void main(String[] args) {

        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        String[] BeanDefinitionNames = applicationContext.getBeanDefinitionNames();
        // 遍历Spring容器中的BeanName
        for (String BeanDefinitionName : BeanDefinitionNames) {
            System.out.println(BeanDefinitionName);
        }
        // 这里调用的applicationContext的close()触发Bean的销毁回调方法
        applicationContext.close();
    }
}

名为boo的Bean相关运行结果如下:

InstantiationAwareBeanPostProcessor的before()执行了....boo
boo实例化构造方法执行了...
InstantiationAwareBeanPostProcessor的after()执行了....boo
boo执行BeanNameAware的setBeanName()方法了...
BeanPostProcessor的before()执行了....boo
boo执行初始化init()方法了...
boo执行InitializingBean的afterPropertiesSet()方法了...
BeanPostProcessor的after()执行了....boo
......
boo执行初始化destroy()方法了...
boo执行DisposableBean的destroy()方法了...

根据控制台打印结果可以boo的相关方法执行顺序严格遵从上面流程图,同时当我们执行容器applicationContext的关闭方法close()会触发调用bean的销毁回调方法。

3.浅析Bean生命周期源码实现

DefaultListableBeanFactory是Spring IOC的Bean工厂的一个默认实现,IOC大部分核心逻辑实现都在这里,可关注。Bean生命周期就是创建Bean的过程,这里我们就不在拐弯抹角兜圈子,直接来到DefaultListableBeanFactory继承的AbstractAutowireCapableBeanFactory#doCreateBean()方法,之前说过在Spring框架中以do开头的方法都是核心逻辑实现所在

protected Object doCreateBean(String BeanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		// Instantiate the Bean.
		// BeanWrapper 是对 Bean 的包装,其接口中所定义的功能很简单包括设置获取被包装的对象,获取被包装 Bean 的属性描述器
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			// <1> 单例模型,则从未完成的 FactoryBean 缓存中删除
			instanceWrapper = this.factoryBeanInstanceCache.remove(BeanName);
		}
		if (instanceWrapper == null) {
			// <2> 使用合适的实例化策略来创建新的实例:工厂方法、构造函数自动注入、简单初始化
			instanceWrapper = createBeanInstance(BeanName, mbd, args);
		}
		// 包装的实例对象
		Object Bean = instanceWrapper.getWrappedInstance();
		// 包装的实例class类型
		Class<?> BeanType = instanceWrapper.getWrappedClass();
		if (BeanType != NullBean.class) {
			mbd.resolvedTargetType = BeanType;
		}

		// Allow post-processors to modify the merged Bean definition.
		// <3> 判断是否有后置处理
		// 如果有后置处理,则允许后置处理修改 BeanDefinition
		synchronized (mbd.postProcessingLock) {
			if (!mbd.postProcessed) {
				try {
					applyMergedBeanDefinitionPostProcessors(mbd, BeanType, BeanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), BeanName,
							"Post-processing of merged Bean definition failed", ex);
				}
				mbd.postProcessed = true;
			}
		}

		// Eagerly cache singletons to be able to resolve circular references
		// even when triggered by lifecycle interfaces like BeanFactoryAware.
		// <4> 解决单例模式的循环依赖
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(BeanName));
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching Bean '" + BeanName +
						"' to allow for resolving potential circular references");
			}
			// 提前将创建的 Bean 实例加入到 singletonFactories 中
			// 这里是为了后期避免循环依赖
			addSingletonFactory(BeanName, () -> getEarlyBeanReference(BeanName, mbd, Bean));
		}

		// Initialize the Bean instance.
		// 开始初始化 Bean 实例对象
		Object exposedObject = Bean;
		try {
			// <5> 对 Bean 进行填充,将各个属性值注入,其中,可能存在依赖于其他 Bean 的属性
			// 则会递归初始依赖 Bean
			populateBean(BeanName, mbd, instanceWrapper);
			// <6> 调用初始化方法
			exposedObject = initializeBean(BeanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && BeanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), BeanName, "Initialization of Bean failed", ex);
			}
		}

		// <7> 循环依赖处理
		if (earlySingletonExposure) {
			// 获取 earlySingletonReference
			Object earlySingletonReference = getSingleton(BeanName, false);
			// 只有在存在循环依赖的情况下,earlySingletonReference 才不会为空
			if (earlySingletonReference != null) {
				// 如果 exposedObject 没有在初始化方法中被改变,也就是没有被增强
				if (exposedObject == Bean) {
					exposedObject = earlySingletonReference;
				}
				// 处理依赖
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(BeanName)) {
					String[] dependentBeans = getDependentBeans(BeanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						throw new BeanCurrentlyInCreationException(BeanName,
								"Bean with name '" + BeanName + "' has been injected into other Beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] in its raw version as part of a circular reference, but has eventually been " +
								"wrapped. This means that said other Beans do not use the final version of the " +
								"Bean. This is often the result of over-eager type matching - consider using " +
								"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

		// Register Bean as disposable.
		try {
			// <8> 注册 Bean的销毁逻辑
			registerDisposableBeanIfNecessary(BeanName, Bean, mbd);
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), BeanName, "Invalid destruction signature", ex);
		}

		return exposedObject;
	}

由上面代码可知,Bean的创建过程核心步骤如下:

  • createBeanInstance(BeanName, mbd, args) 进行Bean的实例化
  • populateBean(BeanName, mbd, instanceWrapper)进行Bean的属性填充赋值
  • initializeBean(BeanName, exposedObject, mbd)处理Bean初始化之后的各种回调事件
  • registerDisposableBeanIfNecessary(BeanName, Bean, mbd)注册Bean的销毁接口
  • 解决创建Bean过程中的循环依赖,Spring使用三级缓存解决循环依赖,这也是一个重要的知识点,这里不详细阐述,后面会安排

接下来我们就来看看和Bean初始化阶段相关各种回调事件执行方法#initializeBean(),分析一下上面流程图的执行顺序是怎么实现的。

protected Object initializeBean(final String BeanName, final Object Bean, RootBeanDefinition mbd) {
        if (System.getSecurityManager() != null) {
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    invokeAwareMethods(BeanName, Bean);
                    return null;
                }
            }, getAccessControlContext());
        }
        else {
            // 涉及到的回调接口点进去一目了然,代码都是自解释的
            // BeanNameAware、BeanClassLoaderAware或BeanFactoryAware
            invokeAwareMethods(BeanName, Bean);
        }

        Object wrappedBean = Bean;
        if (mbd == null || !mbd.isSynthetic()) {
            // BeanPostProcessor 的 postProcessBeforeInitialization 回调
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, BeanName);
        }

        try {
            // init-methods
            // 或者是实现了InitializingBean接口,会调用afterPropertiesSet() 方法
            invokeInitMethods(BeanName, wrappedBean, mbd);
        }
        catch (Throwable ex) {
            throw new BeanCreationException(
                    (mbd != null ? mbd.getResourceDescription() : null),
                    BeanName, "Invocation of init method failed", ex);
        }
        if (mbd == null || !mbd.isSynthetic()) {
            // BeanPostProcessor 的 postProcessAfterInitialization 回调
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, BeanName);
        }
        return wrappedBean;
    }

至于Bean的销毁流程与Bean初始化类似,从上面的使用示例中看可以得出当容器关闭时,才会对Bean销毁方法进行调用。销毁过程是这样的。顺着close()-> doClose() -> destroyBeans() -> destroySingletons() -> destroySingleton() -> destroyBean() -> Bean.destroy() ,会看到最终调用Bean的销毁方法。这里就不在展示源码细节啦,有兴趣的话自行去调试查看了解

4.总结

以上全部就是对Spring Bean生命周期的全面总结, Spring 的 Bean 容器机制是非常强大的,它可以帮助我们轻松地管理 Bean 对象,并且提供了丰富的生命周期回调方法,允许我们在 Bean 的生命周期中执行自己的特定操作,这对于我们平时工作使用中进行增强扩展至关重要,因此掌握Bean的生命周期是必须的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring Bean生命周期 的相关文章

  • 简单搜索--马的走法

    描述 在一个4 5的棋盘上 xff0c 输入马的起始位置坐标 xff08 纵 横 xff09 xff0c 求马能返回初始位置的所有不同走法的总数 xff08 马走过的位置不能重复 马走 日 字 xff09 输入 多个测试数据 每组2个数 x
  • 用cephadm单节点安装ceph

    文章目录 官方文档参考安装虚拟机操作系统安装ntp lvm2配置时区安装docker安装cephadm验证 安装修改dashboard密码 安装ceph common查看可用存储添加存储验证 使用pool创建删除pool 块存储 xff08
  • Ubuntu18.04.6更新nvidia驱动后重启卡住

    这已经不是我第一次重装驱动无法重启了 xff0c 更新完驱动之后重启首先会卡在这样一个界面 xff1a 后来进行了这样的操作 xff1a 第一步 xff1a 重启Ubuntu系统开机按esc或shift xff0c 会进入启动选择页 xff
  • Springcloud--服务调度OpenFeign、RestTemplate

    一 RestTemplate RestTemplate是Spring提供的用于访问Rest服务的客户端 xff0c RestTemplate提供了多种便捷访问远程Http服务的方法 能够大大提高客户端的编写效率 1 基本使用 发送GET请求
  • AdamTechLouis's talk: Deep Learning with Knowledge Graphs

    Last week I gave a talk at Connected Data London on the approach that we have developed at Octavian to use neural networ
  • anaconda3下64位python和32位python共存

    今天需要调用一个dll动态函数库 xff0c 但是本地的python是64位的 xff0c dll是32位的 xff0c 直接调用会报错 OSError WinError 193 1 不是有效的 Win32 应用程序 python版本 xf
  • django工程-根据不同条件查询数据库数据

    https docs djangoproject com en 2 0 ref models querysets field lookups
  • 为什么有透明度gif动态图放在网页上为什么变成纯色了

    为什么有透明度gif动态图放在网页上为什么变成纯色了
  • keil突然烧录不进去了解决记录

    keil突然烧录不进去了 一直显示如下界面 解决 xff1a 1 xff0c 拔插连接电脑的仿真器 xff0c 观察设备管理器保证能够识别到此设备 xff0c 如下图 2 除了选择正确的仿真器 xff0c 下图红框一定要勾选 xff0c 我
  • VTK系列教程一:整体架构

    VTK Visualization Toolkit 顾名思义主要用于三维计算机图形学 图像处理和可视化 xff0c VTK到底能做什么 xff1f 这还得从人类的视觉系统讲起 xff0c 现实世界中的物体在光照的作用下 xff0c 其反射的
  • VTK系列教程四:程序嵌套

    前一篇文章我们简单介绍了MedBeyond项目 xff0c 从一开始的设计初衷可见 xff0c 我们希望它能作为一个独立的进程运行 xff0c 也可嵌入到其他程序中作为其它UI进程的子窗口运行 xff0c 今天我们就来看一下程序的运行方式以
  • c语言数据结构数组修改数组_数组数据结构

    c语言数据结构数组修改数组 An array is a data structure for storing elements of one data type sequentially The elements of an array a
  • VTK系列教程九:VR图像裁剪

    我们已经实现了自定义交互 xff0c 但有一种交互比较特殊 xff1a VR图像的任意形状裁剪 xff0c 俗称套索工具 xff0c 它能够将我们不需要的部分 xff0c 有遮挡的部分裁减掉 xff0c 今天我们就来看一下其实现方式 VTK
  • ubuntu 下使用flatpak的一些记录

    标题 几点说明安装flatpak常用命令安装目录应用程序图标卸载步骤 几点说明 ubuntu 默认没有安装flatpak xff0c 毕竟snap才是亲儿子 ubuntu22 04 上使用体验会更好一些 xff0c 之前的版本有些软件是下载
  • (Rust) error:failed to run custom build command for `pear_codegen v0.1.4`

    执行命令 xff1a cargo run 问题截图 xff1a 问题原因 xff1a nightly没有设置默认 解决 xff1a rustup default nightly
  • 微信小程序——获取视频的URL地址【已解决】

    微信小程序 获取视频的URL地址 已解决 如何获取音乐MV的URL地址 虾米音乐 试了很多音乐网站发现 虾米音乐 的MV的URL地址是可以获取的 xff0c 并且在微信小程序播放视频的时候也可以正常播放 在bilibili 获取的地址有两个
  • vivo手机如何投屏到电脑

    vivo手机如何投屏到电脑 电脑配置 点击投影 选择仅电脑屏幕 选择 连接到无线显示器 点击 投影到此电脑 按照图片进行配置 点击查找其他类型的设备 打开蓝牙 xff0c 准备配对 手机配置 打开手机蓝牙 xff0c 与电脑配对 进入设置
  • 酷狗音乐如何进入完整歌词界面

    酷狗音乐如何进入完整歌词界面 首先打开酷狗音乐播放器 1 听歌的界面是这样的 2 想显示歌词就变成了桌面歌词 3 想显示完整歌词的话 xff0c 需要进行以下操作 3 1随便选择一个可以观看MV的歌曲 3 2点击 号 xff0c 关闭MV播
  • ModuleNotFoundError: No module named 'matplotlib._path'【已解决】适合小白(详细)

    ModuleNotFoundError No module named matplotlib path 已解决 报错情况 这是我的一段程序 运行之后报错 上网查了很多解决方案 xff0c 大家的解决方案一般有两种 比如这位博主列出的两种解决
  • 如何使用手机连接外接U盘(vivo)

    如何使用手机连接外接U盘 xff08 vivo xff09 1 首先你需要一个U盘 大概长这样 xff0c 有两个头 xff0c 一个正常USB口 xff0c 一个而连接手机的口 2 打开手机设置 xff0c 找到更多设置 3 进入更多设置

随机推荐

  • 电脑相机不能用,打开相机出现一个相机的图标加一个斜杠【解决方法】

    电脑相机不能用了 xff0c 打开相机出现一个相机的图标加一个斜杠 解决方法 原来电脑上的相机是可以用的 xff0c 但今天打开之后突然就不能用了 打开后的界面 摄像头的灯是亮着的 xff0c 但是相机就是打不开 上网找了一堆方法之后 xf
  • mysql按某几个字段分组后,查询组内某个属性最大(小)的数据集合,每组取第一条

    mysql按某几个字段分组后 查询组内某个属性最大 xff08 小 xff09 的数据集合 xff0c 每组取第一条 xff1a SELECT task id task role org id task task id task creat
  • readline_Swift readLine(),Swift print()

    readline In this tutorial we ll be discussing how to read the standard input in Swift from the user and the different wa
  • Python 提取 PDF 文件的标题、日期和内容并将其存储到 MySQL 数据库中

    一 要使用 Python 提取 PDF 文件的标题 日期和内容并将其存储到 MySQL 数据库中 xff0c 您可以按照以下步骤操作 xff1a 1 安装必要的库 xff1a pdfminer PyPDF2 mysql connector
  • 玩客云刷入armbian系统总结

    闲着没事把去年搞的玩客云重新刷个armbian系统作为服务器使用 xff0c 以下是个人折腾的总结 准备工具 xff1a USB 转 TTL 线一根 双公头 USB 线一根 闲置 U 盘一个 xff0c 或者读卡器配合存储卡 软件下载地址
  • 按位与、按位或、按位异或、按位取反、按位左移、按位右移

    位运算符比一般的算术运算符速度要快 xff0c 而且可以实现一些算术运算符不能实现的功能 如果要开发高效率程序 xff0c 位运算符是必不可少的 位运算符用来对二进制位进行操作 xff0c 包括 xff1a 按位与 xff08 amp xf
  • linux—shell中的正则表达式

    一 grep 1 grep概述 文本过滤命令 xff1a grep是一种文本搜索工具 xff0c 根据用户指定的 模式 对目标文本进行匹配检查 xff0c 打印匹配到的行 xff1b grep xff1a 由正则表达式或者字符及基本文本字符
  • LaTeX教程2

    LaTeX教程2 latex10 LaTeX数学公式初步latex11 LaTeX数学模式中的矩阵latex12 LaTeX数学公式的多行公式latex13 LaTeX中的参考文献BibTeXlatex14 LaTeX中的参考文献BibLa
  • Linux深入浅出PyTorch(一)安装及基础知识

    目录 PyTorch安装工具使用1 开发工具建议使用pycharm2 安装包管理工具建议使用Anaconda3 安装结果检查 Pytorch安装2 配置pytorch 虚拟环境3 在PyCharm中配置PyTorch虚拟环境 丰富的PyTo
  • AttributeError: ‘NoneType‘ object has no attribute +++ 错误

    AttributeError 39 NoneType 39 object has no attribute 43 43 43 错误 解决办法 xff1a if self pool is not None self pool reset nu
  • Win11系统联想小新Air14Plus笔记本Anaconda环境下安装PyTorch

    PyTorch 创建虚拟环境 conda create name 名称 python 61 3 6 xff08 python版本 xff09 eg conda create name pytorch python 61 3 6 进入虚拟环境
  • 恒源云配置环境过程

    1 下载oss 软件 2 打开oss 上传数据 输入命令login 登录恒源云账号 输入命令 mkdir oss datasets 在个人数据中建立文件夹 名为 34 datasets 34 输入命令 cp 个人数据 zip oss dat
  • ubuntu 无法进入开机界面

    1 按电源键强制关机 xff0c 然后按电源键重新启动电脑 xff0c 然后在光标选择自己使用的系统 不要按 Enter 回车键 xff0c 然后按 e 键 xff0c 进入grub编辑页面 将光标移动到倒数第二行 xff0c 然后在qui
  • iOS警报– UIAlertController

    In this tutorial we ll be discussing the UIAlertController class and how it is handy to create different types of Alerts
  • jquery 读取XML 文件 并按时间进行排序

    在开发中遇到了一个很烦躁的问题 xff0c 就说在jdk 1 4 的情况下 xff0c 读取远程的XML文件不能自动断开 xff0c 于是就想到了 xff0c 用jQuery来写 在测试的时候使用的是本地的文件 xff0c 可到最后发现 x
  • clickHouse相关知识详解

    clickHouse相关知识详解 clickHouse介绍大数据技术背景什么是clickHouseclickHouse核心特性clickHouse适用场景clickHouse不适用的场景使用clickHouse的大厂 clickHouse安
  • 抖音数据库解析总结

    目前在抖音打出的包里面 xff1a 在database文件夹下面存在存着许多数据库 xff0c 这个大概挨个梳理了一下 xff0c 有用目前就两个数据库 xff1a 抖音id im db eg 95034530671 im db xff1a
  • openfeign实现原理

    1 openfeign简介 OpenFeign 提供了一种声明式的远程调用接口 xff0c 它可以大幅简化远程调用的编程体验 调用其他服务接口像调用本地服务service方法一样丝滑顺畅 使用示例如下 xff1a 引入依赖 span cla
  • MySQL高阶知识点(一):SQL语句执行流程

    1 一条 SQL查询语句是如何被执行的 MySQL 的基本架构示意图如下所示 xff1a 大体来说 xff0c MySQL 可以分为 Server 层和存储引擎层两部分 Server 层包括连接器 查询缓存 分析器 优化器 执行器等 xff
  • Spring Bean生命周期

    1 概述 之前我们在总结Spring扩展点 xff1a 后置处理器时谈到了Spring Bean的生命周期和其对Spring框架原理理解的重要性 xff0c 所以接下来我们就来分析一下Bean生命周期的整体流程 首先Bean就是一些Java