Spring(11. 循环依赖 - 周阳)学习笔记

news2024/12/26 12:14:16

上一篇 :10. 面试问题简析

文章目录

  • 1. Spring AOP
    • 1.1. Aop 常用注解
    • 1.2 测试前的准备工作
      • 1.2.1 业务类
      • 1.2.2 切面类
    • 1.3 Spring4 下的测试
      • 1.3.1 POM 文件
      • 1.3.2 创建测试类
      • 1.3.3 Aop 测试结果
    • 1.4 Spring 5 下的测试
      • 1.4.1 POM 文件
      • 1.4.2 创建测试类
      • 1.4.3 Aop 测试结果
    • 1.5 Aop 执行顺序总结
  • 2. Spring 循环依赖
    • 2.1 大厂面试题
    • 2.2 什么是循环依赖?
    • 2.3 两种注入方式对循环依赖的影响
    • 2.4 Spring容器循环依赖及异常情况演示
      • 2.4.1 构造器方式注入依赖
      • 2.4.2 Setter 方式注入
      • 2.4.3 Spring 容器演示循环依赖
      • 2.4.4 Spring 内部使用三级缓存解决循环依赖
    • 2.5 源码 Deug 前置知识
      • 2.5.1 实例化 & 初始化
      • 2.5.2 三级缓存 + 四大方法
      • 2.5.3 对象在三级缓存中的迁移
    • 2.6 详细 Debug 流程
    • 2.7、循环依赖总结
      • 2.7.1 三级缓存总结
      • 2.7.2 Debug 步骤总结

1. Spring AOP

1.1. Aop 常用注解

  • @Before 前置通知: 目标方法之前执行
  • @After 后置通知: 目标方法之后执行(始终执行)
  • @AfterReturning 返回后通知: 执行方法结束前执行(异常不执行)
  • @AfterThrowing 异常通知: 出现异常时候执行
  • @Around 环绕通知: 环绕目标方法执行

1.2 测试前的准备工作

1.2.1 业务类

  • 创建业务接口类:CalcService

    public interface CalcService {
        public int div(int x, int y);
    }
    
  • 创建业务接口的实现类:CalcServiceImpl

    @Service
    public class CalcServiceImpl implements CalcService {
        @Override
        public int div(int x, int y) {
            int result = x / y;
            System.out.println("=========>CalcServiceImpl被调用了,我们的计算结果:" + result);
            return result;
        }
    }
    

1.2.2 切面类

  • 想在除法方法前后各种通知,引入切面编程

    @Aspect:指定一个类为切面类
    @Component:纳入 Spring 容器管理

  • 创建切面类 MyAspect

    @Aspect
    @Component
    public class MyAspect {
        @Before("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))")
        public void beforeNotify() {
            System.out.println("******** @Before我是前置通知MyAspect");
        }
    
        @After("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))")
        public void afterNotify() {
            System.out.println("******** @After我是后置通知");
        }
    
        @AfterReturning("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))")
        public void afterReturningNotify() {
            System.out.println("********@AfterReturning我是返回后通知");
        }
    
        @AfterThrowing("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))")
        public void afterThrowingNotify() {
            System.out.println("********@AfterThrowing我是异常通知");
        }
    
        @Around("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))")
        public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            Object retValue = null;
            System.out.println("我是环绕通知之前AAA");
            retValue = proceedingJoinPoint.proceed();
            System.out.println("我是环绕通知之后BBB");
            return retValue;
        }
    }
    

1.3 Spring4 下的测试

1.3.1 POM 文件

  • 在 POM 文件中导入 SpringBoot 1.5.9.RELEASE 版本

  • SpringBoot 1.5.9.RELEASE 版本的对应的 Spring 版本为 4.3.13 Release

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <!-- <version>2.3.3.RELEASE</version> -->
            <version>1.5.9.RELEASE</version>
            <relativePath/>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
       ……
    </project>
    

