SpringFramework:循环依赖与三级缓存

news2024/10/2 20:29:08

循环依赖与三级缓存


文章目录

  • 循环依赖与三级缓存
    • 一、Spring 中的循环依赖问题
      • 1. Spring 中的循环依赖概述
      • 2. Spring 中的循环依赖的 5 种场景
    • 二、Spring 三级缓存
      • 1. spring 创建 bean 的流程
      • 2. 场景一:单例的 setter 注入
      • 3. 三级缓存
      • 4. 关于二级缓存
    • 三、循环依赖的其他 4 种场景
      • 1. 多例的 setter 注入
      • 2. 构造器注入
      • 3. 单例的代理对象 setter 注入
      • 4. DependsOn 循环依赖
    • 三、出现循环依赖如何解决?


一、Spring 中的循环依赖问题

1. Spring 中的循环依赖概述

Spring 循环依赖指的是 SpringBean 对象之间的依赖关系形成一个闭环。即在代码中,把两个或者多个 Bean 相互之间去持有对方的引用,就会发生循环依赖,循环依赖会导致注入出现死循环,这是 Spring 发生循环依赖的主要原因之一。

Spring 循环依赖主要有三种情况,即:自身依赖自身,两者互相依赖,多者循环依赖

  • 自身依赖自身:自己依赖自己的直接依赖
  • 两者互相依赖:两个对象之间的直接依
  • 赖多者循环依赖:多个对象之间的间接依赖

在这里插入图片描述
自身依赖自身,两者互相依赖 两者互相依赖的情况比较直观,很好辨识,但是我们工作中最有可能触发的还是多者循环依赖,多者循环依赖的情况有时候因为业务代码调用层级很深,不容易识别出来。但无论循环依赖的数量有多少,循环依赖的本质是一样的。就是你的完整创建依赖于我,而我的完整创建也依赖于你,但我们互相没法解耦,最终导致依赖创建失败。

2. Spring 中的循环依赖的 5 种场景

Spring 中出现循环依赖主要有着 5 种场景:

  1. 单例的 setter 注入(能解决);
  2. 多例的 setter 注入(不能解决);
  3. 构造器注入(不能解决);
  4. 单例的代理对象 setter 注入(有可能解决);
  5. DependsOn 循环依赖(不能解决)。

接下来我们逐一来看。

在这里插入图片描述

二、Spring 三级缓存

1. spring 创建 bean 的流程

在开始理解 Spring 三级缓存如何让解决循环依赖问题前我们先来温习一下 spring 创建 bean 的流程:

  • Spring 启动时会根据配置文件或启动类把所有的 bean 注册成 bean 定义(就是映射 标签属性的 Java 类)
  • 遍历 bean 定义中的 beanName,调用 BeanFactory#getBean(beanName) 方法创建、初始化并返回 bean 实例

其中 getBean 方法:

  • 先从缓存(一层到三层依次获取)拿,没有就去创建;
  • 创建 Bean 时,把 beanName 标记为正在创建中,通过其定义里的 class找到构造器方法反射创建实例,并把其对象工厂放入第三层缓存;
  • 对实例初始化,移除正在创建中的标记,把实例放入第一层缓存,移除第二、三层中的缓存,最后返回实例

实例初始化过程:获取此 bean 中有 @Autowired 等注解的成员变量,从所有 bean 定义中找出此类型的 beanName,又通过 BeanFactory#getBean 方法获取实例,然后反射设值成员变量。

位于 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry 中的三级缓存源码:

	/** Cache of singleton objects: bean name to bean instance. */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of singleton factories: bean name to ObjectFactory. */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** Cache of early singleton objects: bean name to bean instance. */
	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

2. 场景一:单例的 setter 注入

这种注入方式应该是 Spring 中最常见的,Demo 如下:

@Service
public class TestService1 {
    @Autowired
    private TestService2 testService2;
    public void test1() {
    }
}

