Spring学习——什么是循环依赖及其解决方式

文章目录

  • 前言
  • 一、什么是循环依赖
  • 二、解决思路
    • 1、循环依赖分类
    • 2、对象初始化步骤及对象分类
    • 3、spring是如何解决的
    • 4、图解
    • 5、三级缓存
      • 1、区别
      • 2、ObjectFactory是什么
  • 三、源码debug
    • 1、spring创建对象过程
      • 1、dubug第一步——找到getBean
      • 2、dubug第二步——getBean与doGetBean
      • 3、dubug第三步——createBean和doCreateBean
      • 4、dubug第四步——createBeanInstance创建对象
      • 5、dubug第五步——populateBean填充属性
      • 6、dubug第六步——创建B对象
      • 7、dubug第七步——给B对象中的a属性赋值
      • 8、dubug第八步——创建A对象
      • 9、dubug第九步——给A对象中b属性赋值
      • 10、dubug第十步——循环创建B
    • 2、流程图
  • 四、问题与思考
    • 1、三个类型的map分别存储什么类型的对象
    • 2、三个map在查找对象的时候,是按照什么顺序查找的
    • 3、如果只有一个map,可以解决循环问题吗
    • 4、如果只有两个map,可以解决循环问题吗
    • 5、三级缓存是怎么解决AOP问题的


前言

当前网络上有许多文章探讨循环依赖及Spring框架如何解决这一问题,然而,他人的观点毕竟是他人的,因此我想写一篇属于自己的文章,来探讨循环依赖的问题。

一、什么是循环依赖

在Spring框架中,循环依赖指的是当两个或多个Bean之间相互依赖,形成循环引用的情况。这种循环依赖可能会导致Spring容器无法正确地实例化Bean,从而引发应用程序启动失败或运行时异常。

在这里插入图片描述
例如图中的情况:A对象中依赖于B,B对象中依赖A。
创建过程如下
在这里插入图片描述

那么在创建A对象时,需要对A进行实例化,随后对其属性B进行赋值,但是这时候还没有B对象,就需要先创建B对象。
在创建B对象时,需要对B进行实例化,随后对其属性A进行赋值,但是这时候A对象还没有创建完成。
这个时候就形成了一个闭环,这种情况就是循环依赖,如果没有正确处理这种情况,程序就会启动失败或运行时异常。

二、解决思路

从上图中可以看到,形成闭环的最后一步为容器中没有A对象蓝色线条),所以这一步如果不存在,那么就不会存在循环依赖问题。

1、循环依赖分类

在Spring框架中,循环依赖可以分为两种主要类型:

1、构造器注入循环依赖:当两个或多个Bean通过构造器注入相互依赖时,如果它们之间形成了循环引用,Spring容器在创建这些Bean时可能会陷入死循环,导致应用启动失败或者内存溢出等问题。

2、属性注入循环依赖:当两个或多个Bean通过属性注入相互依赖时,如果它们之间形成了循环引用,Spring容器会尝试解决这种循环依赖。但是,如果解决不当,可能会导致其中一个Bean的某些属性为null,或者Bean的状态不一致,从而引发运行时错误。

其中,构造器循环依赖不能解决,只能抛出异常,而属性注入循环依赖是可以解决的。

2、对象初始化步骤及对象分类

在这里插入图片描述
在spring创建对象的流程图中可以看出,对象初始化分为三步:实例化填充属性初始化

那么可以将对象分为两类:
1、完成实例化但未完成初始化
2、完成实例化也完成了初始化

发生循环依赖时,即进行到蓝色线条步骤的时候,对象A是完成实例化但未完成初始化

3、spring是如何解决的

在流程图中看出,B对象是在完成实例化之后立刻对属性A进行赋值,虽然A对象没有完成初始化,但是完成了实例化,所以此时拥有了完成实例化但未完成初始化的A对象。
此时,将这个半成品的A对象赋值给B,那么蓝色线条步骤就不会存在,循环依赖问题也就解决了。

即,实例化与初始化分开进行

如果实例化与初始化在必须在一起时,循环依赖问题就不能得到解决,例如构造器注入循环依赖

4、图解

在这里插入图片描述

上图中,在对象实例化之后就放入半成品对象A,在给a属性赋值的时候,把半成品对象A赋值给a,那么会得到一个成品的B对象,那么在回去把成品的B对象赋值给A对象,那么也就得到了一个成品的A对象