1.3.2 创建测试类

  • 注意:SpringBoot 1.5.9 版本在测试类上需要加上 @RunWith(SpringRunner.class) 注解,单元测试需要导入的包名为 import org.junit.Test;

    @SpringBootTest
    @RunWith(SpringRunner.class)  //1.5.9
    public class AopTest {
        @Autowired
        private CalcService calcService;
    
        @Test
        public void testAop4() {
            System.out.println("spring版本:" + SpringVersion.getVersion() + "\t" + "SpringBoot版本:" + SpringBootVersion.getVersion());
            System.out.println();
            calcService.div(10, 2);
            // calcService.div(10, 0);
        }
    }
    

1.3.3 Aop 测试结果

  • 正常执行的结果
    环绕通知将前置通知与目标方法包裹住,执行完 @After 才执行 @AfterReturning
    在这里插入图片描述

  • 异常执行的结果
    由于抛出了异常,因此环绕通知后半部分没有执行,执行完 @After 才执行 @AfterThrowing
    在这里插入图片描述
    注:Spring4 默认用的是 JDK 的动态代理

1.4 Spring 5 下的测试

1.4.1 POM 文件

  • 在 POM 文件中导入 SpringBoot 2.3.3…RELEASE 版本

  • SpringBoot 2.3.3.RELEASE 版本的对应的 Spring 版本为 5.2.8 Release

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.3.RELEASE</version>
            <!-- <version>1.5.9.RELEASE</version> -->
            <relativePath/>
        </parent>
        <modelVersion>4.0.0</modelVersion>
        ……
    </project>
    

1.4.2 创建测试类

  • 在 Spring4 的测试类下修改代码

  • 注意:SpringBoot 2.3.3 版本下,不需要在测试类上面添加 @RunWith(SpringRunner.class) 直接,单元测试需要导入的包名为 import org.junit.jupiter.api.Test;,不再使用 import org.junit.Test;

    @SpringBootTest
    public class AopTest {
        @Autowired
        private CalcService calcService;
    
        @Test
        public void testAop4() {
            System.out.println("spring版本:" + SpringVersion.getVersion() + "\t" + "SpringBoot版本:" + SpringBootVersion.getVersion());
            System.out.println();
            calcService.div(10, 0);
        }
    
        @Test
        public void testAop5() {
            System.out.println("spring版本:" + SpringVersion.getVersion() + "\t" + "SpringBoot版本:" + SpringBootVersion.getVersion());
            System.out.println();
            calcService.div(10, 5);
        }
    }
    

1.4.3 Aop 测试结果

  • 正常执行的结果
    感觉 Spring5 的环绕通知才是真正意义上的华绕通知,它将其他通知和方法都包裹起来了,而且 @AfterReturning 和 @After 之前,合乎逻辑!
    在这里插入图片描述
  • 异常执行的结果
    由于方法抛出了异常,因此环绕通知后半部分没有执行,并且 @AfterThrowing 和 @After 之前
    在这里插入图片描述

1.5 Aop 执行顺序总结

在这里插入图片描述

2. Spring 循环依赖

2.1 大厂面试题

  1. 你解释下spring中的三级缓存?
  2. 三级缓存分别是什么?三个Map有什么异同?
  3. 什么是循环依赖?请你谈谈?看过 Spring源码吗?一般我们说的 Spring容器是什么?
  4. 如何检测是否存在循环依赖?实际开发中见过循环依赖的异常吗?
  5. 多例的情况下,循环依赖问题为什么无法解决?
    ……

2.2 什么是循环依赖?

  • 多个 bean 之间相互依赖,形成了一个闭环

  • 比如:A 依赖于 B、B 依赖于 C、C 依赖于 A

  • 通常来说,如果问 Spring 容器内部如何解决循环依赖, 一定是指默认的单例 Bean 中,属性互相引用的场景。也就是说,Spring 的循环依赖,是 Spring 容器注入时候出现的问题

    在这里插入图片描述

2.3 两种注入方式对循环依赖的影响

  1. 构造器注入:容易造成无法解决的循环依赖,不推荐使用(If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.)

  2. Setter 注入:推荐使用 setter 方式注入单例 bean

结论:我们 AB 循环依赖问题只要 A 的注入方式是 setter 且 singleton,就不会有循环依赖问题

