本文简要介绍了循环依赖以及Spring解决循环依赖的过程

一.定义

循环依赖是指对象之间的循环依赖,即2个或以上的对象互相持有对方,最终形成闭环。这里的对象特指单例对象。

二.表现形式

对象之间的循环依赖主要有两种表现形式:构造函数循环依赖和属性循环依赖。

2-1 构造函数循环依赖

1 public class A {
2 /**
3 * 有参构造函数
4 */
5 public A(B b) {
6 System.out.println(b);
7 }
8 }
1 public class B {
2 /**
3 * 有参构造函数
4 */
5 public B(A a) {
6 System.out.println(a);
7 }
8 }

A, B,之间形成的循环依赖,具体表现在:A构造函数的参数是B,B构造函数的参数是A,即构造函数循环依赖。

将这2个bean交给Spring容器管理,并使用构造函数注入来模拟构造函数循环依赖。

 1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans
5 https://www.springframework.org/schema/beans/spring-beans.xsd">
6
7 <bean id="a" class="com.hutao.model.A">
8 <constructor-arg name="b" ref="b"/>
9 </bean>
10
11 <bean id="b" class="com.hutao.model.B">
12 <constructor-arg name="a" ref="a"/>
13 </bean>
14
15 </beans>

测试构造函数循环依赖:

1 public class Test {
2 public static void main(String[] args) {
3 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
4 A a = context.getBean("a", A.class);
5 }
6 }

测试结果:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
//......

这种构造函数循环依赖无法解决,因为JVM在对类进行实例化时,需要先实例化构造函数的参数,而由于各个构造函数的参数之间是循环依赖的,所以各个参数都无法提前实例化,故只能抛出错误。

2-2 属性循环依赖

1 public class A {
2 private B b;
3
4 public void setB(B b) {
5 this.b = b;
6 }
7 }
1 public class B {
2 private A a;
3
4 public void setC(A a) {
5 this.a = a;
6 }
7 }

A, B,之间形成的循环依赖,具体表现在:A一个属性是B,B一个属性是A,即属性循环依赖。

将这2个bean交给Spring容器管理,并使用属性注入来模拟属性循环依赖。

 1 -<?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans
5 https://www.springframework.org/schema/beans/spring-beans.xsd">
6
7 <bean id="a" class="com.hutao.model.A">
8 <property name="b" ref="b"/>
9 </bean>
10
11 <bean id="b" class="com.hutao.model.B">
12 <property name="a" ref="a"/>
13 </bean>
14
15 </beans>

测试属性循环依赖:

1 public class Test {
2 public static void main(String[] args) {
3 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
4 A a = context.getBean("a", A.class);
5 System.out.println(a);
6 }
7 }

测试结果:

com.hutao.model.A@2d928643

Process finished with exit code 0

这种表现形式的循环依赖Spring可以解决,大致的思路是:Spring先使用各个类的无参构造函数实例化各个对象,再使用Setter去设置对象的属性。但Spring在实例化各个对象时并不是按定义顺序去实例化的,中间还穿插了递归实例化的过程。

假定有A ,B, C三个类,A, C之间存在着循环依赖关系(即A有个C类型的属性,C有个A类型的属性),XML配置文件中定义的顺序为A->B->C。

1 <bean id="a" class="com.hutao.model.A">
2 <property name="c" ref="c"/>
3 </bean>
4
5 <bean id="b" class="com.hutao.model.B"/>
6
7 <bean id="c" class="com.hutao.model.C">
8 <property name="a" ref="a"/>
9 </bean>

Spring首先会去实例化A,A实例化完成后,为A注入其属性C的值,在注入属性C的时候发现C还未被实例化就先去实例化C,实例化C完成后,为C注入属性A,而此时A已经完成实例化了,所以可以直接为C注入属性A,然后完成对A中的C属性注入,最后再去实例化B。所以实例化顺序是A-> C->B而不是A->B->C。

三.如何检测出是循环依赖

