深入探索Spring后置处理器:解析作用与实际应用场景

news2024/11/17 23:35:59

前言

BeanDefinitionRegistryPostProcessor , BeanFactoryPostProcessor ,InstantiationAwareBeanPostProcessor, BeanPostProcessor是spring生命周期中常见的4个后置处理器,但是对于其作用和执行顺序很多人还不是非常清楚,这里给大家讲解一下其作用以及执行顺序,并带上实操。

生命周期图:

img

作用及其执行顺序

  • BeanDefinitionRegistryPostProcessor 是一个扩展了 BeanFactoryPostProcessor 的接口,它可以在 Spring 容器加载了所有的 bean 定义之后,但是在 bean 实例化之前,对 bean 的定义进行修改或者添加 。它的 postProcessBeanDefinitionRegistry 方法会在所有的 bean 定义被加载后,但是在任何 bean 实例化之前被调用。它的 postProcessBeanFactory 方法会在所有的 bean 实例化之后被调用。因此,它的执行顺序是最先执行 postProcessBeanDefinitionRegistry 方法,然后执行 postProcessBeanFactory 方法。

  • BeanFactoryPostProcessor 是一个可以在 Spring 容器加载了所有的 bean 定义之后,但是在 bean 实例化之前,对 bean 的定义进行修改或者添加的接口 。它的 postProcessBeanFactory 方法会在所有的 bean 定义被加载后,但是在任何 bean 实例化之前被调用。因此,它的执行顺序是在 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry 方法之后,但是在 BeanDefinitionRegistryPostProcessor 的 postProcessBeanFactory 方法之前。

  • InstantiationAwareBeanPostProcessor 是一个扩展了 BeanPostProcessor 的接口,它可以在 Spring 容器实例化 bean 的过程中,对 bean 进行修改或者增强。它有四个方法:postProcessBeforeInstantiation、postProcessAfterInstantiation、postProcessPropertyValues 和 postProcessBeforeInitialization。它的执行顺序是:

    • 在每个 bean 实例化之前,调用 postProcessBeforeInstantiation 方法,如果该方法返回非空对象,则跳过后续的实例化过程。
    • 在每个 bean 实例化之后,调用 postProcessAfterInstantiation 方法,如果该方法返回 false,则跳过后续的属性注入过程。
    • 在每个 bean 属性注入之前,调用 postProcessPropertyValues 方法,如果该方法返回非空属性值,则使用返回的属性值进行注入。
    • 在每个 bean 初始化之前,调用 postProcessBeforeInitialization 方法,如果该方法返回非空对象,则使用返回的对象作为初始化结果。
  • BeanPostProcessor 是一个可以在 Spring 容器实例化和初始化 bean 的过程中,对 bean 进行修改或者增强的接口 。它有两个方法:postProcessBeforeInitialization 和 postProcessAfterInitialization。它的执行顺序是:

    • 在每个 bean 初始化之前,调用 postProcessBeforeInitialization 方法,如果该方法返回非空对象,则使用返回的对象作为初始化结果。

    • 在每个 bean 初始化之后,调用 postProcessAfterInitialization 方法,如果该方法返回非空对象,则使用返回的对象作为最终结果。

总结一下,这几个接口的执行顺序和作用如下:

  • BeanDefinitionRegistryPostProcessor: 最先执行,在所有的 bean 定义被加载后,可以修改或者添加 bean 的定义。
  • BeanFactoryPostProcessor: 其次执行,在所有的 bean 定义被加载后,可以修改或者添加 bean 的定义。
  • InstantiationAwareBeanPostProcessor: 第三执行,在每个 bean 实例化和初始化的过程中,可以修改或者增强 bean 的对象和属性。
  • BeanPostProcessor: 最后执行,在每个 bean 初始化的过程中,可以修改或者增强 bean 的对象。
PostProcessor 类型处理目标执行时机可操作的空间
BeanDefinitionRegistryPostProcessorBeanDefinition、.class 文件等配置文件、配置类已解析完毕并注册进 BeanFactory,但还没有被 BeanFactoryPostProcessor 处理向 BeanFactory 中注册新的 BeanDefinition
BeanFactoryPostProcessorBeanDefinitionBeanDefinition 解析完毕并注册进 BeanFactory 之后(此时 bean 未实例化)给 BeanDefinition 中增删属性、移除 BeanDefinition 等
InstantiationAwareBeanPostProcessorbean 实例bean 的实例化阶段前后(在实例化 bean 对象之前和之后)在 bean 实例化的过程中,可以对实例化的 bean 进行定制化的操作,例如自定义实例化逻辑、修改实例化后的属性值等
BeanPostProcessorbean 实例bean 的初始化阶段前后(已创建出 bean 对象)给 bean 的属性赋值、创建代理对象等

