游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

spring循环依赖三级缓存

  循环依赖
  BeanFactory作为bean工厂管理我们的单例bean,那么肯定需要有个缓存来存储这些单例bean,在Spring中就是一个Map结构的缓存,key为beanName,value为bean。在获取一个bean的时候,先从缓存中获取,如果没有获取到,那么触发对象的实例化和初始化操作,完成之后再将对象放入缓存中,这样就能实现一个简单的bean工厂。
  由于Spring提供了依赖注入的功能,支持将一个对象自动注入到另一个对象的属性中,这就可能出现循环依赖(区别于dependsOn)的问题:A对象在创建的时候依赖于B对象,而B对象在创建的时候依赖于A对象。
  可以看到,由于A依赖B,所以创建A的时候触发了创建B,而B又依赖A,又会触发获取A,但是此时A正在创建中,还不在缓存中,就引发了问题。多级缓存一级缓存
  前面引出了循环依赖的问题,那么该如何解决呢?其实很简单,单单依赖一级缓存就能解决。对于一级缓存,我们不再等对象初始化完成之后再存入缓存,而是等对象实例化完成后就存入一级缓存,由于此时缓存中的bean还没有进行初始化操作,可以称之为早期对象,也就是将A对象提前暴露了。这样B在创建过程中获取A的时候就能从缓存中获取到A对象,最后在A对象初始化工作完成后再更新一级缓存即可,这样就解决了循环依赖的问题。
  但是这样又引出了另一个问题:早期对象和完整对象都存在于一级缓存中,如果此时来了其它线程并发获取bean,就可能从一级缓存中获取到不完整的bean,这明显不行,那么我们不得已只能在从一级缓存获取对象处加一个互斥锁,以避免这个问题。
  而加互斥锁也带来了另一个问题,容器刷新完成后的普通获取bean的请求都需要竞争锁,如果这样处理,在高并发场景下使用spring的性能一定会极低。二级缓存
  既然只依赖一级缓存解决循环依赖需要靠加锁来保证对象的安全发展,而加锁又会带来性能问题,那该如何优化呢?答案就是引入另一个缓存。这个缓存也是一个Map结构,key为beanName,value为bean,而这个缓存可以称为二级缓存。我们将早期对象存到二级缓存中,一级缓存还是用于存储完整对象(对象初始化工作完成后),这样在接下来B创建的过程中获取A的时候,先从一级缓存获取,如果一级缓存没有获取到,则从二级缓存获取,虽然从二级缓存获取的对象是早期对象,但是站在对象内存关系的角度来看,二级缓存中的对象和后面一级缓存中的对象(指针)都指向同一对象,区别是对象处于不同的阶段,所以不会有什么问题。
  既然获取bean的逻辑是先从一级缓存获取,没有的话再从二级缓存获取,那么也可能出现其它线程获取到不完整对象的问题,所以还是需要加互斥锁。不过这里的加锁逻辑可以下沉到二级缓存,因为早期对象存储在二级缓存中,从一级缓存获取对象不用加锁,这样的话当容器初始化完成之后,普通的getBean请求可以直接从一级缓存获取对象,而不用去竞争锁。当循环依赖遇上AOP
  似乎二级缓存已经解决了循环依赖的问题,看起来也非常简单,但是不要忘记Spring提供的另一种特性:AOP。Spring支持以CGLIB和JDK动态代理的方式为对象创建代理类以提供AOP支持,在前面总结bean生命周期的文章中已经提到过,代理对象的创建(通常)是在bean初始化完成之后进行(通过BeanPostProcessor后置处理器)的,而且按照正常思维来看,一个代理对象的创建也应该在原对象完整的基础上进行,但是当循环依赖遇上了AOP就不那么简单了。
  还是在前面A和B相互依赖的场景中,试想一下如果A需要被代理呢?由于二级缓存中的早期对象是原对象,而代理对象是在A初始化完成之后再创建的,这就导致了B对象中引用的A对象不是代理对象,于是出现了问题。
  要解决这问题也很简单,把代理对象提前创建不就行了?也就是如果没有循环依赖,那么代理对象还是在初始化完成后创建,如果有循环依赖,那么就提前创建代理对象。那么怎么判断发生了循环依赖呢?在B创建的过程中获取A的时候,发现二级缓存中有A,就说明发生了循环依赖,此时就为A创建代理对象,将其覆盖到二级缓存中,并且将代理对象复制给B的对应属性,解决了问题。当然,最终A初始化完成之后,在一级缓存中存放的肯定是代理对象。
  如果在A和B相互依赖的基础上,还有一个对象C也依赖了A:A依赖B,B依赖A,A依赖C,C依赖A。那么在为A创建代理对象的时候,就要注意不能重复创建。
  可以在对象实例化完成之后,将其beanName存到一个Set结构中,标识对应的bean正在创建中,而当其他对象创建的过程依赖某个对象的时候,判断其是否在这个set中,如果在就说明发生了循环依赖。三级缓存
  虽然仅仅依靠二级缓存能够解决循环依赖和AOP的问题,但是从解决方案上看来,维护代理对象的逻辑和getBean的逻辑过于耦合,Spring没有采取这种方案,而是引入了另一个三级缓存。三级缓存的key还是为beanName,但是value是一个函数(ObjectFactorygetBean方法),在该函数中执行获取早期对象的逻辑:getEarlyBeanReference方法。在getEarlyBeanReference方法中,Spring会调用所有
  SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法,通过该方法可以修改早期对象的属性或者替换早期对象。这个是Spring留给开发者的另一个扩展点,虽然我们很少使用,不过在循环依赖遇到AOP的时候,代理对象就是通过这个后置处理器创建。Spring三级缓存源码实现
  那么三级缓存在spring中是如何体现的呢?我们来到
  DefaultSingletonBeanRegistry中:一级缓存,也就是单例池,存储最终对象privatefinalMapString,ObjectsingletonObjectsnewConcurrentHashMap(256);二级缓存,存储早期对象privatefinalMapString,ObjectearlySingletonObjectsnewHashMap(16);三级缓存,存储的是一个函数接口,privatefinalMapString,ObjectFactorylt;?singletonFactoriesnewHashMap(16);
  其实所谓三级缓存,在源码中就是3个Map,一级缓存用于存储最终单例对象,二级缓存用于存储早期对象,三级缓存用于存储函数接口。
  在容器刷新时调用的十几个方法中,
  finishBeanFactoryInitialization方法主要用于实例化单例bean,在其逻辑中会冻结BeanDefinition的元数据,接着调用beanFactory的preInstantiateSingletons方法,循环所有被管理的beanName,依次创建Bean,我们这里主要关注创建bean的逻辑,也就是AbstractBeanFactory的doGetBean方法(该方法很长,这里只贴了部分代码):protectedTTdoGetBean(finalStringname,NullablefinalClassTrequiredType,NullablefinalObject〔〕args,booleantypeCheckOnly)throwsBeansException{解析FactoryBean的name()和别名finalStringbeanNametransformedBeanName(name);Objectbean;尝试从缓存中获取对象ObjectsharedInstancegetSingleton(beanName);if(sharedInstance!nullargsnull){包含了处理FactoryBean的逻辑,可以通过beanName获取原对象,通过beanName获取真实对象FactoryBean的真实bean有单独的缓存factoryBeanObjectCache(Map结构)存放如果是普通的bean,那么直接返回对应的对象beangetObjectForBeanInstance(sharedInstance,name,beanName,null);}else{只能解决单例对象的循环依赖if(isPrototypeCurrentlyInCreation(beanName)){thrownewBeanCurrentlyInCreationException(beanName);}如果存在父工厂,并且当前工厂中不存在对应的BeanDefinition,那么尝试到父工厂中寻找比如springmvc整合spring的场景BeanFactoryparentBeanFactorygetParentBeanFactory();if(parentBeanFactory!null!containsBeanDefinition(beanName)){bean的原始名称StringnameToLookuporiginalBeanName(name);if(parentBeanFactoryinstanceofAbstractBeanFactory){return((AbstractBeanFactory)parentBeanFactory)。doGetBean(nameToLookup,requiredType,args,typeCheckOnly);}elseif(args!null){return(T)parentBeanFactory。getBean(nameToLookup,args);}else{returnparentBeanFactory。getBean(nameToLookup,requiredType);}}try{处理dependsOn的依赖(不是循环依赖)String〔〕dependsOnmbd。getDependsOn();if(dependsOn!null){for(Stringdep:dependsOn){if(isDependent(beanName,dep)){循环dependson抛出异常thrownewBeanCreationException(mbd。getResourceDescription(),beanName,CirculardependsonrelationshipbetweenbeanNameanddep);}registerDependentBean(dep,beanName);try{getBean(dep);}catch(NoSuchBeanDefinitionExceptionex){thrownewBeanCreationException(mbd。getResourceDescription(),beanName,beanNamedependsonmissingbeandep,ex);}}}创建单例beanif(mbd。isSingleton()){把beanName和一个ObjectFactory类型的singletonFactory传入getSingleton方法ObjectFactory是一个函数,最终创建bean的逻辑就是通过回调这个ObjectFactory的getObject方法完成的sharedInstancegetSingleton(beanName,(){try{真正创建bean的逻辑returncreateBean(beanName,mbd,args);}catch(BeansExceptionex){destroySingleton(beanName);throwex;}});beangetObjectForBeanInstance(sharedInstance,name,beanName,mbd);}}catch(BeansExceptionex){cleanupAfterBeanCreationFailure(beanName);throwex;}}return(T)bean;}
  对于单例对象,调用doGetBean方法的时候会调用getSingleton方法,该方法的入参是beanName和一个ObjectFactory的函数,在getSingleton方法中通过回调ObjectFactory的getBean方法完成bean的创建,那我们来看看getSingleton方法(部分代码):publicObjectgetSingleton(StringbeanName,ObjectFactorylt;?singletonFactory){Assert。notNull(beanName,Beannamemustnotbenull);互斥锁synchronized(this。singletonObjects){首先尝试从尝试从单例缓存池中获取对象,如果获取到了对象则直接返回,否则进入创建的逻辑ObjectsingletonObjectthis。singletonObjects。get(beanName);if(singletonObjectnull){在创建之前将要创建的beanName存入singletonsCurrentlyInCreation中,这个是一个Map结构,用于标识单例正在创建中beforeSingletonCreation(beanName);booleannewSingletonfalse;booleanrecordSuppressedExceptions(this。suppressedExceptionsnull);if(recordSuppressedExceptions){this。suppressedExceptionsnewLinkedHashSet();}try{回调singletonFactory的getObject方法,该函数在外层doGetBean方法中传入实际调用了createBean方法singletonObjectsingletonFactory。getObject();newSingletontrue;}finally{if(recordSuppressedExceptions){this。suppressedExceptionsnull;}将beanName从正在创建单例bean的集合singletonsCurrentlyInCreation中移除afterSingletonCreation(beanName);}if(newSingleton){将创建好的单例bean放入一级缓存,并且从二级缓存、三级缓存中移除添加到registeredSingletons中addSingleton(beanName,singletonObject);}}returnsingletonObject;}}
  getSingleton方法的主线逻辑很简单,就是通过传入的函数接口创建单例bean,然后存入一级缓存中,同时清理bean在二级缓存、三级缓存中对应的数据。前面已经分析过了,传入的函数接口调用的是createBean方法,那么我们又来到createBean方法,createBean方法主要调用doCreateBean方法,在doCreateBean调用之前会先调用
  nstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation拦截bean的实例化,如果这里的后置处理器返回了bean,则不会到后面的doCreateBean方法中,不过我们这里不用关心这个逻辑,直接跳到doCreateBean方法(部分代码):protectedObjectdoCreateBean(finalStringbeanName,finalRootBeanDefinitionmbd,finalNullableObject〔〕args)throwsBeanCreationException{BeanWrapperinstanceWrappernull;if(mbd。isSingleton()){instanceWrapperthis。factoryBeanInstanceCache。remove(beanName);}if(instanceWrappernull){创建bean,也是就是实例化,会把实例化后的bean包装在BeanWrapper中instanceWrappercreateBeanInstance(beanName,mbd,args);}eanWrapper中获取到对象finalObjectbeaninstanceWrapper。getWrappedInstance();Classlt;?beanTypeinstanceWrapper。getWrappedClass();if(beanType!NullBean。class){mbd。resolvedTargetTypebeanType;}synchronized(mbd。postProcessingLock){if(!mbd。postProcessed){try{MergedBeanDefinitionPostProcessor后置处理器的处理applyMergedBeanDefinitionPostProcessors(mbd,beanType,beanName);}catch(Throwableex){thrownewBeanCreationException(mbd。getResourceDescription(),beanName,Postprocessingofmergedbeandefinitionfailed,ex);}mbd。postProcessedtrue;}}判断该对象是否支持提前暴露,核心条件就是要求单例对象booleanearlySingletonExposure(mbd。isSingleton()this。allowCircularReferencesisSingletonCurrentlyInCreation(beanName));if(earlySingletonExposure){把我们的早期对象包装成一个singletonFactory对象该对象提供了一个getObject方法,该方法内部调用getEarlyBeanReference方法将早期对象存入三级缓存,三级缓存存的是一个接口实现(ObjectFactory接口),其getBean方法会调用getEarlyBeanReference方法addSingletonFactory(beanName,()getEarlyBeanReference(beanName,mbd,bean));}Initializethebeaninstance。ObjectexposedObjectbean;try{属性赋值操作,这里就可能出现循环依赖问题populateBean(beanName,mbd,instanceWrapper);实例化操作:调用三个Aware、后置处理器beforeInitialization(包含PostConstruct的实现)、afterPropertiesSet、initmethod、后置处理器afterInitialization等个方法里的后置处理器可能会改变exposeObjectexposedObjectinitializeBean(beanName,exposedObject,mbd);}catch(Throwableex){if(exinstanceofBeanCreationExceptionbeanName。equals(((BeanCreationException)ex)。getBeanName())){throw(BeanCreationException)ex;}else{thrownewBeanCreationException(mbd。getResourceDescription(),beanName,Initializationofbeanfailed,ex);}}if(earlySingletonExposure){这里的入参为false,表示只从一级缓存和二级缓存中获取非循环依赖的情况下,不会调用到三级缓存,三级缓存中的接口会在单例对象入一级缓存的时候移除ObjectearlySingletonReferencegetSingleton(beanName,false);if(earlySingletonReference!null){如果获取到了早期暴露对象earlySingletonReference不为空if(exposedObjectbean){如果exposeObject在经过initializeBean方法后没有改变,那么说明没有被后置处理器修改,那么用earlySingletonReference替换exposeObjectexposedObjectearlySingletonReference;}}}returnexposedObject;}
  这个方法的逻辑就是先实例化对象,如果对象支持暴露早期对象,那么会将早期对象作为入参传入getEarlyBeanReference方法,然后包装成一个ObjectFactory存入三级缓存,接着再调用populateBean方法填充对象的属性。而在填充对象属性的过程中,就可能发现循环依赖,比如当前正在创建A,然后在populateBean方法中发现了依赖B,那么就会调用getBean(B),B也会走上面A走的这个流程,当B也走到populateBean方法填充属性的时候,又发现依赖了A,那么又会调用getBean(A)。那么在populateBean创建A的时候,会从三级缓存中获取bean,如果A需要被代理,那么会创建代理对象,这样B中的A属性就是代理对象了,接着把三级缓存获取的对象存入二级缓存中。
  在B处理完成之后就回到了A的逻辑,假设populateBean(A)的逻辑完成了,那么接着进入initializeBean方法进行A的初始化操作,注意这里执行初始化操作的对象是A原对象,代理对象存储在二级缓存中。由于initializeBean方法会调用后置处理器的
  beforeafterInitialization方法,这个后置处理器可能会改变对象,所以在后面的逻辑中,如果从二级缓存中获取到了A的代理对象,会判断原对象经过后置处理器后有没有变化,如果还是原对象,那么用二级缓存中的代理对象覆盖原对象,所以doCreateBean方法返回的就是代理对象,最终存入一级缓存中。流程就是:创建A实例对象设置A的B属性创建B实例对象设置B的A属性创建A的代理对象存入二级缓存初始化A对象从二级缓存取出A的代理对象覆盖A对象A的代理对象存入一级缓存
  我们需要关注的就是在B的populateBean逻辑里调用getBean(A)是什么样的逻辑。当然也是getBeandoGetBeangetSingleton这个逻辑,我们着重关注一下getSingleton方法的实现:protectedObjectgetSingleton(StringbeanName,booleanallowEarlyReference){首先从一级缓存中获取ObjectsingletonObjectthis。singletonObjects。get(beanName);if(singletonObjectnullisSingletonCurrentlyInCreation(beanName)){如果从一级缓存没有获取到,并且获取的对象正在创建中,这里已经能够确定发生了循环依赖synchronized(this。singletonObjects){为了防止其它线程获取到不完整工单对象,这里使用同步锁控制从二级缓存中获取早期对象singletonObjectthis。earlySingletonObjects。get(beanName);if(singletonObjectnullallowEarlyReference){如果二级缓存中也没有获取到,且allowEarlyReference为true(这里传入的是true)那么尝试从三级缓存中获取ObjectFactorylt;?singletonFactorythis。singletonFactories。get(beanName);if(singletonFactory!null){从三级缓存中获取到了对象,注意三级缓存存储的是ObjectFactory调用getObject方法,前面提到了早期暴露的对象存入三级缓存的ObjectFactory的getBean方法调用的是getEarlyBeanReference方法,所以这里会调用getEarlyBeanReference方法singletonObjectsingletonFactory。getObject();把早期对象存入二级缓存this。earlySingletonObjects。put(beanName,singletonObject);三级缓存中的接口没用了,直接移除this。singletonFactories。remove(beanName);}}}}returnsingletonObject;}
  在getSingleton方法的逻辑中,先从一级缓存获取,如果一级缓存没有找到,那么如果获取的bean正在创建中,则从二级缓存获取,如果二级缓存没有找到,那么从三级缓存获取,三级缓存中存的是ObjectFactory实现,最终会调用其getBean方法获取bean,然后存入二级缓存中,同时清除三级缓存。同时提供了一个allowEarlyReference参数控制是否能从三级缓存中获取。
  对于循环依赖的情况,getBean(A)存入正在创建缓存存入三级缓存populateBean(A)getBean(B)populateBean(B)getBean(A)getSingleton(A),当在populateBean(B)的过程中调用getSingleton(A)的时候,明显一级缓存和二级缓存都为空,但是三级缓存不为空,所以会通过三级缓存获取bean,三级缓存的创建逻辑如下:booleanearlySingletonExposure(mbd。isSingleton()this。allowCircularReferencesisSingletonCurrentlyInCreation(beanName));if(earlySingletonExposure){addSingletonFactory(beanName,()getEarlyBeanReference(beanName,mbd,bean));}
  所以我们直接来到getEarlyBeanReference方法获取早期对象:protectedObjectgetEarlyBeanReference(StringbeanName,RootBeanDefinitionmbd,Objectbean){ObjectexposedObjectbean;如果容器中有InstantiationAwareBeanPostProcessors后置处理器if(!mbd。isSynthetic()hasInstantiationAwareBeanPostProcessors()){for(BeanPostProcessorbp:getBeanPostProcessors()){if(bpinstanceofSmartInstantiationAwareBeanPostProcessor){找到SmartInstantiationAwareBeanPostProcessor后置处理器SmartInstantiationAwareBeanPostProcessoribp(SmartInstantiationAwareBeanPostProcessor)bp;调用SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法exposedObjectibp。getEarlyBeanReference(exposedObject,beanName);}}}returnexposedObject;}
  这个getEarlyBeanReference方法的逻辑很简单,但是非常重要,该方法主要就是对
  SmartInstantiationAwareBeanPostProcessor后置处理器的调用,而循环依赖时的AOP就是通过这个SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法实现的,相关的具体类是AnnotationAwareAspectJAutoProxyCreator,这个留待AOP原理分析的文章中详细说明。总结
  多级缓存并不只是为了解决循环依赖和AOP的问题,还考虑到了逻辑的分离、结构的扩展性和保证数据安全前提下的效率问题等。由于存在二级缓存提前暴露不完整对象的情况,所以为了防止getSingleton方法返回不完整的bean,在该方法中使用了synchronized加锁,而为了不影响容器刷新完成后正常获取bean,从一级缓存获取对象没有加锁,事实上也不用加锁,因为一级缓存中要么没有对象,要么就是完整的对象。
  循环依赖发生在对象都属性赋值,也就是populateBean阶段,在对象实例化完成后将其包装成一个函数接口ObjectFactory存入三级缓存,通过getEarlyBeanReference方法触发
  SmartInstantiationAwareBeanPostProcessor后置处理器的执行,如果循环依赖遇上了AOP,那么就在这里进行处理。
  这里每个单例对象实例化之后存入三级缓存,即使没有发生循环依赖,这个是为之后可能发生的循环依赖做准备,如果最终没有发生循环依赖,那么对象实例化之后,正常初始化,然后存入一级缓存即可,而在存入一级缓存的时候会清空二级和三级缓存。