5、三级缓存

在上一步中已经解决了循环依赖问题,那么又有一个新的问题:spring分别创建了半成品的A、B,成品的A、B。如果放在一个map中,显然比较混乱,那么它们在spring中时怎么存放的。

在spring中,spring的开发者使用三个Map存放我们的Bean对象,这三个也就是我们说的三级缓存。

三个Map位于 DefaultSingletonBeanRegistry 类中
在这里插入图片描述

/** 一级缓存 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);/** 三级缓存 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);/** 二级缓存 */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

那么这三个Map有什么区别?

1、区别

Map类型不同:一级、二级缓存为ConcurrentHashMap,三级缓存为HashMap
容量不同一级缓存为256,二级缓存为16,三级缓存为16
泛型不同:三级缓存里边放的是ObjectFactory类型,一级、二级缓存为Object
其中最重要的是泛型的不同那么ObjectFactory是什么?

2、ObjectFactory是什么

ObjectFactory:函数式接口,可以将lambda表达式作为参数放到方法的实参中。在方法执行的时候不会执行lambda表达式,只有在调用getObject的时候才会执行lambda表达式。

@FunctionalInterface
public interface ObjectFactory<T> {/*** Return an instance (possibly shared or independent)* of the object managed by this factory.* @return the resulting instance* @throws BeansException in case of creation errors*/T getObject() throws BeansException;}

三、源码debug

1、spring创建对象过程

spring创建对象过程中重要的六个方法

在这里插入图片描述

1、dubug第一步——找到getBean

这个方法是在AbstractApplicationContext类中,这个方法中包含了创建对象的13个流程,我们看其中最重要的finishBeanFactoryInitialization

@Override
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing.prepareRefresh();// Tell the subclass to refresh the internal bean factory.ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.registerBeanPostProcessors(beanFactory);// Initialize message source for this context.initMessageSource();// Initialize event multicaster for this context.initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.onRefresh();// Check for listener beans and register them.registerListeners();// Instantiate all remaining (non-lazy-init) singletons.finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();}}
}

在beanFactory中可以找到我们的三级缓存:
在这里插入图片描述
在singletonObjects中有五个对象,是容器创建时需要的。
在这里插入图片描述
在finishBeanFactoryInitialization方法中,有一些判断和设置可以不看,着重看最后一行beanFactory.preInstantiateSingletons();

/*** Finish the initialization of this context's bean factory,* initializing all remaining singleton beans.*/
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {// Initialize conversion service for this context.if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {beanFactory.setConversionService(beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));}// Register a default embedded value resolver if no BeanFactoryPostProcessor// (such as a PropertySourcesPlaceholderConfigurer bean) registered any before:// at this point, primarily for resolution in annotation attribute values.if (!beanFactory.hasEmbeddedValueResolver()) {beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));}// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);for (String weaverAwareName : weaverAwareNames) {getBean(weaverAwareName);}// Stop using the temporary ClassLoader for type matching.beanFactory.setTempClassLoader(null);// Allow for caching all bean definition metadata, not expecting further changes.beanFactory.freezeConfiguration();// Instantiate all remaining (non-lazy-init) singletons.beanFactory.preInstantiateSingletons();
}

打开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);}}}// Trigger post-initialization callback for all applicable beans...for (String beanName : beanNames) {Object singletonInstance = getSingleton(beanName);if (singletonInstance instanceof SmartInitializingSingleton) {SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;if (System.getSecurityManager() != null) {AccessController.doPrivileged((PrivilegedAction<Object>) () -> {smartSingleton.afterSingletonsInstantiated();return null;}, getAccessControlContext());}else {smartSingleton.afterSingletonsInstantiated();}}}
}

可以看到beanDefinitionNames中存放着我们需要创建的对象的集合:a和b。随后开始循环创建
在这里插入图片描述
首先经过for循环和一些判断之后,进入到了getBean中,也就是spring创建对象过程中重要的六个方法的第一个。
在这里插入图片描述

2、dubug第二步——getBean与doGetBean

getBean中,也就是doGetBean,是spring创建对象过程中重要的六个方法的第二个。

@Override
public Object getBean(String name) throws BeansException {return doGetBean(name, null, null, false);
}

打开doGetBean方法:

