SpringBoot源码解读与原理分析(四十)基于jar/war包的运行机制

文章目录

  • 前言
  • 第14章 运行SpringBoot应用
    • 14.1 部署打包的两种方式
      • 14.1.1 以可独立运行jar包的方式
      • 14.1.2 以war包的方式
    • 14.2 基于jar包的独立运行机制
      • 14.2.1 可独立运行jar包的相关知识
      • 14.2.2 SpringBoot的可独立运行jar包结构
      • 14.2.3 JarLauncher的设计及工作原理
        • 14.2.3.1 JarLauncher的继承结构
          • 14.2.3.1.1 Launcher
          • 14.2.3.1.2 ExecutableArchiveLauncher
          • 14.2.3.1.3 JarLauncher
          • 14.2.3.1.4 WarLauncher
        • 14.2.3.2 JarLauncher的引导原理
          • 14.2.3.2.1 创建类加载器:```createClassLoader```
          • 14.2.3.2.2 获取主启动类类名:```getMainClass```
          • 14.2.3.2.3 执行主启动类的```main```方法
        • 14.2.3.3 WarLauncher的引导原理
    • 14.3 基于war包的外部Web容器运行机制
      • 14.3.1 Servlet 3.0规范中引导应用启动的说明
      • 14.3.2 SpringBootServletInitializer
        • 14.3.2.1 ServletContainerInitializer的加载
        • 14.3.2.2 SpringBootServletInitializer的加载

前言

在一个SpringBoot项目开发完成后,最终需要项目部署到服务器使其正常运行,以提供功能服务使用。部署运行SpringBoot项目的方法一般采用打包部署为主。

第14章 运行SpringBoot应用

14.1 部署打包的两种方式

大多数情况下,会选择将SpringBoot项目打包为一个可独立运行的jar包,或者去掉内置的嵌入式Web容器,以war包形式部署到外置的容器中,这取决于开发者最终要部署的目标环境。

14.1.1 以可独立运行jar包的方式

将SpringBoot项目打包为一个可独立运行的jar包,需要在pom.xml文件中引入spring-boot-maven-plugin插件。

<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins>
</build>

配置好后,执行mvn package命令,就可在项目根目录的target目录下获得一个可执行的jar包,直接执行java -jar xxx.jar命令就可以启动该项目。

14.1.2 以war包的方式

将SpringBoot项目打包为一个war包,需要在pom.xml文件中额外添加一些配置。

<packaging>war</packaging><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></dependency>
</dependencies>

然后还要修改主启动类或者新建一个类,使其继承SpringBootServletInitializer类,并重写configure方法指定配置源为当前项目的主启动类。

@SpringBootApplication
public class WebFluxApp extends SpringBootServletInitializer {public static void main(String[] args) {SpringApplication.run(WebFluxApp.class, args);}@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {return builder.sources(WebFluxApp.class);}
}

修改完成后,重新执行mvn package命令,就可以生成一个可以部署到外置Web容器的war包。

14.2 基于jar包的独立运行机制

14.2.1 可独立运行jar包的相关知识

从Oracle的官网上可以找到有关 jar文件规范的文档 :

