spring(15) SpringBoot启动过程

目录

    • 一、过程简介
    • 二、过程流程图
    • 三、源码分析
      • 1、运行 SpringApplication.run() 方法
      • 2、确定应用程序类型
      • 3、加载所有的初始化器
      • 4、加载所有的监听器
      • 5、设置程序运行的主类
      • 6、开启计时器
      • 7、将 java.awt.headless 设置为 true
      • 8、获取并启用监听器
      • 9、设置应用程序参数
      • 10、准备环境变量
      • 11、忽略 Bean 信息
      • 12、打印 banner 信息
      • 13、创建应用程序的上下文
      • 14、实例化异常报告器
      • 15、准备上下文环境
        • 15.1、实例化单例的 beanName 生成器
        • 15.2、执行初始化方法
        • 15.3、将启动参数注册到容器中
      • 16、refresh 刷新上下文(实例化 Bean)
        • 16.1、refresh() 方法内容
        • 16.2、Bean 对象的创建
      • 17、刷新上下文后置处理
      • 18、结束计时器
      • 19、发布上下文准备就绪事件
      • 20、执行自定义的 run 方法

最好的学习方式就是带着问题学习,在分析 SpringBoot 的启动过程前,先问大家两个问题:

  1. 在启动过程中,SpringBoot 是在哪一步实例化 Bean 的?

    答案:在本文的第 16 步 refresh() 刷新上下文的时候实例化的。

  2. ApplicationContext 作为一个 IOC 容器,底层是通过什么方式来存储实例化好的 Bean 呢?

    答案:ApplicationContext 是先使用 Set 集合将 BeanDefinition 存储起来,然后再将不是抽象的、单例的、非懒加载的类进行实例化,然后存放到 Map 集合中统一管理。

文章中使用的源码版本:

  • spring-boot: 2.2.x
  • spring-framework: 5.2.x

话不多说,下面就让我们开始了解 SpringBoot 的启动过程吧。


一、过程简介

首先,SpringBoot 启动的时候,会构造一个 SpringApplication 的实例,构造时会进行初始化的工作。初始化的时候会做以下几件事情:

  1. 把参数 sources 设置到 SpringApplication 属性中,这个 sources 可以是任何类型的参数;
  2. 判断是否是 web 程序,并设置到 webEnvironmentboolean 属性中;
  3. 创建并初始化 ApplicationInitializer,设置到 initializers 属性中;
  4. 创建并初始化 ApplicationListener,设置到 listeners 属性中;
  5. 初始化主类 mainApplicationClass

其次,SpringApplication 构造完成之后调用 run 方法,启动 SpringApplication。run 方法执行的时候会做以下几件事:

  1. 构造一个 StopWatch 计时器,用来记录 SpringBoot 的启动时间;
  2. 初始化监听器,获取 SpringApplicationRunListeners 并启动监听,用于监听 run 方法的执行。
  3. 创建并初始化 ApplicationArguments,获取 run 方法传递的 args 参数。
  4. 创建并初始化 ConfigurableEnvironment(环境配置)。封装 main 方法的参数,初始化参数,写入到 Environment 中,发布 ApplicationEnvironmentPreparedEvent(环境事件),做一些绑定后返回 Environment
  5. 打印 banner 和版本。
  6. 构造 Spring 容器(ApplicationContext)上下文。先填充 Environment 环境和设置的参数,如果 application 有设置 beanNameGenerator(bean)、resourceLoader(加载器)就将其注入到上下文中,调用初始化的切面,发布 ApplicationContextInitializedEvent(上下文初始化)时间。
  7. SpringApplicationRunListeners 发布 finish 事件。
  8. StopWatch 计时器停止计时,日志打印总共启动的时间。
  9. 发布 SpringBoot 程序已启动事件(started())。
  10. 调用 ApplicationRunnerCommandLineRunner
  11. 最后发布就绪事件 ApplicationReadyEvent,标志着 SpringBoot 可以处理接收的请求了(running())。

二、过程流程图

在这里插入图片描述

由此看来,SpringBoot 的启动过程还是挺多的,下面我们结合源码,详细分析讲解启动过程中的步骤。

三、源码分析

1、运行 SpringApplication.run() 方法