/*** Return an instance, which may be shared or independent, of the specified bean.* @param name the name of the bean to retrieve* @param requiredType the required type of the bean to retrieve* @param args arguments to use when creating a bean instance using explicit arguments* (only applied when creating a new instance as opposed to retrieving an existing one)* @param typeCheckOnly whether the instance is obtained for a type check,* not for actual use* @return an instance of the bean* @throws BeansException if the bean could not be created*/
@SuppressWarnings("unchecked")
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {String beanName = transformedBeanName(name);Object bean;// Eagerly check singleton cache for manually registered singletons.Object sharedInstance = getSingleton(beanName);if (sharedInstance != null && args == null) {if (logger.isTraceEnabled()) {if (isSingletonCurrentlyInCreation(beanName)) {logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +"' that is not fully initialized yet - a consequence of a circular reference");}else {logger.trace("Returning cached instance of singleton bean '" + beanName + "'");}}bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);}else {// Fail if we're already creating this bean instance:// We're assumably within a circular reference.if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}// Check if bean definition exists in this factory.BeanFactory parentBeanFactory = getParentBeanFactory();if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {// Not found -> check parent.String nameToLookup = originalBeanName(name);if (parentBeanFactory instanceof AbstractBeanFactory) {return ((AbstractBeanFactory) parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);}else if (args != null) {// Delegation to parent with explicit args.return (T) parentBeanFactory.getBean(nameToLookup, args);}else if (requiredType != null) {// No args -> delegate to standard getBean method.return parentBeanFactory.getBean(nameToLookup, requiredType);}else {return (T) parentBeanFactory.getBean(nameToLookup);}}if (!typeCheckOnly) {markBeanAsCreated(beanName);}try {RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);checkMergedBeanDefinition(mbd, beanName, args);// 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);}}}// Create bean instance.if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.destroySingleton(beanName);throw ex;}});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}else if (mbd.isPrototype()) {// It's a prototype -> create a new instance.Object prototypeInstance = null;try {beforePrototypeCreation(beanName);prototypeInstance = createBean(beanName, mbd, args);}finally {afterPrototypeCreation(beanName);}bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);}else {String scopeName = mbd.getScope();if (!StringUtils.hasLength(scopeName)) {throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");}Scope scope = this.scopes.get(scopeName);if (scope == null) {throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");}try {Object scopedInstance = scope.get(beanName, () -> {beforePrototypeCreation(beanName);try {return createBean(beanName, mbd, args);}finally {afterPrototypeCreation(beanName);}});bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);}catch (IllegalStateException ex) {throw new BeanCreationException(beanName,"Scope '" + scopeName + "' is not active for the current thread; consider " +"defining a scoped proxy for this bean if you intend to refer to it from a singleton",ex);}}}catch (BeansException ex) {cleanupAfterBeanCreationFailure(beanName);throw ex;}}// Check if required type matches the type of the actual bean instance.if (requiredType != null && !requiredType.isInstance(bean)) {try {T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);if (convertedBean == null) {throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());}return convertedBean;}catch (TypeMismatchException ex) {if (logger.isTraceEnabled()) {logger.trace("Failed to convert bean '" + name + "' to required type '" +ClassUtils.getQualifiedName(requiredType) + "'", ex);}throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());}}return (T) bean;
}

getSingleton中,会先检查在单例缓存中是否有手动注册的单例对象,可以看到,现在结果为空。随后开始创建对象。

随后开始走到如下位置,另一个getSingleton方法,可以看到第一个参数为beanName,第二个参数就是lambda表达式,这时lambda表达式并不会执行。
在这里插入图片描述
打开getSingleton方法,

