Spring5源码9-循环依赖源码分析

1. Spring手写循环依赖

1.1 循环依赖问题的出现

代码示例,A实例中有B

@Component
public class InstanceA {
    @Autowired
    private InstanceB instanceB;
    public InstanceB getInstanceB() {
            return instanceB;
    }
    public void setInstanceB(InstanceB instanceB) {
            this.instanceB = instanceB;
    }
    public InstanceA(InstanceB instanceB) {
            this.instanceB = instanceB;
    }
    public InstanceA() {
            System.out.println("InstanceA实例化");
    }
}
复制代码

B实例中有A

@Component
public class InstanceB  {
    @Autowired
    private InstanceA instanceA;
    public InstanceA getInstanceA() {
        return instanceA;
    }
    public void setInstanceA(InstanceA instanceA) {
        this.instanceA = instanceA;
    }
    public InstanceB(InstanceA instanceA) {
        this.instanceA = instanceA;
    }
    public InstanceB() {
        System.out.println("InstanceB实例化");
    }
}
复制代码

手写循环依赖

public class MainStart {
	// beanDefinition容器
	private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
	// 一级缓存:存放完整的Bean
	public static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
 
	/**
	 * 读取bean定义,当然在spring中肯定是根据配置 动态扫描注册
	 */
	public static void loadBeanDefinitions() {
		RootBeanDefinition beanDefinitionA = new RootBeanDefinition(InstanceA.class);
		RootBeanDefinition beanDefinitionB = new RootBeanDefinition(InstanceB.class);
		beanDefinitionMap.put("instanceA", beanDefinitionA);
		beanDefinitionMap.put("instanceB", beanDefinitionB);
	}
 
	public static void main(String[] args) throws Exception {
		// 加载了BeanDefinition
		loadBeanDefinitions();
		// 循环创建Bean
		for (String key : beanDefinitionMap.keySet()) {
			// 先创建A
			getBean(key);
		}
	}
 
	// 通过beanName获取Bean
	public static Object getBean(String beanName) throws Exception {
		// 开始创建Bean -> 实例化、属性赋值、初始化
		// 1-实例化
		RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
		Class<?> beanClass = beanDefinition.getBeanClass();
		Object instanceBean = beanClass.newInstance();  // 通过无参构造函数实例化Bean
		// 2-属性赋值
		Field[] declaredFields = beanClass.getDeclaredFields();// 获得某个类的所有声明的字段
		for (Field declaredField : declaredFields) {
			Autowired annotation = declaredField.getAnnotation(Autowired.class);
			// 如果属性上面有Autowired注解
			if (annotation != null) {
				declaredField.setAccessible(true); // 设置访问权限
				// instanceB -> byName/byType/byConstructor
				String name = declaredField.getName(); // 获取有Autowired注解的实例的名字,去实例化该Bean
				Object fileObject = getBean(name);   // 递归——>获取Bean
				declaredField.set(instanceBean, fileObject); // 设置属性值
			}
		}
		// 3-初始化 -> init-method,执行一些初始化回调方法,省略...
		// 4-把Bean添加到一级缓存
		singletonObjects.put(beanName, instanceBean);
		return instanceBean;
	}
}
复制代码

执行实例化A的创建过程,会出现循环依赖,导致栈溢出

循环依赖的图示

1.2 循环依赖问题的解决 -> 一级缓存

为了打破循环依赖,改动一下代码逻辑,在创建Bean前,先去一级缓存中去获取Bean实例,如果实例已经在缓存中,就不再循环的去创建。同样,为了能在循环之前拿到缓存中的Bean,需要在属性赋值前就把创建好的实例放入到缓存中(尽管此时的实例还没有赋值),图示如下

代码改动

public class MainStart {
	// beanDefinition容器
	private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
	// 一级缓存:存放完整的Bean
	public static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
 
	/**
	 * 读取bean定义,当然在spring中肯定是根据配置 动态扫描注册
	 */
	public static void loadBeanDefinitions() {
		RootBeanDefinition beanDefinitionA = new RootBeanDefinition(InstanceA.class);
		RootBeanDefinition beanDefinitionB = new RootBeanDefinition(InstanceB.class);
		beanDefinitionMap.put("instanceA", beanDefinitionA);
		beanDefinitionMap.put("instanceB", beanDefinitionB);
	}
 