可以肯定的是,所有的标准 SpringBoot 应用都是从 run 方法开始的。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class SpringbootDemoApplication {public static void main(String[] args) {// 启动应用SpringApplication.run(SpringbootDemoApplication.class, args);}}

进入 run 方法后,会 new 一个 SpringApplication 上下文对象,创建这个对象的构造方法做了一些准备工作,第 2 ~ 5 步就是构造函数里面做的事情。

/*** Static helper that can be used to run a {@link SpringApplication} from the* specified source using default settings.* @param primarySource the primary source to load* @param args the application arguments (usually passed from a Java main method)* @return the running {@link ApplicationContext}*/
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class<?>[] { primarySource }, args);
}

另外补充一下,SpringBoot 除了 SpringApplication.run() 方法启动之外,还可以通过 AnnotationConfigApplicationContext 指定配置类启动,这里就不展开说明了。

2、确定应用程序类型

在 SpringApplication 的构造方法内,首先会通过 WebApplicationType.deduceFromClasspath(); 方法判断当前应用程序的容器,默认使用的是 Servlet 容器,除了 Servlet 之外,还有 NONE 和 REACTIVE(响应式编程)。

在这里插入图片描述

/*** Create a new {@link SpringApplication} instance. The application context will load* beans from the specified primary sources (see {@link SpringApplication class-level}* documentation for details. The instance can be customized before calling* {@link #run(String...)}.* @param resourceLoader the resource loader to use* @param primarySources the primary bean sources* @see #run(Class, String[])* @see #setSources(Set)*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.resourceLoader = resourceLoader;Assert.notNull(primarySources, "PrimarySources must not be null");this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));this.webApplicationType = WebApplicationType.deduceFromClasspath();setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();
}

3、加载所有的初始化器

这里加载的初始化器是 SpringBoot 自带的初始化器,从 META-INFO/spring.factories 配置文件中加载的,那么这个文件在哪呢?自带的有2个,分别在源码的 jar 包的 spring-boot-autoconfigure 项目和 spring-boot 项目里面各有一个:

在这里插入图片描述

spring.factories 文件里面,可以看到开头是 org.springframework.context.ApplicationContextInitializer 接口就是初始化器了:

在这里插入图片描述

当然,我们也可以自己实现一个自定义的初始化器:实现 ApplicationContextInitializer 接口即可。

MyApplicationContextInitializer.java

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;/*** 自定义初始化器*/
public class MyApplicationContextInitializer implements ApplicationContextInitializer {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println("MyApplicationContextInitializer.initialize()");}
}

在 resources 目录下添加 META-INF/spring.factories 配置文件,目录如下,将自定义的初始化器注册进去:

org.springframework.context.ApplicationContextInitializer=\
com.demo.application.MyApplicationContextInitializer

在这里插入图片描述

启动 SpringBoot 后,就可以看到控制台打印的内容了,在这里我们可以很直观地看到它地执行顺序,是在打印 banner 的后面执行的:

在这里插入图片描述

4、加载所有的监听器

加载监听器也是从 META-INF/spring.factories 配置文件中加载的,与初始化不同的是,监听器的加载是为了实现 ApplicationListener 接口的类。

在这里插入图片描述

自定义监听器也跟自定义初始化器一样,这里不再举例。

5、设置程序运行的主类

deduceMainApplicationClass(); 这个方法仅仅是找到 main 方法所在的类,为后面的扫包做准备,deduce 是推断的意思,所以准确的说,这个方法作用是推断出主方法所在的类。

在这里插入图片描述

6、开启计时器

程序运行到这里,就已经进入了 run 方法的主体了,第一步调用的 run 方法是静态方法,那个时候还没实例化 SpringApplication 对象,现在调用的 run 方法是非静态的,是需要实例化后才可以调用的,进来后首先会开启计时器,这个计时器有什么作用呢?顾名思义,就是用来记录 SpringBoot 启动时长的,核心代码如下:

// 实例化计时器
StopWatch stopWatch = new StopWatch();
// 开始计时
stopWatch.start();

run 方法代码段截图:

在这里插入图片描述

7、将 java.awt.headless 设置为 true

这里将 java.awt.headless 设置为 true,表示运行在服务器端,在没有显示和鼠标键盘的模式下照样可以工作,模拟输入输出设备功能。

做了这样的操作后,SpringBoot 想干什么呢?其实是像设置该应用程序,即使没有检测到显示器,也允许其启动,对于服务器来说,是不需要显示器的,所以要这样设置。

方法主体如下:

private void configureHeadlessProperty() {System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

通过方法可以看到,setProperty() 方法里面有有个 getProperty(); 这不是多此一举吗?其实 getProperty() 方法里面有2个参数,第一个 key 值,第二个默认值,意思是通过 key 值查找属性值,如果属性值为空,则返回默认值 true;保证了一定有值的情况。

8、获取并启用监听器

这一步,通过监听器来实现初始化的基本操作,这一步做了2件事:

1)创建所有 Spring 运行监听器,并发布应用启动事件。

2)启用监听器。

在这里插入图片描述

9、设置应用程序参数

将执行 run 方法时传入的参数封装成一个对象。

在这里插入图片描述

这里只是将参数封装成对象,没啥好说的,对象的构造函数如下:

public DefaultApplicationArguments(String... args) {Assert.notNull(args, "Args must not be null");this.source = new Source(args);this.args = args;
}

这里的 args 参数其实就是 main 方法里面执行静态 run 方法时传入的参数。

在这里插入图片描述

10、准备环境变量

准备环境变量,包括系统属性和用户配置的属性,执行的代码块在 preparedEnvironment 方法内。

在这里插入图片描述

打了断点之后可以看到,它将 Maven 和系统的环境变量都加载进来了。

在这里插入图片描述

11、忽略 Bean 信息

configureIgnoreBeanInfo() 这个方法是将 spring.beaninfo.ignore 的默认值设置为 true,意思是忽略 Java Bean 的信息解析:

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());}
}

当然也可以在配置文件中添加以下配置来设为 false。

spring.beaninfo.ignore=false

当 spring.beaninfo.ignore 配置被设置为 false 时,Spring 框架会解析 Java Bean 的信息,包括属性、方法、事件等,以便在运行时进行操作。

需要注意的是,在现在的 Java 环境中,Java Bean 的信息解析通常不再需要,而且会对性能产生负面影响。因此,大多数形况下,无需关注或更改该配置。

12、打印 banner 信息

显而易见,这个流程就是用来打印控制台那个很大的 Spring 的 banner 图案,就是下面这个东西:

在这里插入图片描述

那他在哪里打印的呢?是在 SpringBootBanner.java 里面打印的,这个类实现了 Banner 接口,而且 banner 信息时直接在代码里面写死的。

在这里插入图片描述

13、创建应用程序的上下文

实例化 ApplicationContext(应用程序的上下文),调用 createApplicationContext() 方法,这里就使用反射创建对象,没什么好说的。

在这里插入图片描述

14、实例化异常报告器

异常报告器时用来捕获全局异常使用的,当 SpringBoot 应用程序在发生异常时,异常报告器会将其捕捉并作响应处理,在 spring.factories 文件里配置了默认的异常报告器:

在这里插入图片描述

需要注意的是,这个异常报告器只会捕获启动过程抛出的异常,如果是在启动完成后,在用户请求时报错,异常捕获器不会捕获请求中出现的异常。

在这里插入图片描述

了解了远离了,接下来我们自己配置一个异常报告器试试。

创建 MyExceptionReporter.java 类,继承 SpringBootExceptionReporter 接口。

import org.springframework.boot.SpringBootExceptionReporter;
import org.springframework.context.ConfigurableApplicationContext;/*** 自定义异常报告器*/
public class MyExceptionReporter implements SpringBootExceptionReporter {private ConfigurableApplicationContext context;// 必须要有一个有参构造函数,否则启动会报错MyExceptionReporter(ConfigurableApplicationContext context) {this.context = context;}@Overridepublic boolean reportException(Throwable failure) {System.out.println("MyExceptionReporter.reportException() is called.");failure.printStackTrace();// 返回false会打印详细 SpringBoot 报错信息,返回true则纸打印异常信息。return false;}
}

在 spring.factories 文件中注册异常报告器。

# Error Reporters 异常报告器
org.springframework.boot.SpringBootExceptionReporter=\
com.demo.application.MyExceptionReporter

在这里插入图片描述

然后我们在 application.yml 中把端口设置为一个很大的值(端口的最大值为65535),我们设置为5个8:

server:port: 88888

启动后,控制台打印截图如下:

在这里插入图片描述

15、准备上下文环境

这里准备的上下文环境是为了下一步刷新做准备的, 里面还做了一些额外的事情:

在这里插入图片描述

15.1、实例化单例的 beanName 生成器

在 postProcessApplicationContext(context); 方法里面。使用单例模式创建了 BeanNameGenerator 对象,其实就是 beanName 生成器,用来生成 bean 对象的名称。

