Spring Boot启动流程

news2024/12/23 17:45:05

1 Springboot 启动流程

  1. 创建一个StopWatch实例,用来记录SpringBoot的启动时间。

  2. 通过SpringFactoriesLoader加载listeners:比如EventPublishingRunListener。

  3. 发布SprintBoot开始启动事件(EventPublishingRunListener#starting())。

  4. 创建和配置environment(environmentPrepared())。

  5. 打印SpringBoot的banner和版本。

  6. 创建对应的ApplicationContext:Web类型,Reactive类型,普通的类型(非Web)

  7. 刷新上下文 prepareContext:自动装配和启动 tomcat就是在这个方法里面完成的

    7.1 准备ApplicationContext,Initializers设置到ApplicationContext(contextPrepared())。

    7.2 打印启动日志,打印profile信息(如dev, test, prod)。 7.3 最终会调用到AbstractApplicationContext#refresh方法,实际上就是Spring IOC容器的创建过程,并且会进行自动装配的操作,以及发布ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成(contextLoaded())

  1. afterRefresh hook方法。

  2. stopWatch停止计时,日志打印总共启动的时间。

  3. 发布SpringBoot程序已启动事件(started())。

  4. 调用ApplicationRunner和CommandLineRunner。

  5. 最后发布就绪事件ApplicationReadyEvent,标志着SpringBoot可以处理就收的请求了(running())。

public ConfigurableApplicationContext run(String... args) {
    // 创建一个StopWatch实例,用来记录SpringBoot的启动时间
 StopWatch stopWatch = new StopWatch();
 stopWatch.start();
 ConfigurableApplicationContext context = null;
 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
 configureHeadlessProperty();
    // 通过SpringFactoriesLoader加载listeners:比如EventPublishingRunListener
 SpringApplicationRunListeners listeners = getRunListeners(args);
    // 发布SprintBoot启动事件:ApplicationStartingEvent
 listeners.starting();
 try {
  ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
     // 创建和配置environment,发布事件:SpringApplicationRunListeners#environmentPrepared
  ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  configureIgnoreBeanInfo(environment);
     // 打印SpringBoot的banner和版本
  Banner printedBanner = printBanner(environment);
     // 创建对应的ApplicationContext:Web类型,Reactive类型,普通的类型(非Web)
  context = createApplicationContext();
  exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
    new Class[] { ConfigurableApplicationContext.class }, context);
     // 准备ApplicationContext,Initializers设置到ApplicationContext后发布事件:ApplicationContextInitializedEvent
     // 打印启动日志,打印profile信息(如dev, test, prod)
     // 调用EventPublishingRunListener发布ApplicationContext加载完毕事件:ApplicationPreparedEvent
  prepareContext(context, environment, listeners, applicationArguments, printedBanner);
     // 最终会调用到AbstractApplicationContext#refresh方法,实际上就是Spring IOC容器的创建过程,并且会进行自动装配的操作
     // 以及发布ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成
  refreshContext(context);
     // hook方法
  afterRefresh(context, applicationArguments);
     // stopWatch停止计时,日志打印总共启动的时间
  stopWatch.stop();
  if (this.logStartupInfo) {
   new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  }
     // 发布SpringBoot程序已启动事件ApplicationStartedEvent
  listeners.started(context);
     // 调用ApplicationRunner和CommandLineRunner
  callRunners(context, applicationArguments);
 }
 catch (Throwable ex) {
  handleRunFailure(context, ex, exceptionReporters, listeners);
  throw new IllegalStateException(ex);
 }

 try {
     // 最后发布就绪事件ApplicationReadyEvent,标志着SpringBoot可以处理就收的请求了
  listeners.running(context);
 }
 catch (Throwable ex) {
  handleRunFailure(context, ex, exceptionReporters, null);
  throw new IllegalStateException(ex);
 }
 return context;
}

2 Springboot IOC容器初始化过程

