一文详解Spring Bean循环依赖

一、背景

有好几次线上发布老应用时,遭遇代码启动报错,具体错误如下:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'xxxManageFacadeImpl': Bean with name 'xxxManageFacadeImpl'  has been injected into other beans [xxxProductMaintenceFacadeImpl] in its raw version as part of a circular reference, but has eventually been wrap means thff, for expped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesallowEageOfType' with the 'allowEagerInit' flag turned off, for example

眨眼一看,这不就是Spring Bean循环依赖报错吗?脑海立马闪过那些年为了进阿里面试时被死亡N连问的场景,那时我们都知道Spring已经支持bean循环依赖,为啥我们的Springboot应用启动时还报这个错误?带着这个问题于是要重新温习下Spring如何解决bean循环依赖。

二、相关知识点简介

2.1、什么是Bean循环依赖?

循环依赖是指Bean对象循环引用,是两个或多个Bean之间相互持有对方的引用。循环依赖有2种表现形式:

第一种是相互依赖,也就是A依赖B,B又依赖A;

图片

图一 相互依赖示例图

第二种是自我依赖,也就是A依赖自己形成自我依赖。

图片

图二 自我依赖示例图

对象引用循环依赖在某些业务场景上可能是合理存在的,但是由于Spring容器设计了依赖注入机制,即Spring容器在创建bean实例化以后就要给bean中的属性自动赋值,要全部自动赋值之后,才能交给用户使用。如果出现循环依赖的情况,以两个bean互相依赖的情况作为举例,假设有AService已经实例化(但未完成初始化),但是AService中需要自动赋值的BService并没有初始化,如果Spring立刻初始化BService,发现BService中需要自动赋值AService也没有初始化完成,这样就会出现相互等待,形成死循环,可能导致Spring容器都无法启动了。

由此可见,对Bean的填充属性是循环依赖源头的开始。

2.2、Spring创建Bean主要流程

为了容易理解Spring解决循环依赖过程,我们先简单温习下Spring容器创建Bena的主要流程。

从代码看Spring对于Bean的生成过程,步骤还是很多的,我把一些扩展业务代码省略掉,先上点开胃菜:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)        throws BeanCreationException {    if (mbd.isSingleton()) {        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);    }    // Bean初始化第一步:默认调用无参构造实例化Bean    // 如果是只有带参数的构造方法,构造方法里的参数依赖注入,就是发生在这一步    if (instanceWrapper == null) {        instanceWrapper = createBeanInstance(beanName, mbd, args);    }    // Initialize the bean instance.    Object exposedObject = bean;    try {        // bean创建第二步:填充属性(DI依赖注入发生在此步骤)        populateBean(beanName, mbd, instanceWrapper);        // bean创建第三步:调用初始化方法,完成bean的初始化操作(AOP的第三个入口)        // AOP是通过自动代理创建器AbstractAutoProxyCreator的postProcessAfterInitialization()//方法的执行进行代理对象的创建的,AbstractAutoProxyCreator是BeanPostProcessor接口的实现        exposedObject = initializeBean(beanName, exposedObject, mbd);    }    catch (Throwable ex) {        // ...    }    // ...

从上述代码看出,整体脉络可以归纳成3个核心步骤:

1. 实例化Bean

主要是通过反射调用默认构造函数创建Bean实例,此时bean的属性都还是默认值null。被注解@Bean标注的方法就是此阶段被调用的。

2. 填充Bean属性

这一步主要是对bean的依赖属性进行填充,对@Value @Autowired @Resource注解标注的属性注入对象引用。

3. 调用Bean初始化方法

调用配置指定中的init 方法,如xml文件指定bean的init-method方法或注解@Bean(initMethod = "initMethod")指定的方法。

2.3、Bean创建过程BeanPostProcessor接口拓展点

