C#中的事件、代理与任务:深入剖析发布者 - 订阅者模式中的关键元素

news2026/4/1 18:02:07

文章目录

    • 一、引言
    • 二、事件、代理和任务的基本概念
    • 三、发布者与订阅者的角色与关系
    • 四、代理和事件在订阅和发布上的异同
      • 1. 相同点
      • 2. 不同点
    • 五、触发方法与订阅者处理方法
      • 1. 触发方法
      • 2. 订阅者处理方法
    • 六、任务的订阅和发布代码示例
    • 七、事件和代理综合代码示例
      • 事件部分
      • 代理部分
    • 七、总结

原文出处: https://blog.csdn.net/haigear/article/details/142684587

一、引言

在现代编程范式中,事件(Event)和代理(Delegate)是构建松耦合、可扩展系统的重要机制,特别是在实现发布者 - 订阅者模式时发挥着关键作用。此外,任务(Task)概念的引入为异步操作提供了便捷的处理方式。本文将深入探讨事件、代理和任务的内涵,着重解析发布者和订阅者的关系,并详细阐述代理和事件在订阅和发布操作上的异同之处,同时通过代码示例进行直观展示。

二、事件、代理和任务的基本概念

  1. 事件(Event)
    • 事件是一种对象间通信的机制,它与特定的对象状态变化或操作相关联。在面向对象编程中,事件是类的成员,当特定条件满足时被触发,用于通知其他对象发生了某些感兴趣的事情。
    • 事件的定义基于委托类型,委托类型规定了事件处理方法的签名,包括返回值类型和参数列表。事件处理方法是在事件被触发时执行相应操作的方法。
  2. 代理(Delegate)
    • 代理本质上是一种类型安全的函数指针,它定义了方法的签名。通过代理,可以将方法作为参数传递给其他方法,实现方法的间接调用,这种特性在实现回调机制时非常有用。
    • 在发布者 - 订阅者模式中,代理定义了订阅者需要实现的方法签名,以便能够订阅发布者发出的通知。
  3. 任务(Task)
    • 任务用于表示异步操作,它允许程序在不阻塞主线程的情况下执行耗时操作,如文件 I/O、网络请求等。在事件处理场景中,任务可以与事件或代理结合,使事件处理方法以异步方式执行。

三、发布者与订阅者的角色与关系

  1. 发布者(Publisher)
    • 发布者是事件的源头,负责检测特定条件并触发事件。它包含事件成员变量(对于事件)或代理变量(对于代理),这些变量用于存储订阅者注册的事件处理方法引用。
    • 发布者提供触发事件或调用代理的方法。例如,在特定的业务逻辑执行完毕或者状态发生改变时,发布者会执行这些方法,从而通知订阅者。
    • 此外,发布者还可能提供管理订阅者的功能,尽管在某些语言特性下这种管理可能是隐式的。
  2. 订阅者(Subscriber)
    • 订阅者是对发布者发出的事件感兴趣的对象。它实现了与代理定义的签名相匹配的方法,这些方法包含了在接收到事件通知时要执行的逻辑。
    • 订阅者通过向发布者注册事件处理方法来表达对事件的关注。注册过程建立了发布者与订阅者之间的联系,使得在事件触发时订阅者能够接收到通知。

四、代理和事件在订阅和发布上的异同

1. 相同点

- **模式实现**:两者均为实现发布者 - 订阅者模式的重要手段,通过这种模式,发布者和订阅者之间可以实现低耦合的通信。发布者不需要知道订阅者的具体类型,只需要按照定义好的委托签名(对于代理)或事件处理签名(对于事件)来调用订阅者的方法。
- **签名定义**:都依赖于方法签名的定义。代理明确地定义了一个方法签名,而事件基于委托类型,这个委托类型也定义了事件处理方法的签名。这使得订阅者能够按照统一的标准提供事件处理方法。
- **通知机制**:在事件触发(对于事件)或代理调用(对于代理)时,都会通知已注册的订阅者,并且执行相应的订阅者处理方法。
- 示例代码:
// 定义委托类型,与事件处理方法签名相同
public delegate void MyEventHandler(object sender, EventArgs e);

// 发布者类(用于展示事件和代理的相同点)
class PublisherForSimilarity
{
    // 对于事件,可以定义事件成员变量
    public event MyEventHandler MyEvent;

    // 对于代理,可以定义代理变量
    public MyEventHandler MyDelegate;