Springboot 启动过程,其核心就是IOC容器初始化过程,所以可以把上诉过程简化为

  • 第一步,创建IOC容器;

    IOC容器是由类AnnotationConfigServletWebServerApplicationContext初始化的一个实例,其中包含了bean工厂、类加载器、bean定义信息和环境变量等主要信息。

    通过AnnotationConfigServletWebServerApplicationContext的初始化,IOC容器初始化了一些属性,设置了一些特定的bean定义信息。其主要的作用,还是为后续的IOC容器创建Bean做准备。

  • 第二步,准备IOC容器相关信息

    在项目启动后,进行一系列的初始化。包括SpringApplication实例准备,listener监听器准备,environment环境准备。

  • 第三步,刷新IOC容器,也就是我们通常说的bean的初始化过程

    加载当前主启动类下的所有类文件到IOC容器:经过ConfigurationClassParser类中的parse方法调用AnnotatedBeanDefinition类型的parse方法,我们完成了对当前项目工程下所有类,注册到IOC容器中的操作。

    加入Spring自带的框架类到IOC容器(自动装配):在ConfigurationClassParser类中的执行方法为:this.deferredImportSelectorHandler.process(); 这里用到Spring Factories 机制,见Springboot自动配置

    关于bean信息注册的方法,基本都在ConfigurationClassParser这个类中,processConfigBeanDefinitions实现bean信息注册的入口方法在ConfigurationClassParser的parse方法中:

public void parse(Set<BeanDefinitionHolder> configCandidates) {
  //主启动类bean信息集合
  Iterator var2 = configCandidates.iterator();
  while(var2.hasNext()) {
  BeanDefinitionHolder holder = (BeanDefinitionHolder)var2.next();
  BeanDefinition bd = holder.getBeanDefinition();
  try {
  if (bd instanceof AnnotatedBeanDefinition) {
  //注册项目工程类信息
  this.parse(((AnnotatedBeanDefinition)bd).getMetadata(), holder.getBeanName());
  .......
  }
  //注册Spring提供默认类信息
  this.deferredImportSelectorHandler.process();
  }

然后,就可以完成bean的大规模实例化

  • 第四步,完成刷新IOC容器后处理

    这是一个抽象方法,且没有继承类。

  • 第五步,结束Springboot启动流程

    记录结束时间;

    发布SpringBoot程序已启动事件;

    执行容器中所有的ApplicationRunner和CommandLineRunner类型的run方法

public ConfigurableApplicationContext run(String... args) {
 ......
 ConfigurableApplicationContext context = null;
 ......
 //1创建容器
 context = this.createApplicationContext();
 exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
 //2准备容器相关信息
 this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
 //3刷新容器,也就是通常我们说的bean的初始化
 this.refreshContext(context);
 //4完成初始化后处理
 this.afterRefresh(context, applicationArguments);
 ......
 }

3 自定义应用

3.1 自定义初始化器

自定义初始化器首先要实现 ApplicationContextInitializer 接口

执行自定义初始化器的方式有三种

  • 方式一:通过通过SpringApplication对象调用addInitializers(new 自定义初始化器)

    @SpringBootApplication
    public class NettyTestApplication {
    
        public static void main(String[] args) {
            SpringApplication springApplication = new SpringApplication(NettyTestApplication.class);
            springApplication.addInitializers(new SecondInitializer());
            springApplication.run(args);
        }
    }
    
    @Order(2)
    public class SecondInitializer implements ApplicationContextInitializer {
    
        @Override
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
            ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
    
            Map<String, Object> map = new HashMap<>();
            map.put("key2","value2");
    
            MapPropertySource firstInitializer = new MapPropertySource("secondInitializer", map);
            environment.getPropertySources().addLast(firstInitializer);
    
            System.out.println("run second initializer");
        }
    }
    
  • 方式二:通过META-INF/spring.factories中指定

  • org.springframework.context.ApplicationContextInitializer=自定义初始化器

    # 在 resources 文件夹下新建 META-INF 文件夹,META-INF 文件夹下新建 spring.factories 配置文件,添加内容如下:
    org.springframework.context.ApplicationContextInitializer=com.xxx.FirstInitializer
    
    
    
    // order 注解用于排序
    @Order(1)
    public class FirstInitializer implements ApplicationContextInitializer {
    
        @Override
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
            ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
    
            Map<String, Object> map = new HashMap<>();
            map.put("key1","value1");
    
            MapPropertySource firstInitializer = new MapPropertySource("firstInitializer", map);
            environment.getPropertySources().addLast(firstInitializer);
    
            System.out.println("run first initializer");
        }
    }
    
  • 方式三:通过application.properties中指定 context.initializer.classes=自定义初始化器

  • 利用 ApplicationContextInitializer 接口的实现类 DelegatingApplicationContextInitializer

    在 springboot 的默认配置文件 application.properties 中添加
    context.initializer.classes=com.xxx.ThirdInitializer
    
    @Order(3)
    public class ThirdInitializer implements ApplicationContextInitializer {
    
        @Override
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
            ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
    
            Map<String, Object> map = new HashMap<>();
            map.put("key3","value3");
    
            MapPropertySource firstInitializer = new MapPropertySource("thirdInitializer", map);
            environment.getPropertySources().addLast(firstInitializer);
    
            System.out.println("run third initializer");
        }
    }
    
    @SpringBootApplication
    public class NettyTestApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(NettyTestApplication.class, args);
        }
    
    }
    