在Bean创建的流程中Spring提供了多个BeanPostProcessor接口(下称BPP)方便开发者对Bean进行自定义调整和加工。有以下几种BPP接口比较常用:

  • postProcessMergedBeanDefinition:可对BeanDefinition添加额外的自定义配置

  • getEarlyBeanReference:返回早期暴露的bean引用,一个典型的例子是循环依赖时如果有动态代理,需要在此先返回代理实例

  • postProcessAfterInstantiation:在populateBean前用户可以手动注入一些属性

  • postProcessProperties:对属性进行注入,例如配置文件加密信息在此解密后注入

  • postProcessBeforeInitialization:属性注入后的一些额外操作

  • postProcessAfterInitialization:实例完成创建的最后一步,这里也是一些BPP进行AOP代理的时机.

    最后,对bean的生命流程进行一个流程图的总结

    图片

    图三 bean的生命流程图

此处敲黑板划重点:Spring的动态代理(AOP)是通过BPP实现的(在图三中的3.4步实现),其中AbstractAutoProxyCreator是十分典型的自动代理类,它实现了SmartInstantiationAwareBeanPostProcessor接口,并重写了getEarlyBeanReference和postProcessAfterInitialization两个方法实现代理的逻辑,这样完成对原始Bean进行增强,生成新Bean对象,将增强后的新Bean对象注入到属性依赖中。

三、Spring如何解决循环依赖?

先说结论,Spring是通过三级缓存和提前曝光的机制来解决循环依赖的问题。

3.1、三级缓存作用

三级缓存其实就是用三个Map来存储不同阶段Bean对象。

图片


一级缓存 singletonObjects: 主要存放的是已经完成实例化、属性填充和初始化所有步骤的单例Bean实例,这样的Bean能够直接提供给用户使用,我们称之为终态Bean或叫成熟Bean。

二级缓存 earlySingletonObjects: 主要存放的已经完成初始化但属性还没自动赋值的Bean,这些Bean还不能提供用户使用,只是用于提前暴露的Bean实例,我们把这样的Bean称之为临时Bean或早期的Bean(半成品Bean) 

三级缓存 singletonFactories: 存放的是ObjectFactory的匿名内部类实例,调用ObjectFactory.getObject()最终会调用getEarlyBeanReference方法,该方法可以获取提前暴露的单例bean引用。

3.2、三级缓存解决循环依赖过程

现在通过源码分析,深入理解下Spring如何运用三级缓存解决循环依赖。Spring创建Bean的核心代码doGetBean中,在实例化bean之前,会先尝试从三级缓存获取bean,这也是Spring解决循环依赖的开始。

我们假设现在有这样的场景AService依赖BService,BService依赖AService

一开始加载AService Bean首先依次从一二三级缓存中查找是否存在beanName=AService的对象。​​​​​​​

// AbstractBeanFactory.java    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);        // 1.尝试从缓存中获取bean,AService还没创建三级缓存都没命中        Object sharedInstance = getSingleton(beanName);        if (mbd.isSingleton()) {                          sharedInstance = getSingleton(beanName,    () -> {  //注意此处参数是一个lambda表达式即参数传入的是ObjectFactory类型一个匿名内部类对象                                                        try {                                                            return createBean(beanName, mbd, args);  //                                                         }                                                        catch (BeansException ex) {}                                                    });            beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);        }    }

因为AService还没创建三级缓存都没命中于是走到创建Bean代码逻辑。调用方法getSingleton(String beanName,ObjectFactory objectFactory)方法,第二个参数传入一个ObjectFactory接口的匿名内部类实例。​​​​​​​

