@Autowired与@Resource原理知识点详解

文章目录

  • 前言
  • springIOC
  • 依赖注入的三种方式
    • 属性注入(字段注入)
    • 构造方法注入
    • setter注入
    • 用哪个?
  • @Autowired
    • 实现原理
  • @Resource
    • 实现原理
    • 结论
  • @Autowired与@Resource的不同
    • 来源不同
    • 参数不同
    • 使用不同
    • 装配顺序

前言

现在spring可以说是一统天下了,而spring有两个核心部分:IOC和AOP,AOP之前可以看一下之前的文章:Spring AOP原理使用详解

springIOC

AOP的不多做赘述了,说下IOC:Spring IOC 解决的是对象管理和对象依赖的问题,IOC容器可以理解为一个对象工厂,我们都把该对象交给工厂,工厂管理这些对象的创建以及依赖关系,而IOC有两个概念:控制反转及依赖注入。控制反转指的就是:把原有自己掌控的事交给别人去处理,更多的是一种思想或者可以理解为设计模式。
而依赖注入其实是控制反转的实现方式,通过依赖注入对象无需自行创建或者管理它的依赖关系,依赖关系将被自动注入到需要它们的对象当中去。

依赖注入的三种方式

其实还有一种基于XML的注入方式,但是现在这种方式过于繁琐且依赖冗余复杂的配置文件,再次不做讨论,仅说下常用的三种注入方式:

属性注入(字段注入)

该方式底层其实是基于Field注入的方式,也可以成为字段注入,因为注入方式实现比较简介,没有多于代码,所以这个在实际开发过程中比较常用:
在这里插入图片描述
该方式可以使用@Autowired、@Resource及@Inject来实现。后面会着重讲解这一部分。

构造方法注入

这种方式其实是spring官方比较推荐的方式,因为构造方法注入的时候会比较靠前,是在bean的实例化阶段就进行的:
在这里插入图片描述
但是有一点需要注意一下:如果只有一个构造方法的话可以不用加上@Autowired,但是如果有无参构造方法或者其他多个构造方法的话,就需要在指定的构造方法上面添加@Autowired注解了。

setter注入

我都不想列出来,但是又怕别人说我不知道这个(哈哈哈哈哈哈)。。。因为这种方式我觉得用的会越来越少的,因为这种代码太臃肿了,且好丑。。。
在这里插入图片描述

用哪个?

如果是开发的话,我建议直接属性(字段)注入完事儿,一般的业务场景基本不会有纰漏。但是如果想在bean实例化阶段进行一些处理操作或者是不在同一个IOC容器里面的话,我建议构造函数注入。
spring4以后推荐的是通过构造函数注入:

  • 依赖不可变:这个好理解,通过构造方法注入依赖,在对象创建的时候就要注入依赖,一旦对象创建成功,以后就只能使用注入的依赖而无法修改了,这就是依赖不可变(通过 set 方法注入将来还能通过 set 方法修改)。
  • 依赖不为空:通过构造方法注入的时候,会自动检查注入的对象是否为空,如果为空,则注入失败;如果不为空,才会注入成功。
  • 完全初始化:由于获取到了依赖对象(这个依赖对象是初始化之后的),并且调用了要初始化组件的构造方法,因此最终拿到的就是完全初始化的对象了。

idea里面为什么不推荐使用属性(字段)注入:

  • 对于 IOC 容器以外的环境,除了使用反射来提供它需要的依赖之外,无法复用该实现类。因为该类没有提供该属性的 set 方法或者相应的构造方法来完成该属性的初始化。换言之,要是使用属性注入,那么你这个类就只能在 IOC 容器中使用,要是想自己 new 一下这个类的对象,那么相关的依赖无法完成注入。
  • 属性注入时机较晚。在一些是实列化阶段的一些操作,无法使用注入对象。

@Autowired

