Spring之bean的生命周期

Spring之Bean的生命周期详解

在Spring框架中,Bean的创建和管理是通过Spring容器来完成的。Spring Bean生命周期包括实例化、属性填充、初始化、销毁等多个步骤。本文将详细解析Spring中Bean的生命周期,并重点介绍AbstractAutowireCapableBeanFactory类中的createBean方法及其相关的方法调用。

Bean的生命周期概述

Spring Bean的生命周期分为多个阶段,每个阶段都由特定的方法和机制来完成:

  1. 实例化前准备:解析Bean定义,确定Bean类型。
  2. 实例化:创建Bean实例。
  3. 属性填充:对Bean的各个属性进行依赖注入。
  4. 初始化:执行自定义的初始化方法和Spring的BeanPostProcessor回调。
  5. 使用:Bean已经完全初始化并可以使用。
  6. 销毁:容器关闭时,对Bean进行清理工作,执行销毁方法。

下面我们将通过源码详细解析每个步骤的实现。

createBean方法详解

createBean方法是Spring中创建Bean实例的核心方法。该方法的实现位于AbstractAutowireCapableBeanFactory类中:

@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {if (logger.isTraceEnabled()) {logger.trace("Creating instance of bean '" + beanName + "'");}RootBeanDefinition mbdToUse = mbd;// 确保bean类已解析并克隆bean定义以防止共享的bean定义中的动态解析类无法存储Class<?> resolvedClass = resolveBeanClass(mbd, beanName);if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {mbdToUse = new RootBeanDefinition(mbd);mbdToUse.setBeanClass(resolvedClass);}// 准备方法重写try {mbdToUse.prepareMethodOverrides();} catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(), beanName, "Validation of method overrides failed", ex);}try {// 在实例化前,给BeanPostProcessors一个返回代理而不是目标bean实例的机会。Object bean = resolveBeforeInstantiation(beanName, mbdToUse);if (bean != null) {return bean;}} catch (Throwable ex) {throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "BeanPostProcessor before instantiation of bean failed", ex);}try {Object beanInstance = doCreateBean(beanName, mbdToUse, args);if (logger.isTraceEnabled()) {logger.trace("Finished creating instance of bean '" + beanName + "'");}return beanInstance;} catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {throw ex;} catch (Throwable ex) {throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);}
}

1. resolveBeanClass方法

在Bean实例化之前,首先需要解析Bean的类:

protected Class<?> resolveBeanClass(RootBeanDefinition mbd, String beanName, Class<?>... typesToMatch)throws CannotLoadBeanClassException {try {if (mbd.hasBeanClass()) {return mbd.getBeanClass();}if (System.getSecurityManager() != null) {return AccessController.doPrivileged((PrivilegedExceptionAction<Class<?>>) () -> doResolveBeanClass(mbd, typesToMatch), getAccessControlContext());} else {return doResolveBeanClass(mbd, typesToMatch);}} catch (PrivilegedActionException pae) {ClassNotFoundException ex = (ClassNotFoundException) pae.getException();throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex);} catch (ClassNotFoundException ex) {throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex);} catch (LinkageError err) {throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), err);}
}

2. prepareMethodOverrides方法

检查和准备Bean定义中定义的方法重写:

public void prepareMethodOverrides() throws BeanDefinitionValidationException {if (hasMethodOverrides()) {getMethodOverrides().getOverrides().forEach(this::prepareMethodOverride);}
}

3. resolveBeforeInstantiation方法

在实例化Bean之前,调用BeanPostProcessor来尝试返回代理对象:

@Nullable
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {Object bean = null;if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {Class<?> targetType = determineTargetType(beanName, mbd);if (targetType != null) {bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);if (bean != null) {bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);}}}mbd.beforeInstantiationResolved = (bean != null);}return bean;
}
3.1 applyBeanPostProcessorsBeforeInstantiation方法

调用InstantiationAwareBeanPostProcessorpostProcessBeforeInstantiation方法:

@Nullable
protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {Object result = bp.postProcessBeforeInstantiation(beanClass, beanName);if (result != null) {return result;}}return null;
}
3.2 applyBeanPostProcessorsAfterInitialization方法

调用BeanPostProcessorpostProcessAfterInitialization方法:

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)throws BeansException {Object result = existingBean;for (BeanPostProcessor processor : getBeanPostProcessors()) {Object current = processor.postProcessAfterInitialization(result, beanName);if (current == null) {return result;}result = current;}return result;
}

4. doCreateBean方法