在某个对象创建的过程中,如果递归调用回来发现自己正在创建过程中的话,即说明存在循环依赖。

四. 怎么解决循环依赖

Spring解决循环依赖的主要依据是Java的引用传递:当我们获取到对象的引用时,对象的属性可以是还未设置的(允许延后设置属性),但构造器的执行必须在获取引用之前。

Spring单例对象的初始化主要分为3步:

第1步 实例化:调用对象的构造函数实例化对象;

第2步 注入属性:对实例化对象的属性值进行注入;

第3步 初始化:调用SpringXML中的init()方法。

由上述初始化步骤可知,循环依赖主要发生在第1,2 步,即:构造器循环依赖和属性循环依赖。Spring解决属性循环依赖使用了三级缓存。

五.Spring中源码实现

Spring版本信息为:

<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
<scope>compile</scope>

5-1 三级缓存

三级缓存的源码(调换了顺序):

1 /** Cache of singleton objects: bean name to bean instance. */
2 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
3 ​
4 /** Cache of early singleton objects: bean name to bean instance. */
5 private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
6 ​
7 /** Cache of singleton factories: bean name to ObjectFactory. */
8 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

三级缓存:

一级缓存->singletonObjects:存放的是初始化之后的单例对象

二级缓存->earlySingletonObjects:存放的是一个已完成实例化未完成初始化的早期单例对象

三级缓存->singletonFactories:存放的是可产生单例对象的工厂对象(此缓存中的bean name和二级缓存中的bean name是互斥的,即此级缓存中有某个bean的工厂,二级缓存中就没有这个bean)

5-2 具体实现

对于非懒加载的类,是在refresh方法中的 finishBeanFactoryInitialization(beanFactory) 方法完成的包扫描以及bean的初始化。

 1 public void refresh() throws BeansException, IllegalStateException {
2 synchronized (this.startupShutdownMonitor) {
3 //...
4 ​
5 // Instantiate all remaining (non-lazy-init) singletons.
6 finishBeanFactoryInitialization(beanFactory);
7 ​
8 //...
9 }
10 }
 1 /**
2 * Finish the initialization of this context's bean factory,
3 * initializing all remaining singleton beans.
4 */
5 protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
6 //...
7
8 // Instantiate all remaining (non-lazy-init) singletons.
9 beanFactory.preInstantiateSingletons();
10 }

此处调用了beanFactory的一个方法preInstantiateSingletons(),此处的beanFactory是DefaultListableBeanFactory。

DefaultListableBeacFactory的preInstantiateSingletons()方法:

 1 public void preInstantiateSingletons() throws BeansException {
2
3 // Iterate over a copy to allow for init methods which in turn register new bean definitions.
4 // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
5 List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
6
7 // Trigger initialization of all non-lazy singleton beans...
8 for (String beanName : beanNames) {
9 RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
10 //判断Bean是否为非抽象类、单例、非懒加载,如果都满足才初始化
11 if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
12 if (isFactoryBean(beanName)) {
13 //无关代码,对FactoryBean的处理
14 }
15 else {
16 //这里就是对普通Bean的初始化
17 getBean(beanName);
18 }
19 }
20 }
21
22 //...
23 }

在此方法中,循环Spring容器中的所有Bean,依次对其进行初始化,初始化的入口就是getBean()方法。

追踪getBean方法:

1 //AbstractBeanFactory.java
2
3 @Override
4 public Object getBean(String name) throws BeansException {
5 return doGetBean(name, null, null, false);
6 }

引用了AbstractBeanFacotry中重载的doGetBean()方法:

追踪doGetBean()方法:

 1 protected <T> T doGetBean(
2 String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
3 throws BeansException {
4
5 String beanName = transformedBeanName(name);
6 Object bean;
7
8 //方法① 从三个缓存(map)中获取单例bean
9 // Eagerly check singleton cache for manually registered singletons.
10 Object sharedInstance = getSingleton(beanName);
11 if (sharedInstance != null && args == null) {
12 if (logger.isTraceEnabled()) {
13 if (isSingletonCurrentlyInCreation(beanName)) {
14 logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
15 "' that is not fully initialized yet - a consequence of a circular reference");
16 }
17 else {
18 logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
19 }
20 }
21 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
22 }
23
24 else {
25 //如果是多例的循环依赖,直接抛出异常
26 // Fail if we're already creating this bean instance:
27 // We're assumably within a circular reference.
28 if (isPrototypeCurrentlyInCreation(beanName)) {
29 throw new BeanCurrentlyInCreationException(beanName);
30 }
31
32 //...
33
34 try {
35 RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
36
37 //...
38
39 // Create bean instance.
40 if (mbd.isSingleton()) {
41 //方法② 获取单例bean
42 sharedInstance = getSingleton(beanName, () -> {
43 try {
44 //方法③ 创建bean
45 return createBean(beanName, mbd, args);
46 }
47 catch (BeansException ex) {
48 // Explicitly remove instance from singleton cache: It might have been put there
49 // eagerly by the creation process, to allow for circular reference resolution.
50 // Also remove any beans that received a temporary reference to the bean.
51 destroySingleton(beanName);
52 throw ex;
53 }
54 });
55 bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
56 }
57 }
58 catch (BeansException ex) {
59 cleanupAfterBeanCreationFailure(beanName);
60 throw ex;
61 }
62 }
63
64 //...
65
66 return (T) bean;
67 }

该方法中使用的方法① 、方法②、方法③对解决循环依赖起了至关重要的作用,下面来依次分析:

方法①:getSingleton(String beanName, boolean allowEarlyReference)

 1 protected Object getSingleton(String beanName, boolean allowEarlyReference) {
2 //步骤A:先从一级缓存singleObjects中根据beanName获取bean
3 Object singletonObject = this.singletonObjects.get(beanName);
4
5 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
6 synchronized (this.singletonObjects) {
7 //步骤B:如果从一级缓存中未获取到bean,则再根据beanName从二级缓存earlySingletonObjects中获取
8 singletonObject = this.earlySingletonObjects.get(beanName);
9 if (singletonObject == null && allowEarlyReference) {
10
11 //步骤C:如果从二级缓存中未获取到bean,则再根据beanName从三级缓存singletonFactories中获取工厂,然后通过工厂获取对应的bean
12 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
13 if (singletonFactory != null) {
14 singletonObject = singletonFactory.getObject();
15 this.earlySingletonObjects.put(beanName, singletonObject);
16 this.singletonFactories.remove(beanName);
17 }
18 }
19 }
20 }
21 return singletonObject;
22 }

通过上面的步骤可以看出这三级缓存的优先级。其中singletonObjects里面存放的是初始化之后的单例对象;earlySingletonObjects中存放的是一个已完成实例化未完成初始化的早期单例对象;而singletonFactories中存放的是ObjectFactory对象,此对象的getObject方法返回值即刚完成实例化还未开始初始化的单例对象。所以先后顺序是,单例对象先存在于singletonFactories中,后存在于earlySingletonObjects中,最后初始化完成后放入singletonObjects中。

当debug到此处时,以2-2中的A, B为例,第一步走到这个方法的是A,此时从这三个缓存中获取到的都是null,因为还没有内容被添加到这三级缓存中去。这个方法主要是给循环依赖中后过来的对象使用。

方法②:getSingleton(String beanName, ObjectFactory<?> singletonFactory)

 1 public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
2 Assert.notNull(beanName, "Bean name must not be null");
3 synchronized (this.singletonObjects) {
4 Object singletonObject = this.singletonObjects.get(beanName);
5 if (singletonObject == null) {
6 //...
7
8 //步骤A
9 beforeSingletonCreation(beanName);
10 boolean newSingleton = false;
11 //...
12 try {
13 //步骤B
14 singletonObject = singletonFactory.getObject();
15 newSingleton = true;
16 }
17 //...
18 finally {
19 if (recordSuppressedExceptions) {
20 this.suppressedExceptions = null;
21 }
22 //步骤C
23 afterSingletonCreation(beanName);
24 }
25 if (newSingleton) {
26 //步骤D
27 addSingleton(beanName, singletonObject);
28 }
29 }
30 return singletonObject;
31 }
32 }