在这里插入图片描述
@Autowired注解,可以对成员变量、方法和构造函数进行标注,来完成自动装配的工作,@Autowired标注可以放在成员变量上,也可以放在成员变量的set方法上,也可以放在任意方法上表示,自动执行当前方法,如果方法有参数,会在IOC容器中自动寻找同类型参数为其传值。在上面已经讲解过了。
这里必须明确:@Autowired是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Qualifier使用。

实现原理

源码跟踪下bean的实例化及初始化,以下源码定位流程就先不截图了,等到关键重要地方再截图:

  1. SpringApplication#run()方法的this.refreshContext

  2. AbstractApplicationContext#refresh方法的this.finishBeanFactoryInitialization(beanFactory);该方法会初始化所有非延迟加载的单例bean,包括我们在应用中声明的各种业务Bean,Bean的实例化和初始化过程逻辑都在这个函数中。

  3. 上述finishBeanFactoryInitialization方法中有this.getBean(weaverAwareName)方法。

  4. AbstractBeanFactory#doGetBean()方法中有createBean()方法。

  5. 进入AbstractAutowireCapableBeanFactory#createBean()方法,该方法内有doCreateBean()方法:
    在这里插入图片描述
    doCreateBean()方法是用来构建bean的,bean的实例化及初始化逻辑都是在doCreateBean()方法里面。该方法代码截图一下吧,重点关注两个方法:
    在这里插入图片描述

  6. 先看下applyMergedBeanDefinitionPostProcessors方法:
    在这里插入图片描述
    可以看到该方法里面调用了所有的MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition方法,其中有一个我们一直在找的实现类:AutowiredAnnotationBeanPostProcessor,这个就是用来处理依赖注入的后置处理器。MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition方法如下:
    在这里插入图片描述
    AutowiredAnnotationBeanPostProcessor会对bean进行注解扫描,如果扫描到了@Autowired和@Value注解,就会把对应的方法或者属性封装起来,最终封装成InjectionMetadata对象。

  7. 我们再看第二个重要方法populateBean(),这是个非常重要的方法:填充Bean实例属性完成依赖对象的注入,此时Bean中需要依赖注入的成员已经在第六步的applyMergedBeanDefinitionPostProcessors中被对应的后置处理器(AutowiredAnnotationBeanPostProcessor)进行了存储。
    这个方法内部进行部分截图:
    在这里插入图片描述
    这段代码肯定不会return,因为postProcessAfterInstantiation是为true的?重点定位到postProcessPropertyValues方法:
    在这里插入图片描述

  8. 可以看到有两个实现类:一个就是AutowiredAnnotationBeanPostProcessor而另一个是CommonAnnotationBeanPostProcessor。@Resource是通过CommonAnnotationBeanPostProcessor的postProcessPropertyValues实现的,我们先以@Autowired为例说明下,进入到AutowiredAnnotationBeanPostProcessor的postProcessPropertyValues方法内:
    在这里插入图片描述
    在这里插入图片描述
    查看findAutowiringMetadata()方法:
    在这里插入图片描述
    查看下buildAutowiringMetadata()部分方法:
    在这里插入图片描述
    当调用findAutowiringMetadata()方法时,会根据autowiredAnnotationTypes这个全局变量中的元素类型来进行注解的解析,而这个全局变量在AutowiredAnnotationBeanPostProcessor的构造方法中进行了初始化:
    在这里插入图片描述
    构造方法初始化的时候会向autowiredAnnotationTypes全局集合变量里面加入@Autowired、@Inject、@Value
    以上说明了findAutowiringMetadata()方法只解析出bean中带有@Autowired注解、@Inject和@Value注解的属性和方法。然后调用metadata.inject()方法,进行属性填充,继续跟踪:
    在这里插入图片描述
    以上代码中:checkedElements用于检查元素防止重复注入,bean中@Autowired 注入点会被提前解析成元信息并保存到InjectionMetadata中(对于@Resource,@LookUp等注解被解析后,也会解析成对应的InjectedElement的子类:ResourceElement、LookUpElement,但是这两个注解是在CommonAnnotationBeanPostProcessor后置处理器进行处理的),遍历InjectedElement集合继续执行注入逻辑element.inject():
    在这里插入图片描述

  9. 该方法对应两个实现类分别为:AutowiredFieldElement及AutowiredMethodElement,字面意思对应分别为字段注入及方法注入实现,两者实现逻辑基本相同,我们以AutowiredFieldElement为例:
    在这里插入图片描述
    ReflectionUtils.makeAccessible(field);利用Java的反射,为属性进行赋值,我们重点看下resolveFieldValue():
    在这里插入图片描述

  10. 重点看下resolveDependency()方法,该方法主要调用了doResolveDependency()方法,接下来重点分析一下doResolveDependency()这个方法,源码如下:

@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
		@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

	InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
	try {
		Object shortcut = descriptor.resolveShortcut(this);
		if (shortcut != null) {
			return shortcut;
		}

		Class<?> type = descriptor.getDependencyType();
		Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
		if (value != null) {
			if (value instanceof String) {
				String strVal = resolveEmbeddedValue((String) value);
				BeanDefinition bd = (beanName != null && containsBean(beanName) ?
						getMergedBeanDefinition(beanName) : null);
				value = evaluateBeanDefinitionString(strVal, bd);
			}
			TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
			try {
				return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
			}
			catch (UnsupportedOperationException ex) {
				// A custom TypeConverter which does not support TypeDescriptor resolution...
				return (descriptor.getField() != null ?
						converter.convertIfNecessary(value, type, descriptor.getField()) :
						converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
			}
		}

		Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
		if (multipleBeans != null) {
			return multipleBeans;
		}

		Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
		if (matchingBeans.isEmpty()) {
			if (isRequired(descriptor)) {
				raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
			}
			return null;
		}

		String autowiredBeanName;
		Object instanceCandidate;

		if (matchingBeans.size() > 1) {
			autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
			if (autowiredBeanName == null) {
				if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
					return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
				}
				else {
					// In case of an optional Collection/Map, silently ignore a non-unique case:
					// possibly it was meant to be an empty collection of multiple regular beans
					// (before 4.3 in particular when we didn't even look for collection beans).
					return null;
				}
			}
			instanceCandidate = matchingBeans.get(autowiredBeanName);
		}
		else {
			// We have exactly one match.
			Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
			autowiredBeanName = entry.getKey();
			instanceCandidate = entry.getValue();
		}

		if (autowiredBeanNames != null) {
			autowiredBeanNames.add(autowiredBeanName);
		}
		if (instanceCandidate instanceof Class) {
			instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
		}
		Object result = instanceCandidate;
		if (result instanceof NullBean) {
			if (isRequired(descriptor)) {
				raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
			}
			result = null;
		}
		if (!ClassUtils.isAssignableValue(type, result)) {
			throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
		}
		return result;
	}
	finally {
		ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
	}
}

其中有个resolveMultipleBeans()方法,该方法用于处理注入时属性为数组集合等情况,比如我们在bean中使用注入如下:
在这里插入图片描述
可以看下该方法的关键代码:
在这里插入图片描述
该方法内调用了findAutowireCandidates()方法主要用于根据类型查找依赖,大致可以分为三步:先查找内部依赖,再根据类型查找,最后没有可注入的依赖则进行补偿。其中@Qualifier注解就是在这个函数中处理的。实现从容器中找到所有对应类型的bean,最终会调用getBean()方法,这样当对应类型的对象还没被创建时,就会去创建。
我们再返回doResolveDependency()方法,查看下面的逻辑:
在这里插入图片描述
可以看到又一次调用了findAutowireCandidates()方法,会查找对应类型的bean,如果没有查找到并且该属性是必须要要注入的,则会抛异常。
继续往下走,可以看到:
在这里插入图片描述
如果查找对应类型bean>1,则会执行determineAutowireCandidate()方法,从字面意思来看的话是决定选择注入属性。可以看到执行完该方法后,如果autowireedBeanName为null也就是说无法决定使用哪一个,则抛异常。我们重点看下determineAutowireCandidate()方法的逻辑:
在这里插入图片描述
这个逻辑就比较明确了,相同类型的选取规则就是:首先选取@Primary注解的对象;其次根据@Priority来选取优先级高的对象;最后根据属性名称与spring的beanName来判断。如果按照这个还是没有选择出来的话,就会抛出异常(throw new NoUniqueBeanDefinitionException))。
再次返回doResolveDependency()方法,在执行完determineAutowireCandidate()方法后,会执行如下逻辑:
在这里插入图片描述
在这里插入图片描述
可以看到通过beaName及类型,调用getBean()方法向容器获取依赖bean对象,若该对象还没有构建,则会构建该对象,直到依赖的所有对象都已构建好,然后进行属性注入。后面的就是一直校验及判断逻辑了,在此不做赘述。
11. 至此AutowiredAnnotationBeanPostProcessor通过postProcessPropertyValues()方法完成了自动装配。以上就是@Autowired注解的实现原理。

