Spring面试重点(三)——AOP循环依赖

news2024/12/27 17:40:19

Spring面试重点

AOP

  • 前置通知(@Before):在⽬标⽅法运行之前运行;
  • 后置通知(@After):在⽬标⽅法运行结束之后运行;
  • 返回通知(@AfterReturning):在⽬标⽅法正常返回之后运行;
  • 异常通知(@AfterThrowing):在⽬标⽅法出现异常是运行;
  • 环绕通知(@Around):手动推荐⽬标⽅法运行(proceedingJoinPoint.proceed())。

通用AOP日志切面类

<!-- springboot-aop 技术 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

import java.util.Arrays;

/**
 * Title:通用日志切面
 * Description:
 * @author WZQ
 * @version 1.0.0
 * @date 2020/12/1
 */
@Aspect
public class LogAspects {

    /**
     * 公共的切⼊入点表达式
     * 1、本类引⽤
     * 2、其他的切⾯引⽤
     * @author wzq
     * @date 2018/11/1
     */
    @Pointcut(value = "execution(public int com.wzq.spring.study.service.CalcService.*(..))")
    public void pointCut() {
    }

    /**
     * 前置通知
     * pointCut切点指定方法
     * @param joinPoint joinPoint参数⼀一定要出现在参数列列表第⼀一位,放在后⾯面会报错
     */
    @Before(value = "pointCut()")
    public void logStart(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("@Before-->方法前,前置通知" + ",方法名:" + methodName + ",参数列列表是:" + Arrays.toString(args));
    }

    /**
     * 后置通知
     */
    @After(value = "pointCut()")
    public void logEnd() {
        System.out.println("@After-->方法后,后置通知");
    }

    /**
     * 返回通知
     * @param joinPoint
     * @param result
     */
    @AfterReturning(value = "pointCut()", returning = "result")
    public void logReturn(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("@AfterReturning-->返回通知,方法名:" + methodName + ",计算结果:" + result);
    }

    /**
     * 异常通知
     * @param joinPoint
     * @param exception
     */
    @AfterThrowing(value = "pointCut()", throwing = "exception")
    public void logException(JoinPoint joinPoint, Exception exception) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("@AfterThrowing-->异常通知,方法名:" + methodName + ",异常信息:" + exception);
    }

    /**
     * 环绕通知
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object retValue = null;
        System.out.println("@Around-->我是环绕通知之前AAA");
        retValue = proceedingJoinPoint.proceed();
        System.out.println("@Around-->我是环绕通知之后BBB");
        return retValue;
    }

}
import com.wzq.spring.study.service.CalcService;
import com.wzq.spring.study.service.impl.CalcServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * aop
 * 在程序运⾏期间动态的将某段代码切⼊到指定⽅方法指定位置进⾏运行的编程⽅式
 * '@EnableAspectJAutoProxy':开启基于注解的aop动态代理
 *
 * @author wzq
 * @date 2018/11/1
 */
@Configuration
@EnableAspectJAutoProxy
public class ConfigOfAOP {

    /**
     * 业务逻辑类加⼊入容器中
     * @author wzq
     * @date 2018/11/1
     */
    @Bean
    public CalcService calculator() {
        return new CalcServiceImpl();
    }

    /**
     * 切面类加⼊入容器中
     * @author wzq
     * @date 2018/11/1
     */
    @Bean
    public LogAspects logAspects() {
        return new LogAspects();
    }
}

//也可以不需要此类,直接在切面类LogAspects加@Component注解注入容器即可,因为配置了切点

aop注解执行顺序

业务逻辑:

public class CalcServiceImpl implements CalcService {

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

}

测试

@Resource
    private CalcService calcService;

    @Test
    void aopTest1() {
        System.out.println("spring版本:"+ SpringVersion.getVersion()+"\t"+"SpringBoot版本:"+ SpringBootVersion.getVersion());

        System.out.println();

        calcService.div(10,2);
    }

spring5.2.8后正常顺序和异常顺序(注意:实际是SpringBoot2.3.3版本之后):

spring版本:5.2.8.RELEASE	SpringBoot版本:2.3.3.RELEASE