    public void TriggerOrCall()
    {
        if (MyEvent!= null)
        {
            MyEvent(this, EventArgs.Empty);
        }
        if (MyDelegate!= null)
        {
            MyDelegate(this, EventArgs.Empty);
        }
    }
}

// 订阅者类(用于展示事件和代理的相同点)
class SubscriberForSimilarity
{
    public void HandleEventOrDelegate(object sender, EventArgs e)
    {
        Console.WriteLine("Handled by subscriber for similarity.");
    }
}

2. 不同点

  • 触发与调用的限制
    • 事件:事件是一种特殊的成员,只能在定义它的类内部触发。这意味着事件的触发逻辑被限制在特定的类范围内,增强了封装性。例如,在 C#中,事件只能由包含该事件的类中的方法触发,外部类无法直接触发事件。
    • 代理:代理可以在任何具有代理实例引用的地方被调用,没有类范围的限制。这使得代理在使用上更加灵活,但也需要更多的谨慎来确保正确的调用逻辑。
    • 示例代码:
// 发布者类(用于展示事件和代理触发限制的不同)
class PublisherForLimit
{
    public event MyEventHandler MyEvent;

    public void TriggerEvent()
    {
        MyEvent?.Invoke(this, EventArgs.Empty);
    }

    public MyEventHandler MyDelegate;

    public void CallDelegate()
    {
        MyDelegate?.Invoke(this, EventArgs.Empty);
    }
}
  • 订阅者管理
    • 事件:事件通常由语言特性自动维护订阅者列表。在 C#中,使用 +=-= 运算符来添加和移除订阅者时,背后的订阅者列表管理是自动进行的。例如,当使用 publisher.MyEvent += subscriber.HandleEvent; 时,系统会自动将订阅者的方法添加到事件的订阅者列表中,无需手动干预。
    • 代理:代理本身并不自动维护订阅者列表。如果要实现类似事件的多订阅者功能,开发者需要手动创建和维护一个订阅者列表(通常是一个数据结构,如列表或数组)来存储订阅者的方法引用,并在调用代理时遍历这个列表来依次调用每个订阅者的方法。这增加了开发的复杂性,但也提供了更多的定制性。
      • 示例代码:
// 使用事件实现发布者 - 订阅者模式的发布者类
class PublisherWithEvent
{
    public event MyEventHandler MyEvent;

    public void TriggerEvent()
    {
        MyEvent?.Invoke(this, EventArgs.Empty);
    }
}

// 使用代理实现发布者 - 订阅者模式的发布者类,并手动维护订阅者列表
class PublisherWithDelegate
{
    private MyEventHandler myDelegate;
    private List<MyEventHandler> subscriberList = new List<MyEventHandler>();

    public void AddSubscriber(MyEventHandler subscriber)
    {
        subscriberList.Add(subscriber);
    }

    public void RemoveSubscriber(MyEventHandler subscriber)
    {
        subscriberList.Remove(subscriber);
    }

    public void InvokeDelegate()
    {
        foreach (var subscriber in subscriberList)
        {
            subscriber?.Invoke(this, EventArgs.Empty);
        }
    }
}
  • 语法与操作符
    • 事件:在 C#等语言中,事件有特定的语法和操作符用于订阅和取消订阅操作。除了前面提到的 +=-= 操作符外,事件在定义时也有特定的语法格式,如 public event EventHandler MyEvent;。这种语法明确地标识了这是一个事件成员,并且对事件的操作受到语言规则的约束。
    • 代理:代理的操作更像是普通的方法调用和赋值操作。订阅者将自己的方法赋值给代理变量(例如 publisher.MyDelegate = subscriber.HandleEvent;),虽然也可以实现类似事件的多订阅者功能(通过手动维护列表并在调用时遍历列表中的代理方法),但语法上没有像事件那样专门针对订阅和取消订阅的操作符。
      • 示例代码:
// 使用事件进行订阅和触发
class EventSubscriptionExample
{
    PublisherWithEvent publisher = new PublisherWithEvent();
    SubscriberWithEvent subscriber = new SubscriberWithEvent();

    public void EventSubscription()
    {
        publisher.MyEvent += subscriber.HandleEvent;
        publisher.TriggerEvent();
    }
}

// 使用代理进行赋值和调用(单个代理的情况,不涉及多订阅者管理)
class DelegateAssignmentExample
{
    PublisherWithDelegate publisher = new PublisherWithDelegate();
    SubscriberWithDelegate subscriber = new SubscriberWithDelegate();