2.4 Spring容器循环依赖及异常情况演示

2.4.1 构造器方式注入依赖

  • ServiceA

    @Component
    public class ServiceA {
        private ServiceB serviceB;
        public ServiceA(ServiceB serviceB) {
            this.serviceB = serviceB;
        }
    }
    
  • ServiceB

    @Component
    public class ServiceB {
        private ServiceA serviceA;
        public ServiceB(ServiceA serviceA) {
            this.serviceA = serviceA;
        }
    }
    
  • ClientConstructor

    /**
     * 通过构造器的方式注入依赖,构造器的方式注入依赖的bean,下面两个bean循环依赖
     *
     * 测试后发现,构造器循环依赖是无法解决的
     */
    public class ClientConstructor {
        public static void main(String[] args) {
            new ServiceA(new ServiceB(new ServiceA(new ServiceB()))); ....
        }
    }
    
  • 结论:

    构造器注入没有办法解决循环依赖, 你想让构造器注入支持循环依赖,是不存在的。如果构造器能够解决循环依赖问题,那么我就可以无限套娃~

2.4.2 Setter 方式注入

  • ServiceA

    @Component
    public class ServiceA {
        private ServiceB serviceB;
        public void setServiceB(ServiceB serviceB) {
            this.serviceB = serviceB;
            System.out.println("A 里面设置了B");
        }
    }
    
  • ServiceB

  • ClientConstructor

    public class ClientSet {
        public static void main(String[] args) {
            //创建serviceA
            ServiceA serviceA = new ServiceA();
            //创建serviceB
            ServiceB serviceB = new ServiceB();
            //将serviceA注入到serviceB中
            serviceB.setServiceA(serviceA);
            //将serviceB注入到serviceA中
            serviceA.setServiceB(serviceB);
        }
    }
    
  • 结论:

    setter 方式可以解决循环依赖问题

2.4.3 Spring 容器演示循环依赖

  • 类 A

    public class A {
        private B b;
        public B getB() {
            return b;
        }
        public void setB(B b) {
            this.b = b;
        }
        public A() {
            System.out.println("---A created success");
        }
    }
    
  • 类 B
    同 A

  • ClientSpringContainer 类

    /**
     * 只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,因为单例的时候只有一份,随时复用,那么就放到缓存里面
     * 而多例的bean,每次从容器中荻取都是—个新的对象,都会重B新创建,所以非单例的bean是没有缓存的,不会将其放到三级缓存中。
     */
    public class ClientSpringContainer {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            A a = context.getBean("a", A.class);
            B b = context.getBean("b", B.class);
        }
    }
    
  • 在 resources 文件夹下创建 applicationContext.xml 文件,对 bean 中的属性进行注入

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd">
        <!--
            1.spring容器默认的单例模式可以解决循环引用,单例默认支持
            2.spring容器原型依赖模式scope="prototype"多例模式下不能解决循环引用
        -->
        <!--depends-on 的意思就是当前这个bean如果要完成,先看depends-on指定的bean是否已经完成了初始化-->
        <!--scope="prototype"代表每次都要新建一次对象-->
        <bean id="a" class="com.heygo.spring.circulardependency.A">
            <property name="b" ref="b"/>
        </bean>
        <bean id="b" class="com.heygo.spring.circulardependency.B">
            <property name="a" ref="a"/>
        </bean>
    </beans>
    
  • scope = “singleton”,默认的单例(Singleton)的场景是支持循环依赖的,不报错

  • beanA 和 beanB 都创建成功了,程序没有抛异常
    在这里插入图片描述

  • scope = “prototype”,原型(Prototype)的场景是不支持循环依赖的,报错

  • 将 bean 的生命周期改为 prototype
    在这里插入图片描述

  • 此时就会报错 : BeanCurrentlyInCreationException: Error creating bean with name ‘a’: Requested bean is currently in creation: Is there an unresolvable circular reference

