Sping源码:三级缓存
目录
- 一、概念
- 1、三级缓存的作用
- 2、循环依赖的含义
- 二、代码
- 1、代码下载
- 2、文件功能介绍
- 3、源码分析
- 3.1、找到获取A对象的位置,打断点进行debug操作
- 3.2、一步步找到在A对象中注入B对象的位置
- 3.3、一步步找到B对象注入A对象的位置
- 3.4、往下找到通过三级缓存解决循环依赖的地方
- 3.5、完成对象B注入对象A
- 3.6、完成对象B的剩余流程,进而回到对象A注入对象B的地方
- 3.7、查看对象A不会再次执行代理代码的地方
- 3.8、找到往一级缓存中添加对象A的代理对象的位置
- 3.9、往对象C中注入对象A
- 三、流程分析
- 四、参考文章
一、概念
1、三级缓存的作用
- 解决循环依赖(即:三级缓存 singletonFactories的功能)
- 实现动态代理 (即:二级缓存 earlySingletonObjects的功能)
- 存储最终对象 (即:一级缓存 singletonObjects的功能)
2、循环依赖的含义
N个类循环(嵌套)引用。
通俗的讲就是N个Bean互相引用对方,最终形成闭环。用一副经典的图示可以表示成这样(A、B、C都代表对象,虚线代表引用关系):
注意:其实可以N=1,也就是极限情况的循环依赖:自己依赖自己
另需注意:这里指的循环引用不是方法之间的循环调用,而是对象的相互依赖关系。(方法之间循环调用若有出口也是能够正常work的)
可以设想一下这个场景:如果在日常开发中我们用new对象的方式,若构造函数之间发生这种循环依赖的话,程序会在运行时一直循环调用最终导致内存溢出,示例代码如下:
public class Main {public static void main(String[] args) throws Exception {System.out.println(new A());}}class A {public A() {new B();}
}class B {public B() {new A();}
}
这是一个典型的循环依赖问题。Spring通过三级缓存机制巧妙的解决循环依赖问题~
二、代码
1、代码下载
百度网盘链接:https://pan.baidu.com/s/1Cq1kTJJesOfl0eHIU3cMIA?pwd=gyg1
提取码:gyg1
2、文件功能介绍
- CodeStudyApplication类:主启动类
- A类:通过IOC方式注入类B和类C的单例对象,用来测试循环依赖;并且在成员方法test()上添加@Transactional注解,用来测试动态代理
- B类:通过IOC方式注入类A的单例对象,用来测试循环依赖
- C类:通过IOC方式注入类A的单例对象,用来测试循环依赖,类C和类B的主要区别是“当B对象触发A对象的循环依赖后,可以获取A对象的代理对象,然后看C对象如何获取A对象的代理对象”
- application.yml:添加一些mysql信息,大家不用修改,不会报错
3、源码分析
注意: 博主使用的开发工具是IDEA
,所以提到的快捷键都是IDEA中的
3.1、找到获取A对象的位置,打断点进行debug操作
当程序启动时,最先运行的是com.atguigu.test.CodeStudyApplication
类中的main
方法。
根据代码执行顺序,我们按着Ctrl
按键后点击SpringApplication
类的静态方法run()
:
现在进入了SpringApplication
类的静态方法run()
,然后我们按着Ctrl
按键后点击重载的静态方法run()
:
继续按着Ctrl
按键后点击成员方法run()
:
现在进入了一个方法内容较多的run()方法,继续按着Ctrl
按键后点击成员方法refreshContext(context);
,由于方法体内很多内容并非本节重点,所以不在本节讲解,其中IOC的主要内容都在refreshContext(context)
方法体现。
继续按着Ctrl
按键后点击成员方法refresh(context);
:
继续按着Ctrl + Alt
按键后点击applicationContext
对象的refresh()
方法
下图中refresh
方法参数中的applicationContext
对象大家应该很熟悉,来看一道常见面试题:“BeanFactory与ApplicationContext的区别?”,哈哈哈,想起来了吧~
上述操作之后,页面中会出现三个实现类供我们选择,我们选择实现类ServletWebServerApplicationContext
,该实现类是我们常用的,至于选它的原因不是本节重点,所以不再解释。
继续按着Ctrl
按键后点击父类方法super.refresh();
:
继续按着Ctrl
按键后点击成员方法finishBeanFactoryInitialization(beanFactory);
其中对象构建、注入其他对象、代理、初始化等流程都在该方法中,其他代码不是本节重点,所以不在讲解。
继续按着Ctrl + Alt
按键后点击beanFactory.preInstantiateSingletons();
方法:
我们把断点打在成员方法getBean(beanName);
上
其中遍历的beanNames
方法来自于成员集合变量beanDefinitionNames
,该集合的值来自于@ComponentScan
扫描,以及其他注解操作的结果
3.2、一步步找到在A对象中注入B对象的位置
通过按F9
快速运行程序遍历beanNames
,找到beanName
等于a
的情况停下:
点击F7
进入方法getBean(beanName);
:
点击F7
进入方法doGetBean()
方法:
点击F8
进入下一步,到达getSingleton(beanName);
这一行:
点击F7
进入以下方法:
再次点击F7
进入以下方法:
此时singletonObjects
中不存在名字叫做a
的对象,所以singletonObject
等于null;由于A对象还没有开始创建,所以isSingletonCurrentlyInCreation(beanName)
的结果是false,因此第1
个if方法不会执行,所以最终得知singletonObject
的结果是null
点击两次F8
就可以回到Object sharedInstance = getSingleton(beanName);
:
依然在上述doGetBean
方法中,我们往下找到if (mbd.isSingleton())
这一行,然后在这一行上单击鼠标右键,点击Run toCursor
:
然后点击F8
往下执行一行代码,此时就到了getSingleton
方法处,该方法有2个参数,分别是bean对象名称和一个匿名内部类,此时点击F7
进入getSingleton
方法;
我们首先看下beforeSingletonCreation(beanName);
,进入该方法的方法体看下,其中singletonsCurrentlyInCreation
集合记录了正在创建的对象,对象A不在里面,所以会被加入到singletonsCurrentlyInCreation
集合中
然后往下可以看到singletonObject = singletonFactory.getObject();
这行代码,真正执行的是形参对应的匿名内部类中的实现方法,回顾上一张截图,执行getObject方法其实就是执行createBean
方法:
回顾一下上面那张截图,我们找到createBean
方法:
点击Ctrl + Alt
,在createBean
方法上点击鼠标左键,就可以进入该方法:
继续按着Ctrl
按键后点击成员方法doCreateBean(beanName, mbdToUse, args);
:
在该方法中,往下滚动屏幕,可以看到addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
,先来分析一下addSingletonFactory
方法的方法体;现在回头看下addSingletonFactory
方法的第2个参数,这是一个匿名内部类,我们后面会用到它,大家用笔记一下,后续我们还会回来的~
然后往下滑可以看到populateBean(beanName, mbd, instanceWrapper);
方法:
继续按着Ctrl + Alt
按键后点击成员方法postProcessProperties()
方法:
在A类中注入对象B使用@Resource
注解,所以我们选择CommonAnnotationBeanPostProcessor
类
继续按着Ctrl
按键后点击成员方法metadata.inject(bean, beanName, pvs);
:
继续按着Ctrl
按键后点击成员方法element.inject(target, beanName, pvs);
:
继续按着Ctrl + Alt
按键后点击成员方法getResourceToInject()
方法,选择CommonAnnotationBeanPostProcessor
类:
上述方法的作用就是在A对象中注入B对象
3.3、一步步找到B对象注入A对象的位置
由于在A类中引入的对象B上没有加@Lazy
注解,所以lazyLookup
是false
,因此走getResource
方法,继续按着Ctrl
按键后点击成员方法getResource(this, requestingBeanName)
:
继续按着Ctrl
按键后点击成员方法autowireResource()
:
继续按着Ctrl + Alt
按键后点击方法resolveBeanByName()
:
可以看到代码中调用了getBean
方法,非常熟悉吧,又回到最初的起点,继续根据name
信息获取Bean工厂中的对象,现在是从对象A中准备注入对象B
后面过程和通过对象A的名称来调用getBean(name)
方法一样,我们把过程走一下吧~
- 点击Ctrl:
org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String, java.lang.Class<T>)
- 点击Ctrl:
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
- 点击Ctrl:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)
- 点击Ctrl:
org.springframework.beans.factory.support.AbstractBeanFactory#createBean
- 点击Ctrl:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
- 点击Ctrl:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
- 点击Ctrl + Alt:
org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor#postProcessProperties
,选择CommonAnnotationBeanPostProcessor
类 - 点击Ctrl:
org.springframework.beans.factory.annotation.InjectionMetadata#inject
- 点击Ctrl:
org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement#inject
- 我们在对象B中注入了对象A,此时执行的
field.set(target, getResourceToInject(target, requestingBeanName));
就是获取对象A的 - 点击Ctrl + Alt:
org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement#getResourceToInject
,选择CommonAnnotationBeanPostProcessor
类 - 由于B类中注入的对象A也没加@Lazy注解,所以走
getResource(this, requestingBeanName)
方法 - 点击Ctrl:
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#getResource
- 点击Ctrl:
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#autowireResource
- 点击Ctrl + Alt:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeanByName
- 现在我们来到getBean方法,终极反转了,现在是从对象B中准备注入对象A
3.4、往下找到通过三级缓存解决循环依赖的地方
现在是B对象准备注入对象A,继续按着Ctrl
按键后点击方法getBean(name, descriptor.getDependencyType())
:
继续按着Ctrl
按键后点击方法doGetBean(name, requiredType, null, false)
:
继续按着Ctrl
按键后点击方法getSingleton(beanName)
:
继续按着Ctrl
按键后点击方法getSingleton(beanName, true)
:
然后进入方法体中,由于对象A还没有创建完成,所以一级缓存singletonObjects
肯定没有,因此singletonObject
对象是null
我们看下isSingletonCurrentlyInCreation(beanName)
方法的方法体,其中singletonsCurrentlyInCreation
集合作用在上面已经解释过了,就是记录当前正在被创建的bean对象名称,在处理A对象时调用getSingleton》beforeSingletonCreation
方法中已经将a
存储到了singletonsCurrentlyInCreation
集合中,所以此处isSingletonCurrentlyInCreation(beanName)
方法的结果肯定是true
此时代码继续往下走,由于此时对象A的信息存储在三级缓存singletonFactories
中,因此也无法从earlySingletonObjects
集合中去除对象A的相关信息,所以singletonObject = this.earlySingletonObjects.get(beanName);
的结果也是null
由于上面调用getSingleton()
方法时传入的参数allowEarlyReference
是true
,因此方法会继续向下走
为解决并发创建对象问题,所以需要将一级对象singletonObjects
锁起来,在同步代码块中尝试从一级缓存singletonObjects
和二级缓存earlySingletonObjects
中得到对象A,但是此时对象A存储在三级缓存singletonFactories
中,因此依然获取不到
此时准备从三级缓存singletonFactories
中获取对象A,由于之前在往对象A进行依赖注入之前往三级缓存中填充过值,所以此时singletonFactory
是有值的,来给大家回忆一下填充值的位置
此时singletonFactory
的值就是上图框中的匿名内部类,所属的接口是一个函数式接口,此时代码继续往下执行:
当执行singletonFactory.getObject()
的时候,其实执行的是匿名内部类中的方法,我们把断点打到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference
方法中的if
判断上
继续按着Ctrl + Alt
按键后点击方法bp.getEarlyBeanReference(exposedObject, beanName)
:
此时将没有被包装过的对象放到earlyProxyReferences
集合中,该集合作用是记录已经执行过代理方法的对象信息,即执行过wrapIfNecessary(bean, beanName, cacheKey)
方法的对象信息;由于代理这个动作只能执行一次,下次不能在执行了,所以将已经执行过代理的对象信息记录在earlyProxyReferences
集合中,后续在需要执行代理的时候,我们判断下该集合中是否已经包含,进而决定是否还要执行wrapIfNecessary
方法,这个我们后续会聊到的,这里不在展开~
大家注意现在情况是往对象B
中注入对象A
,目前处于一个获取对象A的过程中,由于A
类的test()
方法上添加了@Transactional
注解,所以在执行wrapIfNecessary
方法之后将会生成A类的代理对象,既然到这里了,我们跟进去看下
继续按着Ctrl
按键后点击方法wrapIfNecessary(bean, beanName, cacheKey)
:
当getAdvicesAndAdvisorsForBean
方法执行之后,就可以知道代理信息,如下:
继续按着Ctrl
按键后点击方法createProxy()
:
进入该方法往下就可以找到真正执行代理的地方,即:proxyFactory.getProxy(classLoader)
,继续按着Ctrl
按键后点击方法proxyFactory.getProxy(classLoader)
:
继续按着Ctrl
按键后点击方法createAopProxy()
:
继续按着Ctrl + Alt
按键后点击方法createAopProxy(this)
:
可以非常清晰的看到使用CGLib还是JDK动态代理方式
最终一层层返回代理结果,那么getEarlyBeanReference()
方法的返回值就是A类的代理对象了,可以非常明显的看到代理对象是CGLib类型的
我们此时依然在B对象中注入A对象的过程中,目前准备获取对象A或者A类的代理对象,上面方法是在执行singletonFactory.getObject()
方法,也就是那个匿名内部类中的方法,得到的结果singletonObject
是一个A类的CGLib代理对象,此时将代理对象装在二级缓存earlySingletonObjects
中
此时就可以完成B类中的对象A注入了
3.5、完成对象B注入对象A
上面已经获取到对象A的代理对象了,我们一部分返回到注入对象A的地方:
- org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
- org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)
- org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
- org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String, java.lang.Class)
- org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeanByName
- org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#autowireResource
- org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#getResource
- org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.ResourceElement#getResourceToInject
- org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement#inject
继续往上,回到对象B完成注入对象A之后,执行初始化代码的地方:
- org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement#inject
- org.springframework.beans.factory.annotation.InjectionMetadata#inject
- org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessProperties
- org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
3.6、完成对象B的剩余流程,进而回到对象A注入对象B的地方
初始化流程就是执行一些初始化方法的过程,由于对象B的初始化流程不是本节重点,所以不在讲解,直接略过
- org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
- org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])
- org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
- org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)
- org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
- org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String, java.lang.Class)
- org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeanByName
- org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#autowireResource
- org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#getResource
- org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.ResourceElement#getResourceToInject
- org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement#inject
现在回到l对象A注入对象B的地方
然后一步步回到对象A的初始化方法代码处:
- org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement#inject
- org.springframework.beans.factory.annotation.InjectionMetadata#inject
- org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessProperties
- org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
3.7、查看对象A不会再次执行代理代码的地方
从上图可见,我们进入initializeBean
方法,主要看方法体中基本快结尾的地方,这个里面隐藏着代理类的生成逻辑:
按着Ctrl点击进去:
我们一次又一次的debug进去,终于发现了生成代理对象的地方,大家熟悉earlyProxyReferences
吗?
在B对象中注入对象A时,已经将对象A的信息塞在earlyProxyReferences
集合中了,具体作用是说明对象A的代理对象已经生成并放在二级缓存earlySingletonObjects
中了,所以if
判断结果是false
,因此wrapIfNecessary(bean, beanName, cacheKey)
不会在执行了,这样可以避免该方法执行2次,并且可以避免无法再bean工厂中生成一致的bean对象
如果没有发生循环依赖,那么org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference
方法不会执行,因此earlyProxyReferences
集合中就不存在键为a
的情况了,如果B类中没有注入对象A就可以达到。但是A类和B类产生了循环依赖,所以earlyProxyReferences
中存储键为a
的情况了
3.8、找到往一级缓存中添加对象A的代理对象的位置
对象A的实例化已经完成,代码继续往下执行,可以看到:
从二级缓存中获得对象A的代理对象:
然后将结果一路返回:
- org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
- org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])
当匿名内部类中的方法执行完毕后,将回到singletonObject = singletonFactory.getObject();
执行完成的状态
往下继续看方法addSingleton()
方法,目前singleonObject是一个:
往一级缓存中放入对象A的代理对象信息
3.9、往对象C中注入对象A
由于对象A已经在一级缓存中了,所以对象C通过getSingleton
方法可以直接获取对象A的代理对象
三、流程分析
在上述示例代码中,对象A中注入对象B、对象C,而对象B和对象C中也会注入对象A,这就造成了循环依赖
-
Spring根据
@SpringBootApplication
注解中@ComponentScan
扫描到了A类(被@Component
注解修饰) -
Spring先去Bean容器中获取对象A,但是发现对象A不在Spring容器中,那就来创建对象A;首先根据A类的构造函数创建对象A,但是对象A不会被放在三级缓存中
-
将匿名内部类放到第3级缓存
singletonFactories
中,在后面解决循环依赖时可以用到
-
在对象A中注入对象B
-
Spring就去Bean容器中获取对象B,但是发现对象B不在Spring容器中,那就来创建对象B;和创建对象A一样,也是根据A类的构造函数创建对象A,然后准备往对象B中注入对象A
-
Spring就去Bean容器中获取对象A,我们在上面将包含对象A信息的匿名内部类放到了第3级缓存
singletonFactories
中,现在我们可以把它取出来,之后调用它的getObject()
方法,其实就是执行匿名内部类中的方法
-
我们来看下匿名内部类中的方法,图片下半部分是方法开始的地方,然后在往上看就可以;大家看下标号①,我们将生成代理对象后的原始对象存在
earlyProxyReferences
集合中,后续当对象A完成初始化方法后通过earlyProxyReferences
集合进行判断,然后决定是否执行生成代理对象的方法,避免其他对象中注入的代理对象和Bean工厂中存储的代理对象不一致;大家再看下标号②,在A类中有一个test()
方法上添加了@Transactional
注解,当执行wrapIfNecessary()
方法之后就会通过CGlib生成一个代理对象A;而原始对象A将被存储在earlyProxyReferences
集合中
-
当代理对象A生成完成,相当于
singletonFactory.getObject()
执行完毕,此时singletonObject
的值就是代理对象A,然后将代理对象A放到第2级缓存earlySingletonObjects
中,并删除第3级缓存singletonFactories
中存储的信息
-
上面通过
getSingleton()
方法获取到了代理对象A,然后交给对象B做注入,最终完成对象B的创建工作,然后将创建完成的对象B返回给对象A做注入,当对象B被注入到对象A之后,那就完成了populateBean()
方法
-
然后初始化也是对象创建道路上的关键一环,现在来执行
initializeBean()
方法,生成代理对象的位置在该方法末尾的地方,即执行applyBeanPostProcessorsAfterInitialization()
方法的地方,其中方法会执行到这个地方;在解决循环依赖(对象A和B相互依赖)的时候,我们把原始对象A已经放到了earlyProxyReferences
集合中,所以此时可以从循环依赖中取到原始对象A,所以if判断的结果是false
,因此就不会执行生成代理对象A的过程了,大家是否还记得,我们之前已经把代理对象A放到了第2级缓存earlySingletonObjects
中了,我们在后续步骤中会从二级缓存中去除代理对象A,然后放到一级缓存中,往下慢慢看
-
此时
exposedObject
依然是原始对象A,然后往下执行getSingleton()
方法来获取代理对象A
-
在解决循环依赖的时候,我们将代理对象放到了2级缓存
earlySingletonObjects
集合中,现在我们取出代理对象赋值给earlySingletonReference
-
然后
doCreateBean()
方法将代理对象A返回,然后createBean()
方法在将代理对象A返回,这也标志着红框中匿名内部类中方法的执行结束,而匿名内部类方法的调用位置在getSingleton()
方法中,也就是getObject()
方法,此时singletonObject
的值就是代理对象A
-
最后调用
addSingleton()
方法将代理对象A放入1级缓存中
-
Spring根据
@SpringBootApplication
注解中@ComponentScan
扫描到了C类(被@Component
注解修饰) -
C类中注入了对象A,所以需要获取对象A,此时对象A已经放到了1级缓存中,所以可以直接取到代理对象A,从而在对象C中注入代理对象A
四、参考文章
- 一文告诉你Spring是如何利用"三级缓存"巧妙解决Bean的循环依赖问题的【享学Spring】
相关文章:
Sping源码:三级缓存
目录 一、概念1、三级缓存的作用2、循环依赖的含义 二、代码1、代码下载2、文件功能介绍3、源码分析3.1、找到获取A对象的位置,打断点进行debug操作3.2、一步步找到在A对象中注入B对象的位置3.3、一步步找到B对象注入A对象的位置3.4、往下找到通过三级缓存解决循环依…...
latex有哪些颜色中文叫什么,Python绘制出来
latex有哪些颜色中文叫什么,Python绘制出来 为了展示xcolor包预定义的颜色及其对应的中文名称,并使用Python打印出来,我们可以先列出常见的预定义颜色名称,然后将它们翻译成中文,并最后用Python打印出来。 步骤 列出…...
C语言进程
什么是进程 什么是程序 一组可以被计算机直接识别的 有序 指令 的集合。 通俗讲:C语言编译后生成的可执行文件就是一个程序。 那么程序是静态还是动态的? 程序是可以被存储在磁盘上的,所以程序是静态的。 那什么是进程 进程是程序的执行过…...
C#基础(4)封装——成员方法
前言 我们在上一节学习了关于类的成员变量的使用,甚至也看到了相应的成员方法,我们可以将二者理解为类里面的变量和函数。 如果我这样说你肯定就能很快理解成员方法是什么作用了。 C#中设计成员方法的目的是为了将相关的功能代码组织在一起࿰…...
springbot,JWT令牌的使用。实现http请求拦截校验。
JWT 由三部分组成,用点(.)分隔 Header(头部) Payload(负载)Signature(签名) 一、原理 Jwt原理其实很简单,在后端首先要有个拦截器,他会拦截所有http请求&…...
【SQL】DDL语句
文章目录 1.SQL通用语法2.SQL的分类3.DDL3.1数据库操作3.2 表操作3.2.1 表操作--数据类型3.2.2 表操作--修改3.2.3 表操作--删除 SQL 全称 Structured Query Language,结构化查询语言。操作关系型数据库的编程语言,定义了一套操作关系型数据库统一标准 。…...
【分页】Spring Boot 列表分页 + javaScript前台展示
后端: 准备好查询实体与分页实体 1、分页工具实体 package com.ruoyi.dms.config;import com.alibaba.nacos.api.model.v2.Result; import lombok.Data;import java.io.Serializable; import java.util.List;/*** author 宁兴星* description: 列表返回结果集*/ …...
「安装」 Windows下安装CUDA和Pytorch
「安装」 Windows下安装CUDA和Pytorch 文章目录 「安装」 Windows下安装CUDA和PytorchMac、Linux、云端Windows安装CUDA安装miniconda安装PyTorch测试总结 其他 Mac、Linux、云端 Mac、Linux、云端安装Miniconda和Pytorch的方法参考其他资料。 Windows 下面进行Windows下安装…...
c语言基础作业
选择题 1.1、以下选项中,不能作为合法常量的是 __________ A)1.234e04 B)1.234e0.4C)1.234e4 D)1.234e0 1.2、以下定义变量并初始化错误的是_____________。 A) char c1 ‘H’ ; B) char c1 9…...
uniapp view增加删除线
推荐学习文档 golang应用级os框架,欢迎stargolang应用级os框架使用案例,欢迎star案例:基于golang开发的一款超有个性的旅游计划app经历golang实战大纲golang优秀开发常用开源库汇总想学习更多golang知识,这里有免费的golang学习笔…...
[Day 83] 區塊鏈與人工智能的聯動應用:理論、技術與實踐
區塊鏈在物聯網中的應用 區塊鏈技術與物聯網(IoT)結合,為許多領域提供了強大的解決方案。傳統的IoT架構常面臨數據隱私和安全問題,而區塊鏈的去中心化和加密技術則能有效增強IoT系統的安全性、透明性和效率。本文將探討區塊鏈如何…...
Java ReentrantLock
目录 1 互斥性 2 公平性 3 可重入性 4 获取和释放锁 5 尝试获取锁 6 可中断的锁定 7 条件变量 8 性能 9 使用场景 ReentrantLock 是 Java 提供的一种可重入的互斥锁,位于 java.util.concurrent.locks 包中,它实现了 Lock 接口。这个锁提供了与内…...
【Linux系统编程】第二十六弹---彻底掌握文件I/O:C/C++文件接口与Linux系统调用实践
✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、回顾C语言文件接口 1.1、以写的方式打开文件 1.2、以追加的方式打开文件 2、初步理解文件 2.1、C文件接口 3、进一步理…...
数据分析-29-基于pandas的窗口操作和对JSON格式数据的处理
文章目录 1 窗口操作1.1 滑动窗口思想1.2 函数df.rolling2 JSON格式数据2.1 处理简单JSON对象和JSON列表2.1.1 处理简单的JSON结构2.1.2 处理空字段2.1.3 获取部分字段2.2 处理多级json2.2.1 展开所有级别(默认)2.2.2 自定义展开层级2.3 处理嵌套列表JSON3 参考附录1 窗口操作 …...
Ubuntu-WSL2一键设置代理操作
现状: Window11中拥有自己的代理软件 ,可以科学上网已在WSL2中安装Ubuntu22.04 需求: Ubuntu-WSL2实现科学上网 实现: 参考:为 WSL2 一键设置代理 Linux 子系统中的网关指向的是 Windows,DNS 服务器指…...
ubuntu命令行连接wifi
在Ubuntu上,你可以通过命令行连接到Wi-Fi网络。以下是详细步骤,主要使用 nmcli 和 nmtui 命令。 方法 1:使用 nmcli nmcli 是 NetworkManager 的命令行界面,用于管理网络连接。以下是使用 nmcli 连接到 Wi-Fi 网络的步骤&#x…...
日常工作第10天:
vim 批量编辑的命令是 移动光标到列首。按键 CtrlV 进入 Visual block 模式,标记你想要进行编辑的列(HJKL可以向左下上右移动光标)。按键 ShiftI 进入 Insert 模式,在列首输入文本;或者 Shift A,追加文本…...
CNN+Transformer解说
CNN(卷积神经网络)和Transformer是两种在深度学习领域广泛使用的模型架构,它们在处理不同类型的数据和任务时各有优势。 CNN擅长捕捉局部特征和空间层次结构,而Transformer擅长处理序列数据和长距离依赖关系。 将CNN与Transform…...
jmeter中token测试
案例: 网站:http://shop.duoceshi.com 讲解:用三个接口来讲解 第一个接口code:GET http://manage.duoceshi.com/auth/code 第二个登录接口:http://manage.duoceshi.com/auth/login 第三个接口:http://…...
基于解压缩模块的JPEG同步重压缩检测论文学习
一、论文基本信息: 论文题目:基于解压缩模块的JPEG同步重压缩检测 作者:王金伟1 ,胡冰涛1 ,张家伟1 ,马 宾2 ,罗向阳3 (1.南京信息工程大学计算机学院、网络空间安全学院…...
音视频入门基础:FLV专题(7)——Tag header简介
一、引言 从《音视频入门基础:FLV专题(3)——FLV header简介》中可以知道, 在FLV header之后,FLV文件剩下的部分应由PreviousTagSize和Tag组成。FLV文件 FLV header PreviousTagSize0 Tag1 PreviousTagSize1 Ta…...
【Linux 报错】“make: ‘xxxx‘ is up to date.” 解决办法
一、报错原因 我们使用 make 命令,想要将 text.c 文件编译形成 可执行文件 text 时,报错如下 make: test is up to date. 中文含义:test 文件已经达到最新状态 意思是: test.c 文件里面的 所有源代码都没有修改过,你…...
【FPGA开发】Xilinx FPGA差分输入时钟的使用方法
正文 以前在使用ZYNQ的领航者ZYNQ7020进行FPGA学习时,它们使用的单端50M的输入时钟,在verlog代码编写上比较简单,而现在使用Alinx的AXU3EG开发板时,发现它使用的是200M的差分输入时钟,哪这个时候,输入时钟要…...
面试扩展知识点
1.C语言中分为下面几个存储区 栈(stack): 由编译器自动分配释放堆(heap): 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收全局区(静态区): 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域&#…...
【经验分享】MyCAT 中间件
学习了一下数据量过大的解决方案,使用 MyCAT 中间件。 MyCAT 可以解决分布式事务、读写分离、主从、分片等一系列MySQL集群和分布式问题。 整体过程可以概括为拦截 - 分发 - 响应 例如设置 MyCAT 分片规则为每500万条数据就换一个数据库存储。 分库分表的中心思想都是将数据…...
Kotlin:1.8.0 的新特性
一、概述 Kotlin 1.8.0版本英语官方文档 Kotlin 1.8.0 中文官方文档 The Kotlin 1.8.0 release is out and here are some of its biggest highlights: Kotlin 1.8.0发布了,下面是它的一些亮点: JVM 平台新增实验性函数:递归复制或删除目录内容改进了 …...
深度学习之开发环境(CUDA、Conda、Pytorch)准备(4)
目录 1.CUDA 介绍 1.1 CUDA 的基本概念 1.2 CUDA 的工作原理 1.3 CUDA 的应用领域 2. 安装CUDA 2.1 查看GPU版本 2.2 升级驱动(可选) 2.3 查看CUDA版本驱动对应的支持的CUDA ToolKit工具包 2.4 下载Toolkit 2.5 安装(省略࿰…...
10月2日笔记(内网资源探测篇)
内网资源探测 在内网渗透中,测试人员往往需要通过各种内网扫描技术来探测内网资源的情况,为后续的横向渗透做准备,通常需要发现内网存活的主机,并探测主机的操作系统、主机开放了哪些端口、端口上运行了哪些服务、服务的当前版本…...
SpringCloud-基于Docker和Docker-Compose的项目部署
一、初始化环境 1. 卸载旧版本 首先,卸载可能已存在的旧版本 Docker。如果您不确定是否安装过,可以直接执行以下命令: sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logro…...
Linux下的基本指令/命令(一)
目录 基本命令 1. Is命令/指令: 罗列当前目录下指定的文件或者目录. 2. pwd命令: 查看当前工作的路径 3. cd命令: 切换到指定路径下。 只能切换到目录中 4. tree命令: 树状显式目录 使用前要输入命令 yum install -y tree ,用来安装一个…...
wordpress自定义网站/北京seo外包
charles抓包的安装,使用说明以及常见问题解决(windows) https://blog.csdn.net/zhangxiang_1102/article/details/77855548转载于:https://www.cnblogs.com/hyzhang/p/8962275.html...
罗岗网站建设公司/网站收录服务
首先,要分别在两个文件中实现以下两个类 class Object { public: NewType ToType(); }; class NewType : public Object { } -------------------------------------------------------------------------------- 做法1 -------------------------------------------------…...
合肥网站建设技术/营销策划咨询
2018-11-20 回答就楼主的问题以及这机子的情况说几句:1.你这是第三代ivb系列i3,属于双核四线程,并非四核u。但综合性能是非常不错的我这里要展开篇幅,说说关于核心工作原理的问题,让楼主从核心数量决定一切的误区中走出…...
龙华民治网站建设公司/哈尔滨seo优化
电脑上的名言锁屏怎么设置的电脑键盘上的“Sleep”这个功能键的作用是,在你暂时不用电脑时,如果按Sleep键,电脑即进入睡眠状态;到要使用电脑时,按任意键又可唤醒电脑重新回到工作状态。如果我们设定在唤醒电脑时需要键…...
php做电商网站的难点/企业网络的组网方案
《JAVA面试题集合》word版.docJAVA面试题集 基础知识1.C或Java中的异常处理机制的简单原理和应用。当JAVA程序违反了JAVA的语义规则时,JAVA虚拟机就会将发生的错误表示为一个异常。违反语义规则包括2种情况。一种是JAVA类库内置的语义检查。例如数组下标越界,会引发…...
怎样购买网站程序/seo优化的基本流程
编辑:ll D50XT100-ASEMI大功率三相整流桥50A 1000V 型号:D50XT100 品牌:ASEMI 封装:DXT-5 电性参数:50A 1000V 电流:50A 电压:1000V 引脚数量:5 特性:大功率三相…...