    public void DelegateAssignment()
    {
        publisher.myDelegate = subscriber.HandleEvent;
        publisher.myDelegate?.Invoke(publisher, EventArgs.Empty);
    }
}

原文出处:https://blog.csdn.net/haigear/article/details/142684587

五、触发方法与订阅者处理方法

1. 触发方法

  • 对于发布者而言,触发方法是引发事件通知或调用代理的方法。在事件的情况下,触发方法内部会检查事件是否有订阅者(通过检查事件是否为 null),如果有,则调用所有订阅者的事件处理方法。在代理的情况下,触发方法(如果是自定义的)会遍历手动维护的订阅者列表(如果有),然后依次调用每个订阅者的方法。
    • 示例代码:
// 使用事件实现发布者 - 订阅者模式的发布者类
class PublisherWithEvent
{
    public event MyEventHandler MyEvent;

    public void TriggerEvent()
    {
        MyEvent?.Invoke(this, EventArgs.Empty);
    }
}

// 使用代理实现发布者 - 订阅者模式的发布者类,并手动维护订阅者列表
class PublisherWithDelegate
{
    private MyEventHandler myDelegate;
    private List<MyEventHandler> subscriberList = new List<MyEventHandler>();

    public void AddSubscriber(MyEventHandler subscriber)
    {
        subscriberList.Add(subscriber);
    }

    public void RemoveSubscriber(MyEventHandler subscriber)
    {
        subscriberList.Remove(subscriber);
    }

    public void InvokeDelegate()
    {
        foreach (var subscriber in subscriberList)
        {
            subscriber?.Invoke(this, EventArgs.Empty);
        }
    }
}

2. 订阅者处理方法

  • 订阅者处理方法是由订阅者定义的方法,其签名必须与代理定义的签名(对于代理)或事件基于的委托类型的签名(对于事件)相匹配。当事件被触发或代理被调用时,发布者会将相关参数传递给订阅者处理方法,订阅者根据这些参数执行特定的逻辑操作。
    • 示例代码:
// 订阅者类(用于事件和代理)
class SubscriberForBoth
{
    public void HandleEventOrDelegate(object sender, EventArgs e)
    {
        Console.WriteLine("Handled by subscriber.");
    }
}

六、任务的订阅和发布代码示例

以下是一个使用任务结合事件或代理的示例:


using System;
using System.Threading.Tasks;

// 定义委托类型,与事件处理方法签名相同
public delegate void MyEventHandler(object sender, EventArgs e);

// 使用事件结合任务的发布者类
class PublisherWithEventAndTask
{
    public event MyEventHandler MyEvent;

    public async Task TriggerEventWithTask()
    {
        await Task.Delay(1000);
        if (MyEvent!= null)
        {
            MyEvent(this, EventArgs.Empty);
        }
    }
}

// 使用代理结合任务的发布者类,并手动维护订阅者列表
class PublisherWithDelegateAndTask
{
    private MyEventHandler myDelegate;
    private List<MyEventHandler> subscriberList = new List<MyEventHandler>();

    public void AddSubscriber(MyEventHandler subscriber)
    {
        subscriberList.Add(subscriber);
    }

    public void RemoveSubscriber(MyEventHandler subscriber)
    {
        subscriberList.Remove(subscriber);
    }

    public async Task InvokeDelegateWithTask()
    {
        await Task.Delay(1000);
        foreach (var subscriber in subscriberList)
        {
            subscriber?.Invoke(this, EventArgs.Empty);
        }
    }
}

class SubscriberWithTask
{
    public void HandleEventOrDelegate(object sender, EventArgs e)
    {
        Console.WriteLine("Handled by subscriber with task.");
    }
}

你可以这样使用这些类:


class Program
{
    static void Main()
    {
        // 使用事件结合任务的示例
        PublisherWithEventAndTask eventPublisherWithTask = new PublisherWithEventAndTask();
        SubscriberWithTask eventSubscriberWithTask = new SubscriberWithTask();

        eventPublisherWithTask.MyEvent += eventSubscriberWithTask.HandleEventOrDelegate;

        eventPublisherWithTask.TriggerEventWithTask().Wait();

        // 使用代理结合任务的示例
        PublisherWithDelegateAndTask delegatePublisherWithTask = new PublisherWithDelegateAndTask();
        SubscriberWithTask delegateSubscriberWithTask = new SubscriberWithTask();

        delegatePublisherWithTask.AddSubscriber(delegateSubscriberWithTask.HandleEventOrDelegate);

        delegatePublisherWithTask.InvokeDelegateWithTask().Wait();
    }
}