不开窗也想畅快呼吸,你可能需要它入冬以来,各地的空气质量都不太乐观,PM2。5给人体带来的危害在这几年里早已深入人心。可能很多人早已给家中置办好了空气净化器,紧闭门窗一天24小时不停运行。但是问题也随之而来,……神舟精盾笔记本X55X57系列十代酷睿横空出世,神舟精盾强势2019年8月份,Intel正式发布了第十代酷睿IceLake移动处理器,专为轻薄型笔记本设计,而经过数月努力研发,神舟将于十月底发布搭配IceLake处理器的全新轻薄精盾笔记……这样多的调整只为兼容办公,雷蛇黑寡妇蜘蛛轻装版解读猎魂光蛛的推出让我们看到了着雷蛇对全新设计语言的尝试,极简、扁平化的设计元素顺利的融入到了雷蛇的基因中,但让人万万没有想到的是这样的基因迅速的被雷蛇最具影响力的黑寡妇蜘蛛吸收,……特斯拉Model3新对手!小鹏全新紧凑型车谍照曝光虽然说在新能源市场里,造车新势力和传统主机厂的车型各有千秋。但不能否认的是,当前的纯电动车市场中,凭借着月销1W的战绩,特斯拉Model3是当之无愧的领军车型。所以特斯拉……只需这个,不需下载APP也不用滤镜就能拍出超美的大片,不看后我很喜欢帮人拍照,如果别人满意照片成果的话我就会觉得很开心所以经常我也会拍拍风景,挑战各种角度搞不好可以发掘到一个好看又创意的拍摄方法最近我看到网上很火一组杯……AR成为万物互联时代的新窗口智能手表的屏幕TWS可以作为手机部分功能的替代品,但智能手表的尺寸有限,屏幕太小,使用体验大打折扣。智能眼镜则很好地弥补了这一缺陷,它可以通过投射将虚拟屏幕映射至眼前,用户通过……激光雷达的技术原理和应用优势当下,激光雷达作为一种出色的检测技术,在入侵检测和周界保护方面发挥着重要作用,如今正成为安全领域的后起之秀。根据IndustryARC的数据,2019年全球激光雷达安全市……选电热水器还是燃气热水器好呢?别急先看看这些再决定遥想小时候,每天用柴火灶烧一大锅的热水用来洗澡,每次都是快速地冲洗,不然水很快就会变凉。自从热水器慢慢地普及以后,给每个家庭带来了非常大的便利,大量的热水供应,可以舒舒服……在美国制裁危机中诞生的移动端第三大操作系统HarmonyOS这是一篇迟到的文章,早在2020年9月10日至12日,为了应对美国第三轮制裁危机且布局新生态,华为发布了HarmonyOS2(鸿蒙系统),很多朋友并不是很了解这块系统,到底是用……Flink操练(二十七)之shuffle三种实现方式1、代码逻辑实现packageday02;importorg。apache。flink。streaming。api。environment。StreamExecutionEnv……素食主义者养的宠物也得吃素?这真不算虐待动物吗话说,纯素食主义(Vegan)这几年越来越流行了。饮食习惯、选择吃啥这都是个人选择,别人没啥好评价的但是如果素食主义者养了宠物,也逼着自家的猫猫狗狗吃素呢?今……华为折叠屏手机翻车?Mate20X能赢回一局吗近日,华为在深圳坂田基地发布了首款5G商用手机华为Mate20X5G。在会后的专访中,华为消费者业务手机产品线总裁何刚被问及MateX折叠手机进展时表示,目前还在不断优化和完善……
币圈事件300亿大爆仓!比特币暴跌近19,大泡沫要破了?300亿大爆仓!比特币暴跌近19,大泡沫要破了?刚成为中美洲国家萨尔瓦多的法定货币,比特币就闪崩了!昨天晚上,比特币突然崩盘,价格一路从近53000美元的价格暴跌至……2020年中国HRSaaS行业研究报告核心摘要:研究范畴:HRSaaS就是SaaS模式的人力资源服务软件,与以往研究不同,本报告将顺应HRSaaS行业发展趋势向下渗透,研究底层PaaS平台对解决当前人力资源管……双10分新作上线,死亡循环为何堪为标杆?9月14日,由ArkaneStudios制作,BethesdaSoftworks发行的全新作品《死亡循环》正式上线。上线前夕,该作媒体评分正式解禁,不仅多家媒体给出了满分评价,……520情侣浪漫表白JS特效分享(附源码)520要来了,为回馈朋友们所有专栏8折优惠,感兴趣的朋友可以订阅,感谢大家的支持!520马上就要到,作为程序员的你是不是也想送个特别的礼物。今天给大家分享一个HTML5C……从土妞到女团顶流,女生喷不喷防晒差距这么大!防黑从防晒开始前段时间辣Lisa的舞蹈,大家都看了吗?超飒的舞蹈,还有那瞩目的大长腿,实在是太让人羡慕了。不得不说,韩国造星实力就是强!5年前,被嘲黄皮小土妞的Lisa,摇……LK分享功能域架构与区域架构在架构与关系一文中,笔者介绍了事物之间的五大类关系:概念关系、空间关系、时间关系、行为关系以及因果关系。功能域架构主要是从行为关系上设计架构,区域架构主要是从空间关系上设……魅蓝发布,第一款新品今天魅蓝打头阵的是这款耳机魅蓝Blue内置12mm超大动圈,30dB主动降噪,双mic通话降噪,支持Flyme妙连,静享音乐;30h超长续航,还带无线充电。售价只有199……名企头条新财富500富人榜揭晓顺丰回应送外卖新财富500富人榜揭晓:首富马云跃过3000亿,黄峥首入前十今日,新财富500富人榜公布,这也是自2003年以来,新财富推出的第18份榜单。上榜的500富人掌握财富突破1……丛林野食王深入丛林荒野求生,捕获一只野鹿直接烤着吃,真香这个男人已经在丛林中饿了三天了,为了挽回自己恋人的尊严,此时的他必须对眼前的猎物下手,只见他拉动弓弦,最后射出致命的一箭,就这样一条百斤的迷路就落入了猎人的魔掌,有了如此美味,……首批硬件低蓝光LCD产品量产TCL华星打造行业护眼标杆近年来,随着消费习惯的升级,消费者开始愈发重视电子产品的安全性,电子设备显示屏的蓝光危害也引起了社会的广泛关注。尤其是疫情期间,由于线上网课的大范围普及,使得青少年电脑、电视的……家电科普蒸箱烤箱蒸烤一体机和蒸烤一体集成灶有哪些不同随着人们的生活水平在不断地提高,科技也在不断地发展,为了生活能更便捷,家电本着为使用者提供便利的原则,也在不断地进步。说起家电,在日常生活中,家电需求占比最大的就是厨房家电,大……游戏性能那家强?魅族18告诉你,什么才叫宅男的第一台游戏手机这不知不觉之间,暑假也已经开始了有一段时间。不知道屏幕前的大家,在这假期期间,又都是怎么安排的呢?但笔者相信,无论大家在暑假期间有什么安排,和同学朋友开黑玩游戏,应该都是少不了……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网