@Resource

实现原理

@Resource的实现原理与@Autowired原理基本类似,不做赘述,只说明一下不同点:
1.@Resource注解的实现是通过后置处理器CommonAnnotationBeanPostProcessor类的postProcessPropertyValues()方法实现的。
2. CommonAnnotationBeanPostProcessor的findResourceMetadata()方法只解析出bean中带有@Resource注解、@WebServiceRef和@EjB注解的属性和方法
在这里插入图片描述
在这里插入图片描述
3. 查找顺序,看源码:
在这里插入图片描述
在这里插入图片描述
首先会判断是否是属性(字段)注入,我们以属性输入为例,重点看下getResourceToInject(target, requestingBeanName):

protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
    return this.lazyLookup ? CommonAnnotationBeanPostProcessor.this.buildLazyResourceProxy(this, requestingBeanName) : CommonAnnotationBeanPostProcessor.this.getResource(this, requestingBeanName);
}

(这个代码为啥不换行,截图都不好截取。)
看代码描述如果是@Lazy则会返回一个代理对象,如果不是的话就执行CommonAnnotationBeanPostProcessor.this.getResource(this, requestingBeanName)逻辑,我们看下该逻辑代码:
在这里插入图片描述
继续查看this.autowireResource(this.resourceFactory, element, requestingBeanName):
在这里插入图片描述
如图示:第二与第三的执行逻辑是一样的,所以关键就是第一个的判断条件,如果this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)为true,那么获取属性的逻辑与@Autowired的逻辑一样。如果为false,是根据resource = factory.getBean(name, element.lookupType)获取属性,此时是根据ByName注入属性。
如果 !factory.containsBean(name)为true的话,说明容器内按照名称查找该name的bean是没有的,所以会执行beanFactory.resolveDependency()方法,执行@Autowired相同逻辑。

结论

根据上面源码分析,严谨来说,@Resource并不是传统意义所说的先按照name再按照type注入属性的,有三个if分支,第一个if是先判断有没有name相关bean再执行ByType—@Primary—@Priority—byName。

@Autowired与@Resource的不同

简单描述下,能说上来就行:

来源不同

@Autowired是Spring定义的注解,而@Resource是JSR-250定义的注解。

参数不同

@Autowired只包含一个参数:required,表示是否开启自动准入,默认是true。而@Resource包含七个参数,其中最重要的两个参数是:name 和 type。@Autowired如果要使用byName,需要使用@Qualifier一起配合。而@Resource如果指定了name,则用byName自动装配,如果指定了type,则用byType自动装配。

使用不同

@Autowired能够用在:构造器、方法、参数、成员变量和注解上,而@Resource能用在:类、成员变量和方法上。

装配顺序

@Autowired默认按byType自动装配,而@Resource默认byName自动装配。

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

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

相关文章

【Unity-UGUI控件全面解析】| Canvas 画布组件详解