3.2 自定义监听器

springboot进行事件监听有四种方式:

  1. 手工向ApplicationContext中添加监听器

    public class MyListener1 implements ApplicationListener<MyEvent>{
     Logger logger = Logger.getLogger(MyListener1.class);
     
     public void onApplicationEvent(MyEvent event)
     {
      logger.info(String.format("%s监听到事件源:%s.", MyListener1.class.getName(), event.getSource()));
     }
    }
    
    
    @SpringBootApplication
    public class LisenterApplication
    {
     public static void main(String[] args)
     {
      ConfigurableApplicationContext context = SpringApplication.run(LisenterApplication.class, args);
      //装载监听
      context.addApplicationListener(new MyListener1());
     }
    }
    
  1. 使用@Component注解将监听器装载入spring容器

    @Component
    public class MyListener2 implements ApplicationListener<MyEvent>
    {
     Logger logger = Logger.getLogger(MyListener2.class);
     
     public void onApplicationEvent(MyEvent event)
     {
      logger.info(String.format("%s监听到事件源:%s.", MyListener2.class.getName(), event.getSource()));
     }
    }
    
  1. 在application.properties中配置监听器

    在application.properties中配置监听
    context.listener.classes=com.listener.MyListener3
    
    public class MyListener3 implements ApplicationListener<MyEvent>
    {
     Logger logger = Logger.getLogger(MyListener3.class);
     
     public void onApplicationEvent(MyEvent event)
     {
      logger.info(String.format("%s监听到事件源:%s.", MyListener3.class.getName(), event.getSource()));
     }
    }
    
  1. 通过@EventListener注解实现事件监听

    @Component
    public class MyListener4
    {
     Logger logger = Logger.getLogger(MyListener4.class);
     
     @EventListener
     public void listener(MyEvent event)
     {
      logger.info(String.format("%s监听到事件源:%s.", MyListener4.class.getName(), event.getSource()));
     }
    }
    

3.3 自定义 banner信息

在启动 Spring Boot 项目时会在控制台打印如下内容(logo 和版本信息):

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.6)

如果我们想要使用自己的banner,我们可以直接替换,在项目的 resources 资源目录下创建 banner.txt 文件,banner.txt 内容如下

//                          _ooOoo_                               //
//                         o8888888o                              //
//                         88" . "88                              //
//                         (| ^_^ |)                              //
//                         O\  =  /O                              //
//                      ____/`---'\____                           //
//                    .'  \\|     |//  `.                         //
//                   /  \\|||  :  |||//  \                        //
//                  /  _||||| -:- |||||-  \                       //
//                  |   | \\\  -  /// |   |                       //
//                  | \_|  ''\---/''  |   |                       //
//                  \  .-\__  `-`  ___/-. /                       //
//                ___`. .'  /--.--\  `. . ___                     //
//              ."" '<  `.___\_<|>_/___.'  >'"".                  //
//            | | :  `- \`.;`\ _ /`;.`/ - ` : | |                 //
//            \  \ `-.   \_ __\ /__ _/   .-` /  /                 //
//      ========`-.____`-.___\_____/___.-`____.-'========         //
//                           `=---='                              //
//      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        //
//             佛祖保佑          永无BUG         永不修改                   //
:: Spring Boot :: (${spring-boot.version})