2.4.4 Spring 内部使用三级缓存解决循环依赖

  • 所谓的三级缓存其实就是 Spring 容器内部用来解决循环依赖问题的三个 Map,这三个 Map 在 DefaultSingletonBeanRegistry 类中
    在这里插入图片描述

    • 第一级缓存:Map<String, Object> singletonObjects,我愿称之为成品单例池,常说的 Spring 容器就是指它,我们获取单例 bean 就是在这里面获取的,存放已经经历了完整生命周期的Bean对象

    • 第二级缓存:Map<String, Object> earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整,可以认为是半成品的 bean)

    • 第三级缓存:Map<String, ObiectFactory<?>> singletonFactories,存放可以生成Bean的工厂,用于生产(创建)对象

  • 结论

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

2.5 源码 Deug 前置知识

2.5.1 实例化 & 初始化

  • 实例化和初始化的区别
    1. 实例化:堆内存中申请一块内存空间
      在这里插入图片描述
    2. 初始化:完成属性的填充
      在这里插入图片描述

2.5.2 三级缓存 + 四大方法

  • 三级缓存

    • 第一级缓存:存放的是已经初始化好了的Bean,bean名称与bean实例相对应,即所谓的单例池。表示已经经历了完整生命周期的Bean对象

    • 第一级缓存:存放的是实例化了,但是未初始化的Bean,bean名称与bean实例相对应。表示Bean的生命周期还没走完(Bean的属性还未填充)就把这个Bean存入该缓存中。也就是实例化但未初始化的bean放入该缓存里

    • 第三级缓存:表示存放生成bean的工厂,存放的是FactoryBean,bean名称与bean工厂对应。假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean

  • 四大方法

    • getSingleton():从容器里面获得单例的bean,没有的话则会创建 bean
    • doCreateBean():执行创建 bean 的操作(在 Spring 中以 do 开头的方法都是干实事的方法)
    • populateBean():创建完 bean 之后,对 bean 的属性进行填充
    • addSingleton():bean 初始化完成之后,添加到单例容器池中,也即添加到一级缓存中,下次执行 getSingleton() 方法时就能获取到
  • 注:关于三级缓存 Map<String, ObjectFactory<?>> singletonFactories的说明,singletonFactories 的 value 为 ObjectFactory 接口实现类的实例。ObjectFactory 为函数式接口,在该接口中定义了一个 getObject() 方法用于获取 bean,这也正是工厂思想的体现(工厂设计模式)
    在这里插入图片描述

2.5.3 对象在三级缓存中的迁移

  • A/B 两对象在三级缓存中的迁移说明
  1. A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B

  2. B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A,然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A

  3. B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态),然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

2.6 详细 Debug 流程

  • 先打断点
    在这里插入图片描述

  • 进入 new ClassPathXmlApplicationContext("applicationContext.xml")

    • 先步入,进入一个静态代码块,不需要关注
    • 接着跳出,回到原来行
    • 再步入,进入以下构造方法,但是再该构造方法中又调用了一个重载的构造方法
      在这里插入图片描述
  • 步入重载的构造方法

    在这里插入图片描述

  • 步入 refresh()

    在这里插入图片描述

  • 步入 finishBeanFactoryInitialization(beanFactory)
    在这里插入图片描述

  • 步入 preInstantiateSingletons()

    在这里插入图片描述

  • 步入 getBean(beanName)
    在这里插入图片描述

  • 步入 doGetBean

    在这里插入图片描述

  • 步入 getSingleton(beanName)

  • 开始返回,一直返回到 doGetBean 方法中 Object sharedInstance = getSingleton(beanName);

  • 继续向下执行
    在这里插入图片描述

  • 进入 getSingleton()
    在这里插入图片描述

  • 步入 createBean(beanName, mbd, args)
    在这里插入图片描述

  • 步入 doCreateBean(beanName, mbdToUse, args)
    在这里插入图片描述

  • 步入 populateBean(beanName, mbd, instanceWrapper)
    在这里插入图片描述

  • 步入 applyPropertyValues(beanName, mbd, bw, (PropertyValues)pvs)
    在这里插入图片描述

  • 步入 resolveValueIfNecessary(pv, originalValue)
    在这里插入图片描述

  • 步入 resolveReference(argName, ref)
    在这里插入图片描述

  • 步入 getBean(resolvedName),又回到了这个熟悉的方法
    在这里插入图片描述

  • 再次步入 doGetBean,下面又是和上文中相同的路径,只不过这次是从一级缓存中获取 BeanB
    在这里插入图片描述

  • 再次步入 createBean
    在这里插入图片描述

  • 再次步入 populateBean
    在这里插入图片描述

  • 再次步入 applyPropertyValues,……,沿着刚刚的路径一直走,直到再再一次进入 doGetBean,再再一次进入 getSingleton(beanName)
    在这里插入图片描述

  • getSingleton 方法返回,回到 doGetBean

    在这里插入图片描述

  • 开始返回, 直到 applyPropertyValues -> resolveValueIfNecessary在这里插入图片描述

  • 开始返回 ,返回到 doCreateBean 的 populateBean
    在这里插入图片描述

  • 开始返回,返回到 getSingleton 的 singletonFactory.getObject()
    在这里插入图片描述

  • 步入 addSingleton
    在这里插入图片描述

  • 一直返回,回退到 applyPropertyValues,并且沿着前面一样的路径继续执行

    在这里插入图片描述

  • 继续执行,几次返回之后,再次按照前面的路径一直走,步入 getSingleton

