谈一谈,Spring Boot 中的 starter 到底是什么 ?

1. 为什么要用Starter?

  • 现在我们就来回忆一下,在还没有Spring-boot框架的时候,我们使用Spring 开发项目,如果需要某一个框架,例如mybatis,我们的步骤一般都是:
  • 到maven仓库去找需要引入的mybatis jar包,选取合适的版本
  • 到maven仓库去找mybatis-spring整合的jar包,选取合适的版本
  • 在spring的applicationContext.xml文件中配置dataSource和mybatis相关信息
  • 假如所有工作都到位,一般可以完成;但很多时候都会花一堆时间解决jar冲突,配置项缺失,导致怎么都启动不起来等等问题。

所以在2012 年 10 月,一个叫 Mike Youngstrom 的人在 Spring Jira 中创建了一个功能请求,要求在 Spring Framework 中支持无容器 Web 应用程序体系结构,提出了在主容器引导 Spring 容器内配置 Web 容器服务;这件事情对 SpringBoot 的诞生应该说是起到了一定的推动作用。

所以SpringBoot 设计的目标就是简化繁琐配置,快速建立Spring 应用。

  • 然后在开发Spring-boot 应用的时候, 经常可以看到我们的pom 文件中引入了spring-boot-starter-web、spring-boot-starter-data-redis、mybatis-spring-boot-starter 这样的依赖,然后几乎不用任何配置就可以使用这些依赖的功能。
  • 下面我们就先来尝试自己开发一个Starter。

2. 命名规范

在使用spring-boot-starter,会发现,有的项目名称是 XX-spring-boot-starter,有的是spring-boot-starter-XX,这个项目的名称有什么讲究呢?从springboot官方文档摘录:

这段话的大概意思就是,麻烦大家遵守这个命名规范:

Srping官方命名格式为:spring-boot-starter-{name}

非Spring官方建议命名格式:{name}-spring-boot-starter

3. 开发示例

下面我就以记录日志的一个组件为示例来讲述开发一个starter 的过程。

3.1 新建工程

首先新建一个maven 工程,名称定义为jd-log-spring-boot-starter

3.2 Pom 引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.13</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.jd</groupId>
  <artifactId>jd-log-spring-boot-starter</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>jd-log-spring-boot-starter</name>
  <url>http://www.example.com</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>


  <dependencies>
    <!-- 提供了自动装配功能-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>
    <!-- 在编译时会自动收集配置类的条件,写到一个META-INF/spring-autoconfigure-metadata.json中-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
    </dependency>
    <!--记录日志会用到切面,所以需要引入-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-source-plugin</artifactId>
        <version>2.2.1</version>
        <executions>
          <execution>
            <id>attach-sources</id>
            <goals>
              <goal>jar-no-fork</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

这边稍微解释一下这几个依赖:

spring-boot-autoconfigure :提供自动化装配功能,是为了Spring Boot 应用在各个模块提供自动化配置的作用;即加入对应 pom,就会有对应配置其作用;所以我们想要自动装配功能,就需要引入这个依赖。

spring-boot-configuration-processor:将自定义的配置类生成配置元数据,所以在引用自定义STARTER的工程的YML文件中,给自定义配置初始化时,会有属性名的提示;确保在使用@ConfigurationProperties注解时,可以优雅的读取配置信息,引入该依赖后,IDEA不会出现“spring boot configuration annotation processor not configured”的错误;编译之后会在META-INF 下生成一个spring-configuration-metadata.json 文件,大概内容就是定义的配置的元数据;效果如下截图。

spring-boot-starter-aop :这个就不用解释了,因为示例是记录日志,我们用到切面的功能,所以需要引入。

3.3 定义属性配置

/**
 * @author kongxiangdong2
 * @Title: LogProperties
 * @ProjectName jd-log-spring-boot-starter
 * @Description: TODO
 * @date 2022/9/110:04
 */
@ConfigurationProperties(prefix = "jd")
@Data
public class LogProperties {