/*** Return the (raw) singleton object registered under the given name,* creating and registering a new one if none registered yet.* @param beanName the name of the bean* @param singletonFactory the ObjectFactory to lazily create the singleton* with, if necessary* @return the registered singleton object*/
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(beanName, "Bean name must not be null");synchronized (this.singletonObjects) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {if (this.singletonsCurrentlyInDestruction) {throw new BeanCreationNotAllowedException(beanName,"Singleton bean creation not allowed while singletons of this factory are in destruction " +"(Do not request a bean from a BeanFactory in a destroy method implementation!)");}if (logger.isDebugEnabled()) {logger.debug("Creating shared instance of singleton bean '" + beanName + "'");}beforeSingletonCreation(beanName);boolean newSingleton = false;boolean recordSuppressedExceptions = (this.suppressedExceptions == null);if (recordSuppressedExceptions) {this.suppressedExceptions = new LinkedHashSet<>();}try {singletonObject = singletonFactory.getObject();newSingleton = true;}catch (IllegalStateException ex) {// Has the singleton object implicitly appeared in the meantime ->// if yes, proceed with it since the exception indicates that state.singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {throw ex;}}catch (BeanCreationException ex) {if (recordSuppressedExceptions) {for (Exception suppressedException : this.suppressedExceptions) {ex.addRelatedCause(suppressedException);}}throw ex;}finally {if (recordSuppressedExceptions) {this.suppressedExceptions = null;}afterSingletonCreation(beanName);}if (newSingleton) {addSingleton(beanName, singletonObject);}}return singletonObject;}
}

可以看到,传递的是一个lambda表达式。
在这里插入图片描述
当走到singletonObject = singletonFactory.getObject();时,才会执行lambda表达式。
也就是执行getObject也就是执行刚刚的lambda表达式,执行lambda表达式中的createBean,spring创建对象过程中重要的六个方法的第三个。
在这里插入图片描述

3、dubug第三步——createBean和doCreateBean

createBean方法中,经过一些判断,进入doCreateBean.spring创建对象过程中重要的六个方法的第四个。

/*** Central method of this class: creates a bean instance,* populates the bean instance, applies post-processors, etc.* @see #doCreateBean*/
@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;// Make sure bean class is actually resolved at this point, and// clone the bean definition in case of a dynamically resolved Class// which cannot be stored in the shared merged bean definition.Class<?> resolvedClass = resolveBeanClass(mbd, beanName);if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {mbdToUse = new RootBeanDefinition(mbd);mbdToUse.setBeanClass(resolvedClass);}// Prepare method overrides.try {mbdToUse.prepareMethodOverrides();}catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),beanName, "Validation of method overrides failed", ex);}try {// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.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) {// A previously detected exception with proper bean creation context already,// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.throw ex;}catch (Throwable ex) {throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);}
}
/*** Actually create the specified bean. Pre-creation processing has already happened* at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks.* <p>Differentiates between default bean instantiation, use of a* factory method, and autowiring a constructor.* @param beanName the name of the bean* @param mbd the merged bean definition for the bean* @param args explicit arguments to use for constructor or factory method invocation* @return a new instance of the bean* @throws BeanCreationException if the bean could not be created* @see #instantiateBean* @see #instantiateUsingFactoryMethod* @see #autowireConstructor*/
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {// Instantiate the bean.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;}// Allow post-processors to modify the merged bean definition.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.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));}// Initialize the bean instance.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.");}}}}// Register bean as disposable.try {registerDisposableBeanIfNecessary(beanName, bean, mbd);}catch (BeanDefinitionValidationException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);}return exposedObject;
}

doCreateBean方法中,createBeanInstance就是创建我们的bean实例的。在createBeanInstance方法中,使用的是反射来创建对象
在这里插入图片描述

4、dubug第四步——createBeanInstance创建对象

createBeanInstance方法中,最后一个方法为instantiateBean
在这里插入图片描述
进入instantiateBean,先获取了当前对象的构造器,然后创建对象。
在这里插入图片描述
进入instaniate,点进去instantiateClass这个方法,可以看到:
return ctor.newInstance(argsWithDefaultValues);

此时,就算是利用反射的方式,来创建出了对象了。
在这里插入图片描述
在这里插入图片描述

总体流程:

在这里插入图片描述

从下图可以看出,已经创建完成了A对象,其中属性b为空。

在这里插入图片描述

继续向下,可以看到,执行了addSingletonFactory方法。

在这里插入图片描述

点开addSingletonFactory方法,可以看到,
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
在三级缓存中放入了map,key为beanName,value是一个lambda表达式。
然后将二级缓存清空,将beanName添加到已注册过的集合中。

/*** Add the given singleton factory for building the specified singleton* if necessary.* <p>To be called for eager registration of singletons, e.g. to be able to* resolve circular references.* @param beanName the name of the bean* @param singletonFactory the factory for the singleton object*/
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}
}

现在我们创建了一个半成品的A对象,并且在三级缓存中加了一条数据

在这里插入图片描述

5、dubug第五步——populateBean填充属性

在这里插入图片描述

