【Spring Boot】夺名连环问(持续更新ing)

Spring的了解与特性

  • 简单介绍:快速开发Spring项目的脚手架。简化Spring应用的初始搭建以及开发过程。

  • 特性

    • 提供了很多内置的Starter结合自动配置,对主流框架的无配置集成、开箱即用。即不需要自己去引入很多依赖

    • 并且管理了常用的第三方依赖的版本,减少了版本冲突的问题

    • 简化开发,采用JavaConfig的方式可以使用0xml方式进行开发。

    • 内置Web容器无需依赖外部Web服务器,省略了Web.xml,直接运行就使Web应用。

    • 提供了监控功能,可以监控应用程序的运行状态、内存、线程池、HTTP请求统计等。

Spring与SpringBoot的关系

  • 两者都是Spring生态的产品

  • 前者是容器框架,后者是一个快速开发Spring的脚手架(真正意义上不算新的框架)

核心注解

  • @SpringBootApplication

    • @SpringBootConfiguration(标记为配置类的注解)

      • @Configuration

    • @EnableAutoConfiguration:向Spring容器中导入了一个Selector,用来加载ClassPath下SpringFactories中所定义的配置类,将这些自动加载为配置Bean。

    • @Conditional(待补充)

自动配置原理

  • 通过@SpringBootApplication中的@EnableAutoConfiguration

  • @EnableAutoConfiguration引入了@Import

  • 在Spring容器启动时会加载IoC容器解析@Import注解

  • @Import导入了@DeferredImportSeletor注解,会使SpringBoot的自动配置类的顺序在最后,方便扩展的覆盖

  • 读取META-INF/spring.factories文件

  • 过滤出所有的AutoConfigurationClass类型的类

  • 最后通过@Condition排除无效的自动配置类

为什么SpringBoot的jar可以直接运行

  • 部署SpringBoot的插件才能将package后的jar包使用:java -jar packageName运行

    • 也就是在pom.xml中要有<build>中的<plugins>中<plugin>的spring-boot-maven-plugin。如果没有这个插件在运行命令的时候会报没有主清单属性。

  • 使用插件打包后生成的其实是一个Fat jar(也就是jar包中还有jar包),包含了应用依赖的jar包和Spring Boot Loader相关的类。 

  • java - jar会去找jar中的MANIFEST.MF文件,在那里面找到真正的启动类(Main-Class)。

  • Fat jar的启动Main函数是JarLauncher,它负责创建一个LaunchedURLClassLoader加载boot-lib下面的jar,并以一个新的线程启动应用Main函数(其实就是找到MANIFEST中的Start-Class)。

  • 当然,为了去验证此说法,就需要引入依赖从而去看JarLauncher中的源码,引入此依赖后再package就可以查看源码了。

    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-loader</artifactId>
    </dependency>