public Object getSingleton(String beanName, ObjectFactory singletonFactory) {//将当前beanName放到singletonsCurrentlyInCreation 集合中,标识该bean正在创建    beforeSingletonCreation(beanName);    //通过回调getObject()方法触发AbstractAutowireCapableBeanFactory#createBean(String beanName, RootBeanDefinition mbd, Object[] args)的执行    singletonObject = singletonFactory.getObject();    afterSingletonCreation(beanName);    addSingleton(beanName, singletonObject);}

该方法主要做四件事情:

  • 将当前beanName放到singletonsCurrentlyInCreation集合中标识该bean正在创建;

  • 调用匿名内部类实例对象的getObject()方法触发AbstractAutowireCapableBeanFactory#createBean方法的执行;

  • 将当前beanName从singletonsCurrentlyInCreation集合中移除;

singletonFactory.getObject()方法触发回调AbstractAutowireCapableBeanFactory#createBean(String beanName, RootBeanDefinition mbd, Object[] args)的执行,走真正创建AService Bean流程。​​​​​​​

//真正创建Bean的地方 AbstractAutowireCapableBeanFactory#doCreateBean    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)  throws BeanCreationException {
        // Instantiate the bean.        BeanWrapper instanceWrapper = null;        if (mbd.isSingleton()) {            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);        }        // bean初始化第一步:默认调用无参构造实例化Bean        // 构造参数依赖注入,就是发生在这一步        if (instanceWrapper == null) {            instanceWrapper = createBeanInstance(beanName, mbd, args);        }        // 实例化后的Bean对象        final Object bean = instanceWrapper.getWrappedInstance();        // 将刚创建的bean放入三级缓存中singleFactories(key是beanName,value是ObjectFactory)        //注意此处参数又是一个lambda表达式即参数传入的是ObjectFactory类型一个匿名内部类对象,在后续再缓存中查找Bean时会触发匿名内部类getEarlyBeanReference()方法回调        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));        // Initialize the bean instance.        Object exposedObject = bean;        try {            // bean创建第二步:填充属性(DI依赖注入发生在此步骤)            populateBean(beanName, mbd, instanceWrapper);            // bean创建第三步:调用初始化方法,完成bean的初始化操作(AOP的第三个入口)            // AOP是通过自动代理创建器AbstractAutoProxyCreator的postProcessAfterInitialization()//方法的执行进行代理对象的创建的,AbstractAutoProxyCreator是BeanPostProcessor接口的实现            exposedObject = initializeBean(beanName, exposedObject, mbd);        }        catch (Throwable ex) {            // ...        }
    }

在上面创建AService Bean代码流程可以看出,AService实例化后调用addSingletonFactory(String beanName, ObjectFactory singletonFactory) 方法将以Key为AService,value是ObjectFactory类型一个匿名内部类对象放入三级缓存中,在后续使用AService时会依次在一二三级缓存中查找,最终三级缓存中查到这个匿名内部类对象,从而触发匿名内部类中getEarlyBeanReference()方法回调。

此处为什么不是AService实例直接放入三级缓存呢?因为我们上面说了AOP增强逻辑是在创建Bean第三步:调用初始化方法之后进行的,AOP增强后生成的新代理类AServiceProxy实例对象,假如此时直接把AService实例直接放入三级缓存,那么在对BService Bean依赖的aService属性赋值的就是AService实例,而不是增强后的AServiceProxy实例对象。

在以Key为AService,value为ObjectFactory类型一个匿名内部类对象放入三级缓存后,继续对AService进行属性填充(依赖注入),这时发现AService依赖BService。

于是又依次从一二三级缓存中查询BService Bean,没找到,于是又按照上述的流程实例化BService,将以Key为BService,value是ObjectFactory类型一个匿名内部类对象放入三级缓存中,继续对BService进行属性填充(依赖注入),这时发现BService又依赖AService。于是依次在一二三级缓存中查找AService。​​​​​​​