举例说明实际应用场景

  • BeanDefinitionRegistryPostProcessor: 这个后置处理器可以在 bean 定义加载后,创建并注册新的 bean 定义,或者修改或者删除已有的 bean 定义。它可以用于动态地根据条件或者配置来增加或者减少 bean 的数量和类型。例如,我们可以根据不同的环境(开发、测试、生产)来注册不同的数据源或者缓存 bean,或者根据不同的功能模块来注册不同的服务 bean。一个具体的例子是 Spring Boot 的自动配置功能,它使用了一个名为 AutoConfigurationImportSelector 的 BeanDefinitionRegistryPostProcessor 来根据条件注解和配置文件来自动导入和注册合适的自动配置类。
  • BeanFactoryPostProcessor: 这个后置处理器可以在 bean 定义加载后,修改或者添加已有的 bean 定义,但是不能创建或者删除 bean 定义。它可以用于修改 bean 的属性值、作用域、初始化方法等元数据信息。例如,我们可以根据外部的配置文件或者环境变量来修改 bean 的属性值,或者根据需要来改变 bean 的单例或者原型模式。一个具体的例子是 Spring 的 PropertyPlaceholderConfigurer 类,它使用了一个 BeanFactoryPostProcessor 来解析和替换 bean 定义中的占位符为实际的属性值。
  • InstantiationAwareBeanPostProcessor: 这个后置处理器可以在每个 bean 实例化和初始化的过程中,修改或者增强 bean 的对象和属性。它可以用于控制 bean 的实例化过程,或者对 bean 的属性进行自定义的注入或者验证。例如,我们可以使用它来实现一些特殊的注解功能,如 @Autowired 或者 @Value,或者使用它来实现一些代理功能,如 AOP 或者事务管理。一个具体的例子是 Spring 的 AutowiredAnnotationBeanPostProcessor 类,它使用了一个 InstantiationAwareBeanPostProcessor 来实现了 @Autowired 和 @Value 注解的功能。
  • BeanPostProcessor: 这个后置处理器可以在每个 bean 初始化的过程中,修改或者增强 bean 的对象。它可以用于对 bean 进行一些额外的配置或者初始化操作,或者对 bean 进行一些装饰或者代理操作。例如,我们可以使用它来执行一些初始化方法,如 @PostConstruct 或者 init-method,或者使用它来实现一些代理功能,如 AOP 或者事务管理。一个具体的例子是 Spring 的 InitDestroyAnnotationBeanPostProcessor 类,它使用了一个 BeanPostProcessor 来实现了 @PostConstruct 和 @PreDestroy 注解的功能。

实操

首先,我们需要创建一个 Spring Boot 项目,并在 pom.xml 中添加以下依赖:

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

然后,我们需要定义以下四个后置处理器的实现类,并使用 @Component 注解标注它们,以便让 Spring 自动扫描并注册它们:

  • MyBeanDefinitionRegistryPostProcessor:用于在 bean 定义加载后,创建并注册新的 bean 定义
@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        System.out.println("MyBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry");
        // 创建一个新的 bean 定义
        BeanDefinition beanDefinition = new RootBeanDefinition(MyNewBean.class);
        // 注册到容器中
        registry.registerBeanDefinition("myNewBean", beanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("MyBeanDefinitionRegistryPostProcessor.postProcessBeanFactory");
        // 修改已有的 bean 定义
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("helloService");
        beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
    }
}
  • MyBeanFactoryPostProcessor:用于在 bean 定义加载后,修改或者添加已有的 bean 定义
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("MyBeanFactoryPostProcessor.postProcessBeanFactory");
        // 修改已有的 bean 定义
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("worldService");
        beanDefinition.setLazyInit(true);
    }
}
  • MyInstantiationAwareBeanPostProcessor:用于在每个 bean 实例化和初始化的过程中,修改或者增强 bean 的对象和属性