@Around-->我是环绕通知之前AAA
@Before-->方法前,前置通知,方法名:div,参数列列表是:[10, 2]
=========>CalcServiceImpl被调用了,我们的计算结果:5
@AfterReturning-->返回通知,方法名:div,计算结果:5
@After-->方法后,后置通知
@Around-->我是环绕通知之后BBB



@Around-->我是环绕通知之前AAA
@Before-->方法前,前置通知,方法名:div,参数列列表是:[10, 0]
@AfterThrowing-->异常通知,方法名:div,异常信息:java.lang.ArithmeticException: / by zero
@After-->方法后,后置通知

java.lang.ArithmeticException: / by zero

spring4,spring5前期正常顺序和异常顺序:

spring版本:4.3.13.RELEASE	SpringBoot版本:1.5.9.RELEASE

@Around-->我是环绕通知之前AAA
@Before-->方法前,前置通知,方法名:div,参数列列表是:[10, 2]
=========>CalcServiceImpl被调用了,我们的计算结果:5
@Around-->我是环绕通知之后BBB
@After-->方法后,后置通知
@AfterReturning-->返回通知,方法名:div,计算结果:5



spring版本:5.2.2.RELEASE	SpringBoot版本:2.2.2.RELEASE

@Around-->我是环绕通知之前AAA
@Before-->方法前,前置通知,方法名:div,参数列列表是:[10, 2]
=========>CalcServiceImpl被调用了,我们的计算结果:5
@Around-->我是环绕通知之后BBB
@After-->方法后,后置通知
@AfterReturning-->返回通知,方法名:div,计算结果:5



spring版本:4.3.13.RELEASE	SpringBoot版本:1.5.9.RELEASE

@Around-->我是环绕通知之前AAA
@Before-->方法前,前置通知,方法名:div,参数列列表是:[10, 0]
@After-->方法后,后置通知
@AfterThrowing-->异常通知,方法名:div,异常信息:java.lang.ArithmeticException: / by zero

java.lang.ArithmeticException: / by zero

在这里插入图片描述

Spring循环依赖

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

通常来说,如果问spring容器内部如何解决循环依赖, 一定是指默认的单例Bean中,属性互相引用的场景

也就是说,Spring的循环依赖,是Spring容器注入时候出现的问题,类似死锁现象

在这里插入图片描述

官网说明:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans

在这里插入图片描述

官网可知,解决循环依赖使用setter注入bean,不能使用构造方法注入bean。

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

spring容器循环依赖报错演示BeanCurrentlylnCreationException

循环依赖现象在Spring容器中 注入依赖的对象,有2种情况,构造器方式注入依赖和以set方式注入依赖

构造器方式注入

无法解决循环依赖问题,构造器注入没有办法解决循环依赖, 你想让构造器注入支持循环依赖,是不存在的

@Component
public class ServiceA {

    private ServiceB serviceB;

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

    private ServiceA serviceA;

    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}
/**
 * 通过构造器的方式注入依赖,构造器的方式注入依赖的bean,下面两个bean循环依赖
 *
 * 测试后发现,构造器循环依赖是无法解决的
 */
public class ClientConstructor {
    public static void main(String[] args) {
        //A构造函数依赖B注入,B构造函数依赖A注入,一直反复,无法结束
        new ServiceA(new ServiceB(new ServiceA(new ServiceB()))); ....
    }
}

set方式注入

不会报错

@Component
public class ServiceA {

    private ServiceB b;

    public ServiceB getB() {
        return b;
    }

    public void setB(ServiceB b) {
        this.b = b;
        System.out.println("A 里面设置了B");
    }

}
@Component
public class ServiceB {

    private ServiceA a;

    public ServiceA getA() {
        return a;
    }

    public void setA(ServiceA a) {
        this.a = a;
        System.out.println("B 里面设置了A");
    }

}
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);
    }
}

spring容器

默认的单例(singleton)的set方式注入场景是支持循环依赖的,不报错

原型(Prototype)的场景是不支持循环依赖的,报错,多例下会报循环依赖异常BeanCurrentlylnCreationException