启动原理

  • 运行主启动类的main方法,会初始化SpringApplication,从spring.factories读取listener ApplicationContextInitializer。此primarySources就是当前SpringBoot项目的class文件。

  • 在初始化SpringApplication中对一些属性的初始化,比如开启日志记录、关闭懒加载等等。不同版本的Boot在初始化的时候不一样,主要是最下面的七行。以下讲解最下面五行的作用。就不根据源码展示了。

    • 将启动类primarySources放入到名为primartSrouces的LinkedHashSet集合中

    • 推算当前Web应用类型webApplicationType

    • 读取ApplicationContextInitializer初始化器

    • 读取ApplicationListener监听器

    • 将Main方法所在的类放入mainApplicationClass中

  • 接下来就是运行run()方法了。代码太多就不展示了。主要是做了以下的操作:

    • 从上面读取的ApplicationListener监听器去读取SpringApplicationRunListener监听器运行器

    • 发布ApplicationStartingEvent事件

    • 封装命令行参数ApplicationArguments

    • 读取环境配置信息

      • 根据webApplicationType创建环境变量对象

      • 配置环境变量

      • 将现有环境信息设置为@ConfigurationProperties的数据源 并且放在第一位

      • 发布ApplicationEnvironmentPreparedEvent事件

      • 将spring.main的配置绑定到SpringApplication属性上

      • 将现有的环境信息设置为@ConfigurationProperties的数据源 更新放在第一位

    • 设置忽略bean.spring.beaninfo.ignore

    • 打印Banner

    • 实例化Spring上下文 AnnotationConfigServletWebServerApplicationContext

    • 初始化失败分析器

    • 准备上下文:将启动类作为配置类进行读取 -> 将配置注册为BeanDefinition

      • 将当前环境信息设置到context

      • 调用ApplicationContextInitializer

      • 发布ApplicationContextInitializerdEvent事件

      • 打印启动信息和profile.active(测试/生产/开发)信息

      • 将applicationArguments、printedBanner注册为Bean

      • 设置不允许同名Bean

      • 设置是否懒加载Bean

      • 读取启动类为BeanDefinition

      • 将SpringBoot的监听器添加到context发布 ApplicationPreparedEvent事件

    • 加载IoC容器(refresh()方法)其中的十二个步骤。具体干了什么可以去看我另外几篇源码解读的博文。主要两个方法

      • invokeBeanFactoryPostProcessor() -> 解析@Import 加载所有的自动配置类

      • onRefresh() 创建内置的Servlet容器

    • 待扩展(刷新后的处理)

    • 发布ApplicationStartedEvent事件
    • 发布ApplicationReadyEvent事件
    • 如果启动异常会发送ApplicationFailedEvent事件

