当前位置: 首页 > news >正文

Spring源码面试最难问题——循环依赖

前言

问:Spring 如何解决循环依赖?
答:Spring 通过提前曝光机制,利用三级缓存解决循环依赖(这原理还是挺简单的,参考:三级缓存、图解循环依赖原理)
再问:Spring 通过提前曝光,直接曝光到二级缓存已经可以解决循环依赖问题了,为什么一定要三级缓存?
再细问:如果循环依赖的时候,所有类又都需要 Spring AOP 自动代理,那 Spring 如何提前曝光?曝光的是原始 bean 还是代理后的 bean?

这些问题算是 Spring 源码的压轴题了,如果这些问题都弄明白,恭喜你顺利结业 Spring 源码了。先上图,再分析源码

源码分析

进入正题,在 Spring 创建 Bean 的核心代码 doGetBean 中,在实例化 bean 之前,会先尝试从三级缓存获取 bean,这也是 Spring 解决循环依赖的开始

(一) 缓存中获取 bean

// AbstractBeanFactory.java
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {final String beanName = transformedBeanName(name);Object bean;// 2. 尝试从缓存中获取beanObject sharedInstance = getSingleton(beanName);...
}

getSingleton:

	protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 从一级缓存获取,key=beanName value=beanObject singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {// 从二级缓存获取,key=beanName value=beansingletonObject = this.earlySingletonObjects.get(beanName);// 是否允许循环引用if (singletonObject == null && allowEarlyReference) {/*** 三级缓存获取,key=beanName value=objectFactory,objectFactory中存储getObject()方法用于获取提前曝光的实例** 而为什么不直接将实例缓存到二级缓存,而要多此一举将实例先封装到objectFactory中?* 主要关键点在getObject()方法并非直接返回实例,而是对实例又使用* SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法对bean进行处理** 也就是说,当spring中存在该后置处理器,所有的单例bean在实例化后都会被进行提前曝光到三级缓存中,* 但是并不是所有的bean都存在循环依赖,也就是三级缓存到二级缓存的步骤不一定都会被执行,有可能曝光后直接创建完成,没被提前引用过,* 就直接被加入到一级缓存中。因此可以确保只有提前曝光且被引用的bean才会进行该后置处理*/ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {/*** 通过getObject()方法获取bean,通过此方法获取到的实例不单单是提前曝光出来的实例,* 它还经过了SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法处理过。* 这也正是三级缓存存在的意义,可以通过重写该后置处理器对提前曝光的实例,在被提前引用时进行一些操作*/singletonObject = singletonFactory.getObject();// 将三级缓存生产的bean放入二级缓存中this.earlySingletonObjects.put(beanName, singletonObject);// 删除三级缓存this.singletonFactories.remove(beanName);}}}}return singletonObject;}

三级缓存分别是:

singletonObject: 一级缓存,该缓存key = beanName, value = bean; 这里的 bean 是已经创建完成的,该 bean 经历过实例化->属性填充->初始化以及各类的后置处理。因此,一旦需要获取 bean 时,我们第一时间就会寻找一级缓存
earlySingletonObjects: 二级缓存,该缓存key = beanName, value = bean; 这里跟一级缓存的区别在于,该缓存所获取到的 bean 是提前曝光出来的,是还没创建完成的。也就是说获取到的 bean 只能确保已经进行了实例化,但是属性填充跟初始化肯定还没有做完,因此该 bean 还没创建完成,仅仅能作为指针提前曝光,被其他 bean 所引用
singletonFactories: 三级缓存,该缓存key = beanName, value = beanFactory; 在 bean 实例化完之后,属性填充以及初始化之前,如果允许提前曝光,spring 会将实例化后的 bean 提前曝光,也就是把该 bean 转换成 beanFactory 并加入到三级缓存。在需要引用提前曝光对象时再通过 singletonFactory.getObject()获取。
这里抛出问题,如果我们直接将提前曝光的对象放到二级缓存 earlySingletonObjects,Spring 循环依赖时直接取就可以解决循环依赖了,为什么还要三级缓存 singletonFactory 然后再通过 getObject()来获取呢?这不是多此一举?

(二) 三级缓存的添加

我们回到添加三级缓存,添加 SingletonFactory 的地方,看看 getObject()到底做了什么操作

this.addSingletonFactory(beanName, () -> {return this.getEarlyBeanReference(beanName, mbd, bean);
});

可以看到在返回 getObject()时,多做了一步 getEarlyBeanReference 操作,这步操作是 BeanPostProcess 的一种,也就是给子类重写的一个后处理器,目的是用于被提前引用时进行拓展。即:曝光的时候并不调用该后置处理器,只有曝光,且被提前引用的时候才调用,确保了被提前引用这个时机触发。

(三) 提前曝光代理 earlyProxyReferences

因此所有的重点都落到了 getEarlyBeanReference 上,getEarlyBeanReference 方法是 SmartInstantiationAwareBeanPostProcessor 所规定的接口。再通过 UML 的类图查看实现类,仅有 AbstractAutoProxyCreator 进行了实现。也就是说,除了用户在子类重写,否则仅有 AbstractAutoProxyCreator 一种情况

// AbstractAutoProxyCreator.java
public Object getEarlyBeanReference(Object bean, String beanName) {// 缓存当前bean,表示该bean被提前代理了Object cacheKey = getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean);// 对bean进行提前Spring AOP代理return wrapIfNecessary(bean, beanName, cacheKey);
}

wrapIfNecessary 是用于 Spring AOP 自动代理的。Spring 将当前 bean 缓存到 earlyProxyReferences 中标识提前曝光的 bean 在被提前引用之前,然后进行了 Spring AOP 代理。

但是经过 Spring AOP 代理后的 bean 就已经不再是原来的 bean 了,经过代理后的 bean 是一个全新的 bean,也就是说代理前后的 2 个 bean 连内存地址都不一样了。这时将再引出新的问题:B 提前引用 A 将引用到 A 的代理,这是符合常理的,但是最原始的 bean A 在 B 完成创建后将继续创建,那么 Spring Ioc 最后返回的 Bean 是 Bean A 呢还是经过代理后的 Bean 呢?

这个问题我们得回到 Spring AOP 代理,Spring AOP 代理时机有 2 个:

当自定义了 TargetSource,则在 bean 实例化前完成 Spring AOP 代理并且直接发生短路操作,返回 bean
正常情况下,都是在 bean 初始化后进行 Spring AOP 代理
如果要加上今天说的提前曝光代理,getEarlyBeanReference 可以说 3 种
第一种情况就没什么好探究的了,直接短路了,根本没有后续操作。而我们关心的是第二种情况,在 Spring 初始化后置处理器中发生的 Spring AOP 代理

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)throws BeansException {Object result = existingBean;for (BeanPostProcessor processor : getBeanPostProcessors()) {// 调用bean初始化后置处理器处理Object current = processor.postProcessAfterInitialization(result, beanName);if (current == null) {return result;}result = current;}return result;
}
// AbstractAutoProxyCreator.javapublic Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {// 获取缓存keyObject cacheKey = getCacheKey(bean.getClass(), beanName);// 查看该bean是否被Spring AOP提前代理!而缓存的是原始的bean,因此如果bean被提前代理过,这此处会跳过// 如果bean没有被提前代理过,则进入AOP代理if (this.earlyProxyReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}

earlyProxyReferences 是不是有点熟悉,是的,这就是我们刚刚提前曝光并且进行 Spring AOP 提前代理时缓存的原始 bean,如果缓存的原始 bean 跟当前的 bean 是一至的,那么就不进行 Spring AOP 代理了!返回原始的 bean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException {try {///*** 4. 填充属性* 如果@Autowired注解属性,则在上方完成解析后,在这里完成注入** @Autowired* private Inner inner;*/populateBean(beanName, mbd, instanceWrapper);// 5. 初始化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);}}// 6. 存在提前曝光情况下if (earlySingletonExposure) {// earlySingletonReference:二级缓存,缓存的是经过提前曝光提前Spring AOP代理的beanObject earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {// exposedObject跟bean一样,说明初始化操作没用应用Initialization后置处理器(指AOP操作)改变exposedObject// 主要是因为exposedObject如果提前代理过,就会跳过Spring AOP代理,所以exposedObject没被改变,也就等于bean了if (exposedObject == bean) {// 将二级缓存中的提前AOP代理的bean赋值给exposedObject,并返回exposedObject = earlySingletonReference;}// 引用都不相等了,也就是现在的bean已经不是当时提前曝光的bean了else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {// dependentBeans也就是B, C, DString[] 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 " +"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");}}}}

这个时候我们需要理清一下 3 个变量

  1. **earlySingletonReference:**二级缓存,缓存的是经过提前曝光提前 AOP 代理的 bean

  2. bean:这个就是经过了实例化、填充、初始化的 bean

  3. exposedObject:这个是经过了 AbstractAutoProxyCreator 的 postProcessAfterInitialization 处理过后的 bean,但是在其中因为发现当前 bean 已经被 earlyProxyReferences 缓存,所以并没有进行 AOP 处理,而是直接跳过,因此还是跟第 2 点一样的 bean

理清这 3 个变量以后,就会发现,exposedObject = earlySingletonReference;
AOP 代理过的 Bean 赋值给了 exposedObject 并返回,这时候用户拿到的 bean 就是 AOP 代理过后的 bean 了,一切皆大欢喜了。

但是中间还有一个问题!提前曝光的 bean 在提前引用时被 Spring AOP 代理了,但是此时的 bean 只是经过了实例化的 bean,还没有进行@Autowire 的注入啊!也就是说此时代理的 bean 里面自动注入的属性是空的!

(四) 提前 AOP 代理对象的 属性填充、初始化
是的,确实在 Spring AOP 提前代理后没有经过属性填充和初始化。那么这个代理又是如何保证依赖属性的注入的呢?答案回到 Spring AOP 最早最早讲的 JDK 动态代理上找,JDK 动态代理时,会将目标对象 target 保存在最后生成的代理proxy中,当调用proxy中,当调用proxy中,当调用proxy 方法时会回调 h.invoke,而 h.invoke 又会回调目标对象 target 的原始方法。因此,其实在 Spring AOP 动态代理时,原始 bean 已经被保存在提前曝光代理中了。而后原始 Bean 继续完成属性填充和初始化操作。因为 AOP 代理$proxy 中保存着 traget 也就是是原始 bean 的引用,因此后续原始 bean 的完善,也就相当于 Spring AOP 中的 target 的完善,这样就保证了 Spring AOP 的属性填充与初始化了!

(五) 循环依赖遇上 Spring AOP 图解

为了帮助大家理解,这里灵魂画手画张流程图帮助大家理解
首先又 bean A,bean B,他们循环依赖注入,同时 bean A 还需要被 Spring AOP 代理,例如事务管理或者日志之类的操作。
原始 bean A,bean B 图中用 a,b 表示,而代理后的 bean A 我们用 aop.a 表示

相关文章:

Spring源码面试最难问题——循环依赖

前言 问&#xff1a;Spring 如何解决循环依赖&#xff1f; 答&#xff1a;Spring 通过提前曝光机制&#xff0c;利用三级缓存解决循环依赖&#xff08;这原理还是挺简单的&#xff0c;参考&#xff1a;三级缓存、图解循环依赖原理&#xff09; 再问&#xff1a;Spring 通过提前…...

【计组】RAM的深入理解

一、存储机理 RAM的实现逻辑有种&#xff0c;分别是触发器和电容。 SRAM&#xff08;Static&#xff09;DRAM&#xff08;Dynamic&#xff09;存储方式触发器电容破坏性读出否&#xff08;触发器具有稳态&#xff0c;能够锁住0或1两种状态&#xff09;是&#xff08;电容需要…...

JavaScript 之数据交互

在前后端交互中&#xff0c;前端通常需要对接口返回的数据进行格式转换、遍历、循环等&#xff1b;通常会用到以下函数和方法&#xff1a; forEach()、map()遍历数组&#xff08;map返回新的数组&#xff09;&#xff1b;forEach()只能使用try catah终止循环&#xff1b;for in…...

Python 十大开源Python库,看看你熟悉几个?

嗨害大家好鸭&#xff01;我是芝士❤ 对于码农来说&#xff0c; 关注的永远是新近有什么流行的、 既能解决问题又好用的利器。 本文就为你盘点十大开源Python库。 1、Pipenv 第一名非它莫属&#xff0c; 这个工具2017年初才发布&#xff0c; 但它已经能够影响每个Python开发…...

不愧是阿里开发的SpringBoot实战文档:入门+基础+进阶+项目,应有尽有

SpringBoot SpringBoot毋庸置疑&#xff0c;在Java开发中会因为项目流量太大需要切换到SpringCloud&#xff08;SpringBoot&#xff09;也会极为顺利。而且现在越来越多的公司都在采用SpringBoot&#xff0c;对SpringBoot关注和使用的开发者也越来越多了&#xff01; SpringB…...

Vue(3)-vue中的Ajax、Vuex、路由及UI组件库

课程链接 目录4.Vue中的Ajax4.1.vue脚手架配置代理4.1.1.方法一4.1.2.方法二4.2.插槽5.Vuex5.1.理解Vuex5.1.1.概念5.1.2.何时使用&#xff1f;5.1.3.vuex原理5.2.vuex使用5.2.1.搭建vuex环境5.2.2.基本使用5.2.3.getters的使用5.2.4.四个map方法的使用5.2.5.模块化命名空间6.路…...

jwt 学习笔记

概述 JWT&#xff0c;Java Web Token&#xff0c;通过 JSON 形式作为 Web 应用中的令牌&#xff0c;用于在各方之间安全地将信息作为 JSON 对象传输&#xff0c;在数据传输过程中还可以完成数据加密、签名等相关处理 JWT 的作用如下&#xff1a; 授权&#xff1a;一旦用户登…...

网络安全实战从 0 到 1 彻底掌握 XXE

0x01 什么是 XXE个人认为&#xff0c;XXE 可以归结为一句话&#xff1a;构造恶意 DTD介绍 XXE 之前&#xff0c;我先来说一下普通的 XML 注入&#xff0c;这个的利用面比较狭窄&#xff0c;如果有的话应该也是逻辑漏洞。既然能插入 XML 代码&#xff0c;那我们肯定不能善罢甘休…...

如何安装 Composer

下载 Composer 安装前请务必确保已经正确安装了 PHP。打开命令行窗口并执行 php -v 查看是否正确输出版本号。 打开命令行并依次执行下列命令安装最新版本的 Composer&#xff1a; php -r "copy(https://install.phpcomposer.com/installer, composer-setup.php);"p…...

WPF 常用控件

WPF六种常用控件&#xff1a;布局控件、内容控件、带标题内容控件、条目控件、带标题条目控件和特殊内容控件(如:TextBox,TextBlock,Image等)。实例链接&#xff1a;WPF常用控件实例Window(窗体)Winodw窗体派生自ContentControl&#xff0c;有一个Content属性&#xff0c;里面可…...

河南工程学院蓝桥培训(2.21)

1&#xff0c;金币 461. 金币 - AcWing题库 #include <iostream> using namespace std; int n,a,ans,s; int main(){cin>>n;while(n--){if(a0)as;anss,a--;}cout<<ans;return 0; }...

新人使用Git获取远程仓库项目

前言 这篇git技术篇非常的简单基础&#xff0c;写它的原因很简单&#xff0c;因为现在很多的年轻人都很浮躁&#xff0c;刚入门就想学最牛x的&#xff0c;看不起基础的一些技术&#xff0c;比如说git操作、Linux基础命令&#xff0c;编程基础啥的。我身边有很多这样的年轻人&a…...

理解信号的

在日常生活中我们也经常面临许多的信号&#xff0c;手机通知、过红绿灯。。。这些信号在没有发生之前我们就知道这种信号产生我们需要干什么&#xff0c;那Linux里信号产生后&#xff0c;又怎么知道要做什么呢&#xff1f; -- 那当然是由程序员自己去设置啊 由于我们的用户空间…...

SpringSecurity学习(七)授权

授权 什么是权限管理 权限管理核心概念 SpringSecurity权限管理策略 基于URL地址的权限管理 基于方法的权限管理 一、权限管理 二、授权核心概念 在认证的过程成功之后会将当前用户登录信息保存到Authentication对象中&#xff0c;Authentication对象中有一个getAuthorities…...

【Vue3】模板语法

&#x1f3c6;今日学习目标&#xff1a;模板语法 &#x1f603;创作者&#xff1a;颜颜yan_ ✨个人格言&#xff1a;生如芥子&#xff0c;心藏须弥 ⏰本期期数&#xff1a;第三期 &#x1f389;专栏系列&#xff1a;Vue3 文章目录前言声明响应式状态插值文本Attribute&#xff…...

Linux基础

环境搭建&#xff1a;linux安装、远程连接常用命令&#xff1a;文件、目录、拷贝、移动、打包、压缩、文本编辑安装软件&#xff1a;文件上传、jdk、tomcat、mysql项目部署&#xff1a;Java应用、Python应用、日志查看、系统管理、用户权限Linux是一套免费使用、自由传播的操作…...

Spark-序列化、依赖关系、持久化

序列化 闭包检查 序列化方法和属性 依赖关系 RDD 血缘关系 RDD 窄依赖 RDD 宽依赖 RDD 任务划分 RDD 持久化 RDD Cache 缓存 RDD CheckPoint 检查点 缓存和检查点区别 序列化 闭包检查 从计算的角度, 算子以外的代码都是在 Driver 端执行, 算子里面的代码都是在 E…...

蓝桥杯刷题冲刺 | 倒计时16天

作者&#xff1a;指针不指南吗 专栏&#xff1a;蓝桥杯倒计时冲刺 &#x1f43e;马上就要蓝桥杯了&#xff0c;最后的这几天尤为重要&#xff0c;不可懈怠哦&#x1f43e; 文章目录1.青蛙跳杯子1.青蛙跳杯子 题目 链接&#xff1a; 青蛙跳杯子 - 蓝桥云课 (lanqiao.cn) X 星球的…...

Java设计模式-12 、建造者模式

建造者模式 &#xff08;将一个 复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。&#xff09; 建造者模式是一种创建型的模式&#xff0c;有一些对象的创建过程new 是很繁杂的。 什么时候去使用建造者模式 由上文可以得出在一些对象创建…...

一款全新的基于GPT4的Python神器,关键还免费

chartgpt大火之后&#xff0c;随之而来的就是一大类衍生物了。 然后&#xff0c;今天要给大家介绍的是一款基于GPT4的新一代辅助编程神器——Cursor。 它最值得介绍的地方在于它免费&#xff0c;我们可以直接利用它来辅助我们编程&#xff0c;真正做到事半功倍。 注意&#…...

上岸整理:2023前端面试题-vue,小程序,js,css

前端&#xff1a; 今年疫情结束后&#xff0c;前端行情不好&#xff0c;竞争压力很大&#xff0c;现在整理下个人认为面试很频繁的前端问题。 正题&#xff1a;无分类&#xff0c;因为面试官的问题也是随机的 一、基础 1、浏览器常见的报错信息与含义 2、304与204的区别&am…...

Linux下LED设备驱动开发(LED灯实现闪烁)

文章目录一、配置连接说明二、更新设备树&#xff08;1&#xff09;将led灯引脚添加到pinctrl子系统&#xff08;2&#xff09;设备树中添加LDE灯的设备树节点&#xff08;3&#xff09;编译更新设备树三、驱动开发与测试&#xff08;1&#xff09;编写设备驱动代码&#xff08…...

JavaEE-多线程中wait和notify都有哪些区别?

更多内容请点击了解 本篇文章将详细讲述wait和notify的区别&#xff0c;请往下看 目录 更多内容请点击了解 文章目录 一、wait和notify概念 二、wait()方法详解 三、notify()方法详解 代码如下&#xff1a; 3.1notifyAll()详解 四、wait和sleep的对比 一、wait和notif…...

JavaScript实现列表分页(小白版)

组件用惯了&#xff0c;突然叫你用纯cssJavaScript写一个分页&#xff0c;顿时就慌了。久久没有接触js了&#xff0c;不知道咋写了。本文章也是借与参考做的一个demo案例&#xff0c;小白看了都会的那种。咱们就以ul列表为例进行分页&#xff1a; 首先模拟的数据列表是这样的&a…...

Python调用GPT3.5接口的最新方法

GPT3.5接口调用方法主要包括openai安装、api_requestor.py替换、接口调用、示例程序说明四个部分。 1 openai安装 Python openai库可直接通过pip install openai安装。如果已经安装openai&#xff0c;但是后续提示找不到ChatCompletion&#xff0c;那么请使用命令“pip instal…...

Java开发 - 拦截器初体验

目录 前言 拦截器 什么是拦截器 拦截器和过滤器 Spring MVC的拦截器 Mybatis的拦截器...

【数据仓库-7】-- 使用维度建模的一些缘由

维度建模是一种用于设计数据仓库和商业智能系统的方法。以下是选择维度建模的两类理由。 1.传统方法,有背书且可靠 易于理解和使用:维度建模使用直观的图形和术语,使得非技术人员也能够理解和使用数据仓库和商业智能系统。 快速开发和部署:维度建模是一种迭代开发方法,能…...

【开发实践】在线考试系统(一) 生成错题知识点的思维导图

一、需求分析设计 笔者开发了一个在线考试系统&#xff0c;导师提出一个需求&#xff1a;添加对考试错题相关知识点的总结。 在question表中关联知识点的编号&#xff0c;题目可能关联多个知识点。这里笔者的设计是&#xff0c;只关联一个知识点&#xff0c;便于维护。 下面是知…...

Java Web 实战 17 - 计算机网络之传输层协议(2)

大家好 , 这篇文章继续给大家讲解 TCP 协议当中的一些操作 , 比如 : 滑动窗口、流量控制、拥塞控制、延时应答、捎带应答、面向字节流这几个提升 TCP 效率的操作 . 我们还会给大家分析 TCP 连接出现异常的时候 , 该如何处理 . 最后会将 TCP 和 UDP 进行比较 上一篇文章的链接也…...

MyBatis<3>:动态SQL的使用<if><trim><where><set><foreach>

动态SQL是MyBatis的强大特性之一&#xff0c;能够完成不同条件下不同的sql拼接。参考官方文档&#xff1a;https://mybatis.org/mybatis-3/zh/dynamic-sql.html<if>标签看这个场景&#xff0c;有必填字段 和 非必填字段 &#xff0c;当字段不确定是否传入的时候&#xff…...

ps2017做网站/网站seo视频狼雨seo教程

路径&#xff1a;在一棵树中从一个结点往下到孩子或孙子结点之间的通路 结点的路径长度&#xff1a;从根节点到该节点的路径上分支的数目 树的路径长度&#xff1a;树中每个结点的路径长度之和 结点的权&#xff1a;给树中的结点赋予一个某种含义的值&#xff0c;则该值为该节点…...

wordpress采集工具/网络seo哈尔滨

第二种...

电商网站的成本/外贸独立站怎么建站

今天&#xff0c;不谈软件。这一篇&#xff0c;提供初学者一些产品选购上的建议。最近有一篇Scott Guthrie的Hard Drive Speed and Visual Studio Performance谈到了开发工具和硬盘之间的关系。这部分我也感触深刻&#xff0c;所以稍微提一下自己的看法。很久以前我在买NB的时候…...

网站是先解析还是先备案/国外新闻最新消息

微型计算机工作过程微机原理与接口技术 西安邮电大学计算机学院 王忠民 第一章 微型计算机系统导论(第二讲) 微型计算机硬件系统 1 2 微型计算机软件系统 微型计算机的工作过程 3 本讲主要内容 第一章 微型计算机系统导论—微型计算机硬件系统 硬件 微型机系统 外围设备 过程控…...

潍坊网站建设哪里好/百度账号安全中心官网

吾十有五而志于学三十而立四十而不惑五十而知天命六十而耳顺七十而从心所欲&#xff0c;不逾矩说的是&#xff1a;我(孔子)十五岁, 有志于学问&#xff1b;三十岁, 懂礼仪说话做事都有把握&#xff1b;四十岁, 掌握了各种知识 不致迷惑&#xff1b;五十岁, 得知天命&#xff1b…...

网站开发软件下载/如何建立网站平台

文章目录槽点快捷键修改方法Visual Studio 转战 VSCode 啦槽点 用惯了eclipse方式的快捷键&#xff0c;所以一时接受不了Visual Studio 2017 的快捷键方式 原本注释快捷键&#xff1a; 注释&#xff1a; 先CTRLK&#xff0c;然后CTRLC取消注释&#xff1a; 先CTRLK&#xff…...