@Service
public class TestService2 {
    @Autowired
    private TestService1 testService1;
    public void test2() {
    }
}

在上述代码中,就是一个经典的循环依赖,其中 TestService1 依赖 TestService2,TestService2 依赖 TestService1 构成了一个简单了两者互相依赖关系,但是我们在使用类似代码时,并没有感知过该类型的循环依赖存在,因为此种类型已经被 Spring 默默解决了。

3. 三级缓存

Spring 内部有三级缓存:

  • 一级缓存(singletonObjects),用于保存实例化、注入、初始化完成的 Bean 实例
  • 二级缓存(earlySingletonObjects),用于保存实例化完成的 Bean 实例
  • 三级缓存(singletonFactories),用于保存 Bean 的创建工厂,以便于后面扩展有机会创建代理对象。

以上面 Demo 为例,现在项目启动,spring 开始创建 bean,比如先创建 TestService1:

  1. 标记 TestService1 为正在创建中,反射创建其实例,其对象工厂放入第三层缓存
  2. 初始化 TestService1 实例化时发现需要依赖注入 TestService2,则获取 TestService2 的实例
  3. 标记 TestService2 为正在创建中,反射创建其实例,其对象工厂放入第三层缓存
  4. 初始化 TestService2 实例化时发现需要依赖注入 TestService1,则获取 TestService1 的实例
  5. 这时候从缓存中获取时,TestService1 为正在创建中且第三层缓存有 TestService1 的值了,所以调用缓存的对象工厂的getObject 方法,把返回的 TestService1 实例放入第二层缓存,删除第三层缓存
  6. TestService2 实例初始化完成,放入第一层缓存,移除第二、三层中的缓存
  7. 回到第 2 步,TestService1 实例初始化完成,放入第一层缓存,移除第二、三层中的缓存

在这里插入图片描述
下面是 getBean(beanName) 方法最先调用的从这三层缓存中获取 bean 实例的逻辑(即上面第5步)

	/**
	 * Return the (raw) singleton object registered under the given name.
	 * <p>Checks already instantiated singletons and also allows for an early
	 * reference to a currently created singleton (resolving a circular reference).
	 * @param beanName the name of the bean to look for
	 * @param allowEarlyReference whether early references should be created or not
	 * @return the registered singleton object, or {@code null} if none found
	 */
	@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// Quick check for existing instance without full singleton lock
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				synchronized (this.singletonObjects) {
					// Consistent creation of early reference within full singleton lock
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						singletonObject = this.earlySingletonObjects.get(beanName);
						if (singletonObject == null) {
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							if (singletonFactory != null) {
								singletonObject = singletonFactory.getObject();
								this.earlySingletonObjects.put(beanName, singletonObject);
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}

以及一直提到的对象工厂,及其 getObject 方法的实现:

	/**
	 * Obtain a reference for early access to the specified bean,
	 * typically for the purpose of resolving a circular reference.
	 * @param beanName the name of the bean (for error handling purposes)
	 * @param mbd the merged bean definition for the bean
	 * @param bean the raw bean instance
	 * @return the object to expose as bean reference
	 */
	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				}
			}
		}
		return exposedObject;
	}

4. 关于二级缓存

细心的朋友可能会发现在这种场景中第二级缓存作用不大。那么问题来了,为什么要用第二级缓存呢?

试想一下,如果出现以下这种情况,我们要如何处理?

@Service
public class TestService1 {
    @Autowired
    private TestService2 testService2;
    @Autowired
    private TestService3 testService3;
    public void test1() {
    }
}
@Service
public class TestService2 {
    @Autowired
    private TestService1 testService1;
    public void test2() {
    }
}
@Service
public class TestService3 {
    @Autowired
    private TestService1 testService1;
    public void test3() {
    }
}

TestService1 依赖于 TestService2 和 TestService3,而 TestService2 依赖于 TestService1,同时 TestService3 也依赖于 TestService1。按照上图的流程可以把 TestService1 注入到 TestService2,并且 TestService1 的实例是从第三级缓存中获取的。

