2.【SpringBoot源码】SpringBoot核心启动流程

news2024/10/10 12:25:59

目录

一、简介

二、创建SpringApplication对象

1)、推导出当前启动的项目的类型

2)、设置Initializer初始化器 

3)、初始化Listener监听器 

4)、反推出main方法所在的Class对象 

三、运行SpringApplication#run(java.lang.String...)方法

1)、获取运行监听器

2)、发布启动过程中各阶段事件

3)、准备环境Environment

4)、创建ApplicationContext IOC容器

5)、准备ApplicationContext IOC容器的基本信息

6)、刷新IOC容器

7)、调用所有runners


本篇源码基于spring-boot-2.1.0.RELEASE版本进行分析,各个版本可能存在一些差别。

一、简介

SpringBoot的启动从main()方法开始:

public static void main(String[] args) {
    SpringApplication.run(SampleTomcatApplication.class, args);
}

调用SpringApplication的静态方法run():

// SpringBoot项目的启动方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
}

// 继续调用重载的run()方法
// run方法是一个静态方法,用于启动SpringBoot
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
        String[] args) {
    // 构建一个SpringApplication对象,并调用其run方法来启动
    return new SpringApplication(primarySources).run(args);
}

 主要分为两步:

  • a、创建SpringApplication对象;
  • b、运行SpringApplication#run(java.lang.String...)方法;

下面对这两部分做详细的分析。

二、创建SpringApplication对象

执行SpringApplication的构造方法,创建出一个SpringApplication对象,并初始化了一些属性,代码如下:

// 创建一个新的SpringApplication实例。应用程序上下文将从指定的主要来源加载bean
public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 传递的resourceLoader为null
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 记录主方法的配置类名称
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 推导出当前启动的项目的类型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 加载配置在spring.factories文件中的ApplicationContextInitializer对应的类型并实例化. 并将加载的数据存储在了 initializers 成员变量中。
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 初始化监听器,并将加载的监听器实例对象存储在了listeners成员变量中
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 反推出main方法所在的Class对象
    this.mainApplicationClass = deduceMainApplicationClass();
}

SpringApplication构造方法主要做了以下几件事情:

1)、推导出当前启动的项目的类型

this.webApplicationType = WebApplicationType.deduceFromClasspath();

调用WebApplicationType的静态方法deduceFromClasspath():

/**
 * 根据classpath推导出web项目的类型。Servlet项目或者Reactive项目
 *
 * @return
 */
static WebApplicationType deduceFromClasspath() {
    // 一些绑定的Java类的全类路径
    // ClassUtils.isPresent(): 通过反射的方式获取对应的类型的Class对象,如果存在返回true,否则返回false
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

可以看到,springboot通过判断当前项目有没有引入特定的依赖或者判断当前项目是否存在某个特殊类来推断当前项目是Servlet项目还是Reactive项目。

在本例中,我们是SERVLET项目类型。

2)、设置Initializer初始化器 

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class))

springboot通过加载配置在spring.factories文件中的ApplicationContextInitializer对应的类型并实例化,并将加载的数据存储在了 initializers 成员变量中。

3)、初始化Listener监听器 

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

springboot通过加载配置在spring.factories文件中的ApplicationListener对应的类型并实例化,并将加载的数据存储在了 listeners 成员变量中。

4)、反推出main方法所在的Class对象 

this.mainApplicationClass = deduceMainApplicationClass();
private Class<?> deduceMainApplicationClass() {
    try {
        // StackTrace记录了run()方法执行的堆栈信息
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            // 找到调用main方法的类
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
        // Swallow and continue
    }
    return null;
}

可以看到,springboot从记录了run()方法执行的堆栈信息中,找到main方法所在的类,这个类就是主启动类。

在本例中,就是sample.tomcat.SampleTomcatApplication:

通过上述的一些工作,就构建了一个SpringApplication对象,接下来就调用它的run()方法启动应用。

