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

    @Override
    protected 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.java

public static void main(String[] args) throws Exception {
    new JarLauncher().launch(args);
}
源码2Launcher.java

protected 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.java

default 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.java

static 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.java

protected 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 {
    // 创建类加载器LaunchedURLClassLoader
    return new LaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass().getClassLoader());
}

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

14.2.3.2.2 获取主启动类类名:getMainClass
源码8ExecutableArchiveLauncher.java

private 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.java

protected 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.java

public 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.java

public 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 {

    @Override
    public 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.java

public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
    @Override
    public 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);
        // ......
        // 构建SpringApplication
        SpringApplication 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源码解读与原理分析

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2核4G云服务器租用价格_2核4G云主机优惠价格_2024年报价

租用2核4G服务器费用价格&#xff0c;2核4G云服务器多少钱一年&#xff1f;1个月费用多少&#xff1f;阿里云2核4G服务器30元3个月、轻量应用服务器2核4G4M带宽165元一年、企业用户2核4G5M带宽199元一年&#xff1b;腾讯云轻量2核4G服务器5M带宽165元一年、252元15个月、540元三…

毕业生信息招聘平台|基于springboot+ Mysql+Java的毕业生信息招聘平台设计与实现(源码+数据库+文档+PPT)

目录 论文参考 摘 要 数据库设计 系统详细设计 文末获取源码联系 论文参考 摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 毕业生信息招聘平台&#xff0c;主要的模块包括查看管理员&a…

力扣经典题目解析--最小覆盖子串

原题地址: . - 力扣&#xff08;LeetCode&#xff09; 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串&#xff0c;则返回空字符串 "" 。 注意&#xff1a; 对于 t 中重复字符&#xff0c;我们寻找…

Finetuning Large Language Models: Sharon Zhou

Finetuning Large Language Models 课程地址&#xff1a;https://www.deeplearning.ai/short-courses/finetuning-large-language-models/ 本文是学习笔记。 Goal&#xff1a; Learn the fundamentals of finetuning a large language model (LLM). Understand how finetu…

Vue3:用vite创建Vue3项目

一、简介 vite是新一代前端构建工具&#xff0c;官网地址&#xff1a;https://vitejs.cn vite的优势如下&#xff1a; 轻量快速的热重载&#xff08;HMR&#xff09;&#xff0c;能实现极速的服务启动。对 TypeScript、JSX、CSS 等支持开箱即用。真正的按需编译&#xff0c;不…

【计算机那些事】

目录 【云计算】 【原神用的是UDP还是TCP】 【几个特殊地址】 【socket是什么】 【内网穿透是什么】 【为什么有HTTP协议&#xff0c;还要有websocket协议】 【科普路由器&#xff0c;集线器&#xff0c;交换机&#xff0c;网桥&#xff0c;光猫】 【USB接口那些事】 …

MacOS包管理工具homebrew使用教程

MacOS包管理工具homebrew使用教程 1.概述与安装2.基本使用3.其他常用命令 1.概述与安装 homebrew是Mac OS X上的强大的包管理工具&#xff0c;可以高效管理各种软件包 安装&#xff1a; 1、安装xcode&#xff1a; xcode-select --install2、一行命令下载&#xff1a; /bin…

个人项目介绍3:火车站篇

项目需求&#xff1a; 一比一精确显示火车站主建筑和站台模型。实时响应车辆信息&#xff08;上水&#xff0c;吸污&#xff0c;换乘&#xff09;并同步显示&#xff0c;实时响应车辆进出站信息&#xff0c;并以动画形式模拟。实时响应报警信息&#xff0c;并能在三位中显示&a…

快速搭建Vue前端框架

快速搭建Vue前端框架 安装Vue Vue官方安装过程:https://cli.vuejs.org/zh/guide/installation.html 二.创建Vue工程 2.2 安装淘宝镜像 安装淘宝镜像&#xff08;会让你安装Vue的速度加快&#xff09;&#xff1a; npm config set registry https://registry.npm.taobao.or…

