Spring进阶(二十一)之循环Bean详解

news2024/9/23 19:23:23

目录

什么是循环依赖

检测是否存在循环依赖

Spring底层是如何解决循环依赖的问题

循环依赖无法解决的情况

为什么需要用3级缓存

问题

原因

案例

单例bean解决了循环依赖,还存在什么问题


什么是循环依赖

这个很好理解,多个bean之间相互依赖,形成了一个闭环。 比如:A依赖于B、B依赖于C、C依赖于A。 代码中表示:

public class A{
    B b;
}

public class B{
    C c;
}

public class C{
    A a;
}

检测是否存在循环依赖

检测循环依赖比较简单,使用一个列表来记录正在创建中的bean,bean创建之前,先去记录中看一下自己是否已经在列表中了,如果在,说明存在循环依赖,如果不在,则将其加入到这个列表,bean创建完毕之后,将其再从这个列表中移除。

源码方面来看一下,spring创建单例bean时候,会调用下面方法:

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

singletonsCurrentlyInCreation 就是用来记录目前正在创建中的bean名称列表, this.singletonsCurrentlyInCreation.add(beanName) 返回 false ,说明beanName已经在当前 列表中了,此时会抛循环依赖的异常 BeanCurrentlyInCreationException

再来看看非单例bean的情况,以prototype情况为例,源码位于 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 方法中,主要代码如下:

    //检查正在创建的bean列表中是否存在beanName,如果存在,说明存在循环依赖,抛出循环依赖的异常
    if(isPrototypeCurrentlyInCreation(beanName)){
        throw new BeanCurrentlyInCreationException(beanName);
    }
    //判断scope是否是prototype
    if(mbd.isPrototype()){
        Object prototypeInstance = null;
        try {
            //将beanName放入正在创建的列表中
            beforePrototypeCreation(beanName);
            prototypeInstance = createBean(beanName, mbd, args);
        } finally {
            //将beanName从正在创建的列表中移除
            afterPrototypeCreation(beanName);
        }
    }

 

Spring底层是如何解决循环依赖的问题

我们注入依赖的对象的方式有三种,通过属性注入,通过构造方法注入,通过setter方法注入

先来看构造器的方式注入依赖的bean,下面两个bean循环依赖:

@Service
public class ServiceA {

    private ServiceB serviceB;

    @Autowired
    public ServiceA(ServiceB serviceB){
        this.serviceB=serviceB;
    }

}



@Service
public class ServiceB {

    private ServiceA serviceA;

    @Autowired
    public ServiceB(ServiceA serviceA){
        this.serviceA=serviceA;
    }
}

构造器的情况比较容易理解,实例化ServiceA的时候,需要有serviceB,而实例化ServiceB的时候需要有serviceA,所以上面的构造方法依赖注入的方式会报错。构造器循环依赖Spring是无法帮我们来解决的,需要我们自己解决,比如添加@Lazy来懒加载。

再来看看非构造器的方式注入相互依赖的bean,以set方式注入为例(属性注入和setter注入的循环依赖问题Spring都帮我们解决了,解决的底层原理是一样的),下面是2个单例的bean:serviceA 和serviceB:

@Service
public class ServiceA {

    private ServiceB serviceB;


    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}


@Service
public class ServiceB {

    private ServiceA serviceA;