假设不用第二级缓存,TestService1 注入到 TestService3 的流程如图:

在这里插入图片描述
TestService1 注入到 TestService3 又需要从第三级缓存中获取实例,而第三级缓存里保存的并非真正的实例对象,而是 ObjectFactory对象。说白了,两次从三级缓存中获取都是 ObjectFactory 对象,而通过它创建的实例对象每次可能都不一样的。这样不是有问题?

为了解决这个问题,Spring 引入的第二级缓存。上面其实 TestService1 对象的实例已经被添加到第二级缓存中了,而在 TestService1 注入到 TestService3 时,只用从第二级缓存中获取该对象即可。

在这里插入图片描述
还有个问题,第三级缓存中为什么要添加 ObjectFactory 对象,直接保存实例对象不行吗?答:不行,因为假如你想对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的。


三、循环依赖的其他 4 种场景

1. 多例的 setter 注入

这种注入方法偶然会有,特别是在多线程的场景下,具体代码如下:

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class TestService1 {
    @Autowired
    private TestService2 testService2;
    public void test1() {
    }
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class TestService2 {
    @Autowired
    private TestService1 testService1;
    public void test2() {
    }
}

在上述多例的 setter 注入情况下,Spring 程序也是能够正常启动启动的,其实在AbstractApplicationContext 类的 refresh方法中告诉了我们答案,它会调用 finishBeanFactoryInitialization 方法,该方法的作用是为了 Spring 容器启动的时候提前初始化一些 Bean。该方法的内部又调用了 preInstantiateSingletons 方法

@Override
	public void preInstantiateSingletons() throws BeansException {
		if (logger.isTraceEnabled()) {
			logger.trace("Pre-instantiating singletons in " + this);
		}

		// Iterate over a copy to allow for init methods which in turn register new bean definitions.
		// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		// Trigger initialization of all non-lazy singleton beans...
		for (String beanName : beanNames) {
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				if (isFactoryBean(beanName)) {
					Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
					if (bean instanceof FactoryBean) {
						FactoryBean<?> factory = (FactoryBean<?>) bean;
						boolean isEagerInit;
						if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
							isEagerInit = AccessController.doPrivileged(
									(PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
									getAccessControlContext());
						}
						else {
							isEagerInit = (factory instanceof SmartFactoryBean &&
									((SmartFactoryBean<?>) factory).isEagerInit());
						}
						if (isEagerInit) {
							getBean(beanName);
						}
					}
				}
				else {
					getBean(beanName);
				}
			}
		}

其中非抽象、单例并且非懒加载的类才能被提前初始 Bean。,而多例即 SCOPE_PROTOTYPE 类型的类,非单例,不会被提前初始化 Bean,所以程序能够正常启动。如何让他提前初始化bean呢?

只需要再在 DEMO 中定义一个单例的类,在它里面注入 TestService1

@Service
public class TestService3 {
    @Autowired
    private TestService1 testService1;
}

重新启动程序,执行结果:

Requested bean is currently in creation: Is there an unresolvable circular reference?

果然出现了循环依赖。

这种循环依赖问题是无法解决的,因为它没有用缓存,每次都会生成一个新对象。

2. 构造器注入

这种注入方式现在其实用的已经非常少了,但是我们还是有必要了解一下,如下代码:

@Service
public class TestService1 {
    public TestService1(TestService2 testService2) {
    }
}
@Service
public class TestService2 {
    public TestService2(TestService1 testService1) {
    }
}

运行结果:

Requested bean is currently in creation: Is there an unresolvable circular reference?

出现了循环依赖,为什么呢?

在这里插入图片描述
从图中的流程看出构造器注入没能添加到三级缓存,也没有使用缓存,所以也无法解决循环依赖问题。

3. 单例的代理对象 setter 注入

这种注入方式其实也比较常用,比如平时使用:@Async 注解的场景,会通过 AOP 自动生成代理对象。

@Service
public class TestService1 {
    @Autowired
    private TestService2 testService2;
    @Async
    public void test1() {
    }
}
@Service
public class TestService2 {
    @Autowired
    private TestService1 testService1;
    public void test2() {
    }
}

从前面得知程序启动会报错,出现了循环依赖,为什么会循环依赖呢?答案就在下面这张图中:

在这里插入图片描述
说白了,Bean 初始化完成之后,后面还有一步去检查:第二级缓存和原始对象是否相等。由于它对前面流程来说无关紧要,所以前面的流程图中省略了,但是在这里是关键点,我们重点说说:

if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				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.");
					}
				}
			}
		}