在这里插入图片描述

  • 继续执行,直到再次步入 addSingleton
    在这里插入图片描述

2.7、循环依赖总结

2.7.1 三级缓存总结

  • Spring创建bean主要分为两个步骤,创建原始bean对象,接着去填充对象属性和初始化

    • 每次创建bean之前,我们都会从缓存中查下有没有该bean,因为是单例,只能有一个。
    • 当我们创建 beanA的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了beanB,接着就又去创建beanB,同样的流程,创建完 beanB填充属性时又发现它依赖了beanA又是同样的流程。
    • 不同的是:这时候可以在三级缓存中查到刚放进去的原始对象beanA,所以不需要继续创建,用它注入beanB,完成beanB的创建既然 beanB创建好了,所以beanA就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成
    • Spring解决循环依赖依靠的是Bean的“中间态"这个概念,而这个中间态指的是已经实例化但还没初始化的状态,也即半成品。
    • 实例化的过程又是通过构造器创建的,如果A还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。
      在这里插入图片描述
  • Spring为了解决单例的循环依赖问题,使用了三级缓存:

    1. 其中一级缓存为单例池〈 singletonObjects)
    2. 二级缓存为提前曝光对象( earlySingletonObjects)
    3. 三级缓存为提前曝光对象工厂( singletonFactories)
  • 假设A、B循环引用,实例化A的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了B,同样的流程也是实例化后放入三级缓存,接着去填充属性时又发现自己依赖A,这时候从缓存中查找到早期暴露的A,没有AOP代理的话,直接将A的原始对象注入B,完成B的初始化后,进行属性填充和初始化,这时候B完成后,就去完成剩下的A的步骤,如果有AOP代理,就进行AOP处理获取代理后的对象A,注入B,走剩下的流程。

2.7.2 Debug 步骤总结

  1. 调用doGetBean()方法,想要获取beanA,于是调用getSingleton()方法从缓存中查找beanA
  2. 在getSingleton()方法中,从一级缓存中查找,没有,返回null
  3. doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的)
  4. 在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的creatBean()方法
  5. 进入AbstractAutowireCapableBeanFactory#doCreateBean(),先反射调用构造器创建出beanA的实例,然后判断是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中〈即是否在第四步的集合中)。判断为true则将beanA添加到【三级缓存】中
  6. 对beanA进行属性填充,此时检测到beanA依赖于beanB,于是开始查找beanB
  7. 调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性
  8. 此时beanB依赖于beanA,调用getsingleton()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA
  9. 这样beanB就获取到了beanA的依赖,于是beanB顺利完成实例化,并将beanA从三级缓存移动到二级缓存中
  10. 随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也随之完成了创建,回到getsingleton()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中

