【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 {
        // 封装命令行参数ApplicationArguments
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 读取环境配置信息
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        // 设置忽略bean.spring.beaninfo.ignore       
        this.configureIgnoreBeanInfo(environment);
        // 打印Banner
        Banner printedBanner = this.printBanner(environment);
        // 实例化Spring上下文 AnnotationConfigServletWebServerApplicationContext
        context = this.createApplicationContext();
        // 初始化失败分析器
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
        // 准备上下文:将启动类作为配置类进行读取 -> 将配置注册为BeanDefinition
        this.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 {
    
    @Override
    protected 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 {
    @Bean
    public 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: 10
        threads:
        	# 最少线程数
        	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.mfbz.cn/a/70607.html

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

相关文章

Node.js |(二)Node.js API:fs模块 | 尚硅谷2023版Node.js零基础视频教程

学习视频&#xff1a;尚硅谷2023版Node.js零基础视频教程&#xff0c;nodejs新手到高手 文章目录 &#x1f4da;文件写入&#x1f407;writeFile 异步写入&#x1f407;writeFileSync 同步写入&#x1f407;appendFile / appendFileSync 追加写入&#x1f407;createWriteStrea…

LeetCode150道面试经典题--验证回文串(简单)

1.题目 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s&#xff0c;如果它是 回文串 &#xff0c;返回 true &#xff1b;否…

pve组网实现公网访问pve,访问电脑,访问pve中的openwrt同时经过openwrt穿透主路由地址nginx全公网访问最佳办法测试研究...

一台路由器 做主路由 工控机 装pve虚拟机 虚拟机里面装一个openwrt, 外网可以直接访问pve,可以访问pve里的openwrt 一台主机 可选择连 有4个口&#xff0c;分别eth0,eth1,eth2,eth3 pve有管理口 这个情况下 &#xff0c;没有openwrt 直接电脑和pve管理口连在一起就能进pve管理界…

TCP协议的报头格式和滑动窗口

文章目录 TCP报头格式端口号序号和确认序号确认应答&#xff08;ACK&#xff09;机制超时重传机制 首部长度窗口大小报文类型URGACKSYNPSHFINRST 滑动窗口滑动窗口的大小怎么设定怎么变化滑动窗口变化问题 TCP报头格式 端口号 两个端口号比较好理解&#xff0c;通过端口号来找…

el-tree-select那些事

下拉菜单树形选择器 用于记录工作及日常学习涉及到的一些需求和问题 vue3 el-tree-select那些事 1、获取el-tree-select选中的任意层级的节点对象 1、获取el-tree-select选中的任意层级的节点对象 1-1数据集 1-2画面 1-3代码 1-3-1画面代码 <el-tree-selectv-model"s…

ELK常见部署架构以及出现的问题及解决方案

ELK常见部署架构以及出现的问题及解决方案 ELK 已经成为目前最流行的集中式日志解决方案&#xff0c;它主要是由Beats 、Logstash 、Elasticsearch 、 Kibana 等组件组成&#xff0c;来共同完成实时日志的收集&#xff0c;存储&#xff0c;展示等一站式的解决方案。本文将会介…

Stable Diffusion - 哥特 (Goth) 风格服装与背景的 LoRA 配置

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132177882 图像来源于 Goth Clothing 的 LoRA 效果&#xff0c;配合哥特 (Goth) 风格服饰的相关提示词。 测试模型&#xff1a;DreamShaper 8 哥…

STM32CubeMX之freeRTOS互斥量

这是大哥保护小弟的故事 高中低等级的任务 互斥量就是谁要敢插我小弟的队&#xff0c;我就要打他&#xff0c;不能让其他人插我小弟的队 互斥量的使用是默认开启的不用手动开启&#xff01; 最高优先级任务&#xff1a;延时&#xff08;10ms&#xff09;再上厕所 中间&#x…

【网络编程·传输层】UDP和TCP的经典八股文

目录 一、端口号划分 二、部分指令 1、pidof&#xff08;用于查看进程id&#xff09; 2、netstat&#xff08;查看网络状态&#xff09; 三、UDP协议 1、UDP协议格式 2、UDP协议如何进行封装、解包、分用 2.1封装、解包 2.2分用 3、UDP协议的特点 3.1UDP协议的特点 …

【百度翻译api】中文自动翻译为英文

欸&#xff0c;最近想做一些nlp的项目&#xff0c;做完了中文的想做做英文的&#xff0c;但是呢&#xff0c;国内爬虫爬取的肯定都是中文 &#xff0c;爬取外网的技术我没有尝试过&#xff0c;没有把握。所以我决定启用翻译&#xff0c;在这期间chatGPT给了我非常多的方法&…

hive修改表或者删除表时卡死问题的解决(2023-08-08)

背景&#xff1a;前阶段在做hive表的改表名时&#xff0c;总是超时&#xff0c;表是内部表&#xff0c;数据量特别大&#xff0c;无论你是修改表名还是删除表都是卡死的状态&#xff0c;怎么破&#xff1f; 终于&#xff1a;尝试出来一个新的方法 将内部表转化成外部表&#…

24.Netty源码之合理管理堆内存

highlight: arduino-light 合理管理 Netty 堆外内存 内存使用目标 •内存占用少(空间) •应用速度快(时间) 即多快好省 对 Java 而言&#xff1a;减少 Full GC 的 STW(Stop the world)时间 内存使用技巧 • 减少对象本身大小 md 例 1&#xff1a;用基本类型就不要用包装类。 例…

嵌入式虚拟仿真实验教学平台之串口发送数据

嵌入式虚拟仿真实验教学平台课程系列 串口发送数据实验 课程内容 本实验使用 STM32 的串口发送数据。开始仿真后,打开串口监视器&#xff0c;串口监视器会打印出要发送的数据。 课程目标 学习配置使用GPIO功能学习配置使用复用功能学习配置使用UART功能 硬件设计 本课程…

css小练习:案例6.炫彩加载

一.效果浏览图 二.实现思路 html部分 HTML 写了一个加载动画效果&#xff0c;使用了一个包含多个 <span> 元素的 <div> 元素&#xff0c;并为每个 <span> 元素设置了一个自定义属性 --i。 这段代码创建了一个简单的动态加载动画&#xff0c;由20个垂直排列的…

allegro中不可选时,如何对find进行可选操作

allegro出现不可选时&#xff0c;只能尝试其他单一的操作&#xff0c;但这样效率不高&#xff1b;可以通过菜单栏Display下拉菜单点击Element&#xff0c;即可实现FIND下选择需要调整的选项。

selenium爬虫与配置谷歌浏览器的driver问题

用selenium爬虫时&#xff0c;明明已经安装了selenium模块&#xff0c;程序却运行不了。在使用selenium之前必须先配置浏览器对应版本的webdriver 本文主要涉及驱动有问题driver 网上有很多手动的方法&#xff08;查看谷歌浏览的版本然后在其他博主分享的webdriver中下载与自己…

CosmosAI欧盟数字超算新时代战略合作签约仪式在伦敦举行

据英国权威媒体获悉&#xff0c;由分布式超算网络服务商CosmosAI主办的欧盟数字超算新时代战略合作签约仪式将于8月14日英国伦敦历史悠久的莱福士OWO酒店隆重举办&#xff0c;该酒店曾作为爱德华七世国王加冕仪式以及丘吉尔二战办公室享誉盛名。 本次活动CosmosAI基金会联合创…

HA3 SQL样本实验:一种混合计算查询的全新样本解决方案

作者&#xff1a;陆唯一(芜霜) HA3&#xff08;对外开源代号&#xff1a;Havenask &#xff09;是阿里智能引擎团队自研的大规模分布式检索系统&#xff0c;广泛应用于阿里内部的搜索业务&#xff0c;是十多年来阿里在电商领域积累下来的核心竞争力产品。Ha3 SQL 是在原有Ha3引…

GitOps 与 DevOps:了解关键差异,为企业做出最佳选择

在软件开发领域&#xff0c;GitOps 和 DevOps 是加强协作和实现软件交付流程自动化的重要技术。虽然这两种模式都旨在提高软件开发生命周期的效率&#xff0c;但它们的核心原则和实施方式却各不相同。 本篇文章将帮助您了解 GitOps 和 DevOps 之间的差异、它们的工作流程&am…

python——案例17:判断某年是否是闰年

案例17&#xff1a;判断某年是否是闰年 判断依据&#xff1a;闰年就是能被400整除&#xff0c;或者能被4整除的年份numint(input(输入年份&#xff1a;))if num%1000: if num%4000: #整百年份的判断print("%s年是闰年"%num) #…
最新文章