    /**
     * 是否开启日志
     */
    private boolean enable;


    /**
     * 平台:不同服务使用的区分,默认取 spring.application.name
     */
    @Value("${spring.application.name:#{null}}")
    private String platform;

@ConfigurationProperties:该注解和@Value 注解作用类似,用于获取配置文件中属性定义并绑定到Java Bean 或者属性中;换句话来说就是将配置文件中的配置封装到JAVA 实体对象,方便使用和管理。

这边我们定义两个属性,一个是是否开启日志的开关,一个是标识平台的名称。

3.4 定义自动配置类

/**
 * @author kongxiangdong2
 * @Title: JdLogAutoConfiguration
 * @ProjectName jd-log-spring-boot-starter
 * @Description: TODO
 * @date 2022/9/110:06
 */
@Configuration
@ComponentScan("com.jd")
@ConditionalOnProperty(prefix = "jd",name = "enable",havingValue = "true",matchIfMissing = false)
@EnableConfigurationProperties({LogProperties.class})
public class JdLogAutoConfiguration {


   //
}

这个类最关键了,它是整个starter 最重要的类,它就是将配置自动装载进spring-boot的;具体是怎么实现的,下面在讲解原理的时候会再详细说说,这里先完成示例。

@Configuration :这个就是声明这个类是一个配置类

@ConditionalOnProperty:作用是可以指定prefix.name 配置文件中的属性值来判定configuration是否被注入到Spring,就拿上面代码的来说,会根据配置文件中是否配置jd.enable 来判断是否需要加载JdLogAutoConfiguration 类,如果配置文件中不存在或者配置的是等于false 都不会进行加载,如果配置成true 则会加载;指定了havingValue,要把配置项的值与havingValue对比,一致则加载Bean;配置文件缺少配置,但配置了matchIfMissing = true,加载Bean,否则不加载。

在这里稍微扩展一下经常使用的Condition

注解类型说明
@ConditionalOnClassClass Conditions类条件注解当前classpath下有指定类才加载
@ConditionalOnMissingClassClass Conditions类条件注解当前classpath下无指定类才加载
@ConditionalOnBeanBean ConditionsBean条件注解当期容器内有指定bean才加载
@ConditionalOnMissingBeanBean ConditionsBean条件注解当期容器内无指定bean才加载
@ConditionalOnPropertyProperty Conditions环境变量条件注解(含配置文件)prefix 前缀name 名称havingValue 用于匹配配置项值matchIfMissing 没找指定配置项时的默认值
@ConditionalOnResourceResourceConditions 资源条件注解有指定资源才加载
@ConditionalOnWebApplicationWeb Application Conditionsweb条件注解是web才加载
@ConditionalOnNotWebApplicationWeb Application Conditionsweb条件注解不是web才加载
@ConditionalOnExpressionSpEL Expression Conditions符合SpEL 表达式才加载

@EnableConfigurationProperties使@ConfigurationProperties 注解的类生效。

3.5 配置EnableAutoConfiguration

在resources/META-INF/ 目录新建spring.factories 文件,配置内容如下;

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.jd.JdLogAutoConfiguration

好了,至此自定义Starter 大体框架已经好了,下面就是我们记录日志的功能。

3.6 业务功能实现

首先我们先定义一个注解Jdlog

/**
 * @author kongxiangdong2
 * @Title: Jdlog
 * @ProjectName jd-log-spring-boot-starter
 * @Description: TODO
 * @date 2022/9/110:04
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Jdlog {
}

定义切面执行逻辑,这边就简单的打印一下配置文件的属性值+目标执行方法+耗时。

import com.jd.annotation.Jdlog;
import com.jd.config.LogProperties;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;


/**
 * @author kongxiangdong2
 * @Title: LogAspectjProcess
 * @ProjectName jd-log-spring-boot-starter
 * @Description: TODO
 * @date 2022/9/111:12
 */
@Aspect
@Component
@Slf4j
@AllArgsConstructor
public class LogAspectjProcess {