在这里插入图片描述

流程图链接:https://www.processon.com/view/link/644926c0e6ec87493a22d446

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

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

相关文章

e签宝,「进化」在2023

精准布局生态化、统一化、智能化、信创化&#xff0c;辅以具体产品落地&#xff1b;加速产业、行业、企业、业务&#xff0c;“四业”互通互联&#xff0c;提高产业数字化渗透率。电子签“群战”时代&#xff0c;e签宝再次进化。 作者|斗斗 出品|产业家 “印章在谁手上&…

Camtasia 2023版强悍来袭,会哪些新功能呢?

Camtasia Studio 是一款专门录制屏幕动作的工具&#xff0c;它能在任何颜色模式下轻松地记录 屏幕动作&#xff0c;包括影像、音效、鼠标移动轨迹、解说声音等等&#xff0c;另外&#xff0c;它还具有即时播放和编 辑压缩的功能&#xff0c;可对视频片段进行剪接、添加转场效果…

享受简单上传体验:将Maven仓库迁移到GitHub

前言&#xff1a;我为什么放弃了Maven Central 之前我写过一篇《Android手把手&#xff0c;发布开源组件至 MavenCentral仓库》&#xff0c;文中详细介绍了如何发布组件到Maven Central中供所有开发者共用。但是最近使用下来&#xff0c;发现Sonatype JIRA 的Maven Center上传…

python接口自动化测试 requests库的基础使用

目录 简单介绍 Get请求 Post请求 其他类型请求 自定义headers和cookies SSL 证书验证 响应内容 获取header 获取cookies 简单介绍 requests库简单易用的HTTP库 Get请求 格式&#xff1a; requests.get(url) 注意&#xff1a;若需要传请求参数&#xff0c;可直接在 …

c++STL标准库排序函数std::sort使用

Qt系列文章目录 文章目录 Qt系列文章目录前言一、错误原因二、修改后的代码 前言 C sort()排序函数 C STL 标准库中的 sort() 函数&#xff0c;本质就是一个模板函数。正如表 1 中描述的&#xff0c;该函数专门用来对容器或普通数组中指定范围内的元素进行排序&#xff0c;排序…

【ML】windows 安装使用pytorch

使用pytorch需要python环境&#xff0c;建议是直接装anaconda &#xff0c;IDE用visual studio anaconda安装 Anaconda 是一个用于科学计算的 Python 发行版&#xff0c;支持 Linux, Mac, Windows, 包含了众多流行的科学计算、数据分析的 Python 包 官网链接anaconda 本人下载…

(转)maven安装及配置(详细版)

1.下载&#xff1a; 方式一可以从官方下载&#xff0c;下载页面&#xff1a;http://maven.apache.org/download.cgi 方式二&#xff1a;或者题主提供的版本下载maven安装包 提取码&#xff1a;ysns 下载好后是一个压缩文件 2.安装&#xff1a; maven压缩包解压到一个没有中文&a…

AI 编程

GitHub Copilot&#xff08;收费&#xff09; 开发者&#xff1a;微软 openAI 2022年8月22日之后开始收费&#xff0c;10美元/月&#xff0c;100美元/年。 CodeGeeX&#xff08;免费&#xff09; CodeGeeX 可以根据自然语言注释描述&#xff08;支持中英文注释&#xff09…

20.$refs

$refs是vue操作DOM用的&#xff0c;每一个vue组件实例上&#xff0c;都包含一个$refs对象&#xff0c;里面存储对应的DOM元素或组件的引用&#xff0c;默认情况下$refs对象为空 目录 1 $refs在哪 2 使用ref操作DOM 3 使用ref操作组件 3.1 使用组件方法 3.2 操作组件…

13 JS04——运算符

目标&#xff1a; 1、运算符 2、算数运算符 3、递增和递减运算符 4、比较运算符 5、逻辑运算符 6、赋值运算符 7、运算符优先级 一、运算符 1、概念 运算符&#xff08;operator&#xff09;也被称作操作符&#xff0c;是用于实现赋值、比较和执行算数运算等功能的符号。 2…