15.2、执行初始化方法

初始化方法有哪些呢?还记得第3步里面加载的初始化器吗?其实是执行第3步加载出来的所有初始化器,实现了 ApplicationContextInitializer 接口的类。

15.3、将启动参数注册到容器中

这里将启动参数以单例的模式注册到容器中,是为了以后方便拿来使用,参数的 beanName 为:springApplicationArguments。

16、refresh 刷新上下文(实例化 Bean)

刷新上下文就到了 Spring 的范畴了,这里进行了自动装配和启动 tomcat,以及其他 Spring 自带的机制。这里我们主要看一下 refresh() 方法包含了哪些内容,以及 Bean 对象的创建具体是如何进行的?

16.1、refresh() 方法内容

public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {//为容器初始化做准备prepareRefresh();// 解析xml和注解ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 给BeanFacory设置属性值以及添加一些处理器,即准备Spring的上下文环境prepareBeanFactory(beanFactory);try {// 由子类实现对BeanFacoty的一些后置处理postProcessBeanFactory(beanFactory);/** BeanDefinitionRegistryPostProcessor* BeanFactoryPostProcessor* 完成对这两个接口的调用*/invokeBeanFactoryPostProcessors(beanFactory);/** 把实现了BeanPostProcessor接口的类实例化,并且加入到BeanFactory中*/registerBeanPostProcessors(beanFactory);/** 国际化*/initMessageSource();//初始化事件管理类initApplicationEventMulticaster();//这个方法着重理解模板设计模式,因为在springboot中,这个方法是用来做内嵌tomcat启动的onRefresh();/** 往事件管理类中注册事件类*/registerListeners();/** 1、bean实例化过程* 2、依赖注入* 3、注解支持* 4、BeanPostProcessor的执行* 5、Aop的入口*/finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.finishRefresh();} finally {resetCommonCaches();}}
}

16.2、Bean 对象的创建

当前面的准备工作做好后,就开始初始化 Bean 实例了,也就是 finishBeanFactoryInitialization 方法所作的事。不过这里可不是根据 BeanDefinition 去 new 一个对象就完了,它包含了以下几个工作:

  • 初始化实例。
  • 解析 @PostConstruct、@PreDestroy、@Resource、@Autowired、@Value 等注解。
  • 依赖注入。
  • 调用 BeanPostProcessor 方法。
  • AOP 入口。
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {......//重点看这个方法// Instantiate all remaining (non-lazy-init) singletons.beanFactory.preInstantiateSingletons();
}public void preInstantiateSingletons() throws BeansException {if (logger.isTraceEnabled()) {logger.trace("Pre-instantiating singletons in " + this);}// Iterate over a copy to allow for init methods which in turn register new bean definitions.// While this may not be part of the regular factory bootstrap, it does otherwise work fine.// xml解析时,讲过,把所有beanName都缓存到beanDefinitionNames了List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);// Trigger initialization of all non-lazy singleton beans...for (String beanName : beanNames) {// 把父BeanDefinition里面的属性拿到子BeanDefinition中RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);//如果不是抽象的,单例的,非懒加载的就实例化if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {//判断bean是否实现了FactoryBean接口,这里可以不看if (isFactoryBean(beanName)) {Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);if (bean instanceof FactoryBean) {final FactoryBean<?> factory = (FactoryBean<?>) bean;boolean isEagerInit;if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)((SmartFactoryBean<?>) factory)::isEagerInit,getAccessControlContext());}else {isEagerInit = (factory instanceof SmartFactoryBean &&((SmartFactoryBean<?>) factory).isEagerInit());}if (isEagerInit) {getBean(beanName);}}}else {//主要从这里进入,看看实例化过程getBean(beanName);}}}
}

其他详细内容,可以参考下这位大佬的文章:Spring的Bean实例化原理,这一次彻底搞懂了!

17、刷新上下文后置处理

afterRefresh 方法是启动后的一些处理,留给用户扩展使用,目前这个方法里面是空的。

/*** Called after the context has been refreshed.* @param context the application context* @param args the application arguments*/
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}

18、结束计时器

到这一步,SpringBoot 其实就已经完成了,计时器会打印 SpringBoot 的启动时长。

在这里插入图片描述

在控制台看到启动还是挺快的,2秒多就启动完成了。

在这里插入图片描述

19、发布上下文准备就绪事件