<?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">

    <!--
        1.spring容器默认的单例模式可以解决循环引用,单例默认支持
        2.spring容器原型依赖模式scope="prototype"多例模式下不能解决循环引用
    -->

    <!--depends-on 的意思就是当前这个bean如果要完成,先看depends-on指定的bean是否已经完成了初始化-->
    <!--scope="prototype"代表每次都要新建一次对象-->


    <bean id="a" class="com.wzq.spring.study.service.impl.ServiceA" scope="singleton">
        <property name="b" ref="b"/>
    </bean>

    <bean id="b" class="com.wzq.spring.study.service.impl.ServiceB" scope="singleton">
        <property name="a" ref="a"/>
    </bean>

</beans>
/**
 * nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
 * Error creating bean with name 'a': 578624778
 * Requested bean is currently in creation: Is there an unresolvable circular reference?
 *
 *
 * 只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,因为单例的时候只有一份,随时复用,那么就放到缓存里面
 * 而多例的bean,每次从容器中获取都是一个新的对象,都会重新创建, 
 * 所以非单例的bean是没有缓存的,不会将其放到三级缓存中。
 */
@Test
public void deTest() {    
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    ServiceA a = context.getBean("a", ServiceA.class);   
    ServiceB b = context.getBean("b", ServiceB.class);
    System.out.println(a); 
    System.out.println(b);
}

spring三级缓存

DefaultSingletonBeanRegistry(re zi si g redʒɪstri)

所谓的三级缓存其实就是spring容器内部用来解决循环依赖问题的三个map

在这里插入图片描述

  • 第一级缓存(也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象
  • 第二级缓存: earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)
  • 第三级缓存: Map<String, ObiectFactory<?>> singletonFactories,存放可以生成Bean的工厂

在这里插入图片描述

spring源码分析

实例化/初始化

实例化是堆内存中申请一块内存空间,初始化是属性填充

spring利用单例set注入,解决循环依赖的三级缓存+四大方法

在这里插入图片描述

四大方法:

  1. getSingleton:希望从容器里面获得单例的bean,没有的话
  2. doCreateBean: 没有就创建bean
  3. populateBean: 创建完了以后,要填充属性
  4. addSingleton: 填充完了以后,再添加到容器进行使用

三级缓存:

  1. 第一层singletonObjects存放的是已经初始化好了的Bean,
  2. 第二层earlySingletonObjects存放的是实例化了,但是未初始化的Bean
  3. 第三层singletonFactories存放的是FactoryBean。假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean

在这里插入图片描述

在这里插入图片描述

三级缓存简单理解,AB单例依赖set注入

  1. A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B
  2. B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A,然后把三级缓存里面的这个A放到二级缓存里面(实例,但未初始化),并删除三级缓存里面的A
  3. B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态),然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

总结spring解决循环依赖

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

每次创建bean之前,我们都会从缓存中查下有没有该bean,因为是单例,只能有一个。

当我们创建 beanA的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了beanB,接着就又去创建beanB,同样的流程,创建完 beanB填充属性时又发现它依赖了beanA又是同样的流程。

不同的是:
这时候可以在三级缓存中查到刚放进去的原始对象beanA,所以不需要继续创建,用它注入beanB,完成beanB的创建。

既然 beanB创建好了,所以beanA就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成

在这里插入图片描述

Spring解决循环依赖依靠的是Bean的“中间态"这个概念,而这个中间态指的是已经实例化但还没初始化的状态……>半成品。

实例化的过程又是通过构造器创建的,如果A还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。

Spring为了解决单例的循环依赖问题,使用了三级缓存
其中一级缓存为单例池( singletonObjects)
二级缓存为提前曝光对象( earlySingletonObjects)
三级缓存为提前曝光对象工厂( singletonFactories)。

假设A、B循环引用,实例化A的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了B,同样的流程也是实例化后放入三级缓存,接着去填充属性时又发现自己依赖A,这时候从缓存中查找到早期暴露的A,没有AOP代理的话,直接将A的原始对象注入B,完成B的初始化后,进行属性填充和初始化,这时候B完成后,就去完成剩下的A的步骤,如果有AOP代理,就进行AOP处理获取代理后的对象A,注入B,走剩下的流程。

源码流程图如下:

在这里插入图片描述

debug步骤–》Spring解决循环依赖过程

  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从二级缓存移动到一级缓存中

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

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

相关文章

2023年前端面试知识点总结(CSS篇)

近期整理了一下高频的前端面试题&#xff0c;分享给大家一起来学习。如有问题&#xff0c;欢迎指正&#xff01; 1. 对CSS盒模型的理解 CSS3的盒模型有两种盒子模型&#xff1a;标准盒子模型、IE盒子模型 盒模型都是由四个部分组成的&#xff0c;分别是content&#xff08;内容…

layui框架学习(6:基础菜单)

菜单是应用系统的必备元素&#xff0c;虽然网页中的导航也能作为菜单使用&#xff0c;但菜单和导航的样式和用途有所不同&#xff08;不同之处详见参考文献5&#xff09;。Layui中用不同的预设类定义菜单和导航的样式&#xff0c;同时二者依赖的模块也不一样。本文主要学习和记…

Vue (3)

文章目录1. 数据代理1.1 回顾1.2 开始2. 事件处理2.1 v-on:click 点击事件2.2 事件修饰符2.3 键盘事件3. 计算属性3.1 插值语法实现3.2 methods实现3.3 计算属性实现4. 监视属性4.1 深度监视4.2 监视属性的简写形式4.3 watch 与 computed 对比1. 数据代理 在学习 数据代理 时 先…

SQL数据查询——单表查询和排序

文章目录一、单表查询1.查询列1&#xff09;查询全部列指定列2&#xff09;查询经过计算的值3&#xff09;列的别名2.查询元组1&#xff09;消除取值重复的行(DISTINCT)2&#xff09;条件查询(WHERE)3.空值参与运算4.着重号二、排序(ORDER BY子句)一、单表查询 单表查询指仅涉及…

Webpack的知识要点

在前端开发中&#xff0c;一般情况下都使用 npm 和 webpack。   npm是一个非常流行的包管理工具&#xff0c;帮助开发者管理项目中使用的依赖库和工具。它可以方便地为项目安装第三方库&#xff0c;并在项目开发过程中进行版本控制。   webpack是一个模块打包工具&#xff…

C语言深度剖析之程序环境和预处理

1.程序的翻译环境和执行环境 第一种是翻译环境&#xff0c;在这个环境中源代码被转换为可执行的机器指令 第二种是执行环境&#xff0c;它用于实际执行代码 2.翻译环境 分为四个阶段 预编译阶段 &#xff0c;编译&#xff0c;汇编&#xff0c;链接 程序编译过程&#xff1a;多个…

使用vue3,vite,less,flask,python从零开始学习硅谷外卖(16-40集)

严正声明&#xff01; 重要的事情说一遍&#xff0c;本文章仅供分享&#xff0c;文章和代码都是开源的&#xff0c;严禁以此牟利&#xff0c;严禁侵犯尚硅谷原作视频的任何权益&#xff0c;我知道学习编程的人各种各样的心思都有&#xff0c;但这不是你对开源社区侵权的理由&am…

iptables防火墙之SNAT与DNAT

目录 1、SNAT策略概述 1.SNAT策略的典型应用环境 2.SNAT策略的原理 3.SNAT工作原理 4.SNAT转换前提条件 5.开启SNAT命令 6.SNAT转换 2.SNAT示例 1. 配置网关服务器 2.Xshell 连接192.168.100.100 3.DNAT策略及应用 1. DNAT策略概述 2.DNAT 策略的应用 3.DNAT转换前提条件…

看完这篇 教你玩转渗透测试靶机vulnhub——Hack Me Please: 1

Vulnhub靶机Hack Me Please: 1渗透测试详解Vulnhub靶机介绍&#xff1a;Vulnhub靶机下载&#xff1a;Vulnhub靶机安装&#xff1a;Vulnhub靶机漏洞详解&#xff1a;①&#xff1a;信息收集&#xff1a;②&#xff1a;漏洞利用③&#xff1a;获取反弹shell&#xff1a;④&#x…

how https works?https工作原理