The signature related files are:

  • META-INF/MANIFEST.MF
  • META-INF/*.SF
  • META-INF/*.DSA
  • META-INF/*.RSA
  • META-INF/SIG-*

文档中指出,可独立运行jar包的一个核心目录是 META-INF/,这个目录会存放当前jar包的一些扩展和配置数据,其中一个核心配置文件是 MANIFEST.MF,它以properties的形式保存了jar包的一些核心元信息。

查阅文档可知,MANIFEST.MF文件的核心配置项主要包含以下几项(一共有21项,这里只列出其中3项相对重要的):

配置项配置含义配置值示例
Manifest-Version定义MANIFEST.MF文件的版本1.0(通常)
Class-Path指定当前jar包所依赖的jar包的路径(一般是相对路径)servlet.jar、config/
Main-Class引导可独立运行jar包启动的引导类的全限定类名org.springframework.boot.loader.JarLauncher

重点关注配置项 Main-Class,它指定了一个可以在jar包的顶层结构中直接找到的、带有main方法的、引导jar包启动的引导类的全限定类名。

这里所说的顶层结构,指的是在可独立运行的jar包中,可以直接在目录中找到,不需要再解压jar包内部。换句话说,被Main-Class配置项引用的类必须同它所属的包一起放在可独立运行jar包的顶层。

14.2.2 SpringBoot的可独立运行jar包结构

对于SpringBoot通过Maven插件打包的可独立运行jar包,它的内部由3个目录构成:

可独立运行jar包结构

  • BOOT-INF:存放项目编写且编译好的字节码文件、静态资源文件、配置文件,以及依赖的jar包。
  • META-INF:存放 MANIFEST.MF 等配置元信息。
  • org.springframework.boot.loader:存放spring-boot-loader的核心引导类,这些都放在了顶层结构中

其中META-INF中的 MANIFEST.MF 文件内容如下:

Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: springboot-07-webmvc
Implementation-Version: 1.0-SNAPSHOT
Start-Class: com.star.springboot.webmvc.WebMvcApp
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.3.11.RELEASE
Created-By: Maven Jar Plugin 3.2.0
Main-Class: org.springframework.boot.loader.JarLauncher

注意其中两个配置项:

  • Main-Class: org.springframework.boot.loader.JarLauncher

这个配置前面已经已经解释过,是引导可独立运行jar包启动的引导类的全限定类名。

  • Start-Class: com.star.springboot.webmvc.WebMvcApp

这个配置项中定义的WebMvcApp类,是开发者在项目中自定义的,并且这个配置项在官网的jar文件规范中并没有提及,因此Start-Class配置项本身不是MANIFEST.MF文件标准规范中的配置项,而是SpringBoot自行定义的

由此可以推测,如果直接用WebMvcApp来引导这个可独立运行jar包,是无法启动项目的。

试验一下,将MANIFEST.MF文件的Main-Class属性的值改为com.star.springboot.webmvc.WebMvcApp,并执行java -jar xxx.jar命令,发现根本无法启动项目。

无法启动项目
无法启动的原因在于,引导启动的WebMvcApp类并没有放在jar包的顶层目录下,而是放在了 BOOT-INF/classes/ 目录下,中间隔了两层包。

如果Main-Class属性使用默认指定的JarLauncher类,则可以正常启动SpringBoot项目,说明JarLauncher类是引导启动的核心类。

14.2.3 JarLauncher的设计及工作原理

JarLauncher类来自于spring-boot-loader依赖,用于引导可独立运行jar包的启动。

14.2.3.1 JarLauncher的继承结构

借助IDEA,可以生成JarLauncher类的继承关系图:

JarLauncher类的继承关系图
由上图可知,SpringBoot项目的启动器是通过两个Launcher类的落地实现类JarLauncher和WarLauncher实现的,它们分别处理jar包和war包的启动,而这两个落地实现类又同时继承自ExecutableArchiveLauncher类。

14.2.3.1.1 Launcher

Launcher是启动SpringBoot项目的顶层引导类,它的内部定义了一个非常关键的launch方法,用于启动SpringBoot项目。

14.2.3.1.2 ExecutableArchiveLauncher

从类名上可以理解为“可执行归档文件的启动器”。

所谓“归档文件”,可以简单理解为,一个SpringBoot的独立可执行jar包就是一个归档文件,可以放在外置的Web容器中运行的war包也是一个归档文件。

ExecutableArchiveLauncher的作用在于,从归档文件中检索到SpringBoot项目的主启动类,并提供给父类Launcher以完成主启动类的引导。

14.2.3.1.3 JarLauncher

JarLauncher是基于SpringBoot可独立运行jar包的启动引导器。

Launcher for JAR based archives. This launcher assumes that dependency jars are included inside a /BOOT-INF/lib directory and that application classes are included inside a /BOOT-INF/classes directory.

基于jar包归档文件的启动器。此启动器假定项目所依赖的jar包包含在/BOOT-INF/lib目录中,项目中所定义的类包含在/BOOT-INF/classes目录中。

由JarLauncher类的javadoc可知,“/BOOT-INF/lib"和”/BOOT-INF/classes"这两个目录是项目启动的关键。

14.2.3.1.4 WarLauncher

Launcher for WAR based archives. This launcher for standard WAR archives. Supports dependencies in WEB-INF/lib as well as WEB-INF/lib-provided, classes are loaded from WEB-INF/classes.

基于war包归档文件的启动器。此启动器用于标准的war包,项目所依赖的jar包包含在WEB-INF/lib和WEB-INF/lib-provided中,项目中所定义的类包含在WEB-INF/classes目录中。

由javadoc可知,WarLauncher本身也是一个启动类引导器,可以将打包好的war包使用java -jar xxx.war命令引导启动SpringBoot项目。

WarLauncher引导启动SpringBoot项目
与jar包不同,war包对于所依赖的jar包和项目中的Class文件有一定限制。对于一个标准war包,项目中的Class文件要放在 WEB-INF/classes 目录下,所依赖的jar包要放在 WEB-INF/lib 目录下,另外所有作用范围为provided的依赖统一放在 WEB-INF/lib-provided 目录下。

如果war包独立运行,则会同时加载 WEB-INF/lib 和 WEB-INF/lib-provided 目录下的依赖,而当war包放置于外置Web容器时,由于Web容器不会读取 WEB-INF/lib-provided 目录,这部分依赖不会被加载。这样就同时兼容了两种启动方式。

14.2.3.2 JarLauncher的引导原理
源码1JarLauncher.javapublic static void main(String[] args) throws Exception {new JarLauncher().launch(args);
}
源码2Launcher.javaprotected void launch(String[] args) throws Exception {// 注册URL协议并清除应用缓存if (!isExploded()) {JarFile.registerUrlProtocolHandler();}// 创建类加载器ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());// 获取主启动类的类名String jarMode = System.getProperty("jarmode");String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();// 执行主启动类的main方法launch(args, launchClass, classLoader);
}

由 源码1 可知,JarLauncher内部定义了一个main方法,作为整个可运行jar包运行的入口。在main方法中调用了launch方法,该方法定义在其顶层父类Launcher中。

由 源码2 可知,launch方法的核心步骤可以拆分为三步:

14.2.3.2.1 创建类加载器:createClassLoader

调用createClassLoader方法创建类加载器时,其参数是getClassPathArchivesIterator方法的返回值。

源码3ExecutableArchiveLauncher.java@Override
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {Archive.EntryFilter searchFilter = this::isSearchCandidate;Iterator<Archive> archives = this.archive.getNestedArchives(searchFilter,(entry) -> isNestedArchive(entry) && !isEntryIndexed(entry));if (isPostProcessingClassPathArchives()) {archives = applyClassPathArchivePostProcessing(archives);}return archives;
}
源码4Archive.javadefault Iterator<Archive> getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter)throws IOException {...}

由 源码3 可知,getNestedArchives方法需要传入两个EntryFilter参数,第一个是搜索范围searchFilter,第二个是过滤条件includeFilter。该方法的作用是以迭代器的形式返回在指定的搜索范围内与指定过滤器匹配的嵌套归档文件。

首先是搜索范围EntryFilter:isSearchCandidate方法。

源码5JarLauncher.java@Override
protected boolean isSearchCandidate(Archive.Entry entry) {return entry.getName().startsWith("BOOT-INF/");
}

由 源码5 可知,搜索范围是所有名称以"BOOT-INF/"开头的文件。

其次是过滤条件EntryFilter,筛选出需要收集起来的文件:(entry) -> isNestedArchive(entry) && !isEntryIndexed(entry)

源码6JarLauncher.javastatic final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {// 如果是文件夹,则是"BOOT-INF/classes/"文件夹if (entry.isDirectory()) {return entry.getName().equals("BOOT-INF/classes/");}// 如果是文件,则是"BOOT-INF/lib/"下的文件return entry.getName().startsWith("BOOT-INF/lib/");
};@Override
protected boolean isNestedArchive(Archive.Entry entry) {return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
}

由 源码6 可知,过滤条件是筛选所有"BOOT-INF/lib/"目录下的文件以及"BOOT-INF/classes/"文件夹。

经过以上筛选,getClassPathArchivesIterator以迭代器形式返回了当前SpringBoot应用中依赖的嵌套jar包和字节码文件,并作为参数传入createClassLoader方法中。

源码7Launcher.javaprotected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {// 将Archive对象转换为URL对象List<URL> urls = new ArrayList<>(50);while (archives.hasNext()) {urls.add(archives.next().getUrl());}return createClassLoader(urls.toArray(new URL[0]));
}protected ClassLoader createClassLoader(URL[] urls) throws Exception {// 创建类加载器LaunchedURLClassLoaderreturn new LaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass().getClassLoader());
}

由 源码7 可知,createClassLoader方法首先将上一步获取到的Archive对象转换为一个URL对象,每个URL对象对应一个jar包或字节码文件的路径。转换完成后,最终创建的类加载器是LaunchedURLClassLoader,传入URL对象数组。

14.2.3.2.2 获取主启动类类名:getMainClass
源码8ExecutableArchiveLauncher.javaprivate static final String START_CLASS_ATTRIBUTE = "Start-Class";
@Override
protected String getMainClass() throws Exception {Manifest manifest = this.archive.getManifest();String mainClass = null;if (manifest != null) {// 读取MANIFEST.MF文件中的"Start-Class"属性的值mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);}if (mainClass == null) {// throw ...}return mainClass;
}

由 源码8 可知,获取主启动类的方式就是读取取 MANIFEST.MF 文件中的"Start-Class"属性的值。

在前面的【14.2.2 SpringBoot的可运行jar包结构】中就提到过,"Start-Class"属性刚好就定义了SpringBoot应用主启动类的全限定类名。

14.2.3.2.3 执行主启动类的main方法
源码9Launcher.javaprotected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {Thread.currentThread().setContextClassLoader(classLoader);// 构造MainMethodRunner对象并执行其run方法createMainMethodRunner(launchClass, args, classLoader).run();
}protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {return new MainMethodRunner(mainClass, args);
}
源码10MainMethodRunner.javapublic void run() throws Exception {// 利用反射机制获取主启动类Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());// 获取主启动类的main方法Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);mainMethod.setAccessible(true);// 执行主启动类的main方法mainMethod.invoke(null, new Object[] { this.args });
}

由 源码9-10 可知,重载的launch方法首先会构造一个MainMethodRunner对象,传入主启动类的类名及参数。

随后调用MainMethodRunner对象的run方法,该方法会利用反射机制获取主启动类的Class对象,再通过getDeclaredMethod方法获取主启动类的main方法并执行。

当SpringBoot主启动类的main方法被成功调用后,SpringBoot应用即可顺利启动,基于JarLauncher的启动引导完成。

14.2.3.3 WarLauncher的引导原理

使用WarLauncher的引导原理在本质上和JarLauncher并无太大区别,只是在定位依赖jar包和字节码文件时搜索的目录不同。

源码11WarLauncher.java@Override
protected boolean isSearchCandidate(Entry entry) {return entry.getName().startsWith("WEB-INF/");
}@Override
public boolean isNestedArchive(Archive.Entry entry) {if (entry.isDirectory()) {return entry.getName().equals("WEB-INF/classes/");}return entry.getName().startsWith("WEB-INF/lib/") || entry.getName().startsWith("WEB-INF/lib-provided/");
}

由 源码11 可知,基于WarLauncher的搜索范围是"WEB-INF/classes/"、"WEB-INF/lib/"以及"WEB-INF/lib-provided/"三个目录。

14.3 基于war包的外部Web容器运行机制

基于war包的外置容器运行需要借助Servlet 3.0规范的一个引导机制,这个机制是SpringBoot应用启动的核心。

14.3.1 Servlet 3.0规范中引导应用启动的说明

在Servlet 3.0规范文档的 8.2.4 节有对运行时插件的描述:

An instance of the ServletContainerInitializer is looked up via the jar services API by the container at container / application startup time. The framework providing an implementation of the ServletContainerInitializer MUST bundle in the META-INF/services directory of the jar file a file called javax.servlet.ServletContainerInitializer, as per the jar services API, that points to the implementation class of the ServletContainerInitializer.

在容器/应用程序启动时,通过SPI机制查找ServletContainerInitializer的示例。提供ServletContainerInitializer实现的框架必须在jar包的META-INF/services目录中定义一个名为javax.servlet.ServletContainerInitializer的文件,根据SPI机制,找到对应的ServletContainerInitializer接口的实现类。

In addition to the ServletContainerInitializer we also have an annotation - HandlesTypes. The HandlesTypes annotation on the implementation of the ServletContainerInitializer is used to express interest in classes that may have anotations (type, method or field level annotations) specified in the value of the HandlesTypes or if it extends / implements one those classes anywhere in the class’ super types. The container uses the HandlesTypes annotation to determine when to invoke the initializer’s onStartup method.

除了ServletContainerInitializer之外,我们还有一个注解——@HandlesTypes。ServletContainerInitializer实现类上的@HandlesTypes注解用于表达对一些类(或接口类型)的兴趣。容器使用@HandlesTypes注解来确定何时调用初始化器的onStartup方法。

由该段描述可知,Servlet容器启动应用时会扫描项目及依赖jar包中ServletContainerInitializer接口的实现类,方法是在jar包的META-INF/services目录中提供一个名为javax.servlet.ServletContainerInitializer的文件,文件内容要标明ServletContainerInitializer接口实现类的全限定类名。

此外,实现了ServletContainerInitializer接口的实现类可以标注**@HandlesTypes注解**,并指定一些感兴趣的类(或接口类型),Servlet容器初始化时会将这些感兴趣的类(或接口的实现类)传入onStartup方法的第一个参数中,以此完成一些更高级的处理。

源码12ServletContainerInitializer.javapublic interface ServletContainerInitializer {void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}

由 源码12 可知,ServletContainerInitializer本身是一个接口,它仅有一个onStartup方法,不难推测出Servlet容器启动时会回调onStartup方法以完成应用的初始化逻辑。

14.3.2 SpringBootServletInitializer

SpringBoot为了适配外置Servlet容器启动的方法,提供了一个特殊的实现类SpringBootServletInitializer。

在【14.1.2 以war包的方式】中提到,要将SpringBoot项目打包为一个war包,不仅需要在pom.xml文件中添加一些配置,还需要编写一个SpringBootServletInitializer的子类,指定SpringBoot主启动类作为启动源。

这样编写的目的在于,为当前SpringBoot项目提供一个SpringBootServletInitializer子类,从而让外置Servlet容器在启动时可以加载该子类,从而初始化和启动SpringBoot应用。

14.3.2.1 ServletContainerInitializer的加载

当外置Servlet容器启动时,默认会加载部署的war包,此时被打包成war包的SpringBoot项目被解压,Servlet容器会从当前项目及项目所依赖的jar包中搜索一个全路径名为 META-INF/services/javax.servlet.ServletContainerInitializer 的文件(基于SPI机制)。

如果成功搜索到该文件,则会加载文件中定义的全限定类名对应的类。

在spring-web依赖中,可以找到该文件,文件中定义的全限定类名是org.springframework.web.SpringServletContainerInitializer。

spring-web依赖

源码13SpringServletContainerInitializer.java@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {@Overridepublic void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)throws ServletException {// 加载、实例化WebApplicationInitializer对象 ...for (WebApplicationInitializer initializer : initializers) {initializer.onStartup(servletContext);}}
}

由 源码13 可知,SpringServletContainerInitializer类标注了@HandlesTypes注解,它感兴趣的类型是WebApplicationInitializer,意味着onStartup方法会获取当前项目中所有实现了WebApplicationInitializer接口的落地实现类。

14.3.2.2 SpringBootServletInitializer的加载
源码14SpringBootServletInitializer.javapublic abstract class SpringBootServletInitializer implements WebApplicationInitializer {@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {this.logger = LogFactory.getLog(getClass());WebApplicationContext rootApplicationContext = createRootApplicationContext(servletContext);// ......}protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {SpringApplicationBuilder builder = createSpringApplicationBuilder();// ......// 此处的configure方法执行的是自定义的builder = configure(builder);// ......// 构建SpringApplicationSpringApplication application = builder.build();// ......// 基于外置Servlet容器启动不需要注册回调钩子application.setRegisterShutdownHook(false);return run(application);}
}protected WebApplicationContext run(SpringApplication application) {// 调用SpringApplication的run方法return (WebApplicationContext) application.run();
}

由 源码14 可知,SpringBootServletInitializer实现了WebApplicationInitializer接口,因此SpringServletContainerInitializer的onStartup方法会获取的当前项目中实现了WebApplicationInitializer接口的落地实现类就是SpringBootServletInitializer。

SpringBootServletInitializer的onStartup方法中,核心动作是创建一个SpringApplication对象并调用其run方法真正启动应用。

在构建SpringApplication对象过程中,调用的configure方法实际上就是调用了【14.1.2】节中编写的SpringBootServletInitializer的子类中的configure方法,这里指定了SpringBoot项目真正的主启动类。

SpringApplicationBuilder正是拿到了这个主启动类,才能构建对应的SpringApplication对象。

经过SpringBootServletInitializer的构建并调用SpringApplication的run方法,SpringBoot项目即可成功启动。

······

本节完,更多内容请查阅分类专栏:SpringBoot源码解读与原理分析

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

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

相关文章

06、MongoDB -- MongoDB 基本用法(删除文档、查询文档、查询运算符)

目录 MongoDB 基本用法演示前提&#xff1a;登录单机模式的 mongodb 服务器命令登录【admin】数据库的 mongodb 客户端命令登录【test】数据库的 mongodb 客户端命令 删除文档语法格式两个变体版本&#xff1a;1、remove&#xff1a;根据【name】字段删除一条文档2、deleteOne&…

代码工具APEX的入门使用(未包含安装)

第一次使用APEX是2019年&#xff0c;这个技术成名已久只是我了解的比较晚。请看Oracle ACE的网站&#xff0c;这就是用APEX做的。实际上有一次我看O记的人操作他们的办公流程&#xff0c;都是用APEX做的。 那一年&#xff0c;我用APEX做了一个CMDB的管理系统。那时候还没有流行…

从0搭建Azure DevOps Server

Windows虚拟机搭建DevOps 服务器 背景资源准备安装软件需求流程版本兼容性安装SQL ServerSSMS安装visual StudioAzure DevOps Server测试本地访问端口更改及外界访问 背景 搭建一台Azure DevOps Server 供我们运维项目开发&#xff0c;现在DevOps运维已成为一个主流&#xff0…

C向C++的一个过渡

思维导图 输入输出&#xff0c;以及基础头文件 在c语言中我们常用scanf("%d",&n);和printf("%d\n",n);来输出一些变量和常量&#xff0c;在C中我们可以用cin;和cout;来表示输入输出。 在C语言中输入输出有头文件&#xff0c;在C也有头文件&#xff0…

软件应用,财务收支系统试用版操作教程,佳易王记录账单的软件系统

软件应用&#xff0c;财务收支系统试用版操作教程&#xff0c;佳易王记录账单的软件系统 一、前言 以下软件操作教程以 佳易王账单记账统计管理系统V17.0为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 如上图&#xff0c;统计报表包含 收支汇…

在vue前端开发中基于refreshToken和axios拦截器实现token的无感刷新

文章目录 一、需求背景二、token刷新的方案1、根据过期时间重新获取2、定时刷新token接口3、使用了RefreshToken 三、关于RefreshToken四、Refresh Token的优点五、Refresh Token的工作原理六、Refresh Token的使用流程七、Refresh Token的实现步骤1、登录成功后保存AccessToke…

前端CSS常考问题总结

目录 CSS盒模型 CSS选择器的优先级 隐藏元素的方法 px和rem的区别是什么? 重绘重排有什么区别? 重排&#xff08;回流&#xff09;&#xff1a; 重绘&#xff1a; 浏览器的渲染机制: 浏览器如何解析CSS&#xff1f; 元素水平垂直居中的方式 CSS的哪些属性哪些可以…

php开发项目 docx,pptx,excel表格上传阿里云,腾讯云存储后截取第一页生成缩略图

服务器或者存储上传的word,ppt和excel表格需要截取内容展示的时候,就需要管理后台每次上传文件时根据不同文件类型截取图片保存起来,并讲图片的地址保存到数据字段中.网上搜索了很多相关文章遇到的坑不少,经过2天时间终于完成了,将代码和遇到的问题完整记录下来. 本文用的…

【前端寻宝之路】总结学习使用CSS的引入方式

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法|MySQL| ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-BNJBIEvpN0GHNeJ1 {font-family:"trebuchet ms",verdana,arial,sans-serif;f…

IDEA自动导入provided的依赖

最近在学习flink 流程序&#xff0c;在写demo程序的时候依赖flink依赖&#xff0c;依赖的包在flink集群里面是自己已经提供了的&#xff0c;在导入的时候配置为provided&#xff0c;像下面这样&#xff0c;以使打包的时候不用打到最终的程序包里面。 <dependency><gro…

Java8,函数式编程应用:

持续更新中&#xff1a; 函数式(Functional)接口 什么是函数式(Functional)接口 只包含一个抽象方法的接口&#xff0c;称为函数式接口。 你可以通过 Lambda 表达式来创建该接口的对象。&#xff08;若 Lambda 表达式 抛出一个受检异常(即&#xff1a;非运行时异常)&#xff0c…

js创建对象方式总结

js创建对象方式总结 字面量方式 使用大括号 {} 创建一个新对象&#xff0c;这是最简单直接的方式。适用于创建单个对象&#xff0c;可以直接在大括号内定义属性和方法。 let person {name: John,age: 30,gender: male};let preson2 {name: John,age: 30,gender: male};cons…

光伏发电预测

XGB、LGB在datacamp(学习网站) data fountain与国家电投系列赛,光伏发电预测 题目:给一组特征,预测瞬时发电量,训练集9000个点,测试集8000个点,特征包含光伏板的属性和外部环境等。 数据字段:ID、光伏电池板背侧温度、光伏电站现场温度、计算得到的平均转换效率、数…

MySQL学习Day25——数据库其他调优策略

一、数据库调优的措施: 1.调优的目标: (1)尽可能节省系统资源&#xff0c;以便系统可以提供更大负荷的服务 (2)合理的结构设计和参数调整&#xff0c;以提高用户操作的响应速度 (3)减少系统的瓶颈&#xff0c;提高MySQL数据库整体的性能; 2.如何定位调优:用户的反馈、日志…

stm32f103zet6笔记1-led工程

1、选择串口调试 2、LED0连接到PB5&#xff0c;PB5设置为推挽输出。PE5同理。 3、生成成对的.c,.h文件。 4、debugger选择j-link。 5、connection选择SWD。 6、编写bsp_led.c,bsp_led.h文件。 7、下载调试&#xff0c;可以看到LED0 500ms闪烁一次&#xff0c;LED1 1000ms闪烁一…

浅谈一个CTF中xss小案例

一、案例代码 二、解释 X-XSS-Protection: 0&#xff1a;关闭XSS防护 之后get传参&#xff0c;替换过滤为空&#xff0c;通过过滤保护输出到img src里面 三、正常去做无法通过 因为这道题出的不严谨所以反引号也是可以绕过的 正常考察我们的点不在这里&#xff0c;正常考察…

Unity之街机捕鱼

目录 &#x1f62a;炮台系统 &#x1f3b6;炮口方向跟随鼠标 &#x1f3b6;切换炮台 &#x1f62a;战斗系统 &#x1f3ae;概述 &#x1f3ae;单例模式 &#x1f3ae;开炮 &#x1f3ae;子弹脚本 &#x1f3ae;渔网脚本 &#x1f3ae;鱼属性信息的脚本 &#x1f6…

怎样获得CNVD原创漏洞证书

1. 前言 因为工作变动&#xff0c;我最近把这一年多的工作挖漏洞的一些工作成果提交到了CNVD漏洞平台&#xff08;https://www.cnvd.org.cn/&#xff09;&#xff0c;获得了多张CNVD原创漏洞证书。本篇博客讲下怎么获得CNVD原创漏洞证书&#xff0c;以供大家参考。 2. CNVD原创…

Canvas笔记03:Canvas元素功能、属性、获取、原理等一文讲透

hello&#xff0c;我是贝格前端工场&#xff0c;最近在学习canvas&#xff0c;分享一些canvas的一些知识点笔记&#xff0c;本期分享canvas元素的知识&#xff0c;欢迎老铁们一同学习&#xff0c;欢迎关注&#xff0c;如有前端项目可以私信贝格。 Canvas元素是HTML5中的一个重…

基于Intel x86的轨道交通/印度地铁自动售检票(AFC)系统

印度孟买地铁3号线 目前&#xff0c;印度孟买3号线正在全面建设中&#xff0c;这条全长33.5公里的线路将是孟买第一条地下地铁线路&#xff0c;设有27个地下车站和1个地面车站&#xff0c;此条线路的成功通车将连接其他地铁线路、单轨铁路、郊区铁路、城际铁路和孟买机场等&am…