告诉应用程序,我已经准备好了,可以开始工作了。

在这里插入图片描述

20、执行自定义的 run 方法

这是一个扩展功能,callRunners(context, applicationArguments) 可以在启动完成后执行自定义的 run 方法。有 2 种实现方式:

  1. 实现 ApplicationRunner 接口;
  2. 实现 CommandLineRunner 接口。

接下来我们验证一把,为了一次性验证全,我们把这2种方式都放在同一个类里面。

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;/*** 自定义启动后执行*/
@Component
public class MyRunner implements ApplicationRunner, CommandLineRunner {@Overridepublic void run(ApplicationArguments args) throws Exception {System.out.println(" 我是自定义的 run 方法1,实现 ApplicationRunner 接口即可运行");}@Overridepublic void run(String... args) throws Exception {System.out.println(" 我是自定义的 run 方法2,实现 CommandLineRunner 接口即可运行");}
}

启动 SpringBoot 后就可以看到控制台打印的信息了。

在这里插入图片描述

整理完毕,完结撒花~ 🌻





参考地址:

1.SpringBoot启动过程,https://blog.csdn.net/qq_42259971/article/details/127151316

2.Spring的Bean实例化原理,这一次彻底搞懂了!https://zhuanlan.zhihu.com/p/198087901

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

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

相关文章

LeetCode450. 删除二叉搜索树中的节点