正好是走到这段代码,发现第二级缓存和原始对象不相等,所以抛出了循环依赖的异常。如果这时候把 TestService1 改个名字,改成:TestService6,其他的都不变。

@Service
public class TestService6 {
    @Autowired
    private TestService2 testService2;
    @Async
    public void test1() {
    }
}

再重新启动一下程序,神奇般的好了。这是为什么?这就要从 Spring Bean 加载顺序说起了,默认情况下,Spring 是按照文件完整路径递归查找的,按路径+文件名排序,排在前面的先加载。所以 TestService1 比T estService2 先加载,而改了文件名称之后,TestService2 比 TestService6 先加载。

为什么 TestService2 比 TestService6 先加载就没问题呢?答案在下面这张图中:

在这里插入图片描述
这种情况 testService6 中其实第二级缓存是空的,不需要跟原始对象判断,所以不会抛出循环依赖。

4. DependsOn 循环依赖

还有一种有些特殊的场景,比如我们需要在实例化 Bean A 之前,先实例化 Bean B,这个时候就可以使用 @DependsOn 注解。

@DependsOn(value = "testService2")
@Service
public class TestService1 {
    @Autowired
    private TestService2 testService2;
    public void test1() {
    }
}
@DependsOn(value = "testService1")
@Service
public class TestService2 {
    @Autowired
    private TestService1 testService1;
    public void test2() {
    }
}

程序启动之后,执行结果:

Circular depends-on relationship between 'testService2' and 'testService1'

这个例子中本来如果 TestService1 和 TestService2 都没有加 @DependsOn 注解是没问题的,反而加了这个注解会出现循环依赖问题。

这又是为什么?答案在 AbstractBeanFactory 类的 doGetBean 方法的这段代码中:

// Guarantee initialization of beans that the current bean depends on.
				String[] dependsOn = mbd.getDependsOn();
				if (dependsOn != null) {
					for (String dep : dependsOn) {
						if (isDependent(beanName, dep)) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
						}
						registerDependentBean(dep, beanName);
						try {
							getBean(dep);
						}
						catch (NoSuchBeanDefinitionException ex) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
						}
					}
				}

它会检查 dependsOn 的实例有没有循环依赖,如果有循环依赖则抛异常。


三、出现循环依赖如何解决?

项目中如果出现循环依赖问题,说明是 Spring 默认无法解决的循环依赖,要看项目的打印日志,属于哪种循环依赖。目前包含下面几种情况:

在这里插入图片描述

解决方式:
在这里插入图片描述


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

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

相关文章

ELFK——ELK结合filebeat日志分析系统(2)

目录 一、filebeat 二、ELFK 1.原理简介 2.在ELK基础上部署filebeat ​​​​​​ELK 企业级日志分析系统&#xff08;1&#xff09;_Evens7xxX的博客-CSDN博客 紧接上一期&#xff0c;这期会介绍ELK结合filebeat的部署和使用 一、filebeat Filebeat&#xff0c;轻量级的…

Word编辑论文,实现1.题目、摘要、关键词为通栏,正文为双栏 2.首页底端添加通栏脚注,在脚注中写作者简介,并使其实现悬挂对齐效果

