Spring Boot 集成 Redisson分布式锁(拿来即用版)

news2024/12/25 2:20:05

        Redisson 是一种基于 Redis 的 Java 驻留集群的分布式对象和服务库,可以为我们提供丰富的分布式锁和线程安全集合的实现。在 Spring Boot 应用程序中使用 Redisson 可以方便地实现分布式应用程序的某些方面,例如分布式锁、分布式集合、分布式事件发布和订阅等。本篇是一个使用 Redisson 实现分布式锁的详细示例,在这个示例中,我们定义了DistributedLock注解,它可以标注在方法上,配合DistributedLockAspect切面以及IDistributedLock分布式锁封装的接口,来实现redisson 分布式锁的 API 调用。

Spring Boot 集成 Redisson

 1、在 pom.xml 文件中添加 Redisson 的相关依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.16.1</version>
</dependency>

2、在 application.yml 中配置 Redisson单机模式 的连接信息和相关参数

spring:
  redis:
    host: localhost
    port: 6379
    password: null
redisson:
  codec: org.redisson.codec.JsonJacksonCodec
  threads: 4
  netty:
    threads: 4
  single-server-config:
    address: "redis://localhost:6379"
    password: null
    database: 0

3、Redission还支持主从、集群、哨兵配置

//主从模式
spring:
  redis:
    sentinel:
      master: my-master
      nodes: localhost:26379,localhost:26389
    password: your_password
redisson:
  master-slave-config:
    master-address: "redis://localhost:6379"
    slave-addresses: "redis://localhost:6380,redis://localhost:6381"
    password: ${spring.redis.password}

// 集群模式
spring:
  redis:
    cluster:
      nodes: localhost:6379,localhost:6380,localhost:6381,localhost:6382,localhost:6383,localhost:6384
    password: your_password
redisson:
  cluster-config:
    node-addresses: "redis://localhost:6379,redis://localhost:6380,redis://localhost:6381,redis://localhost:6382,redis://localhost:6383,redis://localhost:6384"
    password: ${spring.redis.password}

// 哨兵模式
spring:
  redis:
    sentinel:
      master: my-master
      nodes: localhost:26379,localhost:26389
    password: your_password
redisson:
  sentinel-config:
    master-name: my-master
    sentinel-addresses: "redis://localhost:26379,redis://localhost:26380,redis://localhost:26381"
    password: ${spring.redis.password}

 本地封装Redisson 分布式锁

1、定义IDistributedLock分布式锁接口

import java.util.concurrent.TimeUnit;

/**
 * <p>
 * </p>
 * @since 2023-06-08 16:54
 */
public interface IDistributedLock {
    /**
     * 获取锁,默认30秒失效,失败一直等待直到获取锁
     *
     * @param key 锁的key
     * @return 锁对象
     */
    ILock lock(String key);

    /**
     * 获取锁,失败一直等待直到获取锁
     *
     * @param key      锁的key
     * @param lockTime 加锁的时间,超过这个时间后锁便自动解锁; 如果lockTime为-1,则保持锁定直到显式解锁
     * @param unit     {@code lockTime} 参数的时间单位
     * @param fair     是否公平锁
     * @return 锁对象
     */
    ILock lock(String key, long lockTime, TimeUnit unit, boolean fair);

    /**
     * 尝试获取锁,30秒获取不到超时异常,锁默认30秒失效
     *
     * @param key 锁的key
     * @return
     * @throws Exception
     */
    ILock tryLock(String key) throws Exception;

    /**
     * 尝试获取锁,获取不到超时异常
     *
     * @param key      锁的key
     * @param tryTime  获取锁的最大尝试时间
     * @param lockTime 加锁的时间
     * @param unit     {@code tryTime @code lockTime} 参数的时间单位
     * @param fair     是否公平锁
     * @return
     * @throws Exception
     */
    ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair) throws Exception;

    /**
     * 解锁
     *
     * @param lock
     * @throws Exception
     */
    void unLock(Object lock);

    /**
     * 释放锁
     *
     * @param lock
     * @throws Exception
     */
    default void unLock(ILock lock) {
        if (lock != null) {
            unLock(lock.getLock());
        }
    }
}