	public static void main(String[] args) throws Exception {
		// 加载了BeanDefinition
		loadBeanDefinitions();
		// 注册Bean的后置处理器,省略...
		// 循环创建Bean
		for (String key : beanDefinitionMap.keySet()) {
			// 先创建A
			getBean(key);
		}
	}
 
	// 通过beanName获取Bean
	public static Object getBean(String beanName) throws Exception {
		// 增加出口:先从缓存中拿
		Object singleton = getSingleton(beanName);
		if (singleton != null) {
			return singleton;
		}
		// 开始创建Bean -> 实例化、属性赋值、初始化
		// 1-实例化
		RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
		Class<?> beanClass = beanDefinition.getBeanClass();
		Object instanceBean = beanClass.newInstance();  // 通过无参构造函数实例化Bean
 
		// 3-新增->把Bean添加到一级缓存
		// 问题:如果在此处put,可以解决循环依赖,但是多线程环境下会拿到没有属性赋值的不完整Bean
		// 除非对整个创建过程加锁->低效率
		singletonObjects.put(beanName, instanceBean);
 
		// 2-属性赋值
		Field[] declaredFields = beanClass.getDeclaredFields();// 获得某个类的所有声明的字段
		for (Field declaredField : declaredFields) {
			Autowired annotation = declaredField.getAnnotation(Autowired.class);
			// 如果属性上面有Autowired注解
			if (annotation != null) {
				declaredField.setAccessible(true); // 设置访问权限
				// instanceB -> byName/byType/byConstructor
				String name = declaredField.getName(); // 获取有Autowired注解的实例的名字,去实例化该Bean
				Object fileObject = getBean(name);   // 递归——>获取Bean
				declaredField.set(instanceBean, fileObject); // 设置属性值
			}
		}
		// 4-初始化 -> init-method,执行一些初始化回调方法,省略...
        // 5-再一次把Bean添加到一级缓存
		singletonObjects.put(beanName, instanceBean);
		return instanceBean;
	}
 
	// 从缓存中获取Bean
	public static Object getSingleton(String beanName) {
		// 从一级缓存中拿
		Object bean = singletonObjects.get(beanName);
		return bean;
	}
}
复制代码

测试结果

不过,这种在属性赋值前,就把Bean存放到一级缓存的做法,在多线程环境下,会使得某些线程获取的Bean,可能是不完整的Bean(实例还没有赋值)。

1.3 循环依赖问题的解决 -> 二级缓存

为了避免在多线程环境下,获取到不完整的Bean,引入二级缓存,示例图如下

代码改动如下,把原来未赋值的Bean改存到二级缓存中

public class MainStart {
	// beanDefinition容器
	private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
	// 一级缓存:存放完整的Bean
	public static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
	// 二级缓存:存放不完整的Bean(还没有属性赋值)——>区分完整的Bean和不完整的Bean
	public static Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
 
	/**
	 * 读取bean定义,当然在spring中肯定是根据配置 动态扫描注册
	 */
	public static void loadBeanDefinitions() {
		RootBeanDefinition beanDefinitionA = new RootBeanDefinition(InstanceA.class);
		RootBeanDefinition beanDefinitionB = new RootBeanDefinition(InstanceB.class);
		beanDefinitionMap.put("instanceA", beanDefinitionA);
		beanDefinitionMap.put("instanceB", beanDefinitionB);
	}
 
	public static void main(String[] args) throws Exception {
		// 加载了BeanDefinition
		loadBeanDefinitions();
		// 注册Bean的后置处理器
		// 循环创建Bean
		for (String key : beanDefinitionMap.keySet()) {
			// 先创建A
			getBean(key);
		}
	}
 
	// 通过beanName获取Bean
	public static Object getBean(String beanName) throws Exception {
		// 增加出口:先从缓存中拿
		Object singleton = getSingleton(beanName);
		if (singleton != null) {
			return singleton;
		}
		// 开始创建Bean -> 实例化、属性赋值、初始化
		// 1-实例化
		RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
		Class<?> beanClass = beanDefinition.getBeanClass();
		Object instanceBean = beanClass.newInstance();  // 通过无参构造函数实例化Bean
		// 添加到二级缓存
		earlySingletonObjects.put(beanName,instanceBean);
		// 2-属性赋值
		Field[] declaredFields = beanClass.getDeclaredFields();// 获得某个类的所有声明的字段
		for (Field declaredField : declaredFields) {
			Autowired annotation = declaredField.getAnnotation(Autowired.class);
			// 如果属性上面有Autowired注解
			if (annotation != null) {
				declaredField.setAccessible(true); // 设置访问权限
				// instanceB -> byName/byType/byConstructor
				String name = declaredField.getName(); // 获取有Autowired注解的实例的名字,去实例化该Bean
				Object fileObject = getBean(name);   // 递归——>获取Bean
				declaredField.set(instanceBean, fileObject); // 设置属性值
			}
		}
		// 3-初始化 -> init-method,执行一些初始化回调方法,省略...
		// 把Bean添加到一级缓存
		singletonObjects.put(beanName, instanceBean);
		return instanceBean;
	}
 