简单一句话&#xff1a; https http TLShttps 工作原理&#xff1a;HTTPS (Hypertext Transfer Protocol Secure)是一种带有安全性的通信协议&#xff0c;用于在互联网上传输信息。它通过使用加密来保护数据的隐私和完整性。下面是 HTTPS 的工作原理&#xff1a;初始化安全会…

Camtasia2023最新版电脑视频录屏记录编辑软件

在Mac或Wind上有各种可用的视频记录和编辑软件&#xff0c;其中Camtasia被称为视频记录器和视频编辑器。录屏软件Camtasia2023到底有什么特色功能&#xff1f;本文将帮助您选择理想的选择来开始视频捕获&#xff0c;创建和编辑。Camtasia2023是Mac/win平台上一款使用非常简单的…

【JavaScript】题(牛客网)——熟练使用函数调用,超详细讲解

1 熟练使用函数调用 1.1 题目 执行以下程序&#xff0c;输出结果为 var uname "window"; var object {uname: "object",fun: function () {console.log(this.uname);return function () {console.log(this.uname);};}, };object.fun()();1.2 答案 ob…

ThingsBoard-设备配置

1、概述 从 ThingsBoard 3.2 开始,租户管理员可以使用设备配置文件为多个设备配置通用设置。每个设备在单个时间点都有一个且唯一的配置文件。 有经验的 ThingsBoard 用户会注意到设备类型已被弃用,取而代之的是设备配置文件。更新脚本将根据唯一的设备类型自动创建设备配置…

三、Java面向对象

1 . 方法 方法(method)是程序中最小的执行单元方法就是一些代码的打包 需要的时候可以直接调用方法之间是平级的关系 不能在方法里面定义方法方法不调用就不执行 方法的定义 // 方法的定义 /* [修饰符] 返回值类型 方法名称([参数 1],[参数 2]){语句A;return 返回值; } *///…

VT虚拟化框架编写

文章目录前言VT架构基础VT框架编写步骤一&#xff1a;检测VT是否开启VMM和VMVMON和VMCSVT框架编写步骤二 填充VMONVT框架编写步骤三 进入VTVT框架编写步骤四 初始化VMCSVT框架编写步骤五 初始化VMCS数据区VT框架编写步骤六 处理必要事件前言 学习VT相关的知识&#xff0c;需要…

C++11新特性

文章目录说在前面花括号{}初始化new的列表初始化STL相关容器的列表初始化相关语法格式容器列表初始化的底层原理forward_list和array与类型相关的新特性decltype左值引用和右值引用什么是左值&#xff0c;什么是右值左值和右值的本质区别右值引用如何理解右值引用std::move移动…

【软考系统架构设计师】2022下综合知识历年真题

【软考系统架构设计师】2022下综合知识历年真题 【2022下架构真题第01题&#xff1a;绿色】 01.云计算服务体系结构如下图所示&#xff0c;图中①、②、③分别与SaaS、PaaS、Iaas相对应&#xff0c;图中①、②、③应为( ) A.应用层、基础设施层、平台层 B.应用层、平台层、基础…

Linux驱动开发(一)

linux驱动学习记录 一、背景 在开始学习我的linux驱动之旅之前&#xff0c;先提一下题外话&#xff0c;我是一个c语言应用层开发工作人员&#xff0c;在工作当中往往会和硬件直接进行数据的交互&#xff0c;往往遇到数据不通的情况&#xff0c;常常难以定位&#xff0c;而恰巧…

静态分析工具Cppcheck在Windows上的使用

之前在https://blog.csdn.net/fengbingchun/article/details/8887843 介绍过Cppcheck&#xff0c;那时还是1.x版本&#xff0c;现在已到2.x版本&#xff0c;这里再总结下。 Cppcheck是一个用于C/C代码的静态分析工具&#xff0c;源码地址为https://github.com/danmar/cppcheck …

Python之字符串精讲(上)

前言 字符串是所有编程语言在项目开发过程中涉及最多的一个内容。大部分项目的运行结果&#xff0c;都需要以文本的形式展示给客户&#xff0c;曾经有一位久经沙场的老程序员说过一句话&#xff1a;“开发一个项目&#xff0c;基本上就是在不断的处理字符串”。下面对Python中…