获取单例对象的主要逻辑就是次方法实现的,主要分为上面4个步骤:

步骤A:

 1 /**
2 * Callback before singleton creation.
3 * <p>The default implementation register the singleton as currently in creation.
4 * @param beanName the name of the singleton about to be created
5 * @see #isSingletonCurrentlyInCreation
6 */
7 protected void beforeSingletonCreation(String beanName) {
8 if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
9 throw new BeanCurrentlyInCreationException(beanName);
10 }
11 }

首次将beanName放入到singletonCurrentlyInCreation中

步骤C:

 1 /**
2 * Callback after singleton creation.
3 * <p>The default implementation marks the singleton as not in creation anymore.
4 * @param beanName the name of the singleton that has been created
5 * @see #isSingletonCurrentlyInCreation
6 */
7 protected void afterSingletonCreation(String beanName) {
8 if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
9 throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
10 }
11 }

得到单例对象后,再讲beanName从singletonsCurrentlyInCreation中移除

步骤D:

 1 /**
2 * Add the given singleton object to the singleton cache of this factory.
3 * <p>To be called for eager registration of singletons.
4 * @param beanName the name of the bean
5 * @param singletonObject the singleton object
6 */
7 protected void addSingleton(String beanName, Object singletonObject) {
8 synchronized (this.singletonObjects) {
9 //添加所创建的单例bean到一级缓存中
10 this.singletonObjects.put(beanName, singletonObject);
11 //从三级缓存中移出,此缓存在解决循环依赖的过程中起了关键作用
12 this.singletonFactories.remove(beanName);
13 //从二级缓存中移出
14 this.earlySingletonObjects.remove(beanName);
15 //添加bean到已注册的单例名字集合中
16 this.registeredSingletons.add(beanName);
17 }
18 }

步骤B:

此处调用了ObjectFactory的getObject方法,此方法是在方法③中实现的

步骤B其实也就是方法③

方法③:createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)

 1 /**
2 * Central method of this class: creates a bean instance,
3 * populates the bean instance, applies post-processors, etc.
4 * @see #doCreateBean
5 */
6 @Override
7 protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
8 throws BeanCreationException {
9
10 //...
11
12 try {
13 Object beanInstance = doCreateBean(beanName, mbdToUse, args);
14 //...
15 return beanInstance;
16 }
17
18 //...
19 }

去掉无关代码后,关键方法只有doCreateBean() 方法

doCreateBean() 方法:

 1 /**
2 * Actually create the specified bean. Pre-creation processing has already happened
3 * at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks.
4 * <p>Differentiates between default bean instantiation, use of a
5 * factory method, and autowiring a constructor.
6 * @param beanName the name of the bean
7 * @param mbd the merged bean definition for the bean
8 * @param args explicit arguments to use for constructor or factory method invocation
9 * @return a new instance of the bean
10 * @throws BeanCreationException if the bean could not be created
11 * @see #instantiateBean
12 * @see #instantiateUsingFactoryMethod
13 * @see #autowireConstructor
14 */
15 protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
16 throws BeanCreationException {
17
18 // Instantiate the bean.
19 BeanWrapper instanceWrapper = null;
20 if (mbd.isSingleton()) {
21 instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
22 }
23 if (instanceWrapper == null) {
24 //实例化bean(4.中单例对象初始化步骤1)
25 instanceWrapper = createBeanInstance(beanName, mbd, args);
26 }
27 Object bean = instanceWrapper.getWrappedInstance();
28
29 //...
30
31 // Eagerly cache singletons to be able to resolve circular references
32 // even when triggered by lifecycle interfaces like BeanFactoryAware.
33 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
34 isSingletonCurrentlyInCreation(beanName));
35 if (earlySingletonExposure) {
36 if (logger.isTraceEnabled()) {
37 logger.trace("Eagerly caching bean '" + beanName +
38 "' to allow for resolving potential circular references");
39 }
40 // ★重要:将实例化的bean添加到三级缓存singletonFactories中
41 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
42 }
43
44 // Initialize the bean instance.
45 Object exposedObject = bean;
46 try {
47 //为实例化的bean注入属性(4.中单例对象初始化步骤2)
48 populateBean(beanName, mbd, instanceWrapper);
49 //初始化bean(4.中单例对象初始化步骤3)
50 exposedObject = initializeBean(beanName, exposedObject, mbd);
51 }
52
53 //...
54
55 return exposedObject;
56 }