2、IDistributedLock实现类

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * <p>     </p>
 * @since 2023-06-08 16:57
 */
@Component
public class RedissonDistributedLock implements IDistributedLock {

    @Resource
    private RedissonClient redissonClient;
    /**
     * 统一前缀
     */
    @Value("${redisson.lok.prefix:bi-distributed-lock}")
    private String prefix;

    @Override
    public ILock lock(String key) {
        return this.lock(key, 30L, TimeUnit.SECONDS, false);
    }

    @Override
    public ILock lock(String key, long lockTime, TimeUnit unit, boolean fair) {
        RLock lock;
        String lockKey = prefix + ":" + key;
        if (fair) {
            // 获取公平锁
            lock = redissonClient.getFairLock(lockKey);
        } else {
            // 获取普通锁
            lock = redissonClient.getLock(lockKey);
        }
        // 获取锁,失败一直等待,直到获取锁
        lock.lock(lockTime, unit);
        return new ILock(lock, this);
    }

    @Override
    public ILock tryLock(String key) throws Exception {
        return this.tryLock(key, 30L, 30l, TimeUnit.SECONDS, false);
    }

    @Override
    public ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair)
            throws Exception {
        RLock lock;
        String lockKey = prefix + ":" + key;
        if (fair) {
            // 获取公平锁
            lock = redissonClient.getFairLock(lockKey);
        } else {
            // 获取普通锁
            lock = redissonClient.getLock(lockKey);
        }
        lock.lock();
        // 尝试获取锁,获取不到超时异常
        if (lock.tryLock(tryTime, lockTime, unit)) {
            return new ILock(lock, this);
        }
        return null;
    }

    @Override
    public void unLock(Object lock) {
        if (lock != null) {
            if (lock instanceof RLock) {
                RLock l = (RLock) lock;
                if (l.isLocked()) {
                    l.unlock();
                }
            }
        }
    }
}

 3、定义ILock锁对象

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Objects;

/**
 * <p>
 * RedissonLock 包装的锁对象 实现AutoCloseable接口,在java7的try(with resource)语法,不用显示调用close方法
 * </p>

 * @since 2023-06-08 16:57
 */
@AllArgsConstructor
public class ILock implements AutoCloseable {
    /**
     * 持有的锁对象
     */
    @Getter
    private Object lock;
    /**
     * 分布式锁接口
     */
    @Getter
    private IDistributedLock distributedLock;

    @Override
    public void close() throws Exception {
        if(Objects.nonNull(lock)){
            distributedLock.unLock(lock);
        }
    }
}

4、注入IDistributedLock接口使用示例

// 定义接口
public interface IProductSkuSupplierMeasureService {
    /**
     * 保存SKU供应商供货信息
     *
     * @param dto
     * @return
     *
    Boolean saveSupplierInfo(ProductSkuSupplierInfoDTO dto);

    /**
     * 编辑SKU供应商供货信息
     *
     * @param dto
     * @return
     */
    Boolean editSupplierInfo(ProductSkuSupplierInfoDTO dto);
}    


手动释放锁示例 

// 实现类
@Service
public class ProductSkuSupplierMeasureServiceImpl 
        implements IProductSkuSupplierMeasureService {

    @Resource
    private IDistributedLock distributedLock;    
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveSupplierInfo(ProductSkuSupplierInfoDTO dto) {
        // 手动释放锁
        String sku = dto.getSku();
        ILock lock = null;
        try {
            lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false);
            // 业务代码
        } catch (Exception e) {
            log.error("保存异常", e);
            throw new BaseException(e.getMessage());
        } finally {
            if (Objects.nonNull(lock)) {
                distributedLock.unLock(lock);
            }
        }
        return Boolean.TRUE;
    }

}

 使用try-with-resources 语法糖自动释放锁