执行了addSingletonFactory方法之后,就开始对属性赋值,打开populateBean方法,

/*** Populate the bean instance in the given BeanWrapper with the property values* from the bean definition.* @param beanName the name of the bean* @param mbd the bean definition for the bean* @param bw the BeanWrapper with bean instance*/
@SuppressWarnings("deprecation")  // for postProcessPropertyValues
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {if (bw == null) {if (mbd.hasPropertyValues()) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");}else {// Skip property population phase for null instance.return;}}// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the// state of the bean before properties are set. This can be used, for example,// to support styles of field injection.if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {return;}}}}PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);int resolvedAutowireMode = mbd.getResolvedAutowireMode();if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {MutablePropertyValues newPvs = new MutablePropertyValues(pvs);// Add property values based on autowire by name if applicable.if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {autowireByName(beanName, mbd, bw, newPvs);}// Add property values based on autowire by type if applicable.if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {autowireByType(beanName, mbd, bw, newPvs);}pvs = newPvs;}boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);PropertyDescriptor[] filteredPds = null;if (hasInstAwareBpps) {if (pvs == null) {pvs = mbd.getPropertyValues();}for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {return;}}pvs = pvsToUse;}}}if (needsDepCheck) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}checkDependencies(beanName, mbd, filteredPds, pvs);}if (pvs != null) {applyPropertyValues(beanName, mbd, bw, pvs);}
}

经过一些逻辑判断之后,直到最后,执行了applyPropertyValues方法。
这里就不贴完整代码了,只看关键部分。在applyPropertyValues方法中,有getName和getValue分别获取属性的名称和值。
获取到的值的类型为RuntimeBeanReference

在这里插入图片描述
随后执行
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);进行类型转换
刚刚的属性值为RuntimeBeanReference类型,随后返回resolveReference(argName, ref);

在这里插入图片描述

打开resolveReference方法

/*** Resolve a reference to another bean in the factory.*/
@Nullable
private Object resolveReference(Object argName, RuntimeBeanReference ref) {try {Object bean;Class<?> beanType = ref.getBeanType();if (ref.isToParent()) {BeanFactory parent = this.beanFactory.getParentBeanFactory();if (parent == null) {throw new BeanCreationException(this.beanDefinition.getResourceDescription(), this.beanName,"Cannot resolve reference to bean " + ref +" in parent factory: no parent factory available");}if (beanType != null) {bean = parent.getBean(beanType);}else {bean = parent.getBean(String.valueOf(doEvaluate(ref.getBeanName())));}}else {String resolvedName;if (beanType != null) {NamedBeanHolder<?> namedBean = this.beanFactory.resolveNamedBean(beanType);bean = namedBean.getBeanInstance();resolvedName = namedBean.getBeanName();}else {resolvedName = String.valueOf(doEvaluate(ref.getBeanName()));bean = this.beanFactory.getBean(resolvedName);}this.beanFactory.registerDependentBean(resolvedName, this.beanName);}if (bean instanceof NullBean) {bean = null;}return bean;}catch (BeansException ex) {throw new BeanCreationException(this.beanDefinition.getResourceDescription(), this.beanName,"Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex);}
}

随后执行至bean = this.beanFactory.getBean(resolvedName);开始建造b对象

在这里插入图片描述

6、dubug第六步——创建B对象

从上得知,进入getBean方法后和创建A的流程一致,只不过doGetBean中的getSingleton方法实现不同

/*** 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 lockObject 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 locksingletonObject = 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;
}

这里分别从一、二、三级缓存中获取b对象,如果没有就返回空,随后创建,如果有就返回

此时创建完成一个半成品的B对象,随后将半成品的B对象放入三级缓存

在这里插入图片描述

在这里插入图片描述

7、dubug第七步——给B对象中的a属性赋值

dubug第五步流程一致,执行至bean = this.beanFactory.getBean(resolvedName);开始建造a对象

在这里插入图片描述

8、dubug第八步——创建A对象

创建A对象执行至getSingleton方法,从一、二、三级缓存中获取A对象

在这里插入图片描述

可知一级、二级缓存中没有A对象,那么从三级缓存中取,执行至singletonFactory.getObject()时,执行上文中存入三级缓存的lambda表达式,即dubug第四步addSingletonFactory方法中存入的 () -> getEarlyBeanReference(beanName, mbd, bean)

在这里插入图片描述

看一下getEarlyBeanReference方法

/*** 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;
}

exposedObject暴露对象,方法中最后返回的也是exposedObject,那就就是在exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);中对exposedObject进行了修改,那么看一下getEarlyBeanReference方法。

@Override
public Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean);return wrapIfNecessary(bean, beanName, cacheKey);
}

继续向下执行

/*** Wrap the given bean if necessary, i.e. if it is eligible for being proxied.* @param bean the raw bean instance* @param beanName the name of the bean* @param cacheKey the cache key for metadata access* @return a proxy wrapping the bean, or the raw bean instance as-is*/
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// Create proxy if we have advice.Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
}

Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));在这里创建一个代理对象。