实际创建Bean实例的主要方法:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {BeanWrapper instanceWrapper = null;if (mbd.isSingleton()) {instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}if (instanceWrapper == null) {instanceWrapper = createBeanInstance(beanName, mbd, args);}Object bean = instanceWrapper.getWrappedInstance();Class<?> beanType = instanceWrapper.getWrappedClass();if (beanType != NullBean.class) {mbd.resolvedTargetType = beanType;}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;}}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");}addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}Object exposedObject = bean;try {populateBean(beanName, mbd, instanceWrapper);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);}}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.");}}}}try {registerDisposableBeanIfNecessary(beanName, bean, mbd);} catch (BeanDefinitionValidationException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);}return exposedObject;
}
4.1 createBeanInstance方法

创建Bean实例的方法:

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {// 构造方法自动装配Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {return autowireConstructor(beanName, mbd, ctors, args);}// 使用工厂方法创建beanif (mbd.getFactoryMethodName() != null) {return instantiateUsingFactoryMethod(beanName, mbd, args);}// 默认的无参构造函数实例化return instantiateBean(beanName, mbd);
}
4.2 populateBean方法

对Bean的属性进行填充:

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {PropertyValues pvs = mbd.getPropertyValues();if (bw == null) {if (!pvs.isEmpty()) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");} else {return;}}if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME || mbd.getResolvedAutowireMode() == AUTOWIRE_BY_TYPE) {MutablePropertyValues newPvs = new MutablePropertyValues(pvs);// Add property values based on autowire by name if applicable.if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME) {autowireByName(beanName, mbd, bw, newPvs);}// Add property values based on autowire by type if applicable.if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_TYPE) {autowireByType(beanName, mbd, bw, newPvs);}pvs = newPvs;}applyPropertyValues(beanName, mbd, bw, pvs);
}
4.3 initializeBean方法

对Bean进行初始化,包括调用BeanPostProcessor

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {invokeAwareMethods(beanName, bean);Object wrappedBean = bean;if (mbd == null || !mbd.isSynthetic()) {wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);}try {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()) {wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);}return wrappedBean;
}

5. registerDisposableBeanIfNecessary方法

注册需要销毁的Bean:

protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) {if (mbd.isSingleton()) {registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessorCache().destruction, acc));} else {Scope scope = this.scopes.get(mbd.getScope());if (scope == null) {throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'");}scope.registerDestructionCallback(beanName, new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessorCache().destruction, acc));}}
}

详细分析每个步骤

为了更全面地理解Spring Bean的生命周期,我们将详细解析每个步骤,探讨其内部机制和关键方法。

实例化前准备

实例化前的准备工作包括解析Bean的类信息和准备方法重写。这一步骤确保在实例化Bean之前,所有必要的元数据和配置已经到位。

解析Bean的类

解析Bean的类是通过resolveBeanClass方法实现的。该方法负责将Bean定义中的类名解析为实际的Class对象:

protected Class<?> resolveBeanClass(RootBeanDefinition mbd, String beanName, Class<?>... typesToMatch)throws CannotLoadBeanClassException {try {if (mbd.hasBeanClass()) {return mbd.getBeanClass();}if (System.getSecurityManager() != null) {return AccessController.doPrivileged((PrivilegedExceptionAction<Class<?>>) () -> doResolveBeanClass(mbd, typesToMatch), getAccessControlContext());} else {return doResolveBeanClass(mbd, typesToMatch);}} catch (PrivilegedActionException pae) {ClassNotFoundException ex = (ClassNotFoundException) pae.getException();throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex);} catch (ClassNotFoundException ex) {throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex);} catch (LinkageError err) {throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), err);}
}

在解析过程中,Spring会检查Bean定义中是否已经包含了Class对象。如果没有,Spring会根据Bean的类名进行加载。这个过程可以在受控的安全环境中进行,以确保在安全管理器启用的情况下也能够正确加载类。

准备方法重写

准备方法重写是通过prepareMethodOverrides方法实现的。该方法会检查Bean定义中是否包含了需要重写的方法,并进行相应的准备工作:

public void prepareMethodOverrides() throws BeanDefinitionValidationException {if (hasMethodOverrides()) {getMethodOverrides().getOverrides().forEach(this::prepareMethodOverride);}
}

方法重写的准备工作包括验证方法签名和确保方法的可访问性。这一步骤对于支持方法重写功能的Bean非常重要,例如使用CGLIB动态代理的Bean。

实例化Bean

实例化Bean是通过调用createBeanInstance方法来实现的。该方法负责根据不同的情况创建Bean实例,包括构造方法自动装配、工厂方法实例化以及默认的无参构造函数实例化。