七、事件和代理综合代码示例

以下是一个 C#代码示例,用于综合展示事件和代理在不同情况下的使用:

using System;
using System.Collections.Generic;

// 1. 定义委托类型,与事件处理方法签名相同
public delegate void MyEventHandler(object sender, EventArgs e);
public delegate double Cal(double x, double y);

// 使用事件实现发布者 - 订阅者模式
class PublisherWithEvent
{
    public event MyEventHandler MyEvent;

    public void TriggerEvent()
    {
        MyEvent?.Invoke(this, EventArgs.Empty);
    }
}

class SubscriberWithEvent
{
    public void HandleEvent(object sender, EventArgs e)
    {
        Console.WriteLine("Event handled by subscriber with event.");
    }
}

// 使用代理实现发布者 - 订阅者模式,并手动维护订阅者列表
class PublisherWithDelegate
{
    private MyEventHandler myDelegate;
    private List<MyEventHandler> subscriberList = new List<MyEventHandler>();

    public void AddSubscriber(MyEventHandler subscriber)
    {
        subscriberList.Add(subscriber);
    }

    public void RemoveSubscriber(MyEventHandler subscriber)
    {
        subscriberList.Remove(subscriber);
    }

    public void InvokeDelegate()
    {
        foreach (var subscriber in subscriberList)
        {
            subscriber?.Invoke(this, EventArgs.Empty);
        }
    }
}

class SubscriberWithDelegate
{
    public void HandleEvent(object sender, EventArgs e)
    {
        Console.WriteLine("Event handled by subscriber with delegate.");
    }
}

// 类似 Cal cal = new Cal(Add) 的代理实例化示例
class CalDelegateExample
{
    public double Calculate(Cal cal, double x, double y)
    {
        return cal(x, y);
    }
}

class Program
{
    static void Main()
    {
        // 使用事件的示例
        PublisherWithEvent publisherWithEvent = new PublisherWithEvent();
        SubscriberWithEvent subscriberWithEvent1 = new SubscriberWithEvent();
        SubscriberWithEvent subscriberWithEvent2 = new SubscriberWithEvent();

        publisherWithEvent.MyEvent += subscriberWithEvent1.HandleEvent;
        publisherWithEvent.MyEvent += subscriberWithEvent2.HandleEvent;

        publisherWithEvent.TriggerEvent();

        // 使用代理的示例
        PublisherWithDelegate publisherWithDelegate = new PublisherWithDelegate();
        SubscriberWithDelegate subscriberWithDelegate1 = new SubscriberWithDelegate();
        SubscriberWithDelegate subscriberWithDelegate2 = new SubscriberWithDelegate();

        publisherWithDelegate.AddSubscriber(subscriberWithDelegate1.HandleEvent);
        publisherWithDelegate.AddSubscriber(subscriberWithDelegate2.HandleEvent);

        publisherWithDelegate.InvokeDelegate();

        // 类似 Cal cal = new Cal(Add) 的代理实例化示例
        CalDelegateExample calExample = new CalDelegateExample();
        Cal cal = new Cal(Add);
        Console.WriteLine(calExample.Calculate(cal, 5, 3));

        cal = new Cal(Subtract);
        Console.WriteLine(calExample.Calculate(cal, 5, 3));
    }

    static double Add(double x, double y)
    {
        return x + y;
    }

    static double Subtract(double x, double y)
    {
        return x - y;
    }
}

在上述代码示例中:

事件部分

  • PublisherWithEvent 类中,定义了事件 MyEvent。通过 += 操作符,轻松地将两个订阅者(subscriberWithEvent1subscriberWithEvent2)的事件处理方法注册到事件中。当调用 TriggerEvent 方法时,事件系统自动调用所有注册的订阅者方法,无需手动维护订阅者列表。

代理部分

  • PublisherWithDelegate 类中,为了实现类似事件的多订阅者功能,手动创建了一个 List<MyEventHandler> 类型的订阅者列表 subscriberList。通过 AddSubscriberRemoveSubscriber 方法来管理订阅者。在 InvokeDelegate 方法中,需要手动遍历订阅者列表来调用每个订阅者的方法。这清晰地展示了代理与事件在订阅者管理方面的差异。
  • 类似 Cal cal = new Cal(Add) 的代理实例化示例展示了代理作为简单的函数指针替代的用法,这里只是将一个符合委托签名的方法实例化到委托变量中,没有涉及多订阅者管理等复杂逻辑。