上面代码中[★重要]标注的重点是此方法的关键。在addSingletonFacotry()方法中,将< beanName->能创建该bean的工厂>键值对添加到了三级缓存中以供其他对象依赖时调用。

populateBean()方法对刚实例化的bean进行属性注入,如果遇到要注入的属性对应的值是由Spring管理的bean,则再通过getBean方法获取该bean。

5-3 总结

属性注入主要是在populateBean() 方法中进行的。对于循环依赖,以2-2中的A, B为例,假定Spring的加载顺序为先加载A。其具体的加载过程为:

  1. 首先触发getBean() 方法对A进行初始化;

  2. 然后走到方法①,此时三级缓存都为空,获取不到实例;

  3. 走到方法②,其中的步骤B触发对方法③的调用,即是对doCreateBean() 方法的调用;

  4. 在方法③中,先通过createBeanInstance实例化A对象,然后将该实例通过addSingletonFactory放入singletonFactories中,完成A对象的早期暴露;

  5. 在方法③中,通过populateBean() 方法对A对象的属性进行注入,发现它有一个B属性,则触发getBean() 对B进行初始化(递归);

  6. 重复步骤3、4、5,只是此时要初始化的是B对象;

  7. 再次走到步骤5时,调用populateBean() 方法对B对象进行属性注入,发现它有一个A属性,则触发getBean() 对A进行初始化;

  8. 对A进行初始化,又来到步骤2,此时第三级缓存中已经有了A的信息了,因为在步骤4时,就已经将A实例放入到了singletonFacotries(第三级缓存中)了,此时可直接得到A的实例并返回;

  9. 完成对B的初始化,继而往上回溯完成对A的B属性注入及初始化。

缓存 开始时 实例化对象A后 为A注入属性B,调用getBean初始化B,在实例化B后 为B注入属性A,调用getBean(String, Boolean)后 B初始化完成后 A初始化完成后
一级缓存(singletonObjects)         B BA
二级缓存(earlySingletonObjects)       A A  
三级缓存(singletonFactories)   A AB B    

参考源:

1. https://www.cnblogs.com/leeego-123/p/12165278.html

2. https://blog.csdn.net/qq_36381855/article/details/79752689

