本文简要介绍了循环依赖以及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. taro 进阶指南

    taro 进阶指南 配置 https://nervjs.github.io/taro/docs/config.html https://nervjs.github.io/taro/docs/confi ...

  2. Spring 中的 MetaData 接口

    什么是元数据(MetaData) 先直接贴一个英文解释: Metadata is simply data about data. It means it is a description and co ...

  3. c/c++ 之静态库

    静态库 编译成目标文件(未链接) g++ -c a.cc b.cc c.cc d.cc #生成 a.o b.o c.o d.o 将目标文件打包为静态库 ar rs libxxx.a a.o b.o c ...

  4. JS判断对象是否包含某个属性

    1.使用hasOwnProperty()判断 hasOwnProperty方法的参数就是要判断的属性名称,当对象的属性存在时返回true,否则返回false. var obj = { name:'ja ...

  5. WPF -- DataTemplate与ControlTemplate结合使用

    如深入浅出WPF中的描述,DataTemplate为数据的外衣,ControlTemplate为控件的外衣.ControlTemplate控制控件的样式,DataTemplate控制数据显示的样式,D ...

  6. HTTP 请求URL中不能含有空格

    如果含有空格 会报 不合法参数异常 正确做法是将其encode URLEncoder.encode(targetString, "utf-8").replaceAll(" ...

  7. Kubernetes-7.Ingress

    docker version:20.10.2 kubernetes version:1.20.1 本文概述Kubernetes Ingress基本原理和官方维护的Nginx-Ingress的基本安装使 ...

  8. Redis缓存穿透、缓存雪崩、缓存击穿好好说说

    前言 Redis是目前非常流行的缓存数据库啦,其中一个主要作用就是为了避免大量请求直接打到数据库,以此来缓解数据库服务器压力:用上缓存难道就高枕无忧了吗?no,no,no,没有这么完美的技术, 缓存穿 ...

  9. 7.vue组件(二)--双向绑定,父子组件访问

    本文主要说两件事 1. 如何实现父子组件之间的双向绑定 2. 父组件如何访问子组件的data,method, 子组件如何访问父组件的data,method等 一. 如何实现父子组件之间的双向绑定 案例 ...

  10. selenium之元素定位的方法(二)

    XPath定位是XML Path的缩写,称为XML路径语言,是在XML文档中查找信息的一种语言,可用来再XML文档中对元素和属性进行搜索.XPath使用路径表达式来选取XML文档中的节点或节点集. X ...