//DefaultSingletonBeanRegistry.javapublic Object getSingleton(String beanName) {    return getSingleton(beanName, true);}protected Object getSingleton(String beanName, boolean allowEarlyReference) {        // 从一级缓存获取,key=AService        Object singletonObject = this.singletonObjects.get(beanName);        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {            synchronized (this.singletonObjects) {                // 从二级缓存获取,key=AService                 singletonObject = this.earlySingletonObjects.get(beanName);                // 是否允许循环引用                if (singletonObject == null && allowEarlyReference) {                   // 前面已经将以Key为AService,value是ObjectFactory类型一个匿名内部类对象放入三级缓存了                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);                    if (singletonFactory != null) {                         //singletonFactory是一个匿名内部类对象,此处触发匿名内部类中getEarlyBeanReference()方法回调。                        singletonObject = singletonFactory.getObject();                        // 将三级缓存生产的bean放入二级缓存中                        this.earlySingletonObjects.put(beanName, singletonObject);                        // 删除三级缓存                        this.singletonFactories.remove(beanName);                    }                }            }        }        return singletonObject;    }

最终三级缓存中查到之前放入的以Key为AService,value为ObjectFactory类型一个匿名内部类对象,从而触发匿名内部类getEarlyBeanReference()方法回调。getEarlyBeanReference()方法决定返回AService实例到底是AService实例本身还是被AOP增强后的AServiceProxy实例对象。如果没AOP切面对AService进行拦截,这时返回的将是AService实例本身。接着将半成品AService Bean放入二级缓存并将Key为AService从三级缓存中删除,这样实现了提前将AService Bean曝光给BService完成属性依赖注入。继续走BService后续初始化逻辑,最后生产了成熟的BService Bean实例。

接着原路返回,AService也成功获取到依赖BService实例,完成后续的初始化工作,然后完美的解决了循环依赖的问题。

最后,来一张解决AService依赖BService,BService又依赖AService这样循环依赖的流程图对上述Spring代码逻辑进行总结。

图片

图四 没有AOP的Bean循环依赖解决的流程图

3.3、 当AOP遇到循环依赖

从2.3、Bean创建过程BeanPostProcessor接口拓展点小节,我们知道Bean的AOP动态代理创建时在初始化之后通过回调postProcessAfterInitialization后置处理器进行的,但是出现循环依赖的Bean如果使用了AOP, 那就需要在getEarlyBeanReference()方法创建动态代理,将生成的代理Bean放在二级缓存提前曝光出来, 这样BService的属性aService注入的就是被代理后的AServiceProxy实例对象。

下面以AService依赖BService,BService依赖AService,AService被AOP切面拦截的场景进行代码分析循环依赖的Bean使用了AOP如何在getEarlyBeanReference()方法如何提前创建动态代理Bean。

图片

​​​​​​​
// 将Aservice添加三级缓存addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));// 添加Bservice的aService属性时从三级中找Aservice的ObjectFactory类型一个匿名内部类对象,从而触发匿名内部类getEarlyBeanReference()方法回调,进入创建AService切面代理对象逻辑protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {    Object exposedObject = bean;    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {        //判断后置处理器是否实现了SmartInstantiationAwareBeanPostProcessor接口        //调用SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);        }    }    return exposedObject;}

可以看出getEarlyBeanReference()方法判断后置处理器是否实现了SmartInstantiationAwareBeanPostProcessor后置处理器接口。

而我们演示代码通过@EnableAspectJAutoProxy注解导入的AOP核心业务处理AnnotationAwareAspectJAutoProxyCreator类,它继承了AbstractAutoProxyCreator了,在AbstractAutoProxyCreator类中实现了getEarlyBeanReference()方法。​​​​​​​