public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();this.configureHeadlessProperty();// 从上面读取的ApplicationListener监听器去读取SpringApplicationRunListener监听器运行器SpringApplicationRunListeners listeners = this.getRunListeners(args);// 发布ApplicationStartingEvent事件listeners.starting();
​Collection exceptionReporters;try {// 封装命令行参数ApplicationArgumentsApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 读取环境配置信息ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);// 设置忽略bean.spring.beaninfo.ignore       this.configureIgnoreBeanInfo(environment);// 打印BannerBanner printedBanner = this.printBanner(environment);// 实例化Spring上下文 AnnotationConfigServletWebServerApplicationContextcontext = this.createApplicationContext();// 初始化失败分析器exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);// 准备上下文:将启动类作为配置类进行读取 -> 将配置注册为BeanDefinitionthis.prepareContext(context, environment, listeners, applicationArguments, printedBanner);// 加载IoC容器this.refreshContext(context);// 待扩展(刷新后的处理)this.afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);}
​// 发布ApplicationStartedEvent事件listeners.started(context);this.callRunners(context, applicationArguments);} catch (Throwable var10) {this.handleRunFailure(context, var10, exceptionReporters, listeners);throw new IllegalStateException(var10);}
​try {// 发布ApplicationReadyEvent事件listeners.running(context);return context;} catch (Throwable var9) {this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);throw new IllegalStateException(var9);}
}

内置Tomcat启动原理 

  • 当依赖spring-boot-starter-web依赖时会在SpringBoot中添加:ServletWebServerFactoryAutoConfiguration(servlet容器自动配置类)

  • 该自动配置类通过@Import导入了可用的(通过@ConditionalOnClass判断决定使用哪一个)的Web容器工厂(默认Tomcat)

  • 在内嵌的Tomcat类中配置了一个TomcatServletWebServerFactory的Bean(Web容器工厂)

  • 它会在SpringBoot启动时加载IoC容器、在onRefrush()方法中创建内嵌的Tomcat并启动

外部Tomcat启动原理

  • SpringBoot启动后会加载SpringServletContainerInitializer类,此类会去加载实现了WebApplicationInitializer接口的类,而其中SpringBootServletInitializer这个类就实现了这个接口,而需要继承这个类去传入当前应用的启动类。

WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
  • 想要使用外部的Tomcat,就需要排除内部的Tomcat。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions>
</dependency>
  • 创建一个配置类去继承SpringBootServletInitializer去重写configure()方法,将当前模块的启动类传入进去 。

public class TomcatConfig extends SpringBootServletInitializer {@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {return builder.sources(StudySpringApplication.class);}
}

如何自定义Starter

  • 首先知道什么是Starter:Starter又名启动器。众所周知SpringBoot是简化了企业级Spring框架的开发,采用了约定优于配置的理念,而这个理念的实现就是用Starter组件。其会把对应功能的jar包导入进来使得开发者无需在意版本冲突问题,而只需在乎业务逻辑即可,这是作为引入依赖方面的简化配置。而此处要介绍的Starter就是代替Spring的内部组件,使用自定义的组件去完成启动。步骤如下

    • 新建resources/META-INF/spring.factories。这是因为Spring Boot在启动的时候会去读取ClassPath下的这个文件信息。

    • 在spring.factories中去用伪SPI去扫描我们自定义的Starter启动器组件

读取配置文件的原理以及加载顺序

  • 通过事件监听的方式读取配置文件,而读取配置文件的类则是ConfigFileApplicationListener

读取配置文件的方式

  • 属性上使用注解@Value("${xxx.yyy}"),需要注意三点:

    • 当前类是交给IoC容器管理的Bean,否则@Value注解不生效。

    • 当前属性不能是static或者final修饰。

    • 如果配置文件中没有对应的xxx: yyy的话会报错,但是可以在@Value中给默认值比如@Value("${xxx.yyy:}"),这样默认为""

    • 缺点是一个属性对应一个,如果需要读取多个属性的话,效率低下。

  • 类上使用注解@ConfigurationProperties(prefix = "xxx")去指定配置文件中的前缀去读取多个,然后属性采用同名方式去进行批量匹配。相比于上面那种而言,如果遇到大量的配置信息,则效率会更高。

@ConfigurationProperties(prefix = "xxx")
public class MyYamlConfig {private String yyy;
}
  • 通过Spring的应用上下文ApplicationContext去获取Environment对象。但相较于直接获取Environment对象,这样显得多此一举,除非除了要使用ApplicationContext获取运行时环境还有其它的作用。毕竟ApplicationContext是Spring应用程序中的中央接口,由于继承了多个组件所以有多个核心的功能。

  • Spring Bean类中去注入IoC管理的Environment对象。可以使用@Resource、@Autowired来注入。然后在代码块中通过此对象的getProperty("xxx.yyy")返回String对象进行使用即可。但是如果不用自动装配的方式(也就是非Spring管理的Bean),就需要此配置类去实现EnvironmentAware接口重写setEnvironment()方法去获取Environment对象。

  • 配置类上获取外部properties后缀配置文件的方式,比如需要读取/resources/gok.properties文件。在配置类上使用注解@PropertySources。然后就可以在属性上使用@Value注解去读取。

@Configuraion
@PropertySources({@propertySource(value = "classpath:gok.properties", encoding = "UTF-8")})
public class MyYamlConfig {@Value("${xxx.yyy}")private String name;
}
  • 获取/resources/gok.yml文件。在被@Configuration注解的配置类去配置

@Configuration
public class MyYamlConfig {@Beanpublic static PropertySourcesPlaceholderConfigurer yamlConfigurer() {PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();YamlPropertiesFactoryBean yaml = new yamlPropertiesFactoryBean();yaml.setResources(new ClassPathResource("gok.yml"));configurer.setProperties(Object.requireNonNull(yaml.getObject()));return configurer;}
}
  • 采用Java原生态的方式(InputStream流)去读取文件

public class MyYamlConfig {public void yamlConfigurer() {Properties props = new Properties();try {InputStreamReader reader = new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("gok.properties"),StandardCharsets.UTF_8);props.load(reader);} catch (IOException e) {}String name = props.getProperty("xxx.yyy");}
}

