文章目录

  • 一、什么是循环依赖
    • 1、代码实例
    • 2、重要信息
  • 二、源码分析
    • 1、初始化Student
      • 对Student中的ClassRoom进行Autowire操作
    • 2、Student的自动注入ClassRoom时,又对ClassRoom的初始化
    • 3、ClassRoom的初始化,又执行自动注入Student的逻辑
    • 4、Student注入ClassRoom
    • 5、初始化完成的Bean放入一级缓存
  • 三、总结
    • 1、二级缓存的用处
    • 2、汇总流程图!
  • 参考资料

一、什么是循环依赖

多个bean之间相互依赖,形成了一个闭环。 比如:A依赖于B、B依赖于C、C依赖于A。

注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。

1、代码实例

public class ClassRoom {private String name;@Autowiredprivate Collection<Student> students;// get set
}public class Student {private Long id;private String name;@Autowiredprivate ClassRoom classRoom;// get set
}
public class CircularReferencesDemo {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();// 注册 Configuration Classcontext.register(CircularReferencesDemo.class);// 如果设置为 false,则抛出异常信息如:currently in creation: Is there an unresolvable circular reference?// 默认值为 truecontext.setAllowCircularReferences(true);// 启动 Spring 应用上下文context.refresh();System.out.println("Student : " + context.getBean(Student.class));System.out.println("ClassRoom : " + context.getBean(ClassRoom.class));// 关闭 Spring 应用上下文context.close();}@Beanpublic static Student student() {Student student = new Student();student.setId(1L);student.setName("张三");return student;}@Beanpublic static ClassRoom classRoom() {ClassRoom classRoom = new ClassRoom();classRoom.setName("C122");return classRoom;}
}

我们看到以上实例ClassRoom和Student是互相依赖的。

Spring默认是打开处理循环依赖的开关的,我们可以使用setAllowCircularReferences方法手动设置关闭循环依赖。

开启解决循环依赖之后,以上代码是可以执行没有问题的。关闭循环依赖,以上就会出现循环依赖的异常,如果确认项目中没有循环依赖的问题可以关闭,以提高性能。

2、重要信息

本文的源码都是基于Spring5.2.2版本进行分析的,其他版本或许有些许出入,但是差别不大。

本文所说的循环引用,都是指的是单例Bean,原型Bean不在此分析中。

阅读本文需要明白一些前置知识点:
(1)单例的Bean初始化时机-finishBeanFactoryInitialization方法执行时:
Spring应用上下文生命周期详解及源码分析,Spring IOC容器启动及关闭过程超详细解释(上)
(2)Bean的生命周期需要熟悉:
Spring Bean生命周期——从源码角度详解Spring Bean的生命周期(上)
Spring Bean生命周期——从源码角度详解Spring Bean的生命周期(下)
(3)@Autowire注入流程
@Autowire源码分析,@Autowire是怎么实现依赖注入的

二、源码分析

我们先从IOC容器启动时执行的finishBeanFactoryInitialization方法开始入口,此方法是单例Bean的创建入口,然后执行preInstantiateSingletons方法正是开始创建单例Bean。

// org.springframework.context.support.AbstractApplicationContext#refresh
@Override
public void refresh() throws BeansException, IllegalStateException {// 省略上面代码// Instantiate all remaining (non-lazy-init) singletons.// 关键方法!完成BeanFactory的初始化finishBeanFactoryInitialization(beanFactory); // 省略下面代码
// org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {// 省略上面代码// Instantiate all remaining (non-lazy-init) singletons.// 关键方法!初始化单例的BeanbeanFactory.preInstantiateSingletons();
}

preInstantiateSingletons方法在DefaultListableBeanFactory中进行了实现。