//真正实现了该方法的类就是AbstractAutoProxyCreatorpublic abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport      implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {     @Override    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {        // 先获取beanName,主要是为FactoryBean类型添加&前缀        Object cacheKey = getCacheKey(bean.getClass(), beanName);        // 判断是否已经在earlyProxyReferences集合中,不在则添加进去        if (!this.earlyProxyReferences.contains(cacheKey)) {            this.earlyProxyReferences.add(cacheKey);        }        // 创建代理对象,如果必要的话        return wrapIfNecessary(bean, beanName, cacheKey);    }      /**     * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.     * @param bean the raw bean instance     * @param beanName the name of the bean     * @param cacheKey the cache key for metadata access     * @return a proxy wrapping the bean, or the raw bean instance as-is     */    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {        // 前面先做一些基本的判断        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {            return bean;        }        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {            return bean;        }        // Advice/Pointcut/Advisor/AopInfrastructureBean接口的beanClass不进行代理以及对beanName为aop内的切面名也不进行代理        // 此处可查看子类复写的shouldSkip()方法        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {            this.advisedBeans.put(cacheKey, Boolean.FALSE);            return bean;        }        // Create proxy if we have advice.        // 查找对代理类相关的advisor对象集合,此处就与point-cut表达式有关了        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);        // 对相应的advisor不为空才采取代理        if (specificInterceptors != DO_NOT_PROXY) {            this.advisedBeans.put(cacheKey, Boolean.TRUE);            // 通过jdk动态代理或者cglib动态代理,产生代理对象,这里传入的是SingletonTargetSource对象喔,对原始bean对象进行了包装            Object proxy = createProxy(                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));            // 放入代理类型缓存            this.proxyTypes.put(cacheKey, proxy.getClass());            return proxy;        }        // 放入通知缓存        this.advisedBeans.put(cacheKey, Boolean.FALSE);        return bean;    }}​​​​​​​

wrapIfNecessary 方法查找AService是否查找存在的advisor对象集合,此处就与point-cut表达式有关了,显然我们的切点 @Around("execution(* com.example.service.AService.helloA(..))")拦截了AService,因此需要创建AService的代理Bean。通过jdk动态代理或者cglib动态代理,产生代理对象,对原始AService对象进行了包装最后返回的是 AService的代理对象aServiceProxy,然后把 aServiceProxy 放入二级缓存里面,并删除三级缓存中的 AService的ObjectFactory。这样实现了提前为AService生成动态对象aServiceProxy并赋值给BService的aService属性依赖注入。这样BService完成了属性依赖注入,继续走BService后续初始化逻辑,最后生产了成熟的BService Bean实例。当 BService创建完了之后, AService在缓存BService Bean对象完成bService属性注入后,接着走到Bean创建流程的第三步:初始化AService,有上面知识我们知道初始化AService会回调postProcessAfterInitialization后置处理器又开始AOP逻辑。

图片


而此时判断 AService已经存在getEarlyBeanReference()方法中放入earlyProxyReferences了,说明 原始对象已经经历过了AOP,因此就不用重复进行AOP逻辑。

这样AService也完成初始化工作,然后完美的解决了Aservice依赖BService,BService依赖Aservice这个循环依赖的问题。

最后,也来一张解决AService、BService相互依赖,且AService使用了AOP的循环依赖的流程图对上述Spring代码逻辑进行总结。红色部分主要与没有AOP情况AService、BService相互依赖流程区别内容。

图片

图五 使用AOP且出现循环依赖的解决流程图

四、为啥我们应用还会报错

前面章节已经详细讲了Spring通过三级缓存和提前曝光机制解决循环依赖问题。那我们的应用怎么还报此类错误呢?首先回顾下报错详情:

图片

从错误描述看xxxProductMaintenanceFacadeImpl注入的xxxManageFacadeImpl对象与最终的xxxManageFacadeImpl对象不一致。从上面代码分析,我们知道Spring能改变单例Bean的对象只有在AOP情况下出现,而出现循环依赖且使用AOP的Bean有getEarlyBeanReference()方法和bean初始化步骤里后置处理器postProcessAfterInitialization两处时机进行AOP,如图五中第18步和第22步。如果是同一个AOP的织入类,那么在bean初始化步骤里后置处理器postProcessAfterInitialization处会判断Bean已经被代理过,不会再做AOP代理。但现在报错xxxManageFacadeImpl对象最终版本不一致,说明XxxManageFacadeImpl存在另一个AOP的织入类且是在后置处理器postProcessAfterInitialization处进行AOP的。