也就是getEarlyBeanReference方法中如果进入if条件就会执行createProxy

但是继续向下dubug,并没有进入if条件,直接返回了exposedObject

在这里插入图片描述

随后取出A对象放入二级缓存,删除三级缓存中的a

在这里插入图片描述

在这里插入图片描述
随后返回至applyPropertyValues方法给B对象的a属性赋值,也就是dubug第七步的目的。

在这里插入图片描述

执行完applyPropertyValues方法中的bw.setPropertyValues(new MutablePropertyValues(deepCopy));后,就完成了B对象的a属性赋值。可以看到B中此时有A,但是A中还没有B。

在这里插入图片描述

此时赋值完成,B为成品对象,返回至getSingleton方法,执行addSingleton方法

在这里插入图片描述

将B对象放入一级缓存。

/*** Add the given singleton object to the singleton cache of this factory.* <p>To be called for eager registration of singletons.* @param beanName the name of the bean* @param singletonObject the singleton object*/
protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {this.singletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}
}

在这里插入图片描述

9、dubug第九步——给A对象中b属性赋值

随后逐步返回至创建A对象时调用的applyPropertyValues方法,执行dubug第五步
依旧是bw.setPropertyValues(new MutablePropertyValues(deepCopy));
执行完成之后,就可以看到已经完成了对A对象中b属性赋值

在这里插入图片描述

之后再次执行创建A对象时调用的addSingleton方法,此时一级缓存中多了两个我们创建的对象。

在这里插入图片描述
在这里插入图片描述

10、dubug第十步——循环创建B

由上文可知,我们创建了A和B对象,但是我们进入到getBean方法是从preInstantiateSingletons中循环创建A、B进入的创建A对象,此时我们应该进入下一个循环创建B对象了。

在这里插入图片描述

随后在创建B对象的getSingleton方法中在一级缓存能取到B对象,所以直接返回。

在这里插入图片描述

至此,完成A、B对象的创建

2、流程图

这里整理了一个大致流程

图片链接
在这里插入图片描述

四、问题与思考

1、三个类型的map分别存储什么类型的对象

一级缓存:成品对象
二级缓存:半成品对象
三级缓存:lambda表达式

2、三个map在查找对象的时候,是按照什么顺序查找的

先从一级缓存中获取对象,如果没有再从二级缓存中获取,二级缓存中没有再从三级缓存中获取。

3、如果只有一个map,可以解决循环问题吗

可以,只要能存储一个标志位区分开成品和半成品就行,但是太麻烦了。

4、如果只有两个map,可以解决循环问题吗

可以,如果对象的创建过程中不包含 aop(没有代理对象的时候) ,那么一级二级缓存就可以解决循环依赖问题,但是如果包含 aop 的操作,那么没有三级缓存的话,循环依赖问题是解决不了的

5、三级缓存是怎么解决AOP问题的

首先一个容器中只能包含一个同名的对象
对象创建的过程中有可能需要生成代理对象,那么如果程序创建了代理对象,那么调用的时候用的是原始对象还是代理对象呢?要使用代理对象,程序怎么知道该用代理对象呢?
程序是没有办法知道的,所以当出现代理对象的时候,就要用代理对象替换掉原始对象

那么代理对象的创建时在初始化过程扩展阶段,即debug第八步Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));,而属性赋值在生成代理对象前。
在这里插入图片描述
怎么完成替换呢? 所以需要在前置过程的时候判断需要生成代理对象。即:

在这里插入图片描述

这段代码是在lambda表达式(debug第四步的代码addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));)中调用的,那么为什么要用lambda表达式机制中完成呢