	// 从缓存中获取Bean
	public static Object getSingleton(String beanName) {
		// 先从一级缓存中拿
		Object bean = singletonObjects.get(beanName);
		// 一级缓存为Null,从二级缓存中获取
		if(bean == null){
			bean = earlySingletonObjects.get(beanName);
		}
		return bean;
	}
}
复制代码

从代码层级来说,解决循环依赖,我们使用二级缓存就可以了,但是为什么要有三级缓存呢?

1.4 循环依赖问题的解决(动态代理) -> 三级缓存

假设实例A需要创建动态代理,对实例A代码做如下改造:

// 接口
public interface IApi {
	void say();
}
 
// 实例A实现接口
@Component
public class InstanceA implements IApi {
    @Autowired
    private InstanceB instanceB;
    
    public InstanceB getInstanceB() {
            return instanceB;
    }
    public void setInstanceB(InstanceB instanceB) {
            this.instanceB = instanceB;
    }
    public InstanceA(InstanceB instanceB) {
            this.instanceB = instanceB;
    }
    public InstanceA() {
            System.out.println("InstanceA实例化");
    }
    @Override
    public void say() {
            System.out.println("I'm A");
    }
}
复制代码

动态代理类

public class JdkDynamicProxy implements InvocationHandler {
	private Object target;
	public JdkDynamicProxy(Object target) {
		this.target = target;
	}
	public <T> T getProxy() {
		return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		return method.invoke(target, args);
	}
}
复制代码

对实例A进行动态代理 -> 模拟AOP,通过后置处理器来进行动态代理

/**
 * 创建JDK动态代理
 */
@Component
public class JdkProxyBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor {
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        // 假设:A 被切点命中 需要创建代理  @PointCut("execution(* *..InstanceA.*(..))")
        if (bean instanceof InstanceA) {
                JdkDynamicProxy jdkDynamicProxy = new JdkDynamicProxy(bean);
                return jdkDynamicProxy.getProxy();
        }
        return bean;
    }
}
复制代码

一般来说,对单例Bean创建动态代理,都是在初始化之后,但是对于循环依赖来说,动态代理的创建需要在实例化之后就创建。比如本例中的Bean A,如果仍然选择在初始化后创建代理,那么实例Bean B中拿到的Bean A就不是代理对象。

如上图,在循环依赖中,如果把实例化后的Bean,创建动态代理后然后再放入二级缓存,此时,关于动态代理的循环依赖问题就已经解决了, 那么为什么还要三级缓存?

试想下,Spring中创建动态代理是通过后置处理器来实现的,如果在创建Bean的过程中,再另外去调用后置处理器创建代理,代码风格上会不会存在不合理?实例化后需要创建动态代理,初始化后又要创建动态代理,两个逻辑能不能合在一起,都放在初始化后创建呢?

答案是,能,可以借助三级缓存和函数式接口(延迟创建动态代理)来实现。

下边需要使用一个函数式接口(ObjectFactory)

@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;
}
复制代码

通过三级缓存来解决循环依赖,手写代码示例:

public class MainStart {
	// beanDefinition容器
	private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
	// 一级缓存:存放完整的Bean
	public static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
	// 二级缓存:存放不完整的Bean(还没有属性赋值)——>区分完整的Bean和不完整的Bean
	public static Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
	// 三级缓存 -> 存函数式接口(延迟调用)
	public static Map<String, ObjectFactory> singletonFactories = new ConcurrentHashMap<>();
	// 循环依赖标识 -> 正在创建的Bean集合
	public static Set<String> singletonsCurrentlyInCreation = new HashSet<>();
 