// org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
@Override
public void preInstantiateSingletons() throws BeansException {// Trigger initialization of all non-lazy singleton beans...for (String beanName : beanNames) {RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {if (isFactoryBean(beanName)) {Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);if (bean instanceof FactoryBean) {final FactoryBean<?> factory = (FactoryBean<?>) bean;boolean isEagerInit;if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)((SmartFactoryBean<?>) factory)::isEagerInit,getAccessControlContext());}else {isEagerInit = (factory instanceof SmartFactoryBean &&((SmartFactoryBean<?>) factory).isEagerInit());}if (isEagerInit) {getBean(beanName);}}}else { // 关键方法!获取Bean的入口getBean(beanName);}}}// 省略下面代码

1、初始化Student

第一个首先是初始化我们的Student类,getBean会调用doGetBean,然后调用getSingleton方法(重点!解决循环依赖最重要的三级缓存):

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName); // 一级缓存if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName); // 二级缓存if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); // 三级缓存if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;
}

第一次调用getSingleton方法不会获取到Bean,获取的是null,走下面的单例bean创建逻辑:

// org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean// 省略上面代码
// Create bean instance.
if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args); // 关键方法!创建Bean,开始正式进入Bena的生命周期}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.destroySingleton(beanName);throw ex;}});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}// 省略下面代码

createBean方法会调用doCreateBean方法,doCreateBean方法会创建bean的实例,然后完成属性的初始化(@Autowire还未开始),然后会判断如果该Bean正在创建,就会调用addSingletonFactory方法:

// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean// 省略上面创建bean的实例,然后完成属性的初始化(@Autowire还未开始)的代码
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {if (logger.isTraceEnabled()) {logger.trace("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references");}addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}// 省略下面@Autowire的代码

我们会发现,此处对allowCircularReferences 进行了判断,假如说我们打开了循环依赖的开关,会走这个逻辑。

addSingletonFactory方法,通过传入的单例对象工厂,

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory
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); // 放入正在注册的单例Bean列表}}
}

我们注意到此时Student中的classRoom并没有被注入,但是id和name属性已经被初始化了。

对Student中的ClassRoom进行Autowire操作

我们回到doCreateBean方法,接下来会执行populateBean初始化属性值,会调用@Autowire的处理类AutowiredAnnotationBeanPostProcessor的postProcessProperties方法进行处理@Autowire注入。

// populateBean方法,此处省略部分代码PropertyDescriptor[] filteredPds = null;
if (hasInstAwareBpps) {if (pvs == null) {pvs = mbd.getPropertyValues();}for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;// 关键方法!最终调用AutowiredAnnotationBeanPostProcessor的postProcessPropertiesPropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {return;}}pvs = pvsToUse;}}
}
// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);try {metadata.inject(bean, beanName, pvs); // 关键方法}catch (BeanCreationException ex) {throw ex;}catch (Throwable ex) {throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);}return pvs;
}
// org.springframework.beans.factory.annotation.InjectionMetadata#inject
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Collection<InjectedElement> checkedElements = this.checkedElements;Collection<InjectedElement> elementsToIterate =(checkedElements != null ? checkedElements : this.injectedElements);if (!elementsToIterate.isEmpty()) {for (InjectedElement element : elementsToIterate) {if (logger.isTraceEnabled()) {logger.trace("Processing injected element of bean '" + beanName + "': " + element);}element.inject(target, beanName, pvs); // 关键方法}}
}

inject方法此处需要自动注入的是属性,需要获取该属性的value值也就是ClassRoom(此时ClassRoom尚未初始化)。

// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
// 此处省略部分源码
try {// value就是需要获取的ClassRoomvalue = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
// org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency
@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());if (Optional.class == descriptor.getDependencyType()) {return createOptionalDependency(descriptor, requestingBeanName);}else if (ObjectFactory.class == descriptor.getDependencyType() ||ObjectProvider.class == descriptor.getDependencyType()) {return new DependencyObjectProvider(descriptor, requestingBeanName);}else if (javaxInjectProviderClass == descriptor.getDependencyType()) {return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);}else {Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);if (result == null) {// 关键代码!会走该逻辑result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);}return result;}
}
// doResolveDependency方法,省略部分源码,会调用resolveCandidate
if (instanceCandidate instanceof Class) {instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
}

resolveCandidate会执行beanFactory.getBean方法,又开始了对ClassRoom的getBean操作,开始处理ClassRoom。