以下模拟我们的项目代码:

图片


从示例代码看出AServiceImpl类被@Aspect和@Async两个切面注解拦截。

@Aspect注解的AOP核心业务处理由AnnotationAwareAspectJAutoProxyCreator类,它继承了AbstractAutoProxyCreator了,在AbstractAutoProxyCreator类中实现了getEarlyBeanReference()方法。

@Async注解的AOP核心业务处理由AsyncAnnotationBeanPostProcessor类,它只实现了postProcessAfterInitialization()方法,至于为什么@Async不实现提早暴露getEarlyBeanReference(),我还没有想明白。这样@Async注解是在AService初始化步骤里后置处理器postProcessAfterInitialization进行AOP,新生成了AServiceProxy2对象。

如下图所示@Aspect注解的AOP是在第18步实现的,这样二级缓存里的存放和BService对象的aService属性注入都是AServiceProxy实例对象;

而@Async注解的AOP是在第22步实现的,这是新生成AServiceProxy2实例对象;下图中蓝色部分就是进行两次AOP地方。

那么单例Bean AService存在两个AOP后的实例对象,这就违背单例的单一性原则,因此报错了;

图片

图六 两个AOP代理时机不同导致生成两个代理Bean实例对象

或许到此你还会疑问,这个循环依赖问题为什么日常或预发没出现,而都是线上部署时才遇到报错此错误?

这就跟Spring的Bean加载顺序有关系了, Spring容器载入bean顺序是不确定的,Spring框架没有约定特定顺序逻辑规范。在某些机器环境下是AService比BService先加载,但在某些环境下是BService比AService先加载。

还是拿上面示例分析,AServiceImpl类被@Aspect和@Async两个切面注解拦截,但是先加载BService再加载AService。

图片


由图可以看出AService的@Aspect和@Async两个注解AOP在都是在后置处理器进行,因此只生成一个代理对象AServiceProxy实例,这种情况下应用启动就不会报错。

五、总结

总结下Spring解决循环依赖的思路:

在创建单例bean时,会把该bean的工厂函数的匿名类对象放入三级缓存中的singletonFactories中;

然后在填充属性时,如果出现循环依赖依赖本 bean,必然执行之前放入的工厂函数的匿名实现,如果该bean无需 AOP的话,工厂函数返回的就是原bean对象;如果该bean有 AOP 的话,也有可能是被某些BBP处理AOP 之后的代理对象,会放入二级缓存中的earlySingletonObjects中;

接着bean开始初始化,如果该bean无需 AOP的话,结果返回的原来创建的bean对象;如果该bean有 AOP 的话,检查AOP织入逻辑是否已经在提前曝光时已经执行了,如果已经执行AOP则返回提前曝光的代理bean对象;如果AOP织入逻辑未执行过,则进行后续的 BeanPostProcessor后置处理器进行AOP织入,生成AOP代理bean对象,并返回。

最后对于提前曝光的单例,就会去检查初始化后的bean对象与二级缓存中提前曝光的bean是不是同一个对象,只有不是的情况下才可能抛出异常。

深入阅读我们应用本身代码,发现项目中出现Bean的循环依赖,本质原因是代码架构设计不合理,某些facade类实现了本应在serivce层的业务逻辑,导致其他业务依赖地方反复应用facade层对象。SpringBoot 2.6.x以上的版本官方已经不推荐使用循环依赖,说不定今后某个最新版本的Spring会强制不能出现Bean循环依赖,因此需要我们开发者在平时编码时要重视代码架构设计。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/9298.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux网卡命名规则与修改方法

一.前言&#xff1a; 在早期的的操作系统中例如fedora13或者ubuntu15之前网卡命名的方式为eth0&#xff0c;eth1&#xff0c;eth2&#xff0c;属于biosdevname 命名规范。当然这是针对intel网卡的命名规则&#xff0c;对于realtek类型的网卡会命名为ens33。但是这个编号往往不一…