三、运行SpringApplication#run(java.lang.String...)方法

// 运行 Spring 应用程序,创建并刷新一个新的ApplicationContext
public ConfigurableApplicationContext run(String... args) {
    // 创建一个任务执行观察器,用于统计run启动过程花了多少时间
    StopWatch stopWatch = new StopWatch();
    // 记录开始时间
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    // exceptionReporters集合用来存储异常报告器,用来报告SpringBoot启动过程的异常
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 设置了一个名为java.awt.headless的系统属性, 其实是想设置该应用程序,即使没有检测到显示器,也允许其启动. 对于服务器来说,是不需要显示器的,所以要这样设置.
    configureHeadlessProperty();
    // 从spring.factories配置文件中加载到EventPublishingRunListener对象并赋值给SpringApplicationRunListeners
    // EventPublishingRunListener对象主要用来发布SpringBoot启动过程中内置的一些生命周期事件,标志每个不同启动阶段
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 发布启动事件
    listeners.starting();
    try {
        // 创建ApplicationArguments对象,封装了args参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 准备环境,包括系统变量、环境变量、命令行参数、默认变量等
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        // 配置需要忽略的BeanInfo信息
        configureIgnoreBeanInfo(environment);
        // 启动时控制台打印Banner
        Banner printedBanner = printBanner(environment);
        // 根据不同类型创建不同类型的spring容器ApplicationContext应用程序上下文
        context = createApplicationContext();
        // 加载spring.factories配置文件配置的异常报告器
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        // 准备上下文,刷新容器前的一些操作
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 刷新应用上下文。完成Spring IOC容器的初始化
        refreshContext(context);
        // 在刷新上下文后调用的钩子,这个方法是一个模板方法
        afterRefresh(context, applicationArguments);
        // 停止记录执行时间
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        // 事件广播,启动完成了
        listeners.started(context);
        // 执行ApplicationRunner、CommandLineRunner的run方法,实现spring容器启动成功后需要执行的一些逻辑
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

这个方法是springboot的核心启动方法,其中包括了准备环境、配置文件的解析、创建IOC容器、内嵌servlet web容器的启动、事件广播等核心逻辑,下面我们挨个进行分析。

1)、获取运行监听器

SpringApplicationRunListeners listeners = getRunListeners(args);

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    // 获取spring.factories配置文件中配置的SpringApplicationRunListener,并通过反射实例化
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
            SpringApplicationRunListener.class, types, this, args));
}

从spring.factories配置文件中找到SpringApplicationRunListener对应的值。其实在springboot中,getSpringFactoriesInstances()方法都是根据传入的Class类型,去"META-INF/spring.factories"文件中,找到执行Class类型对应的值,然后通过反射获取到构造方法,实例化后返回。

在本例中,获取到一个运行监听器EventPublishingRunListener,它主要用来发布SpringBoot启动过程中内置的一些生命周期事件,标志每个不同启动阶段。

2)、发布启动过程中各阶段事件

listeners.starting();

// 上一步获取的listeners就只有EventPublishingRunListener一个。
public void starting() {
    // EventPublishingRunListener
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.starting();
    }
}

 循环获取到的所有的运行监听器,依次执行starting()回调方法:

以org.springframework.boot.context.event.EventPublishingRunListener#starting为例:

public void starting() {
    // 通过事件发布器发布容器启动事件ApplicationStartingEvent
    this.initialMulticaster.multicastEvent(
            new ApplicationStartingEvent(this.application, this.args));
}

实际上EventPublishingRunListener的starting()就是通过事件发布器发布了一个ApplicationStartingEvent事件,这就涉及到发布-订阅模式了,这样订阅了这个ApplicationStartingEvent事件的其它地方就可以感知到,做一些其他处理。

后续启动过程,还会依次发布不同的事件,处理流程类似,只是发布的事件类型不一样,这里就不过多分析。

3)、准备环境Environment

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 在配置环境信息之前发布事件
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