@Component
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        System.out.println("MyInstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation: " + beanName);
        // 如果返回非空对象,则跳过后续的实例化过程
        if (beanName.equals("myNewBean")) {
            return new MyNewBean("Created by postProcessBeforeInstantiation");
        }
        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        System.out.println("MyInstantiationAwareBeanPostProcessor.postProcessAfterInstantiation: " + beanName);
        // 如果返回 false,则跳过后续的属性注入过程
        if (beanName.equals("helloService")) {
            return false;
        }
        return true;
    }

    @Override
    public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
        System.out.println("MyInstantiationAwareBeanPostProcessor.postProcessPropertyValues: " + beanName);
        // 如果返回非空属性值,则使用返回的属性值进行注入
        if (beanName.equals("worldService")) {
            return new MutablePropertyValues().add("message", "Modified by postProcessPropertyValues");
        }
        return pvs;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyInstantiationAwareBeanPostProcessor.postProcessBeforeInitialization: " + beanName);
        // 如果返回非空对象,则使用返回的对象作为初始化结果
        if (beanName.equals("myNewBean")) {
            return new MyNewBean("Modified by postProcessBeforeInitialization");
        }
        return bean;
    }
}
  • MyBeanPostProcessor:用于在每个 bean 初始化的过程中,修改或者增强 bean 的对象
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanPostProcessor.postProcessBeforeInitialization: " + beanName);
        // 如果返回非空对象,则使用返回的对象作为初始化结果
        if (beanName.equals("helloService")) {
            return new HelloService("Modified by postProcessBeforeInitialization");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanPostProcessor.postProcessAfterInitialization: " + beanName);
        // 如果返回非空对象,则使用返回的对象作为最终结果
        if (beanName.equals("worldService")) {
            return new WorldService("Modified by postProcessAfterInitialization");
        }
        return bean;
    }
}

接下来,我们需要定义以下四个普通的 bean 类,并使用 @Component 或者 @Service 注解标注它们,以便让 Spring 自动扫描并注册它们:

  • HelloService:用于测试 MyBeanFactoryPostProcessor 和 MyInstantiationAwareBeanPostProcessor 的修改效果
@Service
public class HelloService {

    private String message;

    public HelloService() {
        this.message = "Hello, Spring!";
    }

    public HelloService(String message) {
        this.message = message;
    }

    public void sayHello() {
        System.out.println(message);
    }
}
  • WorldService:用于测试 MyBeanFactoryPostProcessor 和 MyBeanPostProcessor 的修改效果
@Service
public class WorldService {

    private String message;

    public WorldService() {
        this.message = "World, Spring!";
    }

    public WorldService(String message) {
        this.message = message;
    }

    public void sayWorld() {
        System.out.println(message);
    }
}
  • MyNewBean:用于测试 MyBeanDefinitionRegistryPostProcessor 和 MyInstantiationAwareBeanPostProcessor 的创建和修改效果
public class MyNewBean {

    private String message;

    public MyNewBean() {
        this.message = "This is a new bean!";
    }

    public MyNewBean(String message) {
        this.message = message;
    }

    public void saySomething() {
        System.out.println(message);
    }
}
  • TestController:用于测试各个 bean 的实际表现
@RestController
public class TestController {

    @Autowired
    private HelloService helloService;

    @Autowired
    private WorldService worldService;

    @Autowired
    private MyNewBean myNewBean;

    @GetMapping("/test")
    public String test() {
        helloService.sayHello();
        worldService.sayWorld();
        myNewBean.saySomething();
        return "Test done!";
    }
}

最后,我们可以运行 Spring Boot 项目,并访问 http://localhost:8080/test ,观察控制台的输出,以及各个后置处理器的执行顺序和作用。我们可以看到以下结果:

MyBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry
MyBeanFactoryPostProcessor.postProcessBeanFactory
MyInstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation: helloService
MyInstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation: worldService
MyInstantiationAwareBeanPostProcessor.postProcessAfterInstantiation: worldService
MyInstantiationAwareBeanPostProcessor.postProcessPropertyValues: worldService
MyInstantiationAwareBeanPostProcessor.postProcessBeforeInitialization: worldService
MyBeanPostProcessor.postProcessBeforeInitialization: worldService
MyInstantiationAwareBeanPostProcessor.postProcessAfterInitialization: worldService
MyBeanPostProcessor.postProcessAfterInitialization: worldService
MyInstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation: myNewBean
MyInstantiationAwareBeanPostProcessor.postProcessBeforeInitialization: myNewBean
MyInstantiationAwareBeanPostProcessor.postProcessAfterInitialization: myNewBean

Modified by postProcessBeforeInitialization
Modified by postProcessPropertyValues and postProcessAfterInitialization
Modified by postProcessBeforeInitialization

Test done!