	/**
	 * 读取bean定义,当然在spring中肯定是根据配置 动态扫描注册
	 */
	public static void loadBeanDefinitions() {
		RootBeanDefinition beanDefinitionA = new RootBeanDefinition(InstanceA.class);
		RootBeanDefinition beanDefinitionB = new RootBeanDefinition(InstanceB.class);
		beanDefinitionMap.put("instanceA", beanDefinitionA);
		beanDefinitionMap.put("instanceB", beanDefinitionB);
	}
 
	public static void main(String[] args) throws Exception {
		// 加载了BeanDefinition
		loadBeanDefinitions();
		// 注册Bean的后置处理器...省略...
		// 循环创建Bean
		for (String key : beanDefinitionMap.keySet()) {
			// 先创建A
			getBean(key);
		}
		InstanceA instanceA = (InstanceA) getBean("instanceA");
		instanceA.say();
	}
 
	// 通过beanName获取Bean
	public static Object getBean(String beanName) throws Exception {
		// 增加出口:先从缓存中拿
		Object singleton = getSingleton(beanName);
		if (singleton != null) {
			return singleton;
		}
		// singletonsCurrentlyInCreation -> 正在创建的Bean集合
		if (!singletonsCurrentlyInCreation.contains(beanName)) {
			singletonsCurrentlyInCreation.add(beanName);
		}
		// 开始创建Bean -> 实例化、属性赋值、初始化
		// 1-实例化
		RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
		Class<?> beanClass = beanDefinition.getBeanClass();
		Object instanceBean = beanClass.newInstance();  // 通过无参构造函数实例化Bean
 
		// 假设A 使用了Aop @PointCut("execution(* *..InstanceA.*(..))") 要给A创建动态代理
		// 创建动态代理(BeanPostProcessor) -> 解耦合
		// 三级缓存:存在循环依赖的Bean在实例化后创建proxy(延迟),正常的Bean在初始化后创建 -> 需要判断当前是不是循环依赖
		if(singletonsCurrentlyInCreation.contains(beanName)){
			// 为了简便,先把循环依赖的对象放入二级缓存(源码中没有此步骤)
			earlySingletonObjects.put(beanName, instanceBean);
			singletonFactories.put(beanName, () -> new JdkProxyBeanPostProcessor().getEarlyBeanReference(earlySingletonObjects.get(beanName), beanName));
		}
 
		// 2-属性赋值
		Field[] declaredFields = beanClass.getDeclaredFields();// 获得某个类的所有声明的字段
		for (Field declaredField : declaredFields) {
			Autowired annotation = declaredField.getAnnotation(Autowired.class);
			// 如果属性上面有Autowired注解
			if (annotation != null) {
				declaredField.setAccessible(true); // 设置访问权限
				// instanceB -> byName/byType/byConstructor
				String name = declaredField.getName(); // 获取有Autowired注解的实例的名字,去实例化该Bean
				Object fileObject = getBean(name);   // 递归——>获取Bean
				declaredField.set(instanceBean, fileObject); // 设置属性值
			}
		}
		// 3-初始化 -> init-method,执行一些初始化回调方法,省略...
 
		// AOP -> 正常情况下会在初始化之后创建proxy,但是如果是循环依赖, B里面的A就不是proxy
 
		// 由于递归完后A还是原实例,所以要从二级缓存中拿到proxy
		if (earlySingletonObjects.containsKey(beanName)) {
			instanceBean = earlySingletonObjects.get(beanName);
		}
		// 把Bean添加到一级缓存,移除二、三级缓存
		singletonObjects.put(beanName, instanceBean);
		singletonFactories.remove(beanName);
		earlySingletonObjects.remove(beanName);
		return instanceBean;
	}
 
	// 从缓存中获取Bean
	public static Object getSingleton(String beanName) {
		// 先从一级缓存中拿
		Object bean = singletonObjects.get(beanName);
		// 一级缓存没有,说明是循环依赖的Bean
		if (bean == null && singletonsCurrentlyInCreation.contains(beanName)) {
			bean = earlySingletonObjects.get(beanName);
			// 如果二级缓存没有,尝试从三级缓存获取
			if (bean == null) {
				// 从三级缓存中拿到函数式接口——>然后执行
				ObjectFactory factory = singletonFactories.get(beanName);
				if(factory != null){
					// 返回此工厂管理的对象的实例->拿到动态代理(在这里真正创建,执行代理)
					bean = factory.getObject();
					// 放入二级缓存
					earlySingletonObjects.put(beanName, bean);
					// 移除三级缓存,步骤省略...
					singletonFactories.remove(beanName);
				}
			}
		}
		return bean;
	}
}
复制代码

