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

news2024/10/5 18:31:07

文章目录

  • 前言
  • 第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源码解读与原理分析

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

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

相关文章

怎样获得CNVD原创漏洞证书

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

【设计模式 03】抽象工厂模式

一个具体的工厂&#xff0c;可以专门生产单一某一种东西&#xff0c;比如说只生产手机。但是一个品牌的手机有高端机、中端机之分&#xff0c;这些具体的属于某一档次的产品都需要单独建立一个工厂类&#xff0c;但是它们之间又彼此关联&#xff0c;因为都共同属于一个品牌。我…

数据可视化原理-腾讯-散点图

在做数据分析类的产品功能设计时&#xff0c;经常用到可视化方式&#xff0c;挖掘数据价值&#xff0c;表达数据的内在规律与特征展示给客户。 可是作为一个产品经理&#xff0c;&#xff08;1&#xff09;如果不能够掌握各类可视化图形的含义&#xff0c;就不知道哪类数据该用…

白银期货开户交割规则有哪些?

白银期货交割是指期货合约到期时&#xff0c;交易双方通过该期货合约所载商品所有权的转移&#xff0c;了结到期未平仓合约的过程。小编在此为大家详细介绍白银期货的交割规则有哪些。白银期货的交割规则有哪些&#xff1f;白银期货的交割规则主要有&#xff1a; 一、交割商品…

echarts如何实现3D饼图(环形图)?

一、实现的效果 二、具体步骤 1.安装依赖 npm install echarts 2.引入echarts import * as echarts from echarts; 注意&#xff1a;这里需要用到echarts-gl&#xff0c;必须单独引入才可以 import echarts-gl; 3.echarts部分代码 我知道这部分内容很多&#xff0c;但只要cv…

【ES入门一:基础概念】

集群层面上的基础概念 集群 由多个es实例组成的叫做集群 节点 单个ES的服务实例叫做节点。每个实例都有自己的名字&#xff0c;就是在配置文件中配置的‘node.name’中的内容。为了标识每个节点&#xff0c;每个节点启动后都会分配一个UID&#xff0c;存储在data目录。每个…

第七十四天漏洞发现-Web框架中间件插件BurpSuite浏览器被动主动探针

第74天 漏洞发现-Web框架中间件插件&BurpSuite&浏览器&被动&主动探针 最近几天都是演示工具如何使用如&#xff1a;AWVS、Nessus、nexpose等综合性利用工具。 Burp插件和漏扫工具的区别 知识点&#xff1a; 1、浏览器插件&BurpSuite插件 2、Hack-Tools&…

基于协同过滤的旅游推荐系统设计与实现

基于协同过滤的旅游推荐系统设计与实现 在当今旅游业蓬勃发展的背景下&#xff0c;人们对于旅游体验的需求日益增加&#xff0c;如何为用户提供更加个性化、精准的旅游推荐成为了旅游行业的一个重要课题。为解决这一问题&#xff0c;我们设计并实现了一个基于协同过滤的旅游推…

“互动+消费”时代,借助华为云GaussDB重构新零售中消费逻辑

场与人的关系 “人—货—场”是零售中重要的三要素&#xff0c;我们一直在追求&#xff0c;将零售中的人、货、场进行数字化并在云端进行整合&#xff0c;形成属于我们自己的云平台。 随着互联网技术为信息提供的便利&#xff0c;消费者的集体力量正在逐渐形成一股强大的反向…

RabbitMQ的整体架构是怎么样的?

RabbitMQ是一个开源的消息中间件&#xff0c;用于在应用程序之间传递消息。它实现了AMQP(高级消息队列协议)并支持其他消息传递协议&#xff0c;例如STOMP(简单文本定向消息协议)和MQTT&#xff08;物联网协议&#xff09; 他的整体架构大致如下&#xff1a; Producer&#xf…

【NVCC,CUDA,NVIDIA驱动】装了pytorch,nvcc -V不能用,但能正常使用gpu