3.4 自定义启动异常堆栈信息打印方式

SpringBoot在项目启动时如果遇到异常并不能友好的打印出具体的堆栈错误信息,我们只能查看到简单的错误消息,以致于并不能及时解决发生的问题,针对这个问题SpringBoot提供了故障分析仪的概念(failure-analyzer),内部根据不同类型的异常提供了一些实现。

实现方法是,在springboot启动的核心方法run中会加载所有的SpringBootExceptionReporter,SpringBootExceptionReporter是一个回调接口,用于支持对SpringApplication启动错误的自定义报告。里面就一个报告启动失败的方法。

其实现类:org.springframework.boot.diagnostics.FailureAnalyzers

用于触发从spring.factories加载的FailureAnalyzer和FailureAnalysisReporter实例。

SpringBoot内部通过实现AbstractFailureAnalyzer抽象类定义了一系列的针对性异常类型的启动分析,如下图所示:

 

指定异常分析

SpringBoot内部提供的启动异常分析都是指定具体的异常类型实现的,最常见的一个错误就是端口号被占用(PortInUseException),虽然SpringBoot内部提供一个这个异常的启动分析,我们也是可以进行替换这一异常分析的,我们只需要创建PortInUseException异常的AbstractFailureAnalyzer,并且实现类注册给SpringBoot即可,实现自定义如下所示:

/**
 * 端口号被占用{@link PortInUseException}异常启动分析
 */
public class PortInUseFailureAnalyzer extends AbstractFailureAnalyzer<PortInUseException> {
 /**
  * logger instance
  */
 static Logger logger = LoggerFactory.getLogger(PortInUseFailureAnalyzer.class);
 
 @Override
 protected FailureAnalysis analyze(Throwable rootFailure, PortInUseException cause) {
  logger.error("端口被占用。", cause);
  return new FailureAnalysis("端口号:" + cause.getPort() + "被占用", "PortInUseException", rootFailure);
 }
}

注册启动异常分析

在上面我们只是编写了指定异常启动分析,我们接下来需要让它生效,这个生效方式比较特殊,类似于自定义SpringBoot Starter AutoConfiguration的形式,我们需要在META-INF/spring.factories文件内进行定义,如下所示:

org.springframework.boot.diagnostics.FailureAnalyzer=\ org.minbox.chapter.springboot.failure.analyzer.PortInUseFailureAnalyzer

项目启动遇到的异常顺序不能确定,很可能在Spring IOC并未执行初始化之前就出现了异常,我们不能通过@Component注解的形式使其生效,所以SpringBoot提供了通过spring.factories配置文件的方式定义。

3.5 自定义初始化任务 Runner

在项目启动的时候需要做一些初始化的操作,比如初始化线程池,提前加载好加密证书等。可以通过实现Runner接口完成以上工作。

  • 实现 ApplicationRunner 接口

    @Component
    public class VipSoftServerRunner implements ApplicationRunner{
        
        private static final Logger logger = LoggerFactory.getLogger(JmxhphServerRunner.class);
    
        @Override
        public void run(ApplicationArguments args) throws Exception {
            System.out.println("项目启动,执行 CommandLineRunner 实现类的方法");
        }
    }
    
  • 实现 CommandLineRunner 接口

    @Component
    public class VipSoftServerRunner implements CommandLineRunner {
    
        private static final Logger logger = LoggerFactory.getLogger(JmxhphServerRunner.class);
    
        @Override
        public void run(String... args) throws Exception {
            System.out.println("项目启动,执行 CommandLineRunner 实现类的方法");
        }
    }
    

两者只是参数上的区别而已。

  • ApplicaitonRunner的run方法接收一个ApplicationArguments类型的参数,ApplicationArguments会对spring-boot程序的启动参数进行解析和分类,把[--{operation-name}={operation-value}]解析操作参数,其它情况被分类为非操作参数。

    使用选项参数必须遵循精确的语法,也就是说,选项必须以”–“为前缀,并且可以指定值,也可以不指定值;如果指定了一个值,则名称和值必须用”=“号分割,且不带空格,值可以是空字符串。

    选项参数的有效示例:

     --foo
     --foo=
     --foo=""
     --foo=bar
     --foo="bar then baz"
     --foo=bar,baz,biz
     
     java -jar test.jar --server.port=8080
    
  • CommandLineRunner的run方法接收的是一个String类型的可变参数,它的值就是我们main函数接收到的命令行参数。

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

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