准备环境,包括系统变量、环境变量、命令行参数、默认变量等。首先返回或者创建基础环境信息对象StandardServletEnvironment,然后配置环境信息对象并绑定到当前Spring应用,监听器调用 listener.environmentPrepared()通知所有的监听器当前环境准备完成。

4)、创建ApplicationContext IOC容器

context = createApplicationContext();

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

根据不同类型创建不同类型的spring容器ApplicationContext应用程序上下文。

在本例中,由于是SERVLET项目类型,所以创建的是AnnotationConfigServletWebServerApplicationContext上下文。

5)、准备ApplicationContext IOC容器的基本信息

prepareContext(context, environment, listeners, applicationArguments, printedBanner);

private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    // 将环境信息保存到IOC中
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    // 获取到所有的Initializer,回调initialize()方法
    applyInitializers(context);
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    // Load the sources
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));
    listeners.contextLoaded(context);
}

 准备上下文,就是在刷新IOC容器之前做一些初始化工作,主要包括:

  • a、将环境信息保存到IOC容器中;
  • b、对前面创建的ApplicationContext 做后置处理,设置资源加载器、类加载器等;
  • c、应用初始化器,获取到构建SpringApplication时所有的Initializer初始化器ApplicationContextInitializer ,挨个回调initialize()方法,来对ioc容器进行初始化扩展功能;
  • d、发布ApplicationContextInitializedEvent事件;
  • e、注册单例bean,如springApplicationArguments、springBootBanner等;
  • f、设置bean工厂允许覆盖bean定义信息;

6)、刷新IOC容器

refreshContext(context)

private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}

protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    ((AbstractApplicationContext) applicationContext).refresh();
}

创建容器中的所有组件,这里真正开始创建Spring的IOC容器,即执行AbstractApplicationContext类的refresh()中核心的12个步骤:

  • a、prepareRefresh():容器刷新前的一些预处理工作;
  • b、obtainFreshBeanFactory():创建DefaultListableBeanFactory工厂,给bean工厂设置一些属性,加载配置文件信息,封装成bean定义信息;
  • c、prepareBeanFactory(beanFactory):设置bean工厂的一些属性,如添加一些BeanPostProcessor增强器等
  • d、postProcessBeanFactory(beanFactory):模板方法,留给子类扩展实现;
  • e、invokeBeanFactoryPostProcessors(beanFactory):执行BeanFactoryPostProcessor的postProcessBeanFactory()增强方法;
  • f、registerBeanPostProcessors(beanFactory):注册BeanPostProcessor增强器,注意这里只是注册,真正是在初始化阶段的前后执行;
  • g、initMessageSource():初始化MessageSource,国际化处理;
  • h、initApplicationEventMulticaster():初始化事件多播器;
  • i、onRefresh():模板方法,留给子类扩展实现;
  • j、registerListeners():注册一些监听器;
  • k、finishBeanFactoryInitialization(beanFactory):完成非懒加载的单例bean对象的实例化,包括反射创建bean对象、属性填充、循环依赖的处理、bean的初始化等等;
  • l、finishRefresh():容器刷新完成之后的一些处理工作;

7)、调用所有runners