这里写目录标题 问题描述问题原理为什么anaconda安装的Pytorch&#xff0c;其能够直接在gpu上运行NVCC是什么&#xff0c;怎么查看装没装 如果没有NVCC文件夹&#xff0c;应该如何安装NVCC&#xff1f;CUDNN&#xff1a;Local Installer for Linux x86_64和Local Installer for…

小程序开通流量主

开发小程序有一段时间了&#xff0c;误打误撞开通了流量主。到现在有2400人访问了&#xff0c;当然这是累计的&#xff0c;每天访问人数也就是平均七八十左右。 当然&#xff0c;每日还是有一些收入的&#xff0c;虽然比较低&#xff0c;一块钱上下&#xff1a; 感觉做小程序&…

hnust 湖南科技大学 2022 数据挖掘课设 完整代码+报告+图源文件+指导书

hnust 湖南科技大学 2022 数据挖掘课设 完整代码报告图源文件指导书 目录 实验一 Apriori算法设计与应用 - 1 - 一、 背景介绍 - 1 - 二、 实验内容 - 1 - 三、 实验结果与分析 - 2 - 四、 小结与心得体会 - 3 - 实验二 KNN算法设计与应用 - 4 - 一、 背景介绍 - 4 - 二、 实…

NLP评价指标

一、分类任务常见评估&#xff1a; 准确度(Accuracy) 评估预测正确的比例&#xff0c;精确率(Precision) 评估预测正例的查准率&#xff0c;召回率(Recall) 评估真实正例的查全率。如果是多分类&#xff0c;则每个类别各自求P、R最终求平均值。 TP&#xff08;True Positives…

鸿蒙Harmony应用开发—ArkTS声明式开发(通用属性:分布式迁移标识)

组件的分布式迁移标识&#xff0c;指明了该组件在分布式迁移场景下可以将特定状态恢复到对端设备。 说明&#xff1a; 从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 restoreId restoreId(value: number) 标记支持分布式…

Java_二叉树

文章目录 一、二叉树1.树型结构2.概念&#xff08;重要&#xff09;3.树的表示形式&#xff08;了解&#xff09;4.树的应用5.二叉树的概念6.两种特殊的二叉树7.二叉树的性质练习 8.二叉树的存储9.二叉树的遍历手动创建二叉树1、前中后遍历2、层序遍历3、二叉树的基本操作获取树…

什么是Vue的过渡效果?如何使用Vue的过渡效果?

Vue的过渡效果是Vue.js框架中提供的一种动画效果&#xff0c;可以让元素在插入、更新或移除时拥有更流畅的视觉切换效果。使用Vue的过渡效果可以为页面增添动感和交互性&#xff0c;让用户体验更加友好。 下面我们来看一下如何使用Vue的过渡效果。首先&#xff0c;我们需要在V…

什么是SpringCloud,有哪些组件?

spring Cloud 是基于spring boot的分布式系统开发工具,它提供了一系列开箱即用的,针对分布式系统开发的特性和组件。用于帮助开发人员快速构建和管理云原生应用程序。 Spring Cloud 的主要目标是解决分布式系统中的常见问题,例如服务发现,负载均衡,配置管理,断路器,消息总…

【鸿蒙 HarmonyOS 4.0】应用状态:LocalStorage/AppStorage/PersistentStorage

一、介绍 如果要实现应用级的&#xff0c;或者多个页面的状态数据共享&#xff0c;就需要用到应用级别的状态管理的概念。 LocalStorage&#xff1a;页面级UI状态存储&#xff0c;通常用于UIAbility内、页面间的状态共享。AppStorage&#xff1a;特殊的单例LocalStorage对象&…

ElasticSearch之数据分片和故障转移

写在前面 基础环境可以参考ElasticSearch之分布式模型介绍&#xff0c;选主&#xff0c;脑裂 。 本文看下es的数据分片和故障转移相关内容。 1&#xff1a;数据分片 分片&#xff0c;英文是shard&#xff0c;存储在data node &#xff0c;分为主分片和副本分片&#xff0c;英…