解决java普通项目读取不到resouces目录下资源文件的办法

现象如下&#xff1a; 可以看到resources目录已经在idea中标记成了资源目录resources root&#xff0c;而且target/classes目录下也编译出了resources目录下的pci.properties文件&#xff0c;换句话说&#xff1a;java在编译时是读取到了resources下的文件的。 可是为什么new F…

App性能优化方案——布局层级太多怎么优化?

作者&#xff1a;小海编码日记 View整体布局是通过深度优先的方式来进行组织的&#xff0c;整体形似一颗树&#xff0c;所以优化布局层级主要通过三个方向来实施&#xff1a; 降低布局深度&#xff1a;使用merge标签或者布局层级优化等手段来减少View树的深度&#xff1b;布局…

代码随想录算法训练营第四十三天|1049. 最后一块石头的重量 II 、494. 目标和、474.一和零

文章目录 背包问题题型1049. 最后一块石头的重量 II494. 目标和474.一和零 背包问题题型 等和子集 —0-1背包能否装满最后一块石头—0-1背包尽量装满目标和—0-1背包装满&#xff0c;且有多少种装的方式&#xff08;组合问题&#xff09; 1049. 最后一块石头的重量 II 题目链…

网页爬虫之WebPack模块化解密(JS逆向)

WebPack打包: webpack是一个基于模块化的打包&#xff08;构建&#xff09;工具, 它把一切都视作模块。 概念&#xff1a; webpack是 JavaScript 应用程序的模块打包器,可以把开发中的所有资源&#xff08;图片、js文件、css文件等&#xff09;都看成模块&#xff0c;通过loade…

Java中Lambda表达式(面向初学者)

目录 一、Lambda表达式是什么&#xff1f;什么场景下使用Lambda&#xff1f; 1.Lambda 表达式是什么 2.函数式接口是什么 第二章、怎么用Lambda 1.必须有一个函数式接口 2.省略规则 3.Lambda经常用来和匿名内部类比较 第三章、具体使用场景举例&#xff08;&#xff09; …

水果店(库)管理系统 —— 实现了管理员模式与顾客模式 JAVA

水果店&#xff08;库&#xff09;管理系统 1.前言&#xff1a;2.功能简介及部分测试视频&#xff1a;3.本管理系统的构建原理&#xff08;简介)&#xff1a;(1).如何跳转页面&#xff1a;(2).如何让控制台能输出彩色字体&#xff1a;(3).如何稳定存储数据&#xff1a;(4).如何…

误操作清空了回收站文件如何找到文件

我们在删除文件的时候&#xff0c;文件都是先跑到回收站里的&#xff0c;这样的防止我们出现误删的情况&#xff0c;但往往也会出现我们要恢复删除的文件却误操作清空了回收站的情况&#xff0c;那么误操作清空了回收站如何找到呢&#xff0c;下面小编给大家分享误操作清空了回…

window 10 安装node.js时遇到2502 2503错误(已解决)

node安装失败2503的解决办法&#xff1a;1、在WIN搜索框搜索powershell并右击&#xff1b;2、点击使用管理员身份运行powershell命令行工具&#xff1b;3、输入“msiexec /package node”&#xff1b;4、打开安装包&#xff0c;根据提示安装即可。 本文操作环境&#xff1a;Win…

9.Join的应用

1.reduceJoin的应用 案例&#xff1a;将两个表合并成一个新的表 需求分析&#xff1a;通过将关联条件作为Map输出的key&#xff08;此处指pid&#xff09;&#xff0c;将两表满足Join条件的数据并携带数据所来源的文件信息&#xff0c;发往同一个ReduceTask&#xff0c;在Redu…

汇编小程序解析--3D立方体旋转

汇编小程序解析–3D立方体旋转&#xff0c;源代码如下&#xff0c;是vulture大神于1995年写的&#xff0c;我到现在才基本看懂。 ;本程序由国外的Vulture大哥编写&#xff0c;并公布了源码&#xff0c;这个是他95年的一个作品&#xff0c;可以说是在当时是非常成功的&#xff…