相关文章

性能测试——基本性能监控系统使用

这里写目录标题 一、基本性能监控系统组成二、环境搭建1、准备数据文件 type.db collectd.conf2、启动InfluxDB3、启动grafana4、启动collectd5、Grafana中配置数据源 一、基本性能监控系统组成 Collectd InfluxdDB Grafana Collectd 是一个守护(daemon)进程&#xff0c;用来…

【数据结构】时间复杂度与空间复杂度

目录 前言一、算法效率1. 算法效率的定义 二、时间复杂度1. 时间复杂度的定义2. 时间复杂度的计算 三、空间复杂度1. 空间复杂度的定义2. 空间复杂度的计算 四、时间复杂度曲线图结尾 前言 在学习C语言的时候&#xff0c;大多数的小伙伴们并不会对算法的效率了解&#xff0c;也…

视频采集到录制 - 音频采集到降噪

继续上篇的视频采集到录制 视频采集相对来说还是算正常&#xff0c;如果资源够用&#xff0c;使用第三方库也是种解决办法 但音频采集网上资料相对也少&#xff0c;走了一遍&#xff0c;也发现存在很多坑 1. 音频采集 一般来说&#xff0c;采用MIC采集&#xff0c;采集出来的格…

内存泄露的循环引用问题

内存泄漏一直是很多大型系统故障的根源&#xff0c;也是一个面试热点。那么在编程语言层面已经提供了内存回收机制&#xff0c;为什么还会产生内存泄漏呢&#xff1f; 这是因为应用的内存管理一直处于一个和应用程序执行并发的状态&#xff0c;如果应用程序申请内存的速度&…

希尔伯特旅馆里,住着AI的某种真相

“无穷”和“无穷1”&#xff0c;哪个更大&#xff1f; 已经吸收了不知道多少数据的AI模型&#xff0c;和比他多学习一条数据的模型&#xff0c;哪个更智能&#xff1f; 想聊聊这个问题&#xff0c;出于一个偶然的机会。很早之前我在测试ChatGPT的时候&#xff0c;突然想问他个…

简单工厂、工厂方法、抽象工厂模式-这仨货的区别

要想明白这三玩意的区别就需要知道这三玩意的优缺点&#xff1b; 之所以有三种工厂模式&#xff0c;就说明它们各有所长&#xff0c;能解决不同场景的问题&#xff1b; 一、简单工厂模式 UML图 代码 public class MobileFactory {public static Mobile getMobile(String brand)…

【Linux】线程概述、创建线程、终止线程

目录 线程概述1、创建线程函数解析代码举例 2、终止线程函数解析代码举例 橙色 线程概述 与进程类似&#xff0c;线程是允许应用程序并发执行多个任务的一种机制。一个进程可以包含多个线程。 进程是 CPU 分配资源的最小单位&#xff0c;线程是操作系统调度执行的最小单位。…

回归预测 | MATLAB实现SSA-CNN-LSTM麻雀算法优化卷积长短期记忆神经网络多输入单输出回归预测

回归预测 | MATLAB实现SSA-CNN-LSTM麻雀算法优化卷积长短期记忆神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现SSA-CNN-LSTM麻雀算法优化卷积长短期记忆神经网络多输入单输出回归预测预测效果基本介绍模型描述程序设计学习总结参考资料 预测效果 基本介绍 MATLAB实现…

【笔记整理】轻量级神经网络 MobileNetV3

【笔记整理】轻量级神经网络 MobileNetV3 文章目录 【笔记整理】轻量级神经网络 MobileNetV31、深度可分离卷积2、翻转残差块和线性瓶颈结构3、h-swish 函数和 SE 模块4、网络结构搜索 近年来关于 CNN 的研究在飞速发展&#xff0c;CNN 模型在目标检测、图像分割等领域都取得了…

力扣sql中等篇练习(二十九)