因为对象在什么时候被暴露出去还是被应用是不能提前确定的,只有在被调用的时候才可以判断是原始对象还是代理对象,使用lambda表达式就类似于回调机制,不暴露的时候不执行,被调用的时候才执行,来判断返回的是代理对象还是原始对象。(该lambda表达式是是从三级缓存中取出key为a时调用的,这时候判断是需要队里对象还是原始对象)
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

腾讯 tendis 替代 redis linux安装使用

下载地址 Tendis存储版 点击下载 linux 解压 tar -zxvf 安装包.tgz cd 解压安装包/scripts 启动 ./start.sh 停止 ./stop.sh 详细配置 修改 /scripts tendisplus.conf # tendisplus configuration for testing # 绑定本机IIP bind 192.168.31.112 port 51002 #设…

海格里斯助推实体制造业转型升级 “算法定义硬件”解题AIoT市场

随着自动化的发展&#xff0c;电子商务和智能制造推动了自动化立体仓库的快速发展与创新&#xff0c;产生了“密集仓储”的概念。对于一个实体企业来讲&#xff0c;其数智物流转型正在趋向于“去伪存真”&#xff0c;企业追求高ROI与真实经济价值&#xff0c;具有降本增效的业务…

JavaEE 初阶篇-深入了解多线程安全问题(出现线程不安全的原因与解决线程不安全的方法)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 多线程安全问题概述 1.1 线程不安全的实际例子 2.0 出现线程不安全的原因 2.1 线程在系统中是随机调度且抢占式执行的模式 2.2 多个线程同时修改同一个变量 2.3 线…

游戏行业行业竞争越来越激烈,遇到DDoS攻击遭受严重损失该如何解决

近年来&#xff0c;我们见证了数字化的快速发展&#xff0c;随着这样的发展&#xff0c;网络的威胁也逐渐增多&#xff0c;在网络攻击门槛不断降低&#xff0c;行业竞争越来越激烈&#xff0c;游戏行业的DDoS攻击如雨点般密集&#xff0c;在整个DDoS攻击的份额中&#xff0c;游…

SpringAMQP-Exchange交换机

1、Fanout-Exchange的特点是&#xff1a;和它绑定的消费者都会收到信息 交换机的作用是什么? 接收publisher发送的消息将消息按照规则路由到与之绑定的队列不能缓存消息&#xff0c;路由失败&#xff0c;消息丢失FanoutExchange的会将消息路由到每个绑定的队列 声明队列、交…

【实验报告】--基础VLAN

【VLAN实验报告】 一、项目背景 &#xff08;为 Jan16 公司创建部门 VLAN&#xff09; Jan16 公司现有财务部、技术部和业务部&#xff0c;出于数据安全的考虑&#xff0c;各部门的计算机需进 行隔离&#xff0c;仅允许部门内部相互通信。公司拓扑如图 1 所示&#xff0c; …

http和https的工作原理是什么?

HTTP&#xff08;HyperText Transfer Protocol&#xff09;和HTTPS&#xff08;HyperText Transfer Protocol Secure&#xff09;是两种用于在互联网上传输数据的主要协议&#xff0c;它们均用于在客户端&#xff08;通常是Web浏览器&#xff09;与服务器之间交换信息。尽管它们…

.NET CORE 分布式事务(二) DTM实现TCC

目录 引言&#xff1a; 1. TCC事务模式 2. TCC组成 3. TCC执行流程 3.1 TCC正常执行流程 3.2 TCC失败回滚 4. Confirm/Cancel操作异常 5. TCC 设计原则 5.1 TCC如何做到更好的一致性 5.2 为什么只适合短事务 6. 嵌套的TCC 7. .NET CORE结合DTM实现TCC分布式事务 …

访学博后须知|携带手机等电子产品入境美国注意事项

美国对携带手机等电子产品入境有着严格的规定&#xff0c;因此知识人网小编提醒拟出国做访问学者、博士后或联合培养的博士生了解以下注意事项&#xff0c;尽量减少不必要的麻烦。 随着互联网的普及&#xff0c;手机等电子产品在人民生活中占有不可或缺的地位。因为研究和工作需…

海量数据处理项目-账号微服务和流量包数据库表+索引规范(下)