Spring的循环依赖的更多相关文章

  1. Spring的循环依赖问题

    spring容器循环依赖包括构造器循环依赖和setter循环依赖,那Spring容器如何解决循环依赖呢?首先让我们来定义循环引用类: 在Spring中将循环依赖的处理分成了3种情况: 构造器循环依赖 ...

  2. Spring之循环依赖

    转:http://my.oschina.net/tryUcatchUfinallyU/blog/287936 概述 如何检测循环依赖 循环依赖如何解决 Spring如何解决循环依赖 主要的几个缓存 主 ...

  3. 再谈spring的循环依赖是怎么造成的?

    老生常谈,循环依赖!顾名思义嘛,就是你依赖我,我依赖你,然后就造成了循环依赖了!由于A中注入B,B中注入A导致的吗? 看起来没毛病,然而,却没有说清楚问题!甚至会让你觉得你是不清楚spring的循环依 ...

  4. Spring解决循环依赖

    1.Spring解决循环依赖 什么是循环依赖:比如A引用B,B引用C,C引用A,它们最终形成一个依赖环. 循环依赖有两种 1.构造器循环依赖 构造器注入导致的循环依赖,Spring是无法解决的,只能抛 ...

  5. Spring当中循环依赖很少有人讲,今天一起来学习!

    网上关于Spring循环依赖的博客太多了,有很多都分析的很深入,写的很用心,甚至还画了时序图.流程图帮助读者理解,我看了后,感觉自己是懂了,但是闭上眼睛,总觉得还没有完全理解,总觉得还有一两个坎过不去 ...

  6. Spring的循环依赖,学就完事了【附源码】

    目录 啥是循环依赖? Spring可以解决循环依赖的条件 Spring如何去解决循环依赖 SpringBean的创建流程 Spring维护的三级缓存 getSingleton getSingleton ...

  7. Spring 的循环依赖问题

    什么是循环依赖 什么是循环依赖呢?可以把它拆分成循环和依赖两个部分来看,循环是指计算机领域中的循环,执行流程形成闭合回路:依赖就是完成这个动作的前提准备条件,和我们平常说的依赖大体上含义一致.放到 S ...

  8. 详解Spring DI循环依赖实现机制

    一个对象引用另一个对象递归注入属性即可实现后续的实例化,同时如果两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环即所谓的循环依赖怎么实现呢属性的互相注入呢? Spring bean生命周期具体 ...

  9. 【spring源码分析】spring关于循环依赖的问题

    引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错.下面说一下Spring是如果解决循环依赖的. 第一种: ...

随机推荐

  1. React PureComponent All In One

    React PureComponent All In One import React, { // useState, // useEffect, // Component, PureComponen ...

  2. what's the difference amount of pdf, epub, and mobi format

    what's the difference amount of pdf, epub, and Mobi format What is the difference between pdf, epub ...

  3. navigator.geolocation.getCurrentPosition

    navigator.geolocation.getCurrentPosition Geolocation API Specification 2nd Edition W3C Recommendatio ...

  4. NGK官方又出助力市场计划方案 1万枚VAST任性送

    近期NGK官方的一系列动作,可以说是在向外界宣告:NGK2.0即将来袭,席卷加密数字货币市场.前一段时间,NGK官方宣布,NGK公链布局算力领域,打造NGK算力生态星空计划,并推出了SPC星空币.目前 ...

  5. 为什么说NGK公链的商用落地是可行的?

    互联网.大数据以及云计算的发展给人们的生活.工作带来了诸多便利,也让人们一次又一次感叹科技的进步.而NGK公链的诞生,更是让众人称之为传奇.其商用落地可行性,也让人惊叹.那么,为什么说NGK公链的商用 ...

  6. DBA 的效率加速器——CloudQuery v1.3.2 上线!

    嘿,兄弟,我们好久不见,你在哪里 嘿,朋友,如果真的是你,请打声招呼 我说好久不见,你去哪里 你却对我说,我去江湖 我去看 CloudQuery v1.3.2,看看新增了哪些好用的小功能! 一.自动/ ...

  7. Scrapy 项目:腾讯招聘

    目的: 通过爬取腾讯招聘网站(https://careers.tencent.com/search.html)练习Scrapy框架的使用 步骤: 1.通过抓包确认要抓取的内容是否在当前url地址中,测 ...

  8. Nginx(八): 观进程锁的实现

    前面的nginx系列讲解了nginx很多通用概念,流程,以及核心的http模块的一些实现.应该说大体上对nginx已经不再陌生和神秘. 今天我们不看全局,只看一个非常非常小的细节:nginx是多进程并 ...

  9. Mac创建Root用户

    1.打开Mac终端管理工具 前往-实用工具-终端 2.用命令的形式创建账户 sudo passwd root 3.输入当前登录用户密码 4.输入root用户密码并验证

  10. XAPKInstaller - XAPK游戏包安装器

    XAPKInstaller 一个用于安装XAPK游戏包的安装器. 程序需要读写存储与获取已安装应用权限才可正常运行. 长按条目可显示文件的详细信息. SDK小于24(Android N)的设备会显示应 ...