elevation mapping学习笔记1之Ubuntu18.04+ROS-melodic编译安装elevation mapping高程图及示例运行

文章目录 0 引言1 安装依赖1.1 grid map1.2 Eigen1.3 kindr1.4 Point Cloud Library (PCL) 2 编译和问题解决3 运行示例3.1 turtlesim3_waffle_demo3.2 simple_demo 和 Ground Truth Demo 0 引言 苏黎世开源的elevation mapping指的是苏黎世联邦理工学院&#xff08;ETH Zuric…

【iVX】构建新一代互联网研发体系

低代码开发平台作为一种快速、简化应用程序开发的方法&#xff0c;正在越来越受到关注。今天我们来了解下 iVX —— 首个通用无代码开发平台。 那么什么是iVX呢&#xff1f;下边的图就比较形象了。 文章目录 低代码未来的发展方向整合硬件和AI能力 编程真的很困难吗&#xff1…

SQL语句(三十二)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、SQL语句类型 二、数据库操作 ​三、数据表操作 1. 数据类型 2. 查看 3. 创建 4. 删除 5. 更改 5.1 表 5.2 列 四、数据操作 4.1 增 4.2 删 4.3 改 4.4 查…

Failed to load local font resource:微信小程序加载第三方字体

加载本地字体.ttf 将ttf转换为base64格式&#xff1a;https://transfonter.org/ 步骤如下 将下载后的stylesheet.css 里的font-family属性名字改一下&#xff0c;然后引进页面里就行了&#xff0c;全局样式就放app.scss&#xff0c;单页面就引入单页面 注&#xff1a; .title…

Packet Tracer – 使用 CDP 映射网络

# Packet Tracer – 使用 CDP 映射网络 ## 地址分配表 设备 接口 IP 地址 子网掩码 本地接口和互联邻居 Edge1 G0/0 192.168.1.1 255.255.255.0 G0/1 - S1 S0/0/0 S0/0/0 - ISP Branch-Edge S0/0/1 209.165.200.10 255.255.255.252 S0/0/1 – ISP Branch…

flink to starrocks 问题集锦....

[问题排查]导入失败相关 - 问题排查 - StarRocks中文社区论坛 starrocks官网如下&#xff1a; Search StarRocks Docs starrocks内存配置项&#xff1a; 管理内存 Memory_management StarRocks Docs 问题1&#xff1a;实时写入starrocks &#xff0c;配置参数设置如下&a…

Java书签 #解锁MyBatis的4种批量插入方式及ID返回姿势

1. 今日书签 项目开发中&#xff0c;我们经常会用到单条插入和批量插入。但是实际情况可能是&#xff0c;项目初期由于种种原因&#xff0c;在业务各处直接使用单条插入SQL进行开发&#xff08;未开启批处理&#xff09;&#xff0c;在后面的迭代中&#xff0c;系统性能问题渐…

【数据挖掘】如何修复时序分析缺少的日期

一、说明 我撰写本文的目的是通过引导您完成一个示例来帮助您了解 TVF 以及如何使用它们&#xff0c;该示例解决了时间序列分析中常见的缺失日期问题。 我们将介绍&#xff1a; 如何生成日期以填补数据中缺失的空白如何创建 TVF 和参数的使用如何呼叫 TVF我们将考虑扩展我们的日…

字典序排数(力扣)思维 JAVA

给你一个整数 n &#xff0c;按字典序返回范围 [1, n] 内所有整数。 你必须设计一个时间复杂度为 O(n) 且使用 O(1) 额外空间的算法。 示例 1&#xff1a; 输入&#xff1a;n 13 输出&#xff1a;[1,10,11,12,13,2,3,4,5,6,7,8,9] 示例 2&#xff1a; 输入&#xff1a;n 2 输…