Q1. 如何使题目、摘要、关键词为通栏&#xff0c;而下面开始的正文为双栏&#xff1f; 将光标移动到最后一个关键词后 布局—分隔符—分节符&#xff08;连续&#xff09; 将光标移动到正文第一个大标题前 布局—页面设置—文档网格—文字排列—栏数设置为 2&#xff0c;操作…

Python如何爬取免费爬虫ip

做过大数据抓取的程序员应该都知道&#xff0c;正常市面上的爬虫ip只分为两种&#xff0c;一种是API提取式的&#xff0c;还有一种是账密形式隧道模式的。往往因为高昂费用而止步。对于初学者觉得没有必要&#xff0c;我们知道每个卖爬虫ip的网站有的提供了免费IP&#xff0c;可…

基于CentOS使用宝塔+Nginx搭建个人小H站

本文目录前言第一步 - 购置服务器第二步 - 连接服务器第三步 - 安装宝塔第四步 - 部署网站总结前言 最近心血来潮&#xff0c;恰逢帅地老哥在公众号搞活动&#xff0c;白嫖了一台服务器&#xff0c;于是打算搭一个小网站玩玩。其实早有此念头&#xff0c;只是因为懒(●ˇ∀ˇ●…

项目实战——实现注册和登录模块

目录 一、整体框架 二、实现JwtToken验证 1、添加依赖 2、编写、修改相关类 三、实现后端 API 四、实现前端的登录&#xff0c;注册界面 ps&#xff1a;本篇文章篇幅较长&#xff0c;且难度有所提升&#xff0c;希望大家耐心看完&#xff0c;种一棵树最好的时间是十年前&…

ヾ(⌐ ■_■)— HTML-CSS常用属性

目 录 1.文字的设置 (1)文字的基本属性&#xff08;font&#xff09; (2)文字的排版以及添加文字的修饰 2.颜色的设置 3.背景的设置&#xff08;background&#xff09; 4.边框的相关设置 (1)边框样式的设置&#xff08;border-style&#xff09; (2)边框宽度的设置…

操作系统4小时速成:操作系统发展和分类,运行环境:运行机制和内核,用户态非特权,核心态特权,中断技术,访管指令

操作系统4小时速成&#xff1a;操作系统发展和分类&#xff0c;运行环境&#xff1a;运行机制和内核&#xff0c;用户态非特权&#xff0c;核心态特权&#xff0c;中断技术&#xff0c;访管指令 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂…

有趣的 Go HttpClient 超时机制

hello&#xff0c;大家好呀&#xff0c;我是既写 Java 又写 Go 的小楼&#xff0c;在写 Go 的过程中经常对比这两种语言的特性&#xff0c;踩了不少坑&#xff0c;也发现了不少有意思的地方&#xff0c;今天就来聊聊 Go 自带的 HttpClient 的超时机制。 Java HttpClient 超时底…

Ubuntu22.04中root用户下依然权限不够,执行不了可执行文件

文章目录先看现象解决方法什么情况下会遇到这样的错误先看现象 provider是一个C语言编译得到的可执行文件。 开始&#xff0c;我直接运行它&#xff0c;告诉我权限不够然后我加上sudo运行&#xff0c;告诉我找不到命令最后我进入root用户运行&#xff0c;竟然还告诉我权限不够…

PraNet: Parallel Reverse Attention Networkfor Polyp Segmentation

Tittle:用于息肉分割的并行反向注意力网络 摘要 准确的息肉分割主要面临着两个难点&#xff1a;1&#xff09;相同类型的息肉有不同的大小&#xff0c;颜色和纹理。2&#xff09;息肉与周围粘膜的边界模糊不清晰。 为了解决这些挑战本文提出了一种并行反向注意网络ParNet。具…

10、MySQL——子查询

目录 一、子查询 1、子查询出现的位置 2、子查询结果集的形式 二、实例演示 1、查询工资高于JONES的员工 1.1 分析 1.2 代码 2、查询与SCOTT同一部门的员工 2.1 分析 2.2 代码 3、工资高于30号部门所有人的员工信息 3.1 分析 3.2 代码 4、查询工作和工资…