运行结果:

三级缓存,实际上缓存的是函数式接口。最终的图示如下

2. Spring循环依赖源码分析

从创建Bean开始,我们看一下Spring源码的处理过程,首先从缓存中去获取Bean

继续往下看,如果没有获取到,那么就去创建Bean

注意这里有两个方法:

  • getSingleton(String beanName, ObjectFactory<?> singletonFactory);
  • createBean(beanName, mbd, args); // 真正实例化Bean

其中 createBean(beanName, mbd, args) 会在 getSingleton(String beanName, ObjectFactory<?> singletonFactory) 方法中被回调

接下来,进入getSingleton(String beanName, ObjectFactory<?> singletonFactory) 方法

该方法中,首先会为正在创建的Bean添加到一个标记集合:singletonsCurrentlyInCreation.add(beanName)

protected void beforeSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
    }
}
复制代码

然后,会调用创建Bean的函数式接口,真正的创建Bean,最后,把完整的Bean,放入到一级缓存当中

addSingleton(String beanName, Object singletonObject) 详情:

那么,createBean(beanName, mbd, args) 中具体又做了什么呢?

进入createBean(beanName, mbd, args) 方法中,有一个 doCreateBean(beanName, mbdToUse, args) 方法

进入doCreateBean(beanName, mbdToUse, args) 方法,实例化后,如果是早期对象,那么会放入到三级缓存

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);
      }
   }
}
复制代码

最后,初始化后的早期对象实例,仍然会从缓存中获取:

其中,解决循环依赖的核心就是这个方法:

getSingleton(String beanName, boolean allowEarlyReference)

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   // Quick check for existing instance without full singleton lock
   // 检查单例缓存池 中有没有
   Object singletonObject = this.singletonObjects.get(beanName);  // 以及缓存
   // 如果当前bean正在创建中,而且缓存中没有则继续
   // 单例缓存中没有对象 && 当前单例bean正在创建中,这是为了解决循环依赖的问题
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      // 如果此时bean正在加载(bean 在 earlySingletonObjects 中),则直接将singletonObject 返回。
      singletonObject = this.earlySingletonObjects.get(beanName); // 二级缓存
      // allowEarlyReference = true 才会允许循环依赖
      if (singletonObject == null && allowEarlyReference) {
         // 如果单例缓存中不存在该bean,则加锁进行接下来的处理
         synchronized (this.singletonObjects) {
            // Consistent creation of early reference within full singleton lock
            singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
               singletonObject = this.earlySingletonObjects.get(beanName);
               if (singletonObject == null) {
                  // 当某些方法需要提前初始化的时候则会调用addSingletonFactory
                  // 将对应的ObjectFactory初始化策略存储在singletonFactories中
                  ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);  // 三级缓存
                  if (singletonFactory != null) {
                     // 调用预先设定的getObject方法
                     singletonObject = singletonFactory.getObject();
                     // 记录在缓存中,earlySingletonObjects 和 singletonFactories互斥
                     this.earlySingletonObjects.put(beanName, singletonObject);
                     this.singletonFactories.remove(beanName);
                  }
               }
            }
         }
      }
   }
   return singletonObject;
}
复制代码

正式调用三级缓存中的函数式接口,生成代理对象存入到二级缓存,最后获得早期对象返回去创建其他的Bean,然后经过一系列的循环创建,最终生成完整的实例对象,存入一级缓存。至此,循环依赖解决完毕。

3. Spring 有没有解决构造函数的循环依赖和多实例的循环依赖?

Spring 无法解决构造函数的循环依赖,因为构造函数的循环依赖出现在实例创建之前,而Spring的循环依赖是通过实例的缓存来解决的,前提是需要有实例。

同样,Spring也无法解决多例Bean的循环依赖,当多例Bean存在循环依赖时,直接抛出异常

原因是,多例 Bean 创建时,根本不会把创建完的实例存放到缓存当中,对比单例的创建过程,多例Bean并没有被 getSingleton(String beanName, ObjectFactory<?> singletonFactory) 回调

而且,也不满足解决循环依赖的单例Bean的条件:


 

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340