从上面的结果可以看出,这四个后置处理器的执行顺序和作用如下:

  • MyBeanDefinitionRegistryPostProcessor:最先执行,在所有的 bean 定义被加载后,创建并注册了一个名为 myNewBean 的新的 bean 定义,并修改了 helloService 的 scope 为 prototype。
  • MyBeanFactoryPostProcessor:其次执行,在所有的 bean 定义被加载后,修改了 worldService 的 lazyInit 为 true。
  • MyInstantiationAwareBeanPostProcessor:第三执行,在每个 bean 实例化和初始化的过程中,修改或者增强 bean 的对象和属性。它的 postProcessBeforeInstantiation 方法返回了一个非空对象,导致 myNewBean 的实例化过程被跳过。它的 postProcessAfterInstantiation 方法返回了 false,导致 helloService 的属性注入过程被跳过。它的 postProcessPropertyValues 方法返回了一个非空属性值,导致 worldService 的 message 属性被修改。它的 postProcessBeforeInitialization 方法返回了一个非空对象,导致 myNewBean 的初始化结果被修改。
  • MyBeanPostProcessor:最后执行,在每个 bean 初始化的过程中,修改或者增强 bean 的对象。它的 postProcessBeforeInitialization 方法返回了一个非空对象,导致 helloService 的初始化结果被修改。它的 postProcessAfterInitialization 方法返回了一个非空对象,导致 worldService 的最终结果被修改。

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

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

相关文章

软件测试的调用接口怎么调用,逻辑是什么?

一、什么是接口测试&#xff1f; 接口测试是测试系统组件之间接口的测试。接口主要用于检测外部系统和内部子系统之间的交互点。测试的重点是检查数据交换、传输、控制和管理过程&#xff0c;以及系统之间的相互逻辑依赖。 二、为什么要做接口测试&#xff1f; 在淘宝系统的…

Java8新特性---Stream流

什么是Stream 是数据渠道&#xff0c;用于操作数据源&#xff08;集合、数组等&#xff09;所生成的元素序列。集合讲究的是数据&#xff0c;流讲的是计算 注意&#xff1a; Stream不会自己存储元素Stream不会改变源对象&#xff0c;相反&#xff0c;他们会返回持有结果的新…

GPT-4耗尽全宇宙数据!OpenAI接连吃官司,竟因数据太缺了,UC伯克利教授发出警告

穷尽「全网」&#xff0c;生成式AI很快无数据可用。 近日&#xff0c;著名UC伯克利计算机科学家Stuart Russell称&#xff0c;ChatGPT和其他AI工具的训练可能很快耗尽「全宇宙的文本」。 换句话说&#xff0c;训练像ChatGPT这样的AI&#xff0c;将因数据量不够而受阻。 这可能…

五、修改官方FreeRTOS例程(STM32F1)

1、官方源码下载 (1)进入FreeRTOS官网&#xff1a;FreeRTOS官网 (2)下载FreeRTOS。(选择带示例的下载) 2、删减目录 (1)下载后解压的FreeRTOS文件如下图所示。 (2)删除下图中红框勾选的文件。 FreeRTOS-Plus&#xff0c;FreeRTOS的生态文件&#xff0c;非必需的。tools&…

esp8266+电压检测模块检测电池电压

该模块5v时输出1v&#xff0c;因esp8266 ADC引脚(A0)支持电压范围是0v-1v&#xff0c;所以该方案仅支持0-5v电压检测 接线&#xff1a; - 接 esp8266GND 可不接 S 接 ADC esp8266 为 A0 VCC 被检测直流电 GND 被检测直流电- #include <Wire.h>const int adcPin A0; // …

Redis五大基本数据类型及其使用场景

文章目录 **一 什么是NoSQL&#xff1f;****二 redis是什么&#xff1f;****三 redis五大基本类型**1 String&#xff08;字符串&#xff09;**应用场景** 2 List&#xff08;列表&#xff09;**应用场景** 3 Set&#xff08;集合&#xff09;4 sorted set&#xff08;有序集合…

excel逻辑函数篇1

1、AND(logical1,[logical2],…)&#xff1a;用于测试所有条件是否均为TRUE 检查所有参数均为true&#xff0c;如果是则返回true 2、OR(logical1,[logical2],…)&#xff1a;用于测试是否有为TRUE的条件 如果任意参数值为true&#xff0c;即返回true&#xff1b;只有当所有参数…