原文出处:https://blog.csdn.net/haigear/article/details/142684587

七、总结

事件和代理在发布者 - 订阅者模式的实现中各有特点。事件提供了一种更加封装、自动管理订阅者的方式,适用于大多数常见的发布 - 订阅场景;而代理则给予开发者更多的控制权,虽然需要手动处理一些逻辑,但在某些特定需求下(如需要高度定制订阅者管理逻辑)能够发挥优势。理解它们在订阅和发布操作上的异同,有助于根据实际项目需求选择合适的机制来构建灵活、可维护的软件系统。同时,任务的引入为事件和代理的异步操作提供了更多可能性,进一步丰富了程序设计的手段。对于代理的不同用法,从简单的类似函数指针的实例化到手动维护订阅者列表以实现发布者 - 订阅者模式,开发者可以根据具体情况进行选择和运用。

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

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

相关文章

媒介坊:软文自助发布平台,开启营销新篇章

在数字化时代&#xff0c;企业营销的方式日新月异&#xff0c;软文作为一种高效、低成本的营销手段&#xff0c;越来越受到企业的青睐。然而&#xff0c;如何在众多媒体中精准投放软文&#xff0c;实现品牌的有效传播&#xff0c;成为了众多企业关注的焦点。媒介坊软文自助发布…

闯关训练三:Git 基础知识

任务1: 破冰活动&#xff1a;自我介绍 点击Fork目标项目&#xff0c;创建一个新的Fork 获取仓库链接 在连接好开发机的vscode终端中逐行执行以下代码&#xff1a; git clone https://github.com/KelvinIII/Tutorial.git # 修改为自己frok的仓库 cd Tutorial/ git branch -a g…

在腾讯云上使用docker

第一次使用腾讯云&#xff0c;记录一下过程 因为我平时需求量不大&#xff0c;所以选择的是按需购买 腾讯云服务器购买链接 按照提示一步步往下走&#xff0c;创建实例 如果你不记得密码&#xff0c;那么在下面这幅图中可以重置(选择在线重置&#xff0c;对实例没影响) 因为…

C++——模拟实现vector

1.查看vector的源代码 2.模拟实现迭代器 #pragma oncenamespace jxy {//模板尽量不要分离编译template <class T>class vector{public:typedef T* iterator;//typedef会受到访问限定符的限制typedef const T* const_iterator;//const迭代器是指向的对象不能修改&#xf…

UE5学习笔记23-给角色添加血量,添加收到伤害的功能

零、一些游戏框架知识 1.UE5中包含游戏模式类(Game Mode)、游戏状态类(Game State)、玩家状态类(Player State)、玩家控制器类(Player Controller)、所有的可以被控制的实体或角色类(Pawn)、窗口类(HUD/Widget) Game Mode&#xff1a;存在在服务器上&#xff0c;当我们在客户端…

扣绩效工资,违反劳动法吗?

将工资拆分成绩效工资和岗位工资是很多公司管理员工的一种常见方式。 其中绩效工资跟KPI强挂钩&#xff0c;如果当月没有达到公司绩效标准&#xff0c;那么公司就会扣绩效工资。 那扣绩效工资违反劳动法吗&#xff1f;HR应该如何进行绩效薪酬考核和发放&#xff1f; 扣绩效工…

【网络安全】Cookie与ID未强绑定导致账户接管

未经许可,不得转载。 文章目录 前言正文前言 DigiLocker 是一项在线服务,旨在为公民提供一个安全的数字平台,用于存储和访问重要的文档,如 Aadhaar 卡、PAN 卡和成绩单等。DigiLocker 通过多因素身份验证(MFA)来保护用户账户安全,通常包括 6 位数的安全 PIN 和一次性密…

大论文记录

基础知识回顾 1.强化学习&#xff08;Agent、Environment) 在 RL 中&#xff0c;代理通过不断与环境交互、以试错的方式进行学习&#xff0c;在不确定性下做出顺序决策&#xff0c;并在探索&#xff08;新领域&#xff09;和开发&#xff08;使用从经验中学到的知识&#xff…

五、Java 注释

一、Java 注释 在计算机语言中&#xff0c;注释是计算机语言的一个重要组成部分&#xff0c;用于在源代码中解释代码的作用&#xff0c;可以增强程序的可读性&#xff0c;可维护性。Java 注释是一种在 Java 程序中用于提供代码功能说明的文本。注释不会被编译器包含在最终的可…