🎬【Unity-UGUI控件全面解析】| Canvas 画布组件详解一、组件介绍1.1 绘制元素的顺序二、组件属性面板2.1 Canvas :画布,控制UI的渲染模式2.2 Canvas Scaler:画布缩放器,控制UI画布的放大缩放的比例2.3 Graphic Raycaster:图形射线投射器,控制是否让UI响应射线点击三、…

【干货分享】一文说透分布式一致性协议(上)

本文首发自「慕课网」&#xff0c;想了解更多IT干货内容&#xff0c;程序员圈内热闻&#xff0c;欢迎关注"慕课网"&#xff01; 作者&#xff1a;大熊老师 | 慕课网讲师 在常见的分布式系统中&#xff0c;总会发生诸如机器宕机或网络异常&#xff08;包括消息的延迟…

数据备份系列:Rsync 备份详解(一)

一、Rsync 简介 1.1 Rsync 是一个远程增量文件备份软件工具 1.2 Rsync 的特性 支持拷贝特殊文件&#xff0c;如连接文件、设备等。可以有排除指定文件或目录同步的功能&#xff0c;相当于打包命令 tar 的排除功能。可以做到保持原文件或目录的权限、时间、软硬链接、属主、组…

Python每日一练(20230502)

目录 1. 被围绕的区域 &#x1f31f;&#x1f31f; 2. 两数之和 II &#x1f31f; 3. 二叉树展开为链表 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1…

react native ios 添加启动页 xcode14 react-native-splash-screen

最近更新xcode&#xff0c;有些配置有些不同&#xff0c;网上查的方法都是过时的&#xff0c;导致配了一段时间卡在这里&#xff0c;最后访问官网才弄好了&#xff0c;所以以后解决问题的办法先看官网再查其他各路神仙的办法。 官网的步骤&#xff1a;https://github.com/crazy…

颜色空间转换RGB-YCbCr

颜色空间 颜色空间&#xff08;Color Space&#xff09;是描述颜色的一种方式&#xff0c;它是一个由数学模型表示的三维空间&#xff0c;通常用于将数字表示的颜色转换成可见的颜色。颜色空间的不同取决于所选的坐标轴和原点&#xff0c;以及用于表示颜色的色彩模型。在计算机…

【C++入门】一篇搞懂auto关键字

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【C之路】 目录 作用不那么大的场景auto真正的价值auto和指针结合使用注意点auto不能推导的场景范围for范围for的使用条件 作用不那么大的…

海尔牵头IEEE P2786国际标准通过Sponsor投票并连任工作组主席

01 海尔牵头IEEE P2786国际标准 通过Sponsor投票 并连任工作组主席 海尔牵头制定的全球首个服装物联网国际标准IEEE P2786《Standard for General Requirements and Interoperability for Internet of Clothing》通过Sponsor投票&#xff0c;标志着该国际标准草案得到了行业…

2.6 浮点运算方法和浮点运算器

学习目标&#xff1a; 以下是一些具体的学习目标&#xff1a; 理解浮点数的基本概念和表示方法&#xff0c;包括符号位、指数和尾数。学习浮点数的运算规则和舍入规则&#xff0c;包括加、减、乘、除、开方等。了解浮点数的常见问题和误差&#xff0c;例如舍入误差、溢出、下…

FPGA实现10G万兆网UDP通信 10G Ethernet Subsystem替代网络PHY芯片 提供工程源码和技术支持

目录 1、前言2、我这里已有的UDP方案3、详细设计方案传统 FPGA UDP 方案本 FPGA 10G UDP 方案(牛逼)10G Ethernet 框图10G Ethernet 发送解析10G Ethernet 接收解析10G Ethernet 寄存器配置10G Ethernet UI 配置 4、vivado工程详解5、上板调试验证并演示ping功能测试数据收发测…

一款支持全文检索、工作流审批、知识图谱的企事业知识库

一、项目介绍 一款全源码&#xff0c;可二开&#xff0c;可基于云部署、私有部署的企业级知识库云平台&#xff0c;一款让企业知识变为实打实的数字财富的系统&#xff0c;应用在需要进行文档整理、分类、归集、检索、分析的场景。 获取方式q:262086839 为什么建立知识库平台&…