3.矩阵常用操作

文章目录 线性代数的常用操作1.向量的内积2.向量的外积3.正交向量4.正交向量组5.向量空间的基与维数6.正交矩阵7.反对称矩阵8.齐次坐标与齐次变换矩阵9.相似矩阵10.相似对角化11.矩阵的特征分解12.奇异值分解SVD12.1 SVD求齐次矩阵方程的最小二乘解 13.满秩分解14.Pseudo-Inver…

使用网络 IP 扫描程序的原因

随着网络不断扩展以满足业务需求&#xff0c;高级 IP 扫描已成为网络管理员确保网络可用性和性能的关键任务。在大型网络中扫描 IP 地址可能具有挑战性&#xff0c;这些网络通常包括具有动态 IP、多个 DNS、DHCP 配置和复杂子网的有线和无线设备。使用可提供全面 IP 地址管理 &…

【简单图论】CF1833 E

Problem - E - Codeforces 题意&#xff1a; 思路&#xff1a; 显然&#xff0c;最大值就是什么边都不连的连通块个数&#xff0c;最小值就是能连的都连上 那就是&#xff0c;如果一个连通块存在度为1的点&#xff0c;就把它当作接口连接 Code&#xff1a; #include <b…

Spring Boot 集成 Redis 三种模式实践汇总

背景 项目的某个模块集成了 SpringBoot Redis 包&#xff0c;客户端使用 Lettuce&#xff0c;Redis 测试环境单机模式。但是现场反馈的 Redis 环境是集群&#xff0c;如果简单的修改 spring.redis 配置为集群的配置信息&#xff0c;程序能否能无缝衔接呢&#xff1f; 本文记录…

音视频——帧内预测

H264编码(帧内预测) 在帧内预测模式中&#xff0c;预测块P是基于已编码重建块和当前块形成的。对亮度像素而言&#xff0c;P块用于44子块或者1616宏块的相关操作。44亮度子块有9种可选预测模式&#xff0c;独立预测每一个44亮度子块&#xff0c;适用于带有大量细节的图像编码&…

Gempy三维结构地质建模简明教程

Gempy 是一个开源 Python 库&#xff0c;用于生成完整的 3D 结构地质模型。 该库是一个完整的开发&#xff0c;用于从界面、断层和层方向创建地质模型&#xff0c;它还关联地质层的顺序以表示岩石侵入和断层顺序。 推荐&#xff1a;用 NSDT设计器 快速搭建可编程3D场景。 地质建…

LLaMA2可商用|GPT-4变笨|【2023-0723】【第七期】

一、大咖观点&#xff1a; 傅盛&#xff1a;ChatGPT时代如何创业 - BOTAI - 博客园Google 已经被OpenAI 超越了吗&#xff1f;| AlphaGo 之父深度访谈《人民日报》&#xff1a;大模型的竞争&#xff0c;是国家科技战略的竞争WAIC 2023 | 张俊林&#xff1a;大语言模型带来的交…

设计模式之状态模式

状态决定行为。由于状态在运行期间是可以被改变的。在VoteManager类中根据条件来创建不同的状态实现类&#xff0c;于是到最后就会调用不同的逻辑。 看起来同一个对象&#xff0c;在不同的运行时刻&#xff0c;行为是不一样的&#xff0c;就像是类被修改了一样&#xff01; 场…

labview 信号量实现互斥 避免竞争写

上一篇文章中描述了事件发生、集合点、通知器、信号量。 本文进一步举例描述信号量实现互斥&#xff0c;避免竞争写。 1.不用信号量的例子 图1-不用信号量的结果都不同&#xff0c;不为0 图2-不用信号量的例子&#xff0c;程序框图 2.用信号量的例子 图3-用信号量的例子&…

结构型设计模式之亨元模式【设计模式系列】

系列文章目录 C技能系列 Linux通信架构系列 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 设计模式系列 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everythi…