223、仿真-基于51单片机温湿度PH值二氧化碳(C02)报警Proteus仿真设计(程序+Proteus仿真+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、硬件设计 二、设计功能 三、Proteus仿真图 四、程序源码 资料包括&#xff1a; 需要完整的资料可以点击下面的名片加下我&#xff0c;找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选择 方案一&…

机器学习笔记 - 基于keras + 小型Xception网络进行图像分类

一、简述 Xception 是深度为 71 层的卷积神经网络,仅依赖于深度可分离的卷积层。 论文中将卷积神经网络中的 Inception 模块解释为常规卷积和深度可分离卷积运算(深度卷积后跟点卷积)之间的中间步骤。从这个角度来看,深度可分离卷积可以理解为具有最大数量塔的 Inception 模…

学习笔记230804---restful风格的接口,delete的传参方式问题

如果后端提供的删除接口是restful风格&#xff0c;那么使用地址栏拼接的方式发送请求&#xff0c;数据放在主体中&#xff0c;后端接受不到&#xff0c;当然也还有一种可能&#xff0c;后端在这个接口的接参设置上是req.query接参。 问题描述 今天遇到的问题是&#xff0c;de…

windows子系统wsl2备份(迁移D盘)还原系统

windows子系统wsl2备份还原系统 1.查看wsl版本 wsl -l -v2.停止wsl子系统 wsl --terminate Ubuntu-22.043.导出子系统备份 wsl --export Ubuntu-22.04 D:\Ubuntu_bak.tar4.删除原有系统 wsl --unregister Ubuntu-22.045.还原系统 wsl --import Ubuntu-22.04 d:\Ubuntu-ws…

Jenkins-发送邮件配置

在Jenkins构建执行完毕后&#xff0c;需要及时通知相关人员。因此在jenkins中是可以通过邮件通知的。 一、Jenkins自带的邮件通知功能 找到manage Jenkins->Configure System&#xff0c;进行邮件配置&#xff1a; 2. 配置Jenkins自带的邮箱信息 完成上面的配置后&#xf…

【0基础入门Python笔记】三、python 之函数以及常用内置函数

三、python 之函数以及常用内置函数 函数函数定义函数调用函数参数返回值 常用内置函数input()函数range()函数其它 函数 函数是一种用于封装可重复使用代码块的工具&#xff0c;能够将一系列操作组织成一个逻辑单元。 函数定义 在Python中&#xff0c;函数通过def关键字进行…

Python可视化在量化交易中的应用(12)_Seaborn柱状图

Seaborn中柱状图的绘制方法 seaborn中绘制折线图使用的是sns.barplot()函数&#xff1a; sns.barplot(x,y,hue,data,order,hue_order,estimator,ci95,n_boot1000,units,seed,orient,color,palette,saturation0.75,errcolor‘.26’,errwidth,capsize,dodgeTrue,ax,**kwargs,) …

linux RabbitMQ-3.8.5 安装

软件版本操作系统CentOS Linux release 7.9.2009erlangerlang-23.0.2-1.el7.x86_64rabbitMQrabbitmq-server-3.8.5-1.el7 RabbitMQ的安装首先需要安装Erlang,因为它是基于Erlang的VM运行的。 RabbitMQ安装需要依赖:socat和logrotate&#xff0c;logrotate操作系统已经存在了&…

内网穿透技术之神卓互联如何检测本地环境

在使用神卓互联Windows版客户端的过程中添加和编辑映射通道默认会自动生效&#xff0c;如您遇到无法访问的情况&#xff0c;可通过客户端上的检测工具进行环境检测。 Windows客户端检测本地环境&#xff0c;登录神卓互联客户端&#xff0c;在主界面下方的按钮组里选择【环境检…

《Python入门到精通》os模块详解,Python os标准库

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;小白零基础《Python入门到精通》 os模块详解 1、文件目录操作os.stat() 获取文件状态os.utime() 修改文件时间os.r…

PostgreSQL父子建表查询所有的子数据-利用自定义函数查询

pgsql 函数查询代码 select find_space_tree_list_by_nodeid(1,1) 查询结果示意图 获取子集函数代码 CREATE OR REPLACE FUNCTION "public"."find_space_tree_list_by_nodeid"("nodeid" varchar, "viewid" varchar)RETURNS "…

8.19论文阅读

文章目录 Graph-Segmenter: Graph Transformer with Boundary-aware Attention for Semantic Segmentation方法 SCSC: Spatial Cross-scale Convolution Module to Strengthen both CNNs and Transformers方法 Deformable Mixer Transformer with Gating for Multi-Task Learni…

KFC玩具HelloKitty风扇改造——ESP32蓝牙blinker、1404无刷电机、双18650

改装KFC玩具——ESP32蓝牙无刷风扇 ESP32代码&#xff1a; 使用NodeMCU-32S #define BLINKER_BLE#include <Blinker.h>#include <Arduino.h>// PWM通道 int channel_PWM 3; // PWM频率&#xff0c;那么周期也就是1/50&#xff0c;也就是20ms &#xff0c;PWM一…