perf record对C++程序耗时进行分析

本节将介绍如何使用perf工具的perf record对C代码进行性能分析&#xff0c;一切操作都是在ubuntu 20下进行。 perf工具安装 由于perf工具和内核版本有关&#xff0c;因此直接安装容易出错&#xff0c;建议直接通过如下指令安装&#xff1a; sudo apt-get install linux-tool…

00后卷王的自述,我难道真的很卷?

前言 前段时间去面试了一个公司&#xff0c;成功拿到了offer&#xff0c;薪资也从12k涨到了18k&#xff0c;对于工作都还没两年的我来说&#xff0c;还是比较满意的&#xff0c;毕竟一些工作3、4年的可能还没我高。 我可能就是大家说的卷王&#xff0c;感觉自己年轻&#xff…

独立IP服务器和共享IP服务器有什么区别

在选择一个合适的服务器时&#xff0c;最常见的选择是共享IP服务器和独立IP服务器。尽管两者看起来很相似&#xff0c;但它们有着很大的不同。本文将详细介绍共享IP服务器和独立IP服务器的不同之处&#xff0c;以及如何选择适合您需求的服务器。 一、什么是共享IP服务器? 共享…

Python探索性P图,四种增强方式快速玩转pillow库

嗨害大家好鸭&#xff01;我是爱摸鱼的芝士❤ 我们平时使用一些图像处理软件时&#xff0c; 经常会看到其对图像的亮度、对比度、色度或者锐度进行调整。 你是不是觉得这种技术的底层实现很高大上&#xff1f; 其实最基础的实现原理&#xff0c; 用 Python 实现只需要几行…

Java JDK下载安装环境变量配置

目录 一、下载安装 1.简介 2.JDK下载JDK 官网海外历史地址&#xff1a; 3.安装 二、环境变量配置 1.新建JAVA_HOME变量 2.PATH变量 3.CLASSPATH 变量 4.测试是否安装成功 一、下载安装 1.简介 JDK 是SUN公司提供的一套Java 语言的软件开发工具包&#xff0c;简称JDK(JavaDevelo…

如何编写高质量代码

如何编写高质量代码 1. 前言2. 明确业务场景和用户需求3. 编程实践技巧3.1 提高命名规范3.2 保持代码简洁3.3 好的注释 4. 软件测试5. 总结 1. 前言 现代软件开发中&#xff0c;代码是构建高质量软件的核心。高质量代码能够提高软件系统的可靠性、可维护性和可扩展性&#xff…

给失业的互联网人一个思路:别再苦苦找工作了,要去找门槛低、现金流好、天花板低、资本看不上的创业项目,一年也能几百万!...

失业大潮中的互联网人该何去何从&#xff1f;这大概是许多人在难捱的深夜反复思考的问题。 一位失业很久的网友就在痛苦思索中悟出了适合自己的道路&#xff0c;下面分享给大家&#xff0c;篇幅太长&#xff0c;小编给大家划一下重点。 先说结论&#xff1a;失业的互联网人别再…

浅谈软件测试工程师的技能树

软件测试工程师是一个历史很悠久的职位&#xff0c;可以说从有软件开发这个行业以来&#xff0c;就开始有了软件测试工程师的角色。随着时代的发展&#xff0c;软件测试工程师的角色和职责也在悄然发生着变化&#xff0c;从一开始单纯的在瀑布式开发流程中担任测试阶段的执行者…

优思学院:什么是快速改善方法(Kaizen Blitz)?

什么是快速改善方法&#xff08;Kaizen Blitz&#xff09;&#xff1f; Kaizen blitz是精益管理中的一种方法&#xff0c;指通过集中一段时间内的团队努力来实现快速改进的方法。 Kaizen是一个日语词汇&#xff0c;意为“改善”&#xff0c;是一种广泛应用于企业管理的哲学&a…