callRunners(context, applicationArguments)

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    // 将ApplicationRunner添加到runners中
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    // 将CommandLineRunner添加到runners中
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    // 排序
    AnnotationAwareOrderComparator.sort(runners);
    // 循环所有的runners,执行run()方法
    for (Object runner : new LinkedHashSet<>(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
}

获取容器中的 ApplicationRunner 、CommandLineRunner,然后合并所有runner并且按照@Order进行排序,遍历所有的runner,调用 run 方法,实现spring容器启动成功后需要执行的一些逻辑。

以上就是springboot应用启动的核心流程分析。

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

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

相关文章

unity使用对象池实现冲锋留下的残影效果

目录 效果展示 实现思路 残影代码 对象池代码 控制冲刺产生残影 CD冷却图标 效果展示 实现思路 对象池&#xff0c;有想要用的物体时可以从池子里取&#xff0c;用完再放回去。 因为在生成残影再销毁&#xff0c;这个过程中创建和销毁都需要耗费大量资源&#xff0c;因此…

shell 条件测试详解

目录 shell条件测试 一&#xff0c;条件测试的基本语法 1&#xff0c;test 2&#xff0c;[ ] 3&#xff0c;[[ ]] 二&#xff0c;文件测试表达式 1&#xff0c;判断目录是否存在&#xff1a; 2&#xff0c;判断文件file1是否有写的权限&#xff0c;结果为有 3&#xf…

重学MySQL基础(一)

文章目录重学MySQL基础&#xff08;一&#xff09;MySQL 连接管理MySQL字符编码InnoDB 记录存储结构InnoDB 表的主键生成策略&#xff1a;InnoDB 数据页结构页目录页的效验和索引事务报错记录在MySQL中创建函数时出现这种错误恶补SQL语句SQL中的条件语句SQL中的字符串函数SQL中…

python调用go语言踩坑记录

目录 基本操作 1 在go文件中加注释&#xff0c;设置为导出方法,导出C依赖 2 导出so文件&#xff08;mac或者linux下只需要so&#xff09; 3 进行调用 报错记录 踩坑1 关于结构体 2 cannot use (_Cfunc_CString)("12345") (value of type *_Ctype_char) as ty…

spring中事务失效场景

文章目录spring中事务失效场景一、权限访问问题二、方法用final修饰三、无事务嵌套有事务的方法四、没有被spring管理五、设计的表不支持事务六、没有开启事务七、错误的事务传播八、自己捕获了异常九、手动抛出别的异常十、自定义回滚异常spring中事务失效场景 一、权限访问问…

软件研发管理经验总结 - 事务管理

软件研发管理经验总结 - 事务管理 相关系列文章 软件产品研发管理经验总结-管理细分 软件研发管理经验总结 - 事务管理 目录软件研发管理经验总结 - 事务管理一、概述二、事务管理过程1、制定开发计划2、启动会议3、阅读前一天的日报4、例会/早会5、调整计划6、协调资源7、日报…

LeetCode——2325. 解密消息

一、题目 给你字符串 key 和 message &#xff0c;分别表示一个加密密钥和一段加密消息。解密 message 的步骤如下&#xff1a; 使用 key 中 26 个英文小写字母第一次出现的顺序作为替换表中的字母 顺序 。 将替换表与普通英文字母表对齐&#xff0c;形成对照表。 按照对照表…

vue全家桶(三)前端路由

vue全家桶&#xff08;三&#xff09;前端路由1.路由的概念1.1路由1.2vue Router2.vue-router的基本使用步骤2.1基本使用步骤2.2路由重定向3.vue-router的嵌套路由用法3.1嵌套路由的用法4.vue-router动态路由匹配用法5.vue-router命名路由用法6.vue-router编程式导航用法6.1 页…

06 Sentinel控制台规则配置讲解 (2)

1、实时监控 监控接口的通过的QPS和拒绝的QPS 2、簇点链路 用来显示微服务的所监控的API 3、流控规则 流量控制&#xff08;flow control&#xff09;&#xff0c;其原理是监控应用流量的 QPS 或并发线程数等指标&#xff0c;当达到指定的阈值时对流量进行控制&#xff0c;以…

源于《C陷阱与缺陷》----研究程序死循环问题

本题来源于《C陷阱与缺陷》这本书&#xff0c;从本质上讲解程序死循环的原因&#xff0c;关键在于栈的空间使用方式。研究程序死循环的原因死循环的原因是什么呢&#xff1f;解决方法总结研究程序死循环的原因 题目1&#xff1a; 在VS2019 X86环境下测试&#xff1a; int mai…

Linux服务器怎么设置iptables防火墙?

当今&#xff0c;为避免香港服务器及其端口受到恶意入侵&#xff0c;保障香港服务器的安全&#xff0c;配置Linux服务器防火墙的重要性不言而喻。恒创科技将向您介绍如何在 CentOS、Ubuntu 和 Debian 服务器中安装和配置 iptables防火墙。iptables是一个简单的防火墙&#xff0…

测试工程师必备技能之编写测试用例

1. 必要性 &#x1f449; 指导测试工作&#xff0c;用例通过率是评估质量的基准 &#x1f449; 完善测试人员测试的整体思路&#xff0c;不出现漏测 ❗️ 避免背锅&#xff0c;线上出了问题防止开发甩锅给测试 2. 模板 企业中用例往往是每个人负责不同模块&#xff0c;再根据…

Verilog实现超前进位加法器

在CPU等对性能要求较高的电路中&#xff0c;一般都会采用超前进位加法器&#xff0c;因为超前进位加法器的延时相对来说比较小。下面讲述超前进位加法器的原理&#xff1a; 我们知道&#xff0c;一个三输入&#xff0c;二输出的全加器&#xff0c;其逻辑关系为 SA⊕B⊕CinSA\op…

每日一练9——另类加法走方格的方案数

文章目录另类加法思路&#xff1a;代码&#xff1a;走方格的方案数思路&#xff1a;代码&#xff1a;另类加法 题目链接 思路&#xff1a; 本题可以通过位运算实现&#xff0c;具体实现如下&#xff1a; 两个数求和&#xff0c;其实就是 求和后当前位的数据两个数求和的进位…

Ceres Solver解算已知函数模型参数

一、介绍Ceres solver 是谷歌开发的一款用于非线性优化的库&#xff0c;在已知一个函数表达式&#xff0c;以及一组观测到的值&#xff0c;利用最小二乘是可以解算得到相关参数。下面举例使用ceres solver解算直线函数以及曲线函数参数。其过程包括三个步骤&#xff1a;&#x…

图为技术:专注核心技术 引领行业发展

图为技术T-3D引擎经过多年的技术积累与原始创新&#xff0c;作为国内首批基于分布式技术进行三维图形底层渲染的开拓者&#xff0c;不仅在渲染架构上优势明显&#xff0c;同时其所具备数据管理能力也十分卓越。分布式架构采用独创的(Multi Compute Server To Web Render)架构方…

Java多线程之CAS中的ABA问题与JUC的常见类

文章目录一. CAS指令与ABA问题1. 解析CAS2. 基于CAS实现的原子类3. 基于CAS实现自旋锁4. ABA问题二. JUC中的常见类1. Callable接口2. ReentrantLock类(可重入锁)3. Semaphore类(信号量)4. CountDownLatch同步工具类一. CAS指令与ABA问题 1. 解析CAS CAS即compare and awap, …

Cesium 坐标系的转换,空间数据加载 粒子系统加载

在vue中引入Cesium.js官网下载好的Cesium文件放入vue项目中index.html中引入,在js文件即可智能提示&#xff0c;或者下载依赖包也可<script src"./Cesium/Cesium.js"></script><link rel"stylesheet" href"./Cesium/Widgets/widgets.c…

若依配置教程(六)Excel导入功能实现

若依官网导入实现流程 文章目录一、前端index.vue中1.在所需模块的index.vue中的< script >< /script >中增加如下代码&#xff1a;2.在< template >< /template >中添加导入按钮事件&#xff1a;3.添加导入前端代码&#xff1a;二、在模块文件夹(ruoy…

数学建模学习笔记(17)灰色预测模型和神经网络

文章目录灰色预测模型相关基本概念GM(1,1)模型的使用步骤GM(1,1)模型的拓展模型GM(1,1)模型的注意事项BP神经网络预测模型的注意事项灰色预测模型 相关基本概念 系统的分类&#xff1a; 白色系统&#xff1a;系统的信息是完全明确的。灰色系统&#xff1a;系统的部分信息已知…