构造方法自动装配

如果Bean定义中包含构造函数参数,或者需要进行构造函数自动装配,Spring会调用autowireConstructor方法:

protected BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd, Constructor<?>[] chosenCtors, Object[] explicitArgs) {return new ConstructorResolver(this).autowireConstructor(beanName, mbd, chosenCtors, explicitArgs);
}

autowireConstructor方法会根据Bean定义中的构造函数参数信息选择合适的构造函数,并通过反射机制创建Bean实例。

工厂方法实例化

如果Bean定义中指定了工厂方法,Spring会调用instantiateUsingFactoryMethod方法:

protected BeanWrapper instantiateUsingFactoryMethod(String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {return new ConstructorResolver(this).instantiateUsingFactoryMethod(beanName, mbd, explicitArgs);
}

工厂方法实例化会根据Bean定义中的工厂方法名,通过反射调用工厂方法创建Bean实例。

默认无参构造函数实例化

如果Bean定义中既没有构造函数参数,也没有工厂方法,Spring会调用instantiateBean方法:

protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) {try {Object beanInstance;if (System.getSecurityManager() != null) {beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () ->getInstantiationStrategy().instantiate(mbd, beanName, this), getAccessControlContext());} else {beanInstance =getInstantiationStrategy().instantiate(mbd, beanName, this);}BeanWrapper bw = new BeanWrapperImpl(beanInstance);initBeanWrapper(bw);return bw;} catch (Throwable ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);}
}

该方法会调用默认的无参构造函数创建Bean实例,并返回一个包装了Bean实例的BeanWrapper对象。

属性填充

属性填充是通过populateBean方法来实现的。该方法负责对Bean实例的属性进行依赖注入,包括自动装配和手动指定的属性值。

自动装配

自动装配是通过autowireByNameautowireByType方法来实现的。这些方法会根据Bean定义中的自动装配模式,按照名称或类型进行依赖注入。

手动指定的属性值

如果Bean定义中包含了手动指定的属性值,Spring会调用applyPropertyValues方法将这些属性值设置到Bean实例上:

protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {if (pvs.isEmpty()) {return;}MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ? (MutablePropertyValues) pvs : new MutablePropertyValues(pvs));List<PropertyValue> original = mpvs.getPropertyValueList();TypeConverter converter = getCustomTypeConverter();if (converter == null) {converter = bw;}BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);// Create a deep copy, resolving any references for values.for (PropertyValue pv : original) {if (pv.isConverted()) {continue;}Object originalValue = pv.getValue();Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);PropertyValue newPv = new PropertyValue(pv.getName(), resolvedValue);newPv.setConverted();mpvs.addPropertyValue(newPv);}bw.setPropertyValues(mpvs);
}

该方法会解析属性值中的依赖项,并将解析后的值设置到Bean实例上。

初始化Bean

初始化Bean是通过initializeBean方法来实现的。该方法会调用一系列的回调方法,包括BeanPostProcessorpostProcessBeforeInitializationpostProcessAfterInitialization方法,以及Bean实例中的初始化方法。

调用BeanPostProcessor回调

BeanPostProcessor回调方法用于在Bean初始化的前后执行一些自定义逻辑,例如代理对象的创建和自定义初始化逻辑的执行。

调用Bean实例的初始化方法

如果Bean实例实现了InitializingBean接口,Spring会调用afterPropertiesSet方法。如果Bean定义中指定了初始化方法,Spring会通过反射调用该方法:

protected void invokeInitMethods(String beanName, Object bean, RootBeanDefinition mbd) throws Throwable {boolean isInitializingBean = (bean instanceof InitializingBean);if (isInitializingBean) {if (System.getSecurityManager() != null) {try {AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {((InitializingBean) bean).afterPropertiesSet();return null;}, getAccessControlContext());} catch (PrivilegedActionException pae) {throw pae.getException();}} else {((InitializingBean) bean).afterPropertiesSet();}}if (mbd != null && bean.getClass() != NullBean.class) {String initMethodName = mbd.getInitMethodName();if (StringUtils.hasLength(initMethodName) && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName))) {invokeCustomInitMethod(beanName, bean, mbd);}}
}

销毁Bean

销毁Bean是通过注册销毁回调方法来实现的。当Spring容器关闭时,会调用这些销毁回调方法对Bean进行清理工作。

注册销毁回调

注册销毁回调是通过registerDisposableBeanIfNecessary方法来实现的。该方法会根据Bean的作用域和生命周期,注册相应的销毁回调:

protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) {if (mbd.isSingleton()) {registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessorCache().destruction, acc));} else {Scope scope = this.scopes.get(mbd.getScope());if (scope == null) {throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'");}scope.registerDestructionCallback(beanName, new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessorCache().destruction, acc));}}
}

总结

Spring框架通过一系列复杂的步骤和机制,管理Bean的整个生命周期。理解这些机制有助于开发人员更好地利用Spring框架,编写高效、可维护的代码。通过深入研究AbstractAutowireCapableBeanFactory类中的createBean方法及其相关的内部调用,我们可以清晰地看到Spring是如何创建、初始化和销毁Bean的。

这些机制不仅提供了强大的灵活性和可扩展性,还通过各种回调和扩展点,允许开发人员在Bean的生命周期各个阶段插入自定义逻辑,从而满足各种复杂的应用需求。

了解Spring Bean生命周期的内部工作原理,对于解决实际开发中遇到的问题和优化应用性能具有重要意义。希望本文的详细解析能够帮助您更深入地理解Spring框架,提升您的开发技能。

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

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

相关文章

SinoDB数据库隔离级别

本文主要对SinoDB数据库隔离级别及其设置进行介绍。 1. ANSI SQL-92事务隔离 ANSI 委员会定义了以下级别的事务隔离&#xff08;SQL-92&#xff09;&#xff1a; Read uncommittedRead committedRepeatable readSerializable read 查询的隔离级别决定了查询与其他并发执行的…

独立游戏之路 -- 获取OAID提升广告收益

Unity 之 获取手机&#xff1a;OAID、IMEI、ClientId、GUID 前言一、Oaid 介绍1.1 Oaid 说明1.2 移动安全联盟(MSA) 二、站在巨人的肩膀上2.1 本文实现参考2.2 本文实现效果2.3 本文相关插件 三、Unity 中获取Oaid3.1 查看实现源码3.2 工程配置3.3 代码实现3.4 场景搭建 四、总…

6.6SSH的运用

ssh远程管理 ssh是一种安全通道协议&#xff0c;用来实现字符界面的远程登录。远程复制&#xff0c;远程文本传输。 ssh对通信双方的数据进行了加密 用户名和密码登录 密钥对认证方式&#xff08;可以实现免密登录&#xff09; ssh 22 网络层 传输层 数据传输的过程中是加密的 …

【一百零五】【算法分析与设计】分解质因数,952. 按公因数计算最大组件大小,204. 计数质数,分解质因数,埃式筛

分解质因数 题目&#xff1a;分解质因数 题目描述 给定一个正整数 n&#xff0c;编写一个程序将其分解为质因数&#xff0c;并按从小到大的顺序输出这些质因数。 输入格式 一个正整数 n&#xff0c;其中 n 的范围是 1 < n < 10^18。 输出格式 按从小到大的顺序输出 n 的质…

【Oracle生产运维】表空间可用性告警排查处理

1 前言 在生产环境中&#xff0c;一般设置表空间告警阈值是90%&#xff0c;在接到监控报警后&#xff0c;并不是需要立刻对表空间进行扩容。 决定是否扩容主要看表空间最近的增量是多少&#xff0c;假如剩余10%的空间还能支持1个月的增量&#xff0c;那就不需要急着扩容。如果…

linux经典例题编程

编写Shell脚本&#xff0c;计算1~100的和 首先vi 1.sh,创建一个名为1.sh的脚本&#xff0c;然后赋予这个脚本权限&#xff0c;使用命令chmod 755 1.sh&#xff0c;然后就可以在脚本中写程序&#xff0c;然后运行。 shell脚本内容 运行结果&#xff1a; 编写Shell脚本&#xf…

C++的封装(十二):外部构造函数

通常&#xff0c;C创建一个对象&#xff0c;都是调用构造函数对它初始化&#xff1a; class AB {int a;int b; public:AB(int ia, int ib) {aia; bib;}void display() {printf("a%d, b%d\n", a, b);}void other(); };如果实现过程需要施加更多的控制&#xff0c;比如…

自动化测试实战:如何构建高效且可靠的测试框架

随着软件行业的快速发展&#xff0c;自动化测试已成为确保软件质量不可或缺的环节。作为一名资深自动化测试工程师&#xff0c;我将分享如何构建一个高效且可靠的自动化测试框架&#xff0c;以提升测试效率&#xff0c;降低人工成本&#xff0c;并确保软件产品的稳定性。 一、…

软考-架构设计师-综合知识总结(试卷:2009~2022)(上篇)

说明 本文档对2009到2022年试卷的综合知识进行了归纳总结&#xff0c;同时对叶宏主编的《系统架构设计师教程》划分重点。 第一章&#xff1a;关于架构、架构师概述 1.1 重要知识点&#xff1a; 模块化开发规则&#xff1a; 1> 最高模块内聚&#xff0c;即在一个模块内部的…

一千题,No.0049(跟奥巴马一起编程)

美国总统奥巴马不仅呼吁所有人都学习编程&#xff0c;甚至以身作则编写代码&#xff0c;成为美国历史上首位编写计算机代码的总统。2014 年底&#xff0c;为庆祝“计算机科学教育周”正式启动&#xff0c;奥巴马编写了很简单的计算机代码&#xff1a;在屏幕上画一个正方形。现在…

开发指南028-生成二维码

平台通过zxing来生成二维码 <dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.3.3</version> </dependency> <dependency><groupId>com.google.zxing</groupId>&l…

分享一个 .NET Core Console 项目使用依赖注入的详细例子

前言 依赖注入&#xff08;Dependency Injection&#xff0c;简称DI&#xff09;是一种软件设计模式&#xff0c;主要用于管理和组织一个软件系统中不同模块之间的依赖关系。 在依赖注入中&#xff0c;依赖项&#xff08;也称为组件或服务&#xff09;不是在代码内部创建或查…

【浏览器插件】理解浏览器扩展开发:为什么 `content script` 里的 `window` 与页面的 `window` 不同以及解决方案

理解浏览器扩展开发&#xff1a;为什么 content script 里的 window 与页面的 window 不同以及解决方案 浏览器扩展开发为开发者提供了强大的工具&#xff0c;使得我们可以扩展和增强网页的功能。然而&#xff0c;在开发过程中&#xff0c;尤其是当涉及到与网页内容进行交互时…

Android基础-事件分发机制

在Android系统中&#xff0c;事件分发机制是处理用户交互事件的核心机制。当用户与设备进行交互&#xff0c;如点击、滑动屏幕时&#xff0c;这些动作会被系统捕获并转化为相应的事件&#xff08;如MotionEvent&#xff09;&#xff0c;随后这些事件需要通过一系列的处理和传递…

Redux实现状态管理

在使用 Redux 的应用中&#xff0c;实现状态管理通常涉及以下几个步骤&#xff1a; 创建 Action&#xff1a;Action 是一个简单的 JavaScript 对象&#xff0c;它描述了一个行为&#xff0c;通常有一个 type 字段来表示行为类型。 创建 Reducer&#xff1a;Reducer 是一个纯函…

C# WPF入门学习主线篇(十二)—— Canvas布局容器

欢迎来到C# WPF入门学习系列的第十二篇。在之前的文章中&#xff0c;我们介绍了WPF布局管理的基本概念以及常见的布局容器。本篇博客将详细介绍其中一种最基本的布局容器——Canvas布局容器。通过本篇文章&#xff0c;您将学习如何使用Canvas布局容器来精确控制子控件的位置&am…

二叉树的算法题目

二叉树的遍历题目 二叉树遍历一般包含三种分别为&#xff1a;根左右、左根右、左右根&#xff08;又称为前序遍历、中序遍历、后序遍历&#xff09; 方法一&#xff1a;使用递归遍历 方法二&#xff1a;使用迭代使用栈 我们以左根右&#xff08;中序遍历&…

【SpringBoot】项目搭建基本步骤(整合 Mybatis)

搭建 SpringBoot 项目有两种方式&#xff1a;使用 IDEA、或者在 Spring 官网下载。 1. IDEA 创建 打开 IDEA 后&#xff0c;英文版请点击 File -> New -> Project -> Spring Initialer。 中文版请点击 文件 -> 新建 -> 项目 -> Spring Initialer。 在打开的…

【Proteus8.16】Proteus8.16.SP3.exe的安装包,安装方法

下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/14ZlETF7g4Owh8djLaHwBOw?pwd2bo3 提取码&#xff1a;2bo3 管理员打开proteus8.16.SP3.exe一路装就行了&#xff0c;许可证选Licence2.lxk,点安装后关闭&#xff0c;然后继续装完。 然后打开Patch-Proteus-8.16-…

力扣2972.统计移除递增子数组的数目 II

力扣2972.统计移除递增子数组的数目 II 类似1574. 核心都是定一边最大能取到的位置定一边 移一边当我们确定左右端点位置时 [i1,j]是一定要删除的然后i 1这里可以一直缩小到0也就是总共 i 2个子数组 class Solution {public:long long incremovableSubarrayCount(vector&l…