力扣sql中等篇练习(二十九) 1 计算每个销售人员的影响力 1.1 题目内容 1.1.1 基本题目信息1 1.1.2 基本题目信息2 1.1.3 示例输入输出 a 示例输入 b 示例输出 1.2 示例sql语句 # Write your MySQL query statement below SELECT s1.salesperson_id,s1.name,IFNULL(t.total…

毕业季到底是去大厂还是去小公司

(点击即可收听) 毕业季到底是去大厂还是去小公司 相信很多人在选择大小公司的时候,会比较痛苦,外面的人想进去,里面的人想出来&#xff0c;至于选择大厂还是小公司 这是因人而异的,不同的阶段都可以有不同的选择 进大厂不一定就是对的,进小公司也不一定就是错的,学习东西,增长经…

股票量化分析工具QTYX使用攻略——涨停个股挖掘热门板块(更新2.6.5)

搭建自己的量化系统 如果要长期在市场中立于不败之地&#xff01;必须要形成一套自己的交易系统。 行情不等人&#xff01;边学习边实战&#xff0c;在实战中学习才是最有效地方式。于是我们分享一个即可以用于学习&#xff0c;也可以用于实战炒股分析的量化系统——QTYX。 QTY…

软考A计划-试题模拟含答案解析-卷九

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

JetBrains的多数据库管理和SQL工具DataGrip 2023版本在Win10系统的下载与安装配置教程

目录 前言一、DataGrip 安装二、使用配置总结 前言 DataGrip是一款多数据库管理和SQL工具&#xff0c;适用于不同类型的数据库。它提供了丰富的功能和工具&#xff0c;可以帮助开发人员更高效地管理数据库、编写SQL查询和执行数据操作。 DataGrip的主要特点&#xff1a; ——…

这里有3个Tips,也许可以帮你躲过ChatGPT大规模封号 | AIGC实践

据说&#xff0c;从昨天开始&#xff0c;ChatGPT又双叒叕开始大规模封号&#xff0c;很多注册用户收到这样一则消息&#xff1a; 大意是说&#xff1a;OpenAI 发现了你的 ChatGPT 账号存在可疑活动&#xff0c;为了保障平台安全&#xff0c;已自动退款并取消你的 ChatGPT Plus …

驱动开发:内核解析内存四级页表

当今操作系统普遍采用64位架构&#xff0c;CPU最大寻址能力虽然达到了64位&#xff0c;但其实仅仅只是用到了48位进行寻址&#xff0c;其内存管理采用了9-9-9-9-12的分页模式&#xff0c;9-9-9-9-12分页表示物理地址拥有四级页表&#xff0c;微软将这四级依次命名为PXE、PPE、P…

七年老程序员的三四月总结:三十岁、准备婚礼、三次分享

你好&#xff0c;我是 shixin&#xff0c;一名工作七年的安卓开发。 每两个月我会做一次总结&#xff0c;记下这段时间里有意义的事和值得反复看的内容&#xff0c;为的是留一些回忆、评估自己的行为、沉淀有价值的信息。 一转眼 2023 年过去了三分之一&#xff0c;这两个月经历…

【数据湖仓架构】数据湖和仓库:Databricks 和 Snowflake

是时候将数据分析迁移到云端了。我们比较了 Databricks 和 Snowflake&#xff0c;以评估基于数据湖和基于数据仓库的解决方案之间的差异。 在这篇文章中&#xff0c;我们将介绍基于数据仓库和基于数据湖的云大数据解决方案之间的区别。我们通过比较多种云环境中可用的两种流行技…

HTML+CSS+JavaScript制作弹幕效果

全屏弹幕 <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>弹幕效果</title><style>/* 设置弹幕的样式 */.bullet {position: absolute;font-size: 20px;color: white;text-shadow: 1px 1px 1px black;white-s…

用Python让小朋友的手绘图跳起来(附源码)

大家好&#xff0c;我是小F&#xff5e; 今天给大家介绍一个非常有趣的项目&#xff0c;基于AI识别&#xff0c;制作儿童手绘图舞蹈图。 只需几分钟&#xff0c;就能自动生成儿童手绘人物或类人角色&#xff08;即具有双臂、两条腿等的角色&#xff09;的动画&#xff0c;而且生…