海量数据处理项目-账号微服务和流量包数据库表索引规范&#xff08;下&#xff09; 第2集 账号微服务和流量包数据库表索引规范讲解《下》 简介&#xff1a;账号微服务和流量包数据库表索引规范讲解 账号和流量包的关系&#xff1a;一对多traffic流量包表思考点 海量数据下每…

ES6 学习(二)-- 字符串/数组/对象/函数扩展

文章目录 1. 模板字符串1.1 ${} 使用1.2 字符串扩展(1) ! includes() / startsWith() / endsWith()(2) repeat() 2. 数值扩展2.1 二进制 八进制写法2.2 ! Number.isFinite() / Number.isNaN()2.3 inInteger()2.4 ! 极小常量值Number.EPSILON2.5 Math.trunc()2.6 Math.sign() 3.…

仓库规划(plan)

明天就要考试了&#xff0c;但是我正处于一点都不想学的状态 高考前我也是这样的 逆天 代码如下&#xff1a; #include<vector> #include<cstdio> using namespace std; int n, m; struct Node{int id;vector<int> d;bool operator<(const Node &t…

平台介绍-搭建赛事运营平台(8)

平台介绍-搭建赛事运营平台&#xff08;5&#xff09;提到了字典是分级的&#xff0c;本篇具体介绍实现。 平台级别的代码是存储在核心库中&#xff0c;品牌级别的代码是存储在品牌库中&#xff08;注意代码类是一样的&#xff09;。这部分底层功能封装为jar包&#xff0c;然后…

matlab BP神经网络回归预测(套用任何数据不改代码,最后一列是标签)

部分代码&#xff1a; %% BP神经网络 % 清空环境变量 关闭打开过的图表 clear all;clc;close all %% 导入数据 dataxlsread(data1.xlsx); %% 设置训练集数量 num_rowsize(data,1) %数据集行数 n_trainsfloor(num_row*0.8) %按比例求训练集数目 % n_trains150 …

JDK和IntelliJ IDEA下载和安装及环境配置教程

一、JDK下载&#xff08;点击下方官网链接&#xff09; Java Downloads | Oracle 选择对应自己电脑系统往下拉找到自己想要下载的JDK版本进行下载&#xff0c;我下的是jdk 11&#xff0c;JDK有安装版和解压版&#xff0c;我就直接下安装版的了。 .exe和.zip的区别&#xff1a…

通过MobaXterm工具可视化服务器桌面

一、MobaXterm工具 MobaXterm是一款功能强大的远程连接工具&#xff0c;可以连接到各种类型的服务器&#xff0c;包括Linux、Windows和MacOS。支持多种协议&#xff0c;包括SSH、RDP、VNC和Telnet MobaXterm可以通过X11转发功能可视化服务器桌面。 二、MobaXterm工具可视化服务…

011——人体感应模块驱动开发(SR501)

目录 一、 模块简介 二、 工作原理 三、 软件及验证 一、 模块简介 人体都有恒定的体温&#xff0c;一般在 37 度&#xff0c;所以会发出特定波长 10uM 左右的红外线&#xff0c;被动式红外探头就是靠探测人体发射的 10uM 左右的红外线而进行工作的。 人体发射的 10…

架构师之路--Docker的技术学习路径

Docker 的技术学习路径 一、引言 Docker 是一个开源的应用容器引擎&#xff0c;它可以让开发者将应用程序及其依赖包打包成一个可移植的容器&#xff0c;然后在任何支持 Docker 的操作系统上运行。Docker 具有轻量级、快速部署、可移植性强等优点&#xff0c;因此在现代软件开…

Hides for Mac:应用程序隐藏工具

Hides for Mac是一款功能强大的应用程序隐藏工具&#xff0c;专为Mac用户设计。它能够帮助用户快速隐藏当前正在运行的应用程序窗口&#xff0c;保护用户的隐私和工作内容&#xff0c;避免不必要的干扰。 软件下载&#xff1a;Hides for Mac下载 Hides for Mac的使用非常简单直…

电脑换屏总结——关于我把电脑砸了这件事!

大家好&#xff0c;我是工程师看海&#xff0c;很高兴和各位一起分享我的原创文章&#xff0c;喜欢和支持我的工程师&#xff0c;一定记得给我点赞、收藏、分享哟。 加微信[chunhou0820]与作者进群沟通交流。 【淘宝】https://m.tb.cn/h.5PAjLi7?tkvmMLW43KO7q CZ3457 「运放秘…