解决跨域的方式

  • 首先了解什么是跨域:两个URL的协议/域名/IP/端口不同就会产生跨域。跨域是在前端才会发生的问题,因为浏览器有一个同源策略,这是防止不安全的不同域访问,所以会抛出异常(也就是跨域问题)。

  • Jsoup

    • 前端使用Ajax的jsoup方式,同时后端每个接口的参数都接收一个String callback,并且将其放到JSONObject的第一个参数返回。仅支持GET请求,而且存在安全问题。

  • CORS

    • 前端无需多做处理,后端有三种方式可以进行实现

      • 在接口上使用@CrossOrign注解,并且可以带上请求的协议以及域名/IP以及端口,比如"http://localhost:8080"

  • 让配置类(@Configuration)去实现WebMvcConfigurer接口重写addCorsMappings()方法。这个的弊端主要是浏览器会自动添加一些附加的头信息,甚至还会多发送一次附加的请求,第一次就是"options",第二次才是接口真正调用的请求方法。

    @Override
    public void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**") // 添加映射路径.allowedOriginPatterns("*") // 设置放行哪些原始域 SpringBoot2.4.4低版本使用.allowedOrigins.allowedMethods("GET","HEAD","POST","PUT","DELETE","OPTIONS") // 放行哪些请求方式.allowCredentials(true) // 是否发送Cookie.maxAge(3600).allowedHeaders("*"); // 暴露哪些原始请求头部信息
    }
  • Nginx

    • 使用反向代理不需要在前后端多做处理,不需要添加代码量。只需要在Nginx的nginx.conf文件的server中进行配置,这里就略微详细说明一下:listen 端口; location /前端路径/ { proxy_pass http://localhost:8080/后端路径 }。  

可以同时处理多少请求?

  • 在yml中的Tomcat中配置(因为这是Spring Boot默认的Web容器)。配置与请求线程相关的配置进行测试。

    server:tomcat:# 最大连接数max-connections: 30# 最大等待数accept-count: 10threads:# 最少线程数min-spare: 10# 最多线程数max: 50
  • 可以使用ApiPost/JMeter进行并发测试。如下所示就是测试结果。我们不难发现:首先执行了30个线程,而后先执行完的10个线程又再一次进来执行了。这说明了1s可以处理40次请求,并且这40次请求中可以一次性处理30次,而最多可以等待10次。即配置中的accept-count(最大等待数) + max-connections(最大连接数)。

  • 在spring-configuration-metadata.json中可以发现最大连接数默认为10000,最大等待数为100。注意:我打开的是SpringBoot2.2.1版本,每个版本可能不一样

默认的日志框架以及如何切换

  • 默认情况下SpringBoot会采用slf4j(日志接口) + logback(日志框架)完成日志的实现。那么我们从哪里可以得出来呢?看过源码的基本都知道使用LoggerFactory中获取Logger对象从而记录日志的,所以我也创建了此对象来记录日志。

  •  而这个LoggerFactory与Logger都是slf4j的日志接口,其中slf4j内有三种日志框架:logback(默认)、log4j、jul-to-slf4j

  •  那么如何做到切换默认的日志实现框架呢?这里有两种情况:

    • 将logback切换为lof4j2:这样就需要将logback的场景启动器排除,因为slf4j这个日志接口只能运行1个桥接器。并且添加log4j2的场景启动器

      <!-- 如果不是Web应用就在spring-boot-starter中排除依赖 -->
      <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><!-- 排除logback的场景启动器 --><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions>
      </dependency><!-- Log4j2场景启动器 -->
      <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId>
      </dependency>
    • 将logback切换为log4j。就需要将logback的桥接器排除。

      <!-- 如果不是Web应用就在spring-boot-starter中排除依赖 -->
      <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><!-- 排除logback的场景启动器 --><exclusion><groupId>logback-classic</groupId><artifactId>ch.qos.logback</artifactId></exclusion></exclusions>
      </dependency><!-- Log4j桥接器 -->
      <dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId>
      </dependency>

如何优化启动速度

  • 延迟初始化Bean

    • 一般在SpringBoot中拥有很多的耗时任务,比如数据库连接、初始化线程池创建等等。我们可以延迟这些操作的初始化,来达到优化启动速度的目的。可以在yml中配置:spring:main:lazy-initialization: true。来将所有的Bean延迟初始化(懒加载机制)。

  • 创建扫描索引(Spring 5.0+)

    • 通过编译时创建一个静态候选列表来提高大型应用程序的启动性能。在项目中使用@Indexed之后,编译打包后的时候会在项目中自动生成META-INF/spring.components文件。当Spring应用上下文执行ComponentScan扫描时,就会用CandidateComponentsIndexLoader读取这个文件并加载,转换为CandidateComponentsIndex对象。

    • 步骤如下:引入依赖、启动类加上注解@Indexed。首次编译运行后会在/target/classes/META-INF中生成文件

      <dependency><groupId>org.springframework</groupId><artifactId>spring-context-indexer</artifactId><optional>true</optional>
      </dependency>

  • 减少@ComponentScan即@SpringBootApplication扫描类的范围

  • 关闭SpringBoot的JMX监控,yml中设置:spring:jmx:enabled: false

  • 设置JVM参数 -noverify,不对类进行验证

  • 排除项目中多余的依赖jar

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

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

相关文章

0001nginx简介、相关模型与原理

文章目录 一. 什么是Nginx二. ngnix的一些模型1、nginx的进程模型2、worker的抢占&#xff08;锁&#xff09;机制模型3. nginx事件处理模型 三. nginx加载静态资源的过程 一. 什么是Nginx Nginx是一个高性能HTTP反向代理服务器&#xff0c;以下是nginx的相关能力 反向代理&am…

(力扣)用两个队列实现栈---C语言

分享一首歌曲吧&#xff0c;希望在枯燥的刷题生活中带给你希望和勇气&#xff0c;加油&#xff01; 题目&#xff1a; 请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff09;的栈&#xff0c;并支持普通栈的全部四种操作&#xff08;push、top、pop 和 empty&#…

ElasticSearch单节点部署

&#x1f388; 作者&#xff1a;互联网-小啊宇 &#x1f388; 简介&#xff1a; CSDN 运维领域创作者、阿里云专家博主。目前从事 Kubernetes运维相关工作&#xff0c;擅长Linux系统运维、开源监控软件维护、Kubernetes容器技术、CI/CD持续集成、自动化运维、开源软件部署维护…

大模型在金融医疗、生命系统和物理仿真领域的创新应用探索

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 在当今迅速发展的科技领域&#xff0c;大模型技术正日益成为金融医疗、生命系统和物理仿真等领域中的重要工具。2023年6月16日&#xff0c;AI TIME举办的青年科学家大模型专场活动邀请了国防科技大学理学院数学…

VUE+ElementUI的表单验证二选一必填项,并且满足条件后清除表单验证提示

上代码 <el-form-item label"出库单号" prop"ecode" ref"ecode" :rules"rules.ecode"><el-input v-model"queryParams.ecode" placeholder"出库单号和出库箱号至少填写一项" clearable style"width…

【Docker】AUFS、BTRFS、ZFS、储存池详解

洁洁的个人主页 我就问你有没有发挥&#xff01; 知行合一&#xff0c;志存高远。 前言 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化,容器是…

Node.js新手在哪儿找小项目练手?

前言 可以参考一下下面的nodejs相关的项目&#xff0c;希望对你的学习有所帮助&#xff0c;废话少说&#xff0c;让我们直接进入正题>> 1、 NodeBB Star: 13.3k 一个基于Node.js的现代化社区论坛软件&#xff0c;具有快速、可扩展、易于使用和灵活的特点。它支持多种数…

CDN(Content Delivery Network)内容分发网络

从DNS域名系统到CDN内容分发网络 DNS什么是DNS直接使用DNS的缺点 CDNCDN加速过程使用CDN的优势 DNS 什么是DNS 输入域名www.baidu.com后&#xff0c;浏览器先检查缓存和本地Host文件&#xff0c;看有没有对应的ip地址&#xff0c;有则直接使用&#xff0c;没有就会向本地DNS服…

[保研/考研机试] KY109 Zero-complexity Transposition 上海交通大学复试上机题 C++实现

描述&#xff1a; You are given a sequence of integer numbers. Zero-complexity transposition of the sequence is the reverse of this sequence. Your task is to write a program that prints zero-complexity transposition of the given sequence. 输入描述&#xf…

易服客工作室:WordPress 6.3 Lionel发布

WordPress 6.3 Lionel已经发布&#xff0c;它以美国著名爵士乐艺术家莱昂内尔汉普顿 (Lionel Hampton)的名字命名。汉普顿是一位多产的爵士颤音琴演奏家、钢琴家和打击乐演奏家&#xff0c;因与查尔斯明格斯、昆西琼斯等伟大人物合作以及作为同名莱昂内尔汉普顿管弦乐团的乐队领…

Flink窗口分类简介及示例代码

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 文章目录 1. 流式计算2. 窗口3. 窗口的分类◆ 基于时间的窗口&#xff08;时间驱动&#xff09;1) 滚动窗口&#xff08;Tumbling Windows&#xff09;2) 滑动窗口&#xff08;Sliding Windows&…

分享一个计算器

先看效果&#xff1a; 再看代码&#xff08;查看更多&#xff09;&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>计算器</title><style>* {box-sizing: border-box;}body…

高性能跨平台网络通信框架 HP-Socket v5.9.3

项目主页 : http://www.oschina.net/p/hp-socket开发文档 : https://www.docin.com/p-4478351216.html下载地址 : https://github.com/ldcsaa/HP-SocketQQ Group: 44636872, 663903943 v5.9.3 更新 一、主要更新 问题修复&#xff1a;通过 POST/PUT 等带有请求内容的 HTTP 方…

模拟信号和数字信号的转换

此文章介绍的模拟信号与数字信号转换相关的知识有如下&#xff1a; 通信原理的PCM脉冲编码调制 数字电子技术的A/D与D/A 以及stm32的ADC与DAC 模拟信号是指-----时间和数值均连续变化的电信号&#xff0c;如正弦波、三角波等。 数字信号是指-----在时间上和数值上均是离散的…

数据结构【图的类型定义和存储结构】

数据结构之图 图的定义和概念图的定义图的术语 图的类型定义图的存储结构数组&#xff08;邻接矩阵&#xff09;表示法无向图的邻接矩阵表示法有向图的邻接矩阵表示法网&#xff08;即有权图&#xff09;的邻接矩阵表示法 邻接矩阵的ADT定义邻接表&#xff08;链式&#xff09;…

聊一下互联网开源变现

(点击即可收听) 互联网开源变现其实是指通过开源软件或者开放源代码的方式&#xff0c;实现收益或盈利。这种方式越来越被广泛应用于互联网行业 在互联网开源变现的模式中&#xff0c;最常见的方式是通过捐款、广告、付费支持或者授权等方式获利。 例如&#xff0c;有些开源软件…

内网穿透实战应用-——【如何在树莓派上安装cpolar内网穿透】

如何在树莓派上安装cpolar内网穿透 文章目录 如何在树莓派上安装cpolar内网穿透前言1.在树莓派上安装cpolar2.查询cpolar版本号3.激活本地cpolar客户端4.cpolar记入配置文件 前言 树莓派作为一个超小型的电脑系统&#xff0c;虽然因其自身性能所限&#xff0c;无法如台式机或笔…

【金融量化】Python实现根据收益率计算累计收益率并可视化

1 理论 理财产品&#xff08;本金100元&#xff09; 第1天&#xff1a;3% &#xff1a;&#xff08;13%&#xff09; ✖ 100 103 第2天&#xff1a;2% &#xff1a;&#xff08;12%&#xff09;✖ 以上 103 2.06 第3天&#xff1a;5% : &#xff08;15%&#xff09;✖ 以上…

游戏行业实战案例 5 :玩家在线分布

【面试题】某游戏数据后台设有“登录日志”和“登出日志”两张表。 「登录日志」记录各玩家的登录时间和登录时的角色等级。 「登出日志」记录各玩家的登出时间和登出时的角色等级。 其中&#xff0c;「角色 id 」字段唯一识别玩家。 游戏开服前两天&#xff08; 2022-08-13 至…

Python-组合数据类型

今天要介绍的是Python的组合数据类型 整理不易&#xff0c;希望得到大家的支持&#xff0c;欢迎各位读者评论点赞收藏 感谢&#xff01; 目录 知识点知识导图1、组合数据类型的基本概念1.1 组合数据类型1.2 集合类型概述1.3 序列类型概述1.4 映射类型概述 2、列表类型2.1 列表的…