数据清洗第3篇章 - 数据异常处理

数据清洗是数据分析过程中至关重要的一步&#xff0c;它确保数据的准确性、一致性和完整性。这不仅有助于提高分析结果的可靠性和有效性&#xff0c;还能为算法建模决策提供高质量的数据基础。在进行数据分析和建模的过程中&#xff0c;大量的时间花在数据准备上&#xff1a;加…

WebRTC Connection Negotiate解决

最近有个项目 &#xff0c;部署之后一直显示&#xff0c;查了一些资料还是没有解决&#xff0c;无奈只有自己研究解决&#xff1f; 什么是内网穿透&#xff1f; 我们访问我们自己的官网产品页面&#xff0c;我们的服务器是一个单独的个体&#xff0c;有独立的公网ip&#xf…

【Canvas与徽章】金圈蓝底国庆75周年徽章

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>金边黑盾75周年</title><style type"text/css"&g…

关于深度学习torch的环境配置问题

已经下好了torch在虚拟环境中&#xff0c;结果在ipynb文件中无法运行 后来在终端直接用python语句编译 发现没有问题 在编辑测试py文件 发现runcode有问题 原来是插件默认base环境 具体操作参考VS Code插件Code Runner使用python虚拟环境_coderunner怎么在虚拟环境中使用-CSD…

“2024光明多多垂直农业挑战赛”决赛启动成功举办

由光明食品集团所属上花集团的光明花博邨基地&#xff0c;与拼多多携手&#xff0c;联合中国农业大学、浙江大学等共同举办的“2024光明多多垂直农业挑战赛暨第四届多多农研科技大赛”于9月20-21日正式启动决赛。来自上海交大、中国农大、上海农科院、国家农业智能装备工程技术…

资源《Arduino 扩展板4-单游戏摇杆》说明。

资源链接&#xff1a; Arduino 扩展板4-单游戏摇杆 1.文件明细&#xff1a; 2.文件内容说明 包含&#xff1a;AD工程、原理图、PCB。 3.内容展示 4.简述 该文件为PCB工程&#xff0c;采用AD做的。 该文件打板后配合Arduino使用&#xff0c;属于Arduino的扩展板。 该文件…

STM32 GPIO - 笔记

输出: - 推挽 - 输出高低电平都由芯片决定 - 开漏 - 输出低电平有芯片输出,输出高电平由外部电路决定 输入: - 浮空 - 输入电平不确定 - 上拉 - 输入电平拉高 - 下拉 - 输入电平拉低

【网路通信基础与实践番外二】TCP协议的流量控制和拥塞控制以及二者区别和例题

TCP协议是端对端的协议&#xff0c;因此在数据进行传输的过程受发送方&#xff0c;数据通道&#xff0c;接收方三方状态的影响。我们用水龙头来比喻数据发送方&#xff0c;水管来比喻数据通道&#xff0c;水桶来表示数据接收方。 图(a)表示水桶太小&#xff0c;来不及接受注入…

SpringBoot使用@Slf4j注解实现日志输出

Slf4j 是 Lombok 库中的一个注解&#xff0c;它极大地简化了日志记录的代码。通过使用这个注解&#xff0c;Lombok 会自动在你的类中注入一个静态的日志对象。通过在类上添加 Slf4j 注解后&#xff0c;可以直接在方法中使用 log.info() 等方法进行日志打印。 1、安装相关依赖 …

基于RBAC的通用权限管理系统的详细分析与实现(理念篇——权限对象、权限项、功能权限、数据权限、权限组、权限设计)

一、权限&#xff08;Permission&#xff09; 在与人沟通的过程中&#xff0c;我们很多次提到了权限&#xff0c;但是权限具体的含义每个人理解的含义都不明确&#xff0c;这样很容易造成双方信息不对称&#xff0c;有的人就只是把权限理解成某个页面的是否可访问&#xff0c;…

超级干货:Air780EP AT开发之FTP应用

是时候讲一讲Air780EP模组FTP应用的多个AT命令示例&#xff0c;因为很多小伙伴已经提出要求了。 Air780EP是低功耗4G模组之一&#xff0c;支持全系列的AT指令以及LuatOS脚本二次开发。 一、准备工作 1.1 硬件准备 合宙EVB_Air780EP开发板一套&#xff0c;包括天线、SIM卡&am…