// 实现类
@Service
public class ProductSkuSupplierMeasureServiceImpl 
        implements IProductSkuSupplierMeasureService {

    @Resource
    private IDistributedLock distributedLock;    
   

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean editSupplierInfo(ProductSkuSupplierInfoDTO dto) {
       
        String sku = dto.getSku();
        // try-with-resources 语法糖自动释放锁
        try(ILock lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false)) {
            if(Objects.isNull(lock)){
                throw new IdempotencyException("Duplicate request for method still in process");
            }
            
            // 业务代码
        } catch (Exception e) {
            log.error("异常", e);
           throw new BaseException(e.getMessage());
        }
        return Boolean.TRUE;
   }
}

5、使用AOP切面实现分布式锁的绑定

  定义DistributedLock注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLock {

    /**
     * 保证业务接口的key的唯一性,否则失去了分布式锁的意义 锁key
     * 支持使用spEl表达式
     */
    String key();

    /**
     * 保证业务接口的key的唯一性,否则失去了分布式锁的意义 锁key 前缀
     */
    String keyPrefix() default "";

    /**
     * 是否在等待时间内获取锁,如果在等待时间内无法获取到锁,则返回失败
     */
    boolean tryLok() default false;

    /**
     * 获取锁的最大尝试时间 ,会尝试tryTime时间获取锁,在该时间内获取成功则返回,否则抛出获取锁超时异常,tryLok=true时,该值必须大于0。
     *
     */
    long tryTime() default 0;

    /**
     * 加锁的时间,超过这个时间后锁便自动解锁
     */
    long lockTime() default 30;

    /**
     * tryTime 和 lockTime的时间单位
     */
    TimeUnit unit() default TimeUnit.SECONDS;

    /**
     * 是否公平锁,false:非公平锁,true:公平锁
     */
    boolean fair() default false;
}

 定义DistributedLockAspect Lock切面

@Aspect
@Slf4j
public class DistributedLockAspect {

    @Resource
    private IDistributedLock distributedLock;

    /**
     * 统一前缀
     */
    @Value("${redisson.lok.prefix:bi:distributed:lock}")
    private String prefix;

    /**
     * SpEL表达式解析
     */
    private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();

    /**
     * 用于获取方法参数名字
     */
    private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    @Pointcut("@annotation(com.yt.bi.common.redis.distributedlok.annotation.DistributedLock)")
    public void distributorLock() {
    }