【内推】金山办公 2024届 春季校园招聘

有需要内推的小伙伴吗&#xff1f; 金山办公 各岗位均有 面向应届生春招 QQ群&#xff1a;723529936 内推码&#xff1a;NTASYQI

十秒学会Ubuntu命令行:从入门到进阶

一、引言 在使用Ubuntu操作系统时&#xff0c;命令行界面&#xff08;CLI&#xff09;是不可或缺的一部分。对于初学者来说&#xff0c;掌握基本的命令行操作可以帮助他们更高效地管理系统和软件。本文将介绍一些常见的Ubuntu命令以及如何解决与命令行相关的问题。 二、常用Ubu…

【C语言】内存操作篇---动态内存管理----malloc,realloc,calloc和free的用法【图文详解】

欢迎来CILMY23的博客喔&#xff0c;本篇为【C语言】内存操作篇---动态内存管理----malloc&#xff0c;realloc&#xff0c;calloc和free的用法【图文详解】&#xff0c;感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞关注收藏。 前言 在学完结构体后&#xff08;…

本地搭建xss平台并获取cookie演练

前言 一般而言&#xff0c;搭建xss平台是不被允许的&#xff0c;但是由于教育的目的&#xff0c;搭建xss平台更能让学习者更加直观感受xss漏洞对我们的危害和它的重要性。 搭建xss平台 1.搭建xss平台的基础是在phpstudy一个集成环境上的&#xff0c;所有第一步要安装phpstudy&a…

ardupilot 及PX4姿态误差计算算法对比分析

目录 文章目录 目录摘要1.APM姿态误差计算算法2.PX4姿态误差计算算法3.结论摘要 本节主要记录ardupilot 及PX4姿态误差计算算法差异对比过程,欢迎批评指正。 备注: 1.创作不易,有问题急时反馈 2.需要理解四元物理含义、叉乘及点乘含义、方向余弦矩阵含义、四元数乘法物理含…

Linux环境基础开发工具使用

目录 1.Linux软件包管理器yum 什么是软件包 关于 lrzsz 查看软件包 2.Linux开发工具 2.1.vim的基本概念 2.2vim的基本操作 2.3vim命令模式命令集 1.插入模式 2.从插入模式切换为命令模式 3.移动光标 4.删除文字 5.复制 6.替换 7.撤销上一次的操作 8.更改 2.4v…

Windows10笔记本亮度调节按键失灵

操作&#xff1a;任务管理器 -> 监视器 -> 右键点击 -> 通用即插即用监视器 -> 更新驱动程序 -> 浏览我的电脑以查找我的驱动程序 -> 让我从计算机上的可用驱动程序列表中选取 -> 点击通用即插即用监视器 -> 点击关闭 -> 重启电脑。 第一步&#x…

第三百八十二回

文章目录 1. 概念介绍2. 使用方法3. 代码与效果3.1 示例代码3.2 运行效果 4. 内容总结 我们在上一章回中介绍了"如何修改按钮的形状"相关的内容&#xff0c;本章回中将介绍NavigationBar组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章…

波奇学Linux:进程通信之命名管道

进程通信的前提&#xff1a;让不同的进程看到同一份文件 匿名管道只能具有血缘关系的进程&#xff0c;毫不相关的进程通信得要命名管道 管道文件不需要刷盘&#xff0c;基于内存级文件 命名管道通过路径文件名确定打开同一个文件&#xff0c;在匿名管道中利用父子进程。 创…

(学习日记)2024.02.29:UCOSIII第二节

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

总结:React 中的 state 状态

☝️上文提及&#xff1a;可以通过组件中的重要信息是否由组件自身 state 还是外部 prop 驱动来区分「受控组件」&「非受控组件」。 换言之&#xff0c;props 是对外的&#xff0c;state 是对内的 props&#xff1a;只读&#xff0c;父组件通过 props 传递给子组件其所需要…