//
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)throws BeansException {return beanFactory.getBean(beanName);
}

2、Student的自动注入ClassRoom时,又对ClassRoom的初始化

我们跳过上面的分析,来到addSingletonFactory方法:

我们发现,又将ClassRoom放入了三级缓存,此时三级缓存中有了Student和ClassRoom,但是都没有完成对ClassRoom和Student的自动注入工作。

3、ClassRoom的初始化,又执行自动注入Student的逻辑

此时,我们来到ClassRoom的populateBean属性初始化方法,仍然调用AutowiredAnnotationBeanPostProcessor的postProcessProperties方法,来完成对Student的自动注入。

省略我们分析Student的自动注入步骤,我们继续分析,自动注入的过程仍然会调用resolveDependency方法,然后调用doResolveDependency方法,然后调用resolveCandidate方法,又通过beanFactory.getBean来获取Student。

还记得我们上面分析的,doGetBean方法中调用的getSingleton方法的逻辑,此时从三级缓存中已经能够获取到Student了,但是该Student的ClassRoom仍然是null的,但是能够获取到Studen实例,并不影响我们ClassRoom对Student的注入!

// org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {final String beanName = transformedBeanName(name);Object bean;// Eagerly check singleton cache for manually registered singletons.// 关键方法!Object sharedInstance = getSingleton(beanName);if (sharedInstance != null && args == null) {if (logger.isTraceEnabled()) {if (isSingletonCurrentlyInCreation(beanName)) {logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +"' that is not fully initialized yet - a consequence of a circular reference");}else {logger.trace("Returning cached instance of singleton bean '" + beanName + "'");}}bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);}

从三级缓存中获取到Student后,将Student从三级缓存移出,放入二级缓存。


此时,在ClassRoom的初始化自动注入Student的过程,获取到了我们的Student(还未注入CLassRoom),在Inject方法中,继续执行会发现通过反射,将Student写入了ClassRoom的属性。

// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Field field = (Field) this.member;Object value;if (this.cached) {value = resolvedCachedArgument(beanName, this.cachedFieldValue);}else {DependencyDescriptor desc = new DependencyDescriptor(field, this.required);desc.setContainingClass(bean.getClass());Set<String> autowiredBeanNames = new LinkedHashSet<>(1);Assert.state(beanFactory != null, "No BeanFactory available");TypeConverter typeConverter = beanFactory.getTypeConverter();try {value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);}catch (BeansException ex) {throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);}synchronized (this) {if (!this.cached) {if (value != null || this.required) {this.cachedFieldValue = desc;registerDependentBeans(beanName, autowiredBeanNames);if (autowiredBeanNames.size() == 1) {String autowiredBeanName = autowiredBeanNames.iterator().next();if (beanFactory.containsBean(autowiredBeanName) &&beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {this.cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName, field.getType());}}}else {this.cachedFieldValue = null;}this.cached = true;}}}if (value != null) {ReflectionUtils.makeAccessible(field);field.set(bean, value); // 通过反射写入属性}
}

此时完成了ClassRoom的初始化。

4、Student注入ClassRoom

此时ClassRoom在Student初始化过程,完成了初始化并注入Student实例,此时的Student还没有完成对ClassRoom的注入呢!

我们又回到Student的inject方法了(此时相当于是一个递归的过程)。

对ClassRoom的create的过程,会返回创建好的ClassRoom实例(初始化完成也注入完成),正好将ClassRoom注入到Student中。

此时完成对Student和ClassRoom的初始化过程。

也正式解决了Student和ClassRoom的循环依赖问题。

5、初始化完成的Bean放入一级缓存

我们再次回到doGetBean方法,这里调用了getSingleton方法,getSingleton方法最后面的finally中会调用addSingleton方法。

// Create bean instance.
if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.destroySingleton(beanName);throw ex;}});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

addSingleton方法会将Bean放入一级缓存,同时删除二级缓存和三级缓存中的Bean,此时Bean初始化完成,同时也缓存完成,下次获取Bean直接从一级缓存获取即可,提高性能。

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton
protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {this.singletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}
}