mannose-OH|甘露糖-羟基|mannose-PEG-OH|甘露糖-聚乙二醇-羟基

mannose-OH|甘露糖-羟基|mannose-PEG-OH|甘露糖-聚乙二醇-羟基 羟基&#xff08;oxhydryl&#xff09;是一种常见的极性基团&#xff0c;化学式为-OH。羟基与水有某些相似的性质&#xff0c;羟基是典型的极性基团&#xff0c;与水可形成氢键&#xff0c;在无机化合物水溶液中以…

钙尔奇30周年以行动,力挺中国骨骼健康发展

启动行动力赋能新旅程 近日&#xff0c;2022年度西普会于中国海南博鳌盛大举行。以“构筑患者价值同心圆——二元发展驱动健康产业新增长”为主题&#xff0c;本届西普会的会议内容和参会主体全面升维&#xff0c;从全球视野到中国特色聚合优质资源、拓宽产业边界&#xff0c;…

vue项目中实际构建echarts拓扑关系图业务

vue项目中实际构建echarts拓扑关系图业务前言一、关系拓扑是什么&#xff1f;二、需求梳理三、封装关系图组件1.父组件引用2.测试数据引入3.封装关系子组件4.关系组件完整代码总结前言 由于现在echarts的利用率增强&#xff0c;需要用到拓扑图的设计&#xff0c;如果单纯针对e…

Ubuntu虚拟机安装

文章目录VMware添加虚拟机等待开机&#xff08;需要一些时间安装系统&#xff09;检查网络环境设置 Ubuntu 中文支持一些基础设置VMware添加虚拟机 文件——>新建虚拟机 下一步&#xff1a;安装程序光盘映像文件&#xff08;iso&#xff09; 设置主机名&#xff0c;用户名及…

reportportal 集成 robotframework 自动化执行及结果可视化

最近领导想了个需求&#xff0c;想把目前组内在linux平台上执行的自动化脚本搞成可视化&#xff0c;如果是web站点相关日志可视化倒是简单了&#xff0c;ELK就是不错的选择&#xff0c;大部分可视化项目这种的&#xff0c;可以做的开起来很炫。 我们这边是自己写的脚本&#x…

机器学习西瓜书-1-2章

学习目标&#xff1a; 概览机器学习西瓜书 1、2章 学习内容&#xff1a; 第一章 绪论 1.1 基本术语 1.2 假设空间 1.3 归纳偏好 1.4 发展历程 第二章 模型评估与选择 2.1 经验误差与过拟合 2.2 评估方法 2.3 性能度量 学习时间&#xff1a; 两天 学习产出&#xff1a; 第…

爱了爱了,20个好用到爆的 Python 函数

大家好&#xff0c;今天分享20个日常工作中必不可少的Python函数&#xff0c;这些函数平时看到的不多&#xff0c;但是它们使用起来倒是非常的方便&#xff0c;它们可以大幅度地提高工作效率。内容较长&#xff0c;欢迎收藏学习&#xff0c;喜欢点赞支持。 文章目录技术提升isi…

你以为的Java面试只是背答案?跳槽涨薪不还是得靠自己的技术

前言 Java面试当然不能只靠背答案&#xff0c;为了应付面试背完答案拿到offer只是进入了这个行业&#xff0c;后面的实操还是得看自己的技术&#xff01;技术的挂钩当然和技术底层也是挂钩的。 这是我在工作、面试中学习并总结到的一些知识点&#xff0c;都是一些比较典型的、…

Kafka 消息过期策略(时间相关参数)

Kafka 消息过期策略&#xff08;时间相关参数&#xff09; 标记delete时效 (CDH配置项)log.retention.ms &#xff08;Kafka offset配置&#xff09;retention.ms 标记delete的真删底层文件 delete.delay.ms log.segmetn.delete 背景&#xff1a;在不需要重启kafka的情况下&a…