    LogProperties logProperties;


    /**
     * 定义切点
     */
    @Pointcut("@annotation(com.jd.annotation.Jdlog)")
    public void pointCut(){}


    /**
     * 环绕通知
     *
     * @param thisJoinPoint
     * @param jdlog
     * @return
     */
    @Around("pointCut() && @annotation(jdlog)")
    public Object around(ProceedingJoinPoint thisJoinPoint, Jdlog jdlog){


        //执行方法名称
        String taskName = thisJoinPoint.getSignature()
                .toString().substring(
                        thisJoinPoint.getSignature()
                                .toString().indexOf(" "),
                        thisJoinPoint.getSignature().toString().indexOf("("));
        taskName = taskName.trim();
        long time = System.currentTimeMillis();
        Object result = null;
        try {
            result = thisJoinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        log.info("{} -- method:{} run :{} ms",logProperties.getPlatform(), taskName,
                (System.currentTimeMillis() - time));
        return result;

整体项目结构就是这样子

好了,现在就可以打包编译安装

3.7 测试使用

然后就可以在其他项目中引入使用了;下面以一个简单的spring-boot web 项目做个测试,在pom 中引入下面的依赖配置。

<dependency>
      <groupId>com.jd</groupId>
      <artifactId>jd-log-spring-boot-starter</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>

增加一个http 访问的方法,标注上@Jdlog 注解

application.yaml 文件中配置

jd:
  enable: true
  platform: "测试项目"

启动测试

咋样,自定义的Starter是不是特别的简单啊,快动手试试吧!

上面我们讲的都是怎么去开发一个starter,但是到底为什么要这样,spring-boot 是如何去实现的?是不是还不知道?那下面我们就来说说;

4. 原理讲解

我们上面已经看到一个starter,只需要引入到pom 文件中,再配置一下(其实都可以不配置)jd.enable=true,就可以直接使用记录日志的功能了,Spring-boot 是怎么做到的?

在开始的时候说过,Spring-boot 的好处就是可以自动装配。那下面我就来说说自动装配的原理。

相比于传统Spring 应用,我们搭建一个SpringBoot 应用,我们只需要引入一个注解(前提:引入springBoot y依赖)@SpringBootApplication,就可以直接运行;所以我们就从这个注解开始入手,看看这个注解到底做了写什么?

SpringBootApplication 注解

点开@SpringBootApplication注解可以看到包含了@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解。

前面的四个注解就不用过多叙述了,是定义注解最基本的,关键在于后面的三个注解:@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan,其实也就是说在启动类上如果不使用@SpringBootApplication 这个复合注解,直接使用者三个注解一样可以达到相同的效果。

@SpringBootConfiguration 注解:我们再次点进去看这个注解,其实它就是一个@Configuration 注解。

@ComponentScan 注解

@ComponentScan 注解:配置包扫描定义的扫描路径,把符合扫描规则的类装配到spring容器

@EnableAutoConfiguration 注解

@EnableAutoConfiguration 打开自动装配(自动配置着重来看该注解)

注解作用解释
@SpringBootConfiguration标记当前类为配置类加上这个注解就是为了让当前类作为一个配置类交由 Spring 的 IOC 容器进行管理,因为前面我们说了,SpringBoot 本质上还是 Spring,所以原属于 Spring 的注解 @Configuration 在 SpringBoot 中也可以直接应用
@ComponentScan配置包扫描定义的扫描路径,把符合扫描规则的类装配到spring容器用于定义 Spring 的扫描路径,等价于在 xml 文件中配置 context:component-scan,假如不配置扫描路径,那么 Spring 就会默认扫描当前类所在的包及其子包中的所有标注了 @Component,@Service,@Controller 等注解的类。
@EnableAutoConfiguration打开自动装配下面着重讲解

我们再次点击@EnableAutoConfiguration进入查看,它是一个由 @AutoConfigurationPackage 和 @Import 注解组成的复合注解;

首先我们先来看@Import 这个注解,这个是比较关键的一个注解;

在说这个注解之前我们先举个例子,假如我们有一个类Demo,它是一个不在启动配置类目录之下的,也就意味着它不会被扫描到,Spring 也无法感知到它的存在,那么如果需要能将它被扫描到,是不是我们可以通过加@Import 注解来导入Demo 类,类似如下代码

@Configuration
@Import(Demo.class)
public class MyConfiguration {
}

所以,我们可以知道@Import 注解其实就是为了去导入一个类。所以这里@Import({AutoConfigurationImportSelector.class}) 就是为了导入AutoConfigurationImportSelector 类,那我们继续来看这个类,AutoConfigurationImportSelector实现的是DeferredImportSelector接口,这是一个延迟导入的类;再细看会有一个方法比较显眼,根据注解元数据来选择导入组件,当注解元数据空,直接返回一个空数组;否则就调用getAutoConfigurationEntry ,方法中会使用AutoConfigurationEntry的getConfigurations(),configurations是一个List<String>,那么我们看下AutoConfigurationEntry是怎么生成的。

进入到getAutoConfigurationEntry 方法中可以看到主要是getCandidateConfigurations 来获取候选的 Bean,并将其存为一个集合;后续的方法都是在去重,校验等一系列的操作。

我们继续往getCandidateConfigurations 方法里看,最终通过SpringFactoriesLoader.loadFactoryNames来获取最终的configurations,并且可以通过断言发现会使用到META-INF/spring.factories文件,那么我们再进入SpringFactoriesLoader.loadFactoryNames()中来看下最终的实现。

SpringFactoriesLoader.loadFactoryNames()方法会读取META-INF/spring.factories文件下的内容到Map中,再结合传入的factoryType=EnableAutoConfiguration.class,因此会拿到 org.springframework.boot.autoconfigure.EnableAutoConfiguration为key对应的各个XXAutoConfiguration的值,然后springboot在结合各个starter中的代码完成对于XXAutoConfiguration中的Bean的加载动作。

这边再扩展一下这个内容,通过 SpringFactoriesLoader 来读取配置文件 spring.factories 中的配置文件的这种方式是一种 SPI 的思想。

@AutoConfigurationPackage 注解

进入这个注解看,其实它就是导入了Registrar 这个类

再进入这个类查看,它其实是一个内部类,看代码的大概意思就是读取到我们在最外层的 @SpringBootApplication 注解中配置的扫描路径(没有配置则默认当前包下),然后把扫描路径下面的Bean注册到容器中;

总结

好了,现在我们大概来理一下整个自动装配的流程:

  1. 启动类中通过使用@SpringBootApplication实现自动装配的功能;
  2. 实际注解@SpringBootApplication是借助注解@EnableAutoConfiguration的功能。
  3. 在注解@EnableAutoConfiguration中又有两个注解,@AutoConfigurationPackage,@EnableAutoConfiguration。
  4. 通过@AutoConfigurationPackage实现对于当前项目中Bean的进行加载;
  5. @EnableAutoConfiguration通过@Import({AutoConfigurationImportSelector.class})实现对于Pom引入的start中的XXAutoConfiguration的加载;
  6. @AutoConfigurationImportSelector类中通过SpringFactoriesLoader读取 META-INF/spring.factories中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的各个XXAutoConfiguration的值,然后springboot在结合各个start中的代码完成对于XXAutoConfiguration中的Bean的加载动作;

到这里,已经可大致以了解对我们之前开发starter中的定义。

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

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

相关文章

【C++】-stack和queue的具体使用以及模拟实现(dqeue的介绍+容器适配器的介绍)

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

TCP四次挥手过程

TCP 断开连接是通过四次挥手方式。 双方都可以主动断开连接&#xff0c;断开连接后主机中的「资源」将被释放&#xff0c; 刚开始双方都处于 establised 状态&#xff0c;假如是客户端先发起关闭请求&#xff0c;过程如下图&#xff1a; 第一次挥手&#xff1a;客户端打算关闭…

【机器学习】基于卷积神经网络 CNN 的猫狗分类问题

文章目录 一、卷积神经网络的介绍1.1 什么是卷积神经网络1.2 重要层的说明1.3 应用领域二、 软件、环境配置2.1 安装Anaconda2.2 环境准备 三、猫狗分类示例3.1 图像数据预处理3.2 基准模型3.3 数据增强3.4 dropout层四、总结 一、卷积神经网络的介绍 1.1 什么是卷积神经网络 …

师承AI世界新星|7天获新加坡南洋理工大学访学邀请函

能够拜师在“人工智能10大新星”名下&#xff0c;必定可以学习到前沿技术&#xff0c;受益良多&#xff0c;本案例中的C老师无疑就是这个幸运儿。我们只用了7天时间就取得了这位AI新星导师的邀请函&#xff0c;最终C老师顺利获批CSC&#xff0c;如愿出国。 C老师背景&#xff1…

基于单片机的盲人导航智能拐杖老人防丢防摔倒发短息定位

功能介绍 以STM32单片机作为主控系统&#xff1b; OLED液晶当前实时距离&#xff0c;安全距离&#xff0c;当前经纬度信息&#xff1b;超声波检测小于设置的安全距离&#xff0c;蜂鸣器报警提示&#xff1a;低于安全距离&#xff01;超声波检测当前障碍物距离&#xff0c;GPS进…

【分布式系统案例课】查询服务设计、计数栈选型、总结

查询服务设计 数据获取路径 两个问题考虑&#xff1a; 1、老数据归档的问题。 如果所有分钟小时级的数据一直存在这个DB当中&#xff0c;那么DB的存储空间会被不断的消耗&#xff0c;性能也会不断的下降。所以一旦小时天月的数据聚合完成&#xff0c;我们就可以将一些老的原始…

TCP/IP网络编程 第十二章:I/O复用

基于I/O复用的服务器端 多进程服务器端的缺点和解决方法 为了构建并发服务器&#xff0c;只要有客户端连接请求就会创建新进程。这的确是实际操作中采用的种方案&#xff0c;但并非十全十美&#xff0c;因为创建进程时需要付出极大代价。这需要大量的运算和内存空间&#xff…

智慧校园能源管控系统

智慧校园能源管控系统是一种搭载了物联网技术、大数据技术、大数据等技术性智能化能源管理方法系统&#xff0c;致力于为学校提供更高效、安全性、可信赖的能源供应管理和服务。该系统包括了校内的电力工程、水、气、暖等各类能源&#xff0c;根据对能源的实时检测、数据统计分…

uni-app中a标签下载文件跳转后左上角默认返回键无法继续返回

1.首先使用的是onBackPress //跟onShow同级别 onBackPress(option){ uni.switchTab({ url:/pages/....... return true }) }发现其在uni默认头部中使用是可以的 但是h5使用了"navigationStyle":"custom"后手机默认的返回并不可以&#xff0c; 2.经过查询…

【MySQL技术专题】「问题实战系列」深入探索和分析MySQL数据库的数据备份和恢复实战开发指南(备份+恢复篇)

深入探索和分析MySQL数据库的数据备份和恢复实战开发指南 MySQL数据库备份全量备份全量备份应用场景 增量备份binlogbinlog主要作用binlog的作用主要有两个方面 开启binlog日志功能要开启MySQL的binlog日志步骤 mysqlbinlogmysqlbinlog的使用案例 全量备份与增量备份结合按天全…

WebRTC不同方案对比

1.功能上会有一些出入&#xff0c;尤其是国内的metaRTC版本迭代很快&#xff0c; 2.后续的ffmpeg也在进行支持webrtc特性&#xff0c;obs新的版本好像已经支持了webrtc&#xff0c; 3.对于webrtc部分缺少的信令部分的标准化也有了对应的标准whip和whep协议 所以&#xff0c;如…

好的CRM需要有哪些特点?

CRM客户管理系统在企业中占有举足轻重的地位&#xff0c;既是战略工具又可以强化部门间的团队协作、优化销售流程、缩短销售周期。市面上crm做得比较好的公司有哪些&#xff1f; 1.上榜Gartner魔力象限 好的CRM市场的引领、产品研发的持续投入、技术创新以及不断增长的市场份…

性能测试:Jmeter-Beanshell请求加密实例

目录 1. 打包加密方法Jar包&#xff0c;导入Jmeter 2. 加密参数 总结&#xff1a; 进行性能测试时&#xff0c;有可能遇到一种场景&#xff1a;接口请求由于安全问题&#xff0c;需要进行加密发送。 这种场景下&#xff0c;使用Jmeter实现性能测试&#xff0c;则也需要使用…

自学网络安全究竟该从何学起?

一、为什么选择网络安全&#xff1f; 这几年随着我国《国家网络空间安全战略》《网络安全法》《网络安全等级保护2.0》等一系列政策/法规/标准的持续落地&#xff0c;网络安全行业地位、薪资随之水涨船高。 未来3-5年&#xff0c;是安全行业的黄金发展期&#xff0c;提前踏入行…

Redis基本全局命令(含key过期策略)

Redis基本全局命令 KEYEXISTSDELEXPIRETTLRedis的key过期策略TYPE KEY 返回所有满⾜样式&#xff08;pattern&#xff09;的key。⽀持如下统配样式。 h?llo 匹配 hello , hallo 和 hxlloh*llo 匹配 hllo 和 heeeelloh[ae]llo 匹配 hello 和 hallo 但不匹配 hilloh[^e]llo 匹配…

使用Pandas计算两个系统客户名称的相似度

引言&#xff1a; 在日常业务处理中&#xff0c;我们经常会面临将不同系统中的数据进行匹配和比对的情况。特别是在涉及到客户管理的领域&#xff0c;我们需要确保两个系统中的客户记录是准确、一致和无重复的。 本文将介绍如何使用Python的Pandas库来处理这个问题。我们将以…

美颜SDK与动态贴纸技术的发展趋势:向更智能、更新颖的美化

美颜SDK和动态贴纸技术在近年来迅速发展&#xff0c;成为移动应用、社交媒体和视频直播等领域中不可或缺的元素。本文将探讨美颜SDK和动态贴纸技术的最新发展趋势&#xff0c;包括智能化算法的应用、增强现实的融合以及个性化定制的兴起。我们将展望未来&#xff0c;展示这些技…

STM32(HAL库)通过ADC读取MQ2数据

目录 1、简介 2、CubeMX初始化配置 2.1 基础配置 2.1.1 SYS配置 2.1.2 RCC配置 2.2 ADC外设配置 2.3 串口外设配置 2.4 项目生成 3、KEIL端程序整合 3.1 串口重映射 3.2 ADC数据采集 3.3 主函数代 3.4 效果展示 1、简介 本文通过STM32F103C8T6单片机通过HAL库方式对M…

注释气泡图函数(更新)

之前我们写过一个原创可视化函数Dotplot_anno.R&#xff0c;nature级别图表&#xff1a;一个注释气泡热图函数&#xff08;适用于单细胞及普通数据&#xff09;。主要解决的问题是1) 单细胞基因可视化分组注释。2) Bulk RNA差异基因热图、气泡图。3) 富集分析结果气泡图展示。这…

【案例实战】高并发业务的多级缓存架构一致性解决方案

我们在高并发的项目中基本上都离不开缓存&#xff0c;那么既然引入缓存&#xff0c;那就会有一个缓存与数据库数据一致性的问题。 首先&#xff0c;我们先来看看高并发项目里面Redis常见的三种缓存读写模式。 Cache Aside 读写分离模式&#xff0c;是最常见的Redis缓存模式&a…