关于Bean的缓存请移步:
Spring中Bean会被缓存吗?Spring的Bean是如何被缓存的?

三、总结

Spring解决循环依赖,正式靠着这三级缓存完成的,相当于一个递归初始化的过程:

在 Spring 底层 IoC 容器 BeanFactory 中处理循环依赖的方法主要借助于以下 3 个 Map 集合:

  1. singletonObjects(一级 Map),里面保存了所有已经初始化好的单例 Bean
  2. earlySingletonObjects(二级 Map),里面会保存从 三级 Map 获取到的正在初始化的 Bean,保存的同时会移除 三级 Map 中对应的 ObjectFactory 实现类,在完全初始化好某个 Bean 时会移除 二级 Map中对应的早期对象
  3. singletonFactories(三级 Map),里面保存了正在初始化的 Bean 对应的 ObjectFactory 实现类,调用其 getObject() 方法返回正在初始化的 Bean 对象(仅实例化还没完全初始化好)

而这三个缓存,其实也就是人们称的三级缓存,其实严格意义上并不能称为“缓存”,这三个缓存其实都是Map:

1、二级缓存的用处

此处解决循环依赖似乎并没有用到二级缓存,那么二级缓存是哪里用到的呢?

因为通过 三级 Map获取 Bean 会有相关 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference 的处理,避免重复处理。
例如在循环依赖中一个 Bean 可能被多个 Bean 依赖, A -> B(也依赖 A) -> C -> A,当你获取 A 这个 Bean 时,后续 B 和 C 都要注入 A,没有上面的 二级 Map的话,三级 Map 保存的 ObjectFactory 实现类会被调用两次,会重复处理,可能出现问题。这就是为什么需要 3 个 Map,另外这样做在性能上也有所提升 。

也是为了处理AOP动态代理的问题,也是一个对象被多个对象重复依赖,导致重复创建的问题:
假如 A 需要进行 AOP,因为代理对象每次都是生成不同的对象,如果干掉第二级缓存,只有第一、三级缓存:
B 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A1。
C 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A2。
看到问题没?你通过 A 的工厂的代理对象,生成了两个不同的对象 A1 和 A2,所以为了避免这种问题的出现,我们搞个二级缓存,把 A1 存下来,下次再获取时,直接从二级缓存获取,无需再生成新的代理对象。

2、汇总流程图!

参考资料

spring如何解决循环依赖
源码深度解析,Spring 如何解决循环依赖?
极客时间《小马哥讲 Spring 核心编程思想》