450. 删除二叉搜索树中的节点 文章目录 [450. 删除二叉搜索树中的节点](https://leetcode.cn/problems/delete-node-in-a-bst/)一、题目二、题解方法一&#xff1a;递归&#xff08;一种麻烦的方法&#xff09;方法二&#xff1a;优化后的递归 一、题目 给定一个二叉搜索树的根…

论文阅读——Imperceptible Adversarial Attack via Invertible Neural Networks

Imperceptible Adversarial Attack via Invertible Neural Networks 作者&#xff1a;Zihan Chen, Ziyue Wang, Junjie Huang*, Wentao Zhao, Xiao Liu, Dejian Guan 解决的问题&#xff1a;虽然视觉不可感知性是对抗性示例的理想特性&#xff0c;但传统的对抗性攻击仍然会产…

每天一道leetcode:1129. 颜色交替的最短路径(图论中等广度优先遍历)

今日份题目&#xff1a; 给定一个整数 n&#xff0c;即有向图中的节点数&#xff0c;其中节点标记为 0 到 n - 1。图中的每条边为红色或者蓝色&#xff0c;并且可能存在自环或平行边。 给定两个数组 redEdges 和 blueEdges&#xff0c;其中&#xff1a; redEdges[i] [ai, bi…

Dubbo Spring Boot Starter 开发微服务应用

环境要求 系统&#xff1a;Windows、Linux、MacOS JDK 8 及以上&#xff08;推荐使用 JDK17&#xff09; Git IntelliJ IDEA&#xff08;可选&#xff09; Docker &#xff08;可选&#xff09; 项目介绍 在本任务中&#xff0c;将分为 3 个子模块进行独立开发&#xff…

计算机组成与设计 Patterson Hennessy 笔记(一)MIPS 指令集

计算机的语言&#xff1a;汇编指令集 也就是指令集。本书主要介绍 MIPS 指令集。 汇编指令 算数运算&#xff1a; add a,b,c # abc sub a,b,c # ab-cMIPS 汇编的注释是 # 号。 由于MIPS中寄存器大小32位&#xff0c;是基本访问单位&#xff0c;因此也被称为一个字 word。M…

webpack中常见的Loader

目录 1.webpack中的loader是什么&#xff1f;配置方式 2. loader特性3.常见的loader 1.webpack中的loader是什么&#xff1f; loader 用于对模块的"源代码"进行转换&#xff0c;在 import 或"加载"模块时预处理文件 webpack做的事情&#xff0c;仅仅是分…

爬虫逆向实战(三)--天某云登录

一、数据接口分析 主页地址&#xff1a;天某云 1、抓包 通过抓包可以发现登录接口是account/login 2、判断是否有加密参数 请求参数是否加密&#xff1f; 通过“载荷”模块可以发现password、comParam_signature、comParam_seqCode是加密的 请求头是否加密&#xff1f; 无…

cmake扩展(5)——file命令排除部分文件

在cmake中可以使用file命令获取需要的文件&#xff0c;并且支持正则/通配符&#xff0c;使用起来还是很方便的。 #语法file({GLOB | GLOB_RECURSE} <out-var> [...] [<globbing-expr>...])#example file(GLOB_RECURSE SOURCES "src/*.h" "src/*.cp…

HTTP与HTTPS的区别

面试常见问题&#xff0c;HTTPS优化总结易记版&#xff1a; 1、HSTS重定向技术&#xff1a;将http自动转换为https&#xff0c;减少301重定向 2、TLS握手优化&#xff1a;在TLS握手完成前客户端就提前向服务器发送数据 3、会话标识符&#xff1a;服务器记录下与某客户端的会…

Mac鼠标增强工具Smooze Pro

Smooze Pro是一款Mac上的鼠标手势增强工具&#xff0c;可以让用户使用鼠标手势来控制应用程序和系统功能。 它支持多种手势操作&#xff0c;包括单指、双指、三指和四指手势&#xff0c;并且可以自定义每种手势的功能。例如&#xff0c;您可以使用单指向下滑动手势来启动Expos视…

Linux 僵死进程

fork复制进程之后&#xff0c;会产生一个进程叫做子进程&#xff0c;被复制的进程就是父进程。不管父进程先结束&#xff0c;还是子进程先结束&#xff0c;对另外一个进程完全没有影响&#xff0c;父进程和子进程是两个不同的进程。 一、孤儿进程 现在有以下代码&#xff1a;…

前端基础(JavaScript语法)

前言&#xff1a;今天学习JavaScript的基本语法&#xff0c;包括变量、函数、对象、数组。 目录 JavaScript 变量 函数 对象 数组 JavaScript 变量 定义变量 判断语句 判断等于&#xff1a; 判断不等于&#xff1a;! if else语句 if(vavb){ console.log("…

springboot 整合swagger 入门 使用

1.前提 一定要看好版本。 Springboot ✚ Swagger各版本整理_swagger版本_qq_33334411的博客-CSDN博客 我的版本: <dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</ver…

前馈神经网络正则化例子

直接看代码&#xff1a; import torch import numpy as np import random from IPython import display from matplotlib import pyplot as plt import torchvision import torchvision.transforms as transforms mnist_train torchvision.datasets.MNIST(root…

Docker:Windows container和Linux container

点击"Switch to Windows containers"菜单时&#xff1a; 提示 然后 实际上是运行&#xff1a;com.docker.admin.exe start-service

成集云 | 乐享问题邀请同步企微提醒 | 解决方案

源系统成集云目标系统 方案介绍 腾讯乐享是腾讯公司开发的一款企业社区化知识管理平台&#xff0c;它提供了包括知识库、问答、课堂、考试、活动、投票和论坛等核心应用。这个平台凝聚了腾讯10年的管理经验&#xff0c;可以满足政府、企业和学校在知识管理、学习培训、文化建…

【gitkraken】gitkraken自动更新问题

GitKraken 会自动升级&#xff01;一旦自动升级&#xff0c;你的 GitKraken 自然就不再是最后一个免费版 6.5.1 了。 在安装 GitKraken 之后&#xff0c;在你的安装目录&#xff08;C:\Users\<用户名>\AppData\Local\gitkraken&#xff09;下会有一个名为 Update.exe 的…

Linux环境变量

环境变量 一.基本概念二.常见的环境变量1.PATH&#xff1a;指令搜索路径2.HOME&#xff1a; 指定用户的主工作目录3.SHELL&#xff1a;当前Shell,它的值通常是/bin/bash 三.查看环境变量的方法四.命令行参数五.环境变量增加和删除六.本地变量 一个问题&#xff1a;我们在写一段…

Kotlin~Bridge桥接模式

概念 抽象和现实之间搭建桥梁&#xff0c;分离实现和抽象。 抽象&#xff08;What&#xff09;实现&#xff08;How&#xff09;用户可见系统正常工作的底层代码产品付款方式定义数据类型的类。处理数据存储和检索的类 角色介绍 Abstraction&#xff1a;抽象 定义抽象接口&…

一起创建Vue脚手架吧

目录 一、安装Vue CLI1.1 配置 npm 淘宝镜像1.2 全局安装1.3 验证是否成功 二、创建vue_test项目2.1 cmd进入桌面2.2 创建项目2.3 运行项目2.4 查看效果 三、脚手架结构分析3.1 文件目录结构分析3.2 vscode终端打开项目 一、安装Vue CLI CLI&#xff1a;command-line interface…