    @Around("distributorLock()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        // 获取DistributedLock
        DistributedLock distributedLock = this.getDistributedLock(pjp);
        // 获取 lockKey
        String lockKey = this.getLockKey(pjp, distributedLock);
        ILock lockObj = null;
        try {
            // 加锁,tryLok = true,并且tryTime > 0时,尝试获取锁,获取不到超时异常
            if (distributedLock.tryLok()) {
                if(distributedLock.tryTime() <= 0){
                    throw new IdempotencyException("tryTime must be greater than 0");
                }
                lockObj = this.distributedLock.tryLock(lockKey, distributedLock.tryTime(), distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
            } else {
                lockObj = this.distributedLock.lock(lockKey, distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
            }

            if (Objects.isNull(lockObj)) {
                throw new IdempotencyException("Duplicate request for method still in process");
            }

            return pjp.proceed();
        } catch (Exception e) {
            throw e;
        } finally {
            // 解锁
            this.unLock(lockObj);
        }
    }

    /**
     * @param pjp
     * @return
     * @throws NoSuchMethodException
     */
    private DistributedLock getDistributedLock(ProceedingJoinPoint pjp) throws NoSuchMethodException {
        String methodName = pjp.getSignature().getName();
        Class clazz = pjp.getTarget().getClass();
        Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();
        Method lockMethod = clazz.getMethod(methodName, par);
        DistributedLock distributedLock = lockMethod.getAnnotation(DistributedLock.class);
        return distributedLock;
    }

    /**
     * 解锁
     *
     * @param lockObj
     */
    private void unLock(ILock lockObj) {
        if (Objects.isNull(lockObj)) {
            return;
        }

        try {
            this.distributedLock.unLock(lockObj);
        } catch (Exception e) {
            log.error("分布式锁解锁异常", e);
        }
    }

    /**
     * 获取 lockKey
     *
     * @param pjp
     * @param distributedLock
     * @return
     */
    private String getLockKey(ProceedingJoinPoint pjp, DistributedLock distributedLock) {
        String lockKey = distributedLock.key();
        String keyPrefix = distributedLock.keyPrefix();
        if (StringUtils.isBlank(lockKey)) {
            throw new IdempotencyException("Lok key cannot be empty");
        }
        if (lockKey.contains("#")) {
            this.checkSpEL(lockKey);
            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            // 获取方法参数值
            Object[] args = pjp.getArgs();
            lockKey = getValBySpEL(lockKey, methodSignature, args);
        }
        lockKey = StringUtils.isBlank(keyPrefix) ? prefix + ":" + lockKey : prefix + ":" + keyPrefix + lockKey;
        return lockKey;
    }

    /**
     * 解析spEL表达式
     *
     * @param spEL
     * @param methodSignature
     * @param args
     * @return
     */
    private String getValBySpEL(String spEL, MethodSignature methodSignature, Object[] args) {
        // 获取方法形参名数组
        String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());
        if (paramNames == null || paramNames.length < 1) {
            throw new IdempotencyException("Lok key cannot be empty");
        }
        Expression expression = spelExpressionParser.parseExpression(spEL);
        // spring的表达式上下文对象
        EvaluationContext context = new StandardEvaluationContext();
        // 给上下文赋值
        for (int i = 0; i < args.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        return expression.getValue(context).toString();
    }

    /**
     * SpEL 表达式校验
     *
     * @param spEL
     * @return
     */
    private void checkSpEL(String spEL) {
        try {
            ExpressionParser parser = new SpelExpressionParser();
            parser.parseExpression(spEL, new TemplateParserContext());
        } catch (Exception e) {
            log.error("spEL表达式解析异常", e);
            throw new IdempotencyException("Invalid SpEL expression [" + spEL + "]");
        }
    }
}

定义分布式锁注解版启动元注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({DistributedLockAspect.class})
public @interface EnableDistributedLock {
}

6、AOP切面实现分布式锁的绑定使用示例

   启动类添加@EnableDistributedLock启用注解支持

@SpringBootApplication
@EnableDistributedLock
public class BiCenterGoodsApplication {

    public static void main(String[] args) {
        
        SpringApplication.run(BiCenterGoodsApplication.class, args);
        
    }
}

@DistributedLock标注需要使用分布式锁的方法 

    @ApiOperation("编辑SKU供应商供货信息")
    
    @PostMapping("/editSupplierInfo")
    //@DistributedLock(key = "#dto.sku + '-' + #dto.skuId", lockTime = 10L, keyPrefix = "sku-")
    @DistributedLock(key = "#dto.sku", lockTime = 10L, keyPrefix = "sku-")
    public R<Boolean> editSupplierInfo(@RequestBody @Validated ProductSkuSupplierInfoDTO dto) {
        return R.ok(productSkuSupplierMeasureService.editSupplierInfo(dto));
    }
#dto.sku是 SpEL表达式。Spring中支持的它都支持的。比如调用静态方法,三目表达式。SpEL 可以使用方法中的任何参数。SpEL表达式参考

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

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

相关文章

JSON5的作用、安装及使用

JSON5是对JSON的扩展&#xff0c;让人可以更容易手工编写和维护&#xff0c;用来减少一些JSON的限制&#xff0c;诸如json语法不支持注释&#xff0c;不支持字符串换行&#xff0c;所有的key都必须双引号&#xff0c;末尾不能有多余的逗号…等等&#xff0c;一大堆极其严格的要…

chatgpt赋能python:Python的退役与SEO

Python的退役与SEO 随着Python编程语言的流行和普及&#xff0c;越来越多的人开始使用它来开发各种类型的应用程序。但是&#xff0c;就像我们所知道的&#xff0c;所有技术都会发生变化&#xff0c;包括编程语言。因此&#xff0c;Python程式员可能会感到困惑和担忧&#xff…

JavaScript对象 (八):对象类型的使用、值类型和引用类型、函数的this指向、工厂方法创建对象、构造函数和类、new创建对象

1. 对象类型的使用 1.1 认识对象类型 基础数据类型可以存储一些简单的值&#xff0c;但是现实世界的事物抽象成程序时&#xff0c;往往比较复杂。 比如一个人&#xff0c;有自己的特性&#xff08;比如姓名、年龄、身高&#xff09;&#xff0c;有一些行为&#xff08;比如跑…

课程17:菜单管理功能实现

🚀前言 本文是《.Net Core从零学习搭建权限管理系统》教程专栏的课程(点击链接,跳转到专栏主页,欢迎订阅,持续更新…) 专栏介绍:以实战为线索,基于.Net 7 + REST + Vue、前后端分离,不依赖任何第三方框架,从零一步一步讲解权限管理系统搭建。 专栏适用于人群:We…

为什么运行时安全性重新流行起来?

容器通过以更高效和可扩展的方式创建、打包和部署应用程序&#xff0c;彻底改变了软件开发过程。 然而&#xff0c;能力越大&#xff0c;责任越大&#xff0c;对“左移安全性”的高度关注为那些在运行时忽视安全性的组织带来了风险。 通过对容器运行时安全采取多层次、全面的…

云服务器ECS_云主机_服务器托管_弹性计算-阿里云

阿里云服务器ECS&#xff08;Elastic Compute Service&#xff09;是一种安全可靠、弹性可伸缩的云计算服务&#xff0c;阿里云提供多种云服务器ECS实例规格&#xff0c;如通用算力型u1、ECS计算型c7、通用型g7、GPU实例等&#xff0c;阿里云服务器网分享阿里云服务器ECS详细介…

Linux文件操作四剑客

目录 一、grep &#xff08;一&#xff09;作用 &#xff08;二&#xff09;格式 &#xff08;三&#xff09;选项 &#xff08;四&#xff09;案例 1、查看/etc目录下所有包含bash的文件名&#xff1a;grep -rl bash /etc 2、查看/var/log目录下所有包含error的文…

读发布!设计与部署稳定的分布式系统(第2版)笔记04_集成点

1. 第一个拥有10亿用户的网站 1.1. 2016年&#xff0c;Facebook宣布其每日活跃用户数量为11.3亿 1.2. 对整个应用程序来说&#xff0c;“五个9”的可靠性远远不够&#xff0c;这每天会让成千上万的用户失望 1.3. 假如按照六西格玛质量标准来衡量&#xff0c;那么Facebook每天…

LIN-网络管理:休眠(Go To Sleep)和唤醒(Wake up)

文章目录 一、LIN总线的两种状态二、休眠模式&#xff08;Go To Sleep&#xff09;①利用诊断帧中的主机请求帧 0x3C 作休眠命令②当总线静默(没有显性和隐性电平之间的切换)4s&#xff5e;10s 时&#xff0c;节点自动进入休眠状态。 三、唤醒模式&#xff08;Wake up&#xff…

智能图片降噪-Topaz Photo AI

今天给各位小伙伴们测试了一款可以使视频智能无损放大的软件——Topaz Photo AI。 小编在很早之前也有了解过Topaz系列的软件&#xff0c;都是通过人工智能处理的&#xff0c;对小白新手们很适用&#xff0c;由于使用人工智能方面的软件或程序对硬件要求都比较高&#xff0c;因…

交通指南系统

一、实验目的 1. 掌握图的基本存储方法&#xff1b; 2. 掌握有关图的操作算法并用高级语言实现&#xff1b; 3. 熟练掌握图的两种搜索路径的遍历方法。 二、实验内容 假设以一个带权有向图表示某一区域的公交线路网&#xff0c;图中顶点代表一些区域中的重要场所&#xff0…

设计模式(二十二):行为型之备忘录模式

设计模式系列文章 设计模式(一)&#xff1a;创建型之单例模式 设计模式(二、三)&#xff1a;创建型之工厂方法和抽象工厂模式 设计模式(四)&#xff1a;创建型之原型模式 设计模式(五)&#xff1a;创建型之建造者模式 设计模式(六)&#xff1a;结构型之代理模式 设计模式…

【C】转义字符以及注释的介绍

转义字符 转义字符顾名思义就是转变意思。就是把原来字符的意思转变了&#xff0c;让它拥有别的意思。 如果我们想要在屏幕上打印&#xff1a;c:\code:\test.c 这样一串文字的话&#xff0c;我们代码肯定会这样写&#xff1a; #include<stdio.h> int main() {printf(&q…

压缩感知入门④基于总体最小二乘的扰动压缩感知重构算法

压缩感知系列博客&#xff1a;压缩感知入门①从零开始压缩感知压缩感知入门②信号的稀疏表示和约束等距性压缩感知入门③基于ADMM的全变分正则化的压缩感知重构算法压缩感知入门④基于总体最小二乘的扰动压缩感知重构算法 文章目录 1. Problem2. 仿真结果3. MATLAB算法4. 源码地…

Bean 的六种作用域

观前提示:本篇博客演示使用的 IDEA 版本为2021.3.3版本,使用的是Java8(又名jdk1.8) 前端使用VSCode(Visual Studio Code1.78.2) 电脑使用的操作系统版本为 Windows 10 目录 前言 Bean Spring 容器在初始化⼀个 Bean 的实例时&#xff0c;同时会指定该实例的作⽤域。 1. …

chatgpt赋能python:Python怎样能通过值找到键

Python怎样能通过值找到键 Python是一种高级编程语言&#xff0c;它在工业、医疗、科学、财务等多个行业中被广泛使用&#xff0c;是数据科学、人工智能和深度学习等领域的首选语言。在Python编程中&#xff0c;有时候我们需要在字典中根据值查询对应的键&#xff0c;本文将介…

chatgpt赋能python:Python排序算法大全

Python排序算法大全 导言 排序是程序员日常工作中最常见的操作之一。Python提供了许多实现排序算法的库和函数&#xff0c;本文将带您了解这些排序方法。 初级排序算法 冒泡排序 Bubble Sort 冒泡排序是一种简单的排序算法。它通过不断交换相邻的元素&#xff0c;将大的元…

【微服务架构设计和实现】4.2 服务边界的定义和划分

第一章&#xff1a;【云原生概念和技术】 第二章&#xff1a;【容器化应用程序设计和开发】 第三章&#xff1a;【基于容器的部署、管理和扩展】 第四章&#xff1a;【4.1 微服务架构概述和设计原则】 4.2 服务边界的定义和划分 4.2 服务边界的定义和划分4.2.1 什么是服务边…

docker创建Ubuntu,Ubuntu创建桌面环境,本机使用VNC连接

题目&#xff1a;docker创建Ubuntu&#xff0c;Ubuntu创建桌面环境&#xff0c;本机使用VNC连接 文章目录 前言docker创建基于Ubuntu:20.04的容器使用ssh连接容器容器安装桌面环境本机电脑使用VNC连接测试用python来创建的ui能否显示坑参考 前言 为什么我想要用ubuntu的桌面环…

RFID课程要点总结_2 Identification

2. Identification 简单说RFID就是物体上贴tag&#xff0c;用reader上的antenna去读取&#xff0c;这三个是主要组成。 Reader’s function Energy supply: 比如有的标签自身不带能量需要reader提供信号中蕴含的能量 Communication: 最基本的功能&#xff0c;和tag识别&…