    @Autowired
    public void setServiceA(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

由于单例bean在spring容器中只存在一个,所以spring容器中肯定是有一个缓存来存放所有已创建好的 单例bean;获取单例bean之前,可以先去缓存中找,找到了直接返回,找不到的情况下再去创建,创 建完毕之后再将其丢到缓存中,可以使用一个map来存储单例bean,比如下面这个:

Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

 

下面来看一下spring中setter方法创建上面2个bean的过程

1.spring轮询准备创建2个bean:serviceA和serviceB

2.spring容器发现singletonObjects中没有serviceA

3.调用serviceA的构造器创建serviceA实例

4.serviceA准备注入依赖的对象,发现需要通过setServiceB注入serviceB

5.serviceA向spring容器查找serviceB

6.spring容器发现singletonObjects中没有serviceB

7.调用serviceB的构造器创建serviceB实例

8.serviceB准备注入依赖的对象,发现需要通过setServiceA注入serviceA

9.serviceB向spring容器查找serviceA

10.此时又进入步骤2了

上面过程死循环了,怎么才能终结?

可以在第3步后加一个操作:将实例化好的serviceA丢到singletonObjects中,此时问题就解决了。 spring中也采用类似的方式,稍微有点区别,上面使用了一个缓存,而spring内部采用了3级缓存来解决 这个问题,我们一起来细看一下。

3级缓存对应的代码:

/** 第一级缓存:单例bean的缓存 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** 第二级缓存:早期暴露的bean的缓存 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/** 第三级缓存:单例bean工厂的缓存 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

下面来看看spring中具体的过程,一起来分析源码:

    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
        
        //1.查看缓存中是否已经有这个bean了
        Object sharedInstance = getSingleton(beanName); //@1
        if (sharedInstance != null && args == null) {
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        } else {

            //若缓存中不存在,准备创建这个bean
            if (mbd.isSingleton()) {

                //2.下面进入单例bean的创建过程
                sharedInstance = getSingleton(beanName, () -> {
                    try {
                        return createBean(beanName, mbd, args);
                    } catch (BeansException ex) {
                        throw ex;
                    }
                });
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            }
        }
        return (T) bean;
    }

@1:查看缓存中是否已经有这个bean了,如下:

public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}

然后进入getSingleton()方法,会依次尝试从3级缓存中查找bean,注意下面的第2个参数,为ture的时候,才会从第3级中查找,否则只会查找1、2级缓存:

    //allowEarlyReference:是否允许从三级缓存singletonFactories中通过getObject拿到bean
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {

        //1.先从一级缓存中找
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {

                //2.从二级缓存中找
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {

                    //3.二级缓存中没找到 && allowEarlyReference为true的情况下,从三级缓存中找
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {

                        //三级缓存返回的是一个工厂,通过工厂来获取创建bean
                        singletonObject = singletonFactory.getObject();

                        //将创建好的bean丢到二级缓存中
                        this.earlySingletonObjects.put(beanName, singletonObject);

                        //从三级缓存移除
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

刚开始,3个缓存中肯定是找不到的,会返回null,接着会执行下面代码准备创建 serviceA

    if(mbd.isSingleton()){
        sharedInstance = getSingleton(beanName, () -> { //@1
            try {
                return createBean(beanName, mbd, args);
            } catch (BeansException ex) {
                destroySingleton(beanName);
                throw ex;
            }
        });
    }

@1:进入 getSingleton 方法:

    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {

                //单例bean创建之前调用,将其加入正在创建的列表中,上面有提到过,主要用来检测循环依赖用的
                beforeSingletonCreation(beanName);
                boolean newSingleton = false;
                try {

                    //调用工厂创建bean
                    singletonObject = singletonFactory.getObject();//@1
                    newSingleton = true;
                } finally {

                    //单例bean创建之前调用,主要是将其从正在创建的列表中移除
                    afterSingletonCreation(beanName);
                }
                if (newSingleton) {

                    //将创建好的单例bean放入缓存中
                    addSingleton(beanName, singletonObject);//@2
                }
            }
            return singletonObject;
        }
    }

上面@1和@2是关键代码,先来看一下@1,这个是一个ObjectFactory类型的,从外面传入的,如下:

红框中的 createBean 最终会调用下面这个方法

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doC reateBean 

其内部关键代码如下:

BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
            
      //通过反射调用构造器实例化serviceA
      instanceWrapper = createBeanInstance(beanName, mbd, args);
}
        
//变量bean:表示刚刚同构造器创建好的bean示例
final Object bean = instanceWrapper.getWrappedInstance();
        
//判断是否需要暴露早期的bean,条件为(是否是单例bean && 当前容器允许循环依赖 && bean名称存在于正在创建的bean名称清单中)(早期的bean是指刚实例化好,但还未属性赋值初始化等操作的bean)
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
            
     //若earlySingletonExposure为true,通过下面代码将早期的bean暴露出去
     addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));//@1
}

@1 :通过 addSingletonFactory 用于将早期的bean暴露出去,主要是将其丢到第3级缓存中,代码如下:

    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
            
            //第1级缓存中不存在bean
            if (!this.singletonObjects.containsKey(beanName)) {
                
                //将其丢到第3级缓存中
                this.singletonFactories.put(beanName, singletonFactory);
                //后面的2行代码不用关注
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }

上面的方法执行之后,serviceA就被丢到第3级的缓存中了。

后续的过程serviceA开始注入依赖的对象,发现需要注入serviceB,会从容器中获取serviceB,而 serviceB的获取又会走上面同样的过程实例化serviceB,然后将serviceB提前暴露出去,然后serviceB开 始注入依赖的对象,serviceB发现自己需要注入serviceA,此时去容器中找serviceA,找serviceA会先去 缓存中找,会执行 getSingleton("serviceA",true) ,此时会走下面代码:

    //allowEarlyReference:是否允许从三级缓存singletonFactories中通过getObject拿到bean
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {

        //1.先从一级缓存中找
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {

                //2.从二级缓存中找
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {

                    //3.二级缓存中没找到 && allowEarlyReference为true的情况下,从三级缓存中找
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {

                        //三级缓存返回的是一个工厂,通过工厂来获取创建bean
                        singletonObject = singletonFactory.getObject();

                        //将创建好的bean丢到二级缓存中
                        this.earlySingletonObjects.put(beanName, singletonObject);

                        //从三级缓存移除
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

上面的方法走完之后,serviceA会被放入二级缓存 earlySingletonObjects 中,会将serviceA返回,此 时serviceB中的serviceA注入成功,serviceB继续完成创建,然后将自己返回给serviceA,此时serviceA 通过set方法将serviceB注入。

serviceA创建完毕之后,会调用 addSingleton 方法将其加入到缓存中,这块代码如下:

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {

        //将bean放入第1级缓存中
        this.singletonObjects.put(beanName, singletonObject);

        //将其从第3级缓存中移除
        this.singletonFactories.remove(beanName);

        //将其从第2级缓存中移除
        this.earlySingletonObjects.remove(beanName);
    }
}

到此,serviceA和serviceB之间的循环依赖注入就完成了。

下面捋一捋整个过程

1.从容器中获取serviceA

2.容器尝试从3个缓存中找serviceA,找不到

3.准备创建serviceA

4.调用serviceA的构造器创建serviceA,得到serviceA实例,此时serviceA还未填充属性,未进行其他任何初始化的操作

5.将早期的serviceA暴露出去:即将其丢到第3级缓存singletonFactories中

6.serviceA准备填充属性,发现需要注入serviceB,然后向容器获取serviceB

7.容器尝试从3个缓存中找serviceB,找不到

8.准备创建serviceB

9.调用serviceB的构造器创建serviceB,得到serviceB实例,此时serviceB还未填充属性,未进行其他任何初始化的操作

10.将早期的serviceB暴露出去:即将其丢到第3级缓存singletonFactories中

11.serviceB准备填充属性,发现需要注入serviceA,然后向容器获取serviceA

12.容器尝试从3个缓存中找serviceA,发现此时serviceA位于第3级缓存中,经过处理之后,serviceA会从第3级缓存中移除,然后会存到第2级缓存中,然后将其返回给serviceB,此时serviceA通过serviceB中 的setServiceA方法被注入到serviceB中

13.serviceB继续执行后续的一些操作,最后完成创建工作,然后会调用addSingleton方法,将自己丢到第1级缓存中,并将自己从第2和第3级缓存中移除

14.serviceB将自己返回给serviceA

15.serviceA通过setServiceB方法将serviceB注入进去

16.serviceB继续执行后续的一些操作,最后完成创建工作,然后会调用addSingleton方法,将自己丢到第1级缓存中,并将自己从第2和第3级缓存中移除

循环依赖无法解决的情况

只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的bean,每次从容器中获 取都是一个新的对象,都会重新创建,所以非单例的bean是没有缓存的,不会将其放到三级缓存中

还有很多情况,比如说一个单例,一个多例,或者一个一个构造方法注入,一个setter注入等等,对于这些组成的循环依赖,到底Sring能不能解决,咱们要学会一步步去进行分析,求证!!!

为什么需要用3级缓存

问题

如果只使用2级缓存,直接将刚实例化好的bean暴露给二级缓存可以吗?

答案:不可以。会出现早期暴露给其他依赖者的bean和最终暴露的bean不一致的问题。

原因

若将刚刚实例化好的bean直接丢到二级缓存中暴露出去,如果后期这个bean对象被更改了,比如可能 在上面加了一些拦截器,将其包装为一个代理了,那么暴露出去的bean和最终的这个bean就不一样 的,将自己暴露出去的时候是一个原始对象,而自己最终却是一个代理对象,最终会导致被暴露出去的 和最终的bean不是同一个bean的,将产生意向不到的效果,而三级缓存就可以发现这个问题,会报错。

案例

需求:在ServiceA上面加个拦截器,要求在调用service1的任何方法之前需要先输出一行日志

@Service
public class ServiceA {

    private ServiceB serviceB;


    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }

    public void m1(){
        System.out.println("serviceA ...");
    }
}
@Service
public class ServiceB {

    private ServiceA serviceA;


    @Autowired
    public void setServiceA(ServiceA serviceA) {
        this.serviceA = serviceA;
    }

    public void m1(){
        System.out.println("serviceB ...");
    }
}
@Component
public class MethodBeforeInterceptor implements BeanPostProcessor {
    @Nullable
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if ("serviceA".equals(beanName)) {
            //代理创建工厂,需传入被代理的目标对象
            ProxyFactory proxyFactory = new ProxyFactory(bean);
            //添加一个方法前置通知,会在方法执行之前调用通知中的before方法
            proxyFactory.addAdvice(new MethodBeforeAdvice() {
                @Override
                public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
                    System.out.println("你好,serviceA");
                }
            });
            //返回代理对象
            return proxyFactory.getProxy();
        }
        return bean;
    }
}

启动后,报错了:

报错的源码如下:

        if (earlySingletonExposure) {
            //@1
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
                //@2
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
                //@3
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						throw new BeanCurrentlyInCreationException(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] in its raw version as part of a circular reference, but has eventually been " +
								"wrapped. 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 " +
								"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

重点分析

@1:调用getSingleton(beanName, false)方法,这个方法用来从3个级别的缓存中获取bean,但是注 意了,这个地方第二个参数是false,此时只会尝试从第1级和第2级缓存中获取bean,如果能够获取 到,说明了什么?说明了第2级缓存中已经有这个bean了,而什么情况下第2级缓存中会有bean?说明 这个bean从第3级缓存中已经被别人获取过,然后从第3级缓存移到了第2级缓存中,说明这个早期的 bean被别人通过getSingleton(beanName, true)获取过

@2:这个地方用来判断早期暴露的bean和最终spring容器对这个bean走完创建过程之后是否还是同一 个bean,上面我们的service1被代理了,所以这个地方会返回false,此时会走到 @3

@3: allowRawInjectionDespiteWrapping 这个参数用来控制是否允许循环依赖的情况下,早期暴露 给被人使用的bean在后期是否可以被包装,通俗点理解就是:是否允许早期给别人使用的bean和最终 bean不一致的情况,这个值默认是false,表示不允许,也就是说你暴露给别人的bean和你最终的bean 需要是一致的

而上面代码注入到serviceB中的serviceA是早期的serviceA,而最终spring容器中的serviceA变成一个代理对象了,早期的和最终的不一致了,而 allowRawInjectionDespiteWrapping 又是false,所以报异常了。 那么如何解决这个问题: 很简单,将 allowRawInjectionDespiteWrapping 设置为true就可以了,下面改一下代码如下:

    @Test
    void contextLoads() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(configMain.class);
        context.addBeanFactoryPostProcessor(beanFactory -> {
            if (beanFactory instanceof DefaultListableBeanFactory)
                ((DefaultListableBeanFactory)beanFactory).setAllowRawInjectionDespiteWrapping(true);
        });
        context.refresh();


        ServiceA serviceA = context.getBean(ServiceA.class);
        ServiceB serviceB = context.getBean(ServiceB.class);

        serviceA.m1();
        System.out.println("------------");
        serviceB.m1();

    }

运行不报错,输出:

你好,serviceA
serviceA ...
------------
serviceB ...

单例bean解决了循环依赖,还存在什么问题

循环依赖的情况下,由于注入的是早期的bean,此时早期的bean中还未被填充属性,初始化等各种操作,也就是说此时bean并没有被完全初始化完毕,此时若直接拿去使用,可能存在有问题的风险。

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

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

相关文章

Python制作进度条,原来有这么多方法

如果你之前没用过进度条&#xff0c;八成是觉得它会增加不必要的复杂性或者很难维护&#xff0c;其实不然。要加一个进度条其实只需要几行代码。 在这几行代码中&#xff0c;我们可以看看如何在命令行脚本以及 PySimpleGUI UI 中添加进度条。 下文将介绍 4 个常用的 Python 进度…

TL431介绍及其应用电路

TL431介绍及其应用电路 硬件攻城狮 1. TL431简介 TL431是常用的三端并联稳压器&#xff0c;具有较好的热稳定性&#xff0c;输出电压可以设置为Vref~36V之间的任何值。被广泛用作基准源、比较器、运放等功能。在隔离电源中&#xff0c;TL431经常与光耦配合构成隔离反馈回路&…

PDF文件怎么合并?看完这篇就懂了!

不知道小伙伴们有没有这样的经历&#xff0c;那就是准备好一些pdf去打印店打印的时候&#xff0c;本来想要打印双面的&#xff0c;但是店家却表示pdf太小&#xff0c;只能一页一页的单页打印。这就给我们的钱包无形之中增加了很多的负担&#xff0c;因为有时候我们没必要打印单…

搜索与图论-有向图的拓扑序列

文章目录一、有向图的拓扑序列1. 拓扑序列2. 拓扑排序3. 如何进行拓扑排序4. 拓扑排序具体实现详见例题有向图的拓扑序列二、有向图的拓扑序列例题——有向图的拓扑序列具体实现1. 样例演示2. 实现思路3. 代码注解4. 实现代码一、有向图的拓扑序列 有向图的拓扑序列就是图的广…

遥感测深方法综述(二)CZMIL 机载LiDAR 测深系统

机载激光雷达测深测量是集激光测距、GNSS定位/姿态测量、航空摄影等多种技术于一体的新型主动机载激光测绘&#xff0c;可应用于海滩和海岸线、浅海编图、海岛、岛礁、水下障碍物的调查&#xff0c;是高效获取高精度近岸海底地形的重要技术&#xff0c;尤其对“人下不去、船上不…

31_ADC基本原理及单次采集实验

目录 ADC简介 STM32F10x ADC特点 ADC执行模式 相关寄存器及数据对齐方式 ADC的采样时间 常用库函数 单次转换一般步骤 实验源码 ADC简介 Analog-to-Digital Converter的缩写。指模/数转换器或者模拟/数字转换器。是指将连续变量的模拟信号转换为离散的数字信号的器件。…

Hylicos --- krprint实现

可变参函数的工作原理 函数调用时先用寄存器R0 ~R3 存放参数&#xff0c;如果不够放还要使用到堆栈&#xff0c;参数列表的形参从右到左依次入栈&#xff0c;弹栈时就是从左到右的顺序。 如果函数参数的个数固定&#xff0c;编译器就可以很清楚地在函数运行时依次从堆栈中弹出…

关于城市旅游的HTML网页设计 HTML+CSS上海博物馆网站 dreamweaver作业静态HTML网页设计 html网页制作期末大作业

&#x1f468;‍&#x1f393;静态网站的编写主要是用 HTML DⅣV CSSJS等来完成页面的排版设计&#x1f469;‍&#x1f393;&#xff0c;一般的网页作业需要融入以下知识点&#xff1a;div布局、浮动定位、高级css、表格、表单及验证、js轮播图、音频视频Fash的应用、uli、下拉…

JVM--这一篇就够了

1、JVM内存模型 Java内存模型是指Java虚拟机的内存模型&#xff0c;我们来看下Java内存模型的图片: 其中&#xff0c;在JAVA的JVM调优中&#xff0c;我们JAVA程序员需要重点关注的&#xff0c;首先是堆&#xff0c;我们看下堆内存的内存模型: 2、类的加载过程 (1)、加载 …

基于jsp+ssm的高速公路收费管理系统-计算机毕业设计

项目介绍 现阶段&#xff0c;政府都会对高速公路收费进行管理&#xff0c;用户通过进入相关系统对高速公路收费状况进行了解&#xff0c;简化了高速公路收费管理流程&#xff0c;进而提高政府高速收费管理效率&#xff0c;达到更好的管理目的。 本设计利用JSP作为开发语言。后…

Nature Communications:人类大脑的皮层下-皮层的动态状态及其在中风中的损伤

摘要 控制大脑自发活动中的动态模式的机制尚不清楚。在这里&#xff0c;我们提供的证据表明&#xff0c;在超低频率范围内&#xff08;<0.01-0.1Hz&#xff09;的皮层动力学需要完整的皮层-皮层下通信。利用静息态功能磁共振成像&#xff08;fMRI&#xff09;&#xff0c;我…

2021-2022 ICPC, NERC, Northern Eurasia Onsite L. Labyrinth

翻译&#xff1a; 莱斯利和利昂进入了一个迷宫。迷宫由&#x1d45b;大厅和&#x1d45a;大厅之间的单向通道组成。大厅编号从1到&#x1d45b;。 莱斯利和利昂在大厅开始了他们的旅程&#x1d460;。他们立刻争吵起来&#xff0c;决定各自去探索迷宫。然而&#xff0c;他们希…

分享一个你很可能不知道的Java异常实现的缺陷

前言 Java中一个大家熟知的知识点就是异常捕获&#xff0c;try…catch…finally组合&#xff0c;但是很多人不知道这里面有一个关于Java的缺陷&#xff0c;或者说是异常实现的一点不足之处。 我这边就通过一个很简单的实验给大家演示下效果玩玩儿&#xff0c;希望大家能觉得有趣…

linux搭建测试环境(tomcat)

安装jdk 1,查看是否装的有&#xff08;centos7会自带的有&#xff09;如果是普通用户切记要切换到root 输入命令查看是否安装&#xff1a; java -version2,跟踪查看自带jdk 默认安装了openjdk&#xff0c;那就要知道具体文件安装到哪里了&#xff0c;这里我们可以通过命令“rp…

黑马程序员Java数据结构与java算法笔记(1)

数据结构和算法详细内容 来源&#xff1a;黑马程序员Java数据结构与java算法 1.数据结构和算法概述 1.1什么是数据结构&#xff1f; 数据结构就是把数据元素按照一定的关系组织起来的集合&#xff0c;用来组织和存储数据 1.2数据结构分类 传统上&#xff0c;我们可以把数…

跟我学Python图像处理丨图像分类原理与案例

摘要&#xff1a;本篇文章将分享图像分类原理&#xff0c;并介绍基于KNN、朴素贝叶斯算法的图像分类案例。本文分享自华为云社区《[Python图像处理] 二十六.图像分类原理及基于KNN、朴素贝叶斯算法的图像分类案例丨【百变AI秀】》&#xff0c;作者&#xff1a;eastmount 。 一…

5-4:发送系统通知

触发事件 评论后&#xff0c;发布通知点赞后&#xff0c;发布通知关注后&#xff0c;发布通知。 处理事件 封装事件对象开发事件的生产者开发事件的消费者 消费者线程&#xff1a;从队列里读消息&#xff0c;并做处理&#xff1b; 生产者线程&#xff1a;往线程中存入数据&…

深度神经网络是什么意思,神经网络准确度只有50

1、研究人工神经网络的权值分布有什么意义 神经网络一般都是非常庞大的&#xff0c;每个边对应一个权值&#xff0c;如果权值不共享的话&#xff0c;数据量就更大了&#xff0c;但是为了提高效率&#xff0c;引入了权值共享&#xff0c;但是还不够&#xff0c;想再次提高效率和…

五、伊森商城 前端基础-Vue Vue脚手架创建 p26

目录 一、vue 模块化开发 1、全局安装 webpack 2、全局安装 vue 脚手架vue-cli 3、初始化 vue 项目 3.1、创建文件夹 3.2、初始化vue脚手架 3.3、项目结构 4、项目启动命令 拓展&#xff1a;创建超时 一、vue 模块化开发 打开终端&#xff0c;进行命令安装 1、全局安装…

Linux网络基础(初级)

Linux网络基础 文章目录Linux网络基础1.计算机网络的发展过程1.1 独立模式1.2 网络互联模式1.3 局域网 LAN1.4 广域网 WAN2.计算机网络协议2.1 协议的概念2.2 什么是网络协议2.3 什么是网络协议簇2.4 OSI 七层模型2.5 TCP/IP 五层模型3.网络传输基本流程3.1 同局域网内的两台主…