Spring循环依赖问题,Spring是如何解决循环依赖的?相关推荐

  1. Java头文件找出循环依赖_Node.js 如何找出循环依赖的文件?如何解决循环依赖问题?...

    本文重点是讲解如何解决循环依赖这个问题.关心这个问题是如何产生的,可以自行谷歌. 如何重现这个问题 // a.js const {sayB} = require('./b.js') sayB() fu ...

  2. 又出现依赖冲突?试试 IDEA 解决 Maven 依赖冲突的高能神器!

    以下文章来源方志朋的博客,回复"666"获面试宝典 1.何为依赖冲突 Maven是个很好用的依赖管理工具,但是再好的东西也不是完美的.Maven的依赖机制会导致Jar包的冲突.举个 ...

  3. python 引用计数 循环引用_引用计数无法解决循环引用,CPython为何还使用它?

    取消excel表中的循环引用的步2113骤如下:1.打开一个5261Excel文件.打开之后,4102在操作表中的单元格时,1653出现了循环引用警告.2.点击左上角的 文件 菜单,在出现的菜单中点击 ...

  4. 京东一面:Spring 为何需要三级缓存解决循环依赖,而不是二级缓存?我懵了。。...

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:cnblogs.com/semi-sub/p/13548479.html 前言 bean生命周期 三级缓存解决循环依赖 总结 ...

  5. Spring IOC 如何解决循环依赖?

    前言 假设对象A.B 之间相互依赖,Spring IOC是如何解决A.B两个对象的实例化的?答案是三级缓存. 三级缓存 SpringIOC 通过三级缓存来解决循环依赖问题,三级缓存指的是三个Map: ...

  6. 为什么Spring需要三级缓存解决循环依赖,而不是二级缓存?

    来源:https://www.cnblogs.com/semi-sub/p/13548479.html 在使用spring框架的日常开发中,bean之间的循环依赖太频繁了,spring已经帮我们去解决 ...

  7. 框架源码专题:Spring是如何解决循环依赖的?

    文章目录 1.什么是循环依赖? 2.解决循环依赖思路 3. 使用了三级缓存还有什么问题?怎么解决的? 4. 手写伪代码解决缓存依赖 5. 二级缓存能否解决循环依赖,三级缓存存在的意义 6. Sprin ...

  8. 人人都能看懂的Spring源码解析,Spring如何解决循环依赖

    人人都能看懂的Spring源码解析,Spring如何解决循环依赖 原理解析 什么是循环依赖 循环依赖会有什么问题? 如何解决循环依赖 问题的根本原因 如何解决 为什么需要三级缓存? Spring的三级 ...

  9. what?spring已经解决循环依赖了,为啥还报循环依赖错误?

    前言 spring中的循环依赖及三层map解决方案,八股文中重灾区,强如小伙伴们或许一字一句倒背如流了,当年的我也是如此,然而现实狠狠给了我一巴掌,报错虽迟但到 报错一: The dependenci ...

  10. Spring 如何解决循环依赖的问题

    (一)Spring  IOC容器---对象循环依赖 1. 什么是循环依赖?  what? (1)循环依赖-->循环引用.--->即2个或以上bean 互相持有对方,最终形成闭环. eg:A ...

最新文章

  1. Amber16和AmberTools16在CentOS 7下GPU加速版的安装
  2. [内部项目]i前端如何增加一个页面
  3. linux c select 设置超时
  4. python画曲线图例-Python数据可视化之Matplotlib(折线图)
  5. 手机黑圆点怎么打_黑鲨游戏手机3S手机配置怎么样,是否值得购买?
  6. 中考计算机考试作文,中考理化实验计算机考试作文
  7. SAP Spartacus org unit list点击item之后的页面跳转实现
  8. Linux系统自动备份脚本,供参考的Linux系统中自动执行分段备份脚本
  9. mysql将查到的数据删除_MySQL数据库的基本操作——增、删、改、查
  10. sqlserver有外键无法创建触发器_数据库不使用外键的 9 个理由
  11. ajax请求成功后打开新开窗口(window.open())被拦截的解决方法
  12. 华为星环大数据_华为和星环大数据平台关键能力对比(附报告)
  13. matlab dynprog,matlab信号处理工具箱
  14. WinCC 扇形旋转制作
  15. Excel数据分析和建模
  16. ceph osd学习小结
  17. 百度地图 截图java_我从百度地图静态图API中通过url获取到的图片,用java有没有什么办法可以把图片上的百度logo去掉呢...
  18. 招聘网站代码模板 mysql_招聘网站爬虫模板
  19. HiveHive的两种访问方式
  20. 使用完整拼音查找汉字(完整拼音,不是网上散布的首字符拼音那种方法)

热门文章

  1. 【MySQL开启密码复杂度】
  2. boris fx 教程_通过编程将金融工具分散化:Dfinance的Boris Povod访谈
  3. BIOS追code之DXE phase
  4. Mycat分库分表原理
  5. Rock PI 4B Plus(Linux Ubuntu20.04 .4LTS) 安装anaconda教程
  6. 地理大圆距离 C语言,通过经纬度计算两点之间的距离
  7. 基于STM32的常用数码管芯片TM1637驱动
  8. crt1.o, crti.o, crtbegin.o, crtend.o, crtn.o
  9. php如何开发调色器,PHP imagecreate - 新建一个基于调色板的图像
  10. 人工智能数学基础--概率与统计3:随机变量与概率分布