Spring的循环依赖
本文简要介绍了循环依赖以及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。其具体的加载过程为:
首先触发getBean() 方法对A进行初始化;
然后走到方法①,此时三级缓存都为空,获取不到实例;
走到方法②,其中的步骤B触发对方法③的调用,即是对doCreateBean() 方法的调用;
在方法③中,先通过createBeanInstance实例化A对象,然后将该实例通过addSingletonFactory放入singletonFactories中,完成A对象的早期暴露;
在方法③中,通过populateBean() 方法对A对象的属性进行注入,发现它有一个B属性,则触发getBean() 对B进行初始化(递归);
重复步骤3、4、5,只是此时要初始化的是B对象;
再次走到步骤5时,调用populateBean() 方法对B对象进行属性注入,发现它有一个A属性,则触发getBean() 对A进行初始化;
对A进行初始化,又来到步骤2,此时第三级缓存中已经有了A的信息了,因为在步骤4时,就已经将A实例放入到了singletonFacotries(第三级缓存中)了,此时可直接得到A的实例并返回;
完成对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的循环依赖的更多相关文章
- Spring的循环依赖问题
spring容器循环依赖包括构造器循环依赖和setter循环依赖,那Spring容器如何解决循环依赖呢?首先让我们来定义循环引用类: 在Spring中将循环依赖的处理分成了3种情况: 构造器循环依赖 ...
- Spring之循环依赖
转:http://my.oschina.net/tryUcatchUfinallyU/blog/287936 概述 如何检测循环依赖 循环依赖如何解决 Spring如何解决循环依赖 主要的几个缓存 主 ...
- 再谈spring的循环依赖是怎么造成的?
老生常谈,循环依赖!顾名思义嘛,就是你依赖我,我依赖你,然后就造成了循环依赖了!由于A中注入B,B中注入A导致的吗? 看起来没毛病,然而,却没有说清楚问题!甚至会让你觉得你是不清楚spring的循环依 ...
- Spring解决循环依赖
1.Spring解决循环依赖 什么是循环依赖:比如A引用B,B引用C,C引用A,它们最终形成一个依赖环. 循环依赖有两种 1.构造器循环依赖 构造器注入导致的循环依赖,Spring是无法解决的,只能抛 ...
- Spring当中循环依赖很少有人讲,今天一起来学习!
网上关于Spring循环依赖的博客太多了,有很多都分析的很深入,写的很用心,甚至还画了时序图.流程图帮助读者理解,我看了后,感觉自己是懂了,但是闭上眼睛,总觉得还没有完全理解,总觉得还有一两个坎过不去 ...
- Spring的循环依赖,学就完事了【附源码】
目录 啥是循环依赖? Spring可以解决循环依赖的条件 Spring如何去解决循环依赖 SpringBean的创建流程 Spring维护的三级缓存 getSingleton getSingleton ...
- Spring 的循环依赖问题
什么是循环依赖 什么是循环依赖呢?可以把它拆分成循环和依赖两个部分来看,循环是指计算机领域中的循环,执行流程形成闭合回路:依赖就是完成这个动作的前提准备条件,和我们平常说的依赖大体上含义一致.放到 S ...
- 详解Spring DI循环依赖实现机制
一个对象引用另一个对象递归注入属性即可实现后续的实例化,同时如果两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环即所谓的循环依赖怎么实现呢属性的互相注入呢? Spring bean生命周期具体 ...
- 【spring源码分析】spring关于循环依赖的问题
引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错.下面说一下Spring是如果解决循环依赖的. 第一种: ...
随机推荐
- React PureComponent All In One
React PureComponent All In One import React, { // useState, // useEffect, // Component, PureComponen ...
- 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 ...
- navigator.geolocation.getCurrentPosition
navigator.geolocation.getCurrentPosition Geolocation API Specification 2nd Edition W3C Recommendatio ...
- NGK官方又出助力市场计划方案 1万枚VAST任性送
近期NGK官方的一系列动作,可以说是在向外界宣告:NGK2.0即将来袭,席卷加密数字货币市场.前一段时间,NGK官方宣布,NGK公链布局算力领域,打造NGK算力生态星空计划,并推出了SPC星空币.目前 ...
- 为什么说NGK公链的商用落地是可行的?
互联网.大数据以及云计算的发展给人们的生活.工作带来了诸多便利,也让人们一次又一次感叹科技的进步.而NGK公链的诞生,更是让众人称之为传奇.其商用落地可行性,也让人惊叹.那么,为什么说NGK公链的商用 ...
- DBA 的效率加速器——CloudQuery v1.3.2 上线!
嘿,兄弟,我们好久不见,你在哪里 嘿,朋友,如果真的是你,请打声招呼 我说好久不见,你去哪里 你却对我说,我去江湖 我去看 CloudQuery v1.3.2,看看新增了哪些好用的小功能! 一.自动/ ...
- Scrapy 项目:腾讯招聘
目的: 通过爬取腾讯招聘网站(https://careers.tencent.com/search.html)练习Scrapy框架的使用 步骤: 1.通过抓包确认要抓取的内容是否在当前url地址中,测 ...
- Nginx(八): 观进程锁的实现
前面的nginx系列讲解了nginx很多通用概念,流程,以及核心的http模块的一些实现.应该说大体上对nginx已经不再陌生和神秘. 今天我们不看全局,只看一个非常非常小的细节:nginx是多进程并 ...
- Mac创建Root用户
1.打开Mac终端管理工具 前往-实用工具-终端 2.用命令的形式创建账户 sudo passwd root 3.输入当前登录用户密码 4.输入root用户密码并验证
- XAPKInstaller - XAPK游戏包安装器
XAPKInstaller 一个用于安装XAPK游戏包的安装器. 程序需要读写存储与获取已安装应用权限才可正常运行. 长按条目可显示文件的详细信息. SDK小于24(Android N)的设备会显示应 ...