手写 EventBus:从零到一实现自己的事件总线库

news2024/10/6 22:23:31

简介:在本文中,我们将详细介绍如何从头开始实现一个轻量级的 EventBus 库。我们将以 XEventBus 为例,阐述整个实现过程,以及在实现过程中遇到的关键问题和解决方法。

一 引言

什么是 EventBus?

EventBus 是一个基于发布/订阅模式的事件总线库,用于在组件之间进行解耦的异步通信。它允许组件(如 Activity、Fragment、Service 等)之间相互发送和接收事件,而无需显式地引用和调用彼此的实例。这样,组件之间的耦合性降低,代码更易于维护和扩展。

XBus 简介及设计目标

为了学习和掌握 EventBus 的实现原理和关键技术,我们将通过本文带领大家从零开始实现一个自己的轻量级的事件总线库 XBus。

XBus 是一个轻量级的事件总线库,旨在提供简单、高效、易用的事件通信机制。我们的设计目标如下:

  1. 易用性:XBus 的 API 设计简洁明了,易于集成和使用。
  2. 高性能:提供过反射和注解处理器(APT)生成订阅者方法索引,后者可提高事件查找和调用的性能。
  3. 可扩展性:XEventBus 具有良好的可扩展性,可以根据需求添加更多功能,如优先级控制、延时处理等。

二 XBus 设计与实现

在这一节中,我们将结合代码详细介绍 XEventBus 的设计与实现。

XEventBus 的实现思路包括以下几个方面:

  1. 使用 XEventBus 类作为事件总线的核心,管理订阅者与订阅方法之间的关系。
  2. 通过 SubscribedMethod 类封装订阅者方法的信息,包括所在类、参数类型、线程模式、优先级和方法名。
  3. 使用 Subscription 类表示订阅关系,包含订阅者对象和订阅方法,方便在事件发布时找到对应的订阅者方法并执行。
  4. 利用subscriptionsByEventType和typesBySubscriber两个集合分别存储EventType类与所有注册方法、Subscriber和其注册的所有event的,并基于这两个集合进行事件订阅的查询和订阅者的注册反注册。
  5. 利用注解处理器(APT)在编译期间生成订阅者方法的查找和调用逻辑,避免运行时使用反射,提高性能。

通过这种设计,XEventBus 实现了一个简单、高效、易用的事件总线,可以方便地在不同组件之间进行事件通信。

订阅事件和订阅者:

subscriptionsByEventType image-20230505171338467

subsciption的结构

image-20230505165450269

XEventBus

XEventBus 类是该库的核心类,它维护了一个 Map<Class<?>, List<Subscription>> 结构,用于存储订阅者和它们对应的订阅方法列表。通过 register 和 unregister 方法,实现订阅和取消订阅的功能。

public class XEventBus {
    private static final Map<Class<?>, List<Subscription>> subscriptionsByEventType = new ConcurrentHashMap<>();

    public void register(Object subscriber) {
        //...
    }

    public void unregister(Object subscriber) {
        //...
    }

    public void post(Object event) {
        //...
    }
}

SubscribedMethod

SubscribedMethod 类封装了订阅者方法的相关信息,包括所在类、参数类型、线程模式、优先级和方法名。在事件发布时,EventBus 将根据这些信息找到并调用订阅者方法。

public class SubscribedMethod {
    private final Class<?> subscriberClass;
    private final Class<?> eventType;
    private final ThreadMode threadMode;
    private final int priority;
    private final String methodName;

    //... constructor and getters
}

Subscription

Subscription 类表示一个订阅关系,包括订阅者对象(Subscriber)和订阅方法(SubscribedMethod)。当事件发布时,EventBus 会遍历所有的 Subscription,根据事件类型找到匹配的订阅者方法并执行。

public class Subscription {
    private final Object subscriber;
    private final SubscribedMethod subscribedMethod;

    //... constructor and getters
}

Subscriber

Subscriber 类表示订阅者对象,包含了订阅者实例和其订阅的事件方法。Subscriber 类主要用于在 EventBus 内部管理订阅关系。

public class Subscriber {
    private final Object subscriberInstance;
    private final List<SubscribedMethod> subscribedMethods;

    //... constructor and getters
}

MethodHandle

MethodHandle 接口用于处理订阅者方法的查找和调用。通过注解处理器(APT)生成具体的实现类,实现在编译期间就能获取订阅者方法信息,提高运行时性能。

public interface MethodHandle {
    List<SubscribedMethod> getAllSubscribedMethods(Object subscriber);

    void invokeMethod(Subscription subscription, Object event);
}

在 XEventBus 的实现中,注解处理器 MyEventBusAnnotationProcessor 负责在编译期间查找所有使用 @Subscribe 注解的方法,并生成 AptMethodFinder 类,实现 MethodHandle 接口。这样,在运行时,XEventBus 无需使用反射,可以直接调用订阅者方法,提高性能。

// MyEventBusAnnotationProcessor
public class MyEventBusAnnotationProcessor extends AbstractProcessor {
    //... processing logic
}

// Generated AptMethodFinder
public class AptMethodFinder implements MethodHandle {
    //... implementation
}

通过上述代码实现,XEventBus 可以在不同组件之间进行高效的事件通信。在编译期间,MyEventBusAnnotationProcessor 注解处理器会自动生成 AptMethodFinder 类,实现了 MethodHandle 接口。这使得 XEventBus 在运行时能够直接调用订阅者方法,而不需要使用反射,从而提高了性能。

三 实现订阅者方法查找与注册

为了实现 XEventBus 中订阅者方法的查找与注册,我们需要使用注解处理器(APT)在编译期间生成相应的代码。这将避免在运行时使用反射,从而提高性能。

使用注解处理器(APT)实现订阅者方法查找

我们在 MyEventBusAnnotationProcessor 注解处理器中遍历所有使用 @Subscribe 注解的方法,将它们按照所属类进行分类。接着,为每个类生成一个查找订阅者方法的静态方法,该方法返回一个包含所有订阅者方法信息的 SubscribedMethod 列表。

for (Element element : elements) {
    // 省略代码...

    CreateMethod createMethod = mCachedCreateMethod.get(qualifiedName);
    if (createMethod == null) {
        createMethod = new CreateMethod(typeElement);
        mCachedCreateMethod.put(qualifiedName, createMethod);
    }

    // 省略代码...
}

注册订阅者方法到 EventBus 中

在 EventBus 类中,我们提供了 register() 方法,用于将订阅者方法注册到事件总线中。注册过程中,EventBus 会调用 AptMethodFinder 类中的 getAllSubscribedMethods() 方法,获取订阅者类中所有订阅者方法的信息,并将它们封装成 Subscription 对象,存储在事件总线的内部数据结构中。

public void register(Object subscriber) {
    List<SubscribedMethod> subscribedMethods = methodHandle.getAllSubscribedMethods(subscriber);
    // 省略代码...
}

生成订阅者方法的索引和对应的方法调用

在 MyEventBusAnnotationProcessor 注解处理器中,我们为每个订阅者类生成一个查找订阅者方法的静态方法,该方法返回一个包含所有订阅者方法信息的 SubscribedMethod 列表。同时,我们还需要为 AptMethodFinder 类生成一个 invokeMethod() 方法,用于执行具体的订阅者方法。

这样,在 EventBus 发布事件时,可以直接通过 AptMethodFinder 的 invokeMethod() 方法调用订阅者方法,而无需使用反射。

通过以上步骤,我们实现了订阅者方法的查找与注册,从而使 XEventBus 能够在不同组件之间高效地传递事件。

四 实现事件发布与订阅者方法调用

在 XEventBus 中,我们实现了事件的发布与订阅者方法的调用。接下来,我们将讨论这些关键功能的实现细节。

事件发布

在 EventBus 类中,我们提供了 post() 方法,用于发布事件。当调用此方法时,EventBus 将遍历内部数据结构中的所有订阅者,并根据事件类型找到对应的订阅者方法。

public void post(Object event) {
    List<Subscription> subscriptions = mEventTypeSubscriptions.get(event.getClass());
    if (subscriptions != null) {
        // 省略代码...
    }
}

通过 MethodHandle 接口查找订阅者方法

为了避免使用反射调用订阅者方法,我们使用 MethodHandle 接口,它允许我们在编译期生成的代码中直接调用订阅者方法。在 EventBus 类中,我们将 MethodHandle 作为一个成员变量,并在构造函数中初始化。

public EventBus() {
    this.methodHandle = new AptMethodFinder();
}

调用订阅者方法

当我们找到订阅者方法时,我们使用 MethodHandle 接口的 invokeMethod() 方法来调用它。这样,我们可以避免使用反射,从而提高性能。

private void invokeSubscriber(Subscription subscription, Object event) {
    try {
        methodHandle.invokeMethod(subscription.subscriber, subscription.subscribedMethod, event);
    } catch (Exception e) {
        // 省略代码...
    }
}

支持不同线程模型的订阅者方法调用

XEventBus 支持多种线程模型,如主线程、后台线程等。我们可以在 @Subscribe 注解中指定线程模型。为了实现这一功能,我们在 Subscription 类中保存订阅者方法的线程模型,并在调用订阅者方法时根据线程模型执行相应的操作。

private void invokeSubscriber(Subscription subscription, Object event) {
    ThreadMode threadMode = subscription.subscribedMethod.threadMode;
    switch (threadMode) {
        case MAIN:
            // 省略代码...
            break;
        case BACKGROUND:
            // 省略代码...
            break;
        // 更多线程模型...
    }
}

通过以上实现,我们使 XEventBus 能够根据订阅者方法的线程模型在不同线程中调用订阅者方法,从而实现了灵活的事件发布与订阅。

五 实现订阅者方法的反注册

为了避免内存泄漏和不必要的事件接收,我们需要提供反注册功能,以便在不再需要接收事件的时候移除订阅者方法。以下是反注册功能的实现细节。

移除订阅者方法

我们在 EventBus 类中提供了一个名为 unregister() 的方法,用于移除订阅者方法。在调用此方法时,我们将遍历订阅者方法并从内部数据结构中删除它们。

public void unregister(Object subscriber) {
    List<Class<?>> subscribedEventTypes = mSubscriberEventTypes.get(subscriber);
    if (subscribedEventTypes != null) {
        for (Class<?> eventType : subscribedEventTypes) {
            removeSubscriber(subscriber, eventType);
        }
    }
}

private void removeSubscriber(Object subscriber, Class<?> eventType) {
    List<Subscription> subscriptions = mEventTypeSubscriptions.get(eventType);
    if (subscriptions != null) {
        // 省略代码...
    }
}

清理资源

在反注册订阅者方法时,我们需要确保清理相关的资源。首先,我们从 mSubscriberEventTypes 中删除订阅者。然后,我们检查事件类型是否还有其他订阅者,如果没有,则从 mEventTypeSubscriptions 中删除该事件类型。

private void removeSubscriber(Object subscriber, Class<?> eventType) {
    List<Subscription> subscriptions = mEventTypeSubscriptions.get(eventType);
    if (subscriptions != null) {
        Iterator<Subscription> iterator = subscriptions.iterator();
        while (iterator.hasNext()) {
            Subscription subscription = iterator.next();
            if (subscription.subscriber == subscriber) {
                iterator.remove();
            }
        }
        
        // 清理资源
        if (subscriptions.isEmpty()) {
            mEventTypeSubscriptions.remove(eventType);
        }
    }
    mSubscriberEventTypes.remove(subscriber);
}

通过实现反注册功能,我们使得 XEventBus 可以灵活地处理订阅者的生命周期,避免了潜在的内存泄漏问题。同时,这也有助于提高事件分发的性能,因为我们不再需要为不再关心的事件处理订阅者方法。

六 XEventBus 的优化与拓展

为了提高 XEventBus 的性能和灵活性,我们可以考虑以下几个方面:

增加缓存策略,提高性能

在 XEventBus 中,我们可以使用缓存策略来减少重复的订阅者方法查找和事件发布。例如,我们可以使用一个 HashMap 来存储已经注册过的订阅者方法,以便在需要时快速查找。同时,在事件发布时,我们可以对订阅者方法的调用结果进行缓存,避免重复计算。

private static final Map<Class<?>, List<SubscribedMethod>> METHOD_CACHE = new HashMap<>();

支持优先级和延时处理的订阅者方法

在某些场景下,我们可能需要对订阅者方法的执行顺序进行控制。为此,我们可以为订阅者方法添加优先级属性。通过在 @Subscribe 注解中添加 priority 属性,我们可以实现订阅者方法的优先级控制。

@Subscribe(priority = 1)
public void onEvent(Event event) {
    // ...
}

此外,我们还可以通过添加 delay 属性来实现延时处理。例如,当 delay 设为 1000 时,订阅者方法将在事件发布后的 1000 毫秒后执行。

@Subscribe(delay = 1000)
public void onEvent(Event event) {
    // ...
}

扩展更多功能,如事件粘性等

除了以上提到的优化,我们还可以为 XEventBus 添加更多功能,如支持粘性事件。粘性事件是指在订阅者注册后,仍然可以接收到在注册之前发布的事件。我们可以通过在 @Subscribe 注解中添加一个 sticky 属性来实现这个功能。

@Subscribe(sticky = true)
public void onStickyEvent(Event event) {
    // ...
}

在实现粘性事件时,我们需要在 XEventBus 中维护一个粘性事件的集合。当订阅者注册时,如果其订阅方法设置了 sticky 属性,那么将会收到集合中保存的对应类型的粘性事件。

这样,通过对 XEventBus 的优化与拓展,我们可以实现一个功能更加丰富、性能更优的事件总线。

七 XBus 实战演示

XBus的开源地址。关于XBus的具体使用可以参考。简述如下。

快速使用:

根build.gradle中添加仓库来源地址

allprojects {
    repositories {
        ...
        maven {
            url 'https://lucasxu01.github.io/maven-repository/'
        }
        
    }
}

app项目级别build.gradle中添加依赖

    implementation 'com.lucas:xbus:1.0.0'
    implementation 'com.lucas:xbus-annotations:1.0.0'
    annotationProcessor 'com.lucas:xbus-apt-processor:1.0.0'

Antivity的onCreate方法中注册bus:

XEventBus.getDefault().register(MainActivity.this);

定义一个自己的Event事件:

public class WorkEvent {
    private int num;

    public WorkEvent(int num) {
        this.num = num;
    }

    public int getNum() {
        return num;
    }
}

对应的Activity中注册方法

    @Subscribe(priority = 1)
    public void onEvent(final WorkEvent event) {
         Log.e(TAG, "onEvent: " + " Thread, WorkEvent num=" + event.getNum());
    }

发送事件进行调用

XEventBus.getDefault().post(new WorkEvent(5))

其他功能

若想使用apt方式代替注解,可在bus注册时这样注册:

AptMethodFinder aptMethodFinder = new AptMethodFinder();
XEventBus.builder().setMethodHandle(aptMethodFinder).build().register(this);

八 Android中消息总线的其他实现方式

简单介绍一下其他技术方案实现的技术总线。

基于RxJava

RxBus是基于RxJava实现的,需要额外导入RxJava RxAndroid等库,因此库体积还是较大的。使用RxBus你得了解rxjava的原理,对于不使用rxjava的项目来说,成本太高了,而且容易内存泄露。想了解具体实现细节的可参考《使用RxJava实现的EventBus》。

使用起来大概是如下:

RxBus.getInstance().toObservable(MsgEvent.class).subscribe(new Observer<MsgEvent>() {
            @Override
            public void onSubscribe(Disposable d) {
                
            }

            @Override
            public void onNext(MsgEvent msgEvent) {
                //处理事件
            }

            @Override
            public void onError(Throwable e) {
                  
            }

            @Override
            public void onComplete() {

            }
        });

        
RxBus.getInstance().post(new MsgEvent("Java"));

基于ASM

ASM(Abstract Syntax Machine)是一个Java字节码操作和分析框架。ASM用于动态生成、转换或者操作Java字节码。在Android中,ASM通常用于性能优化、代码注入、AOP(面向切面编程)等场景。然而,它并不是一个事件总线的典型实现方式。基于ASM实现的时间总线有:BusUtils;

如果你确实想使用ASM实现事件总线,可以尝试以下方法:

  1. 使用ASM扫描已编译的Java字节码,识别出包含特定注解(例如@Subscribe)的类和方法。
  2. 对于扫描到的类和方法,使用ASM在运行时动态修改字节码,注入事件总线的逻辑代码,例如注册、注销和发送事件。
  3. 在事件发送方,通过反射或者其他方式调用相应的方法来触发事件。

这种实现方式在实践中可能非常复杂,容易出现问题,而且可能导致性能和兼容性问题。因此,我们建议在实现事件总线时,优先考虑使用更为成熟、简便的方案,如EventBus、LiveData和ViewModel或RxJava,这些方法在通信和解耦方面表现出色,更为简便和高效,易于使用。

基于LiveData

LiveData是Android Architecture Components库的一部分,它是一个可观察的数据持有类,能够在数据发生变化时通知订阅者。使用LiveData实现事件总线可以确保通信在主线程上执行,而且与应用程序的生命周期紧密结合,从而避免内存泄漏。

以下是实现基于LiveData的Bus的简要步骤:

  1. 创建一个单例的Bus类,用于存放多个LiveData对象,每个LiveData对象负责一种类型的事件。
  2. 在Bus类中,为每种类型的事件提供注册和发送方法。注册方法用于订阅事件,发送方法用于发布事件。
  3. 在需要接收事件的组件(如Activity、Fragment等)中,调用Bus类的注册方法来订阅事件。订阅时,需要传入一个Observer对象,用于处理收到的事件。
  4. 当事件发生时,调用Bus类的发送方法发布事件。订阅者会收到通知,并通过Observer对象处理事件。
  5. 在组件的生命周期结束时,LiveData会自动取消订阅,无需手动注销订阅者。

基于LiveData的事件总线具有以下优点:

  • 生命周期感知:LiveData与组件生命周期紧密结合,可以在组件销毁时自动取消订阅,避免内存泄漏。
  • 线程安全:LiveData确保事件通知在主线程上执行,避免了多线程同步的问题。
  • 简单易用:LiveData的API简单易用,与Android架构组件库兼容良好。

基于Flow

Flow是Kotlin协程库的一部分,它提供了一种声明式、响应式的编程模型,能够更方便地处理异步事件流。使用Flow实现事件总线可以确保通信在主线程上执行,同时提供了更丰富的操作符和组合方式,能够处理更复杂的场景。

以下是实现基于Flow的Bus的简要步骤:

  1. 创建一个单例的Bus类,用于存放多个Flow对象,每个Flow对象负责一种类型的事件。
  2. 在Bus类中,为每种类型的事件提供注册和发送方法。注册方法用于订阅事件,发送方法用于发布事件。
  3. 在需要接收事件的组件(如Activity、Fragment等)中,调用Bus类的注册方法来订阅事件。订阅时,需要传入一个lambda表达式,用于处理收到的事件。
  4. 当事件发生时,调用Bus类的发送方法发布事件。订阅者会收到通知,并通过lambda表达式处理事件。
  5. 在组件的生命周期结束时,可以通过协程取消订阅,避免内存泄漏。

基于Flow的事件总线具有以下优点:

  • 声明式编程模型:Flow提供了一种声明式、响应式的编程模型,能够更方便地处理异步事件流,从而提供更丰富的操作符和组合方式,能够处理更复杂的场景。
  • 线程安全:Flow确保事件通知在主线程上执行,避免了多线程同步的问题。
  • 协程支持:Flow与Kotlin协程库紧密结合,可以方便地在协程中使用。

九 总结

本文实现过程的总结

本文从设计到实现,详细介绍了一个简单的 EventBus 系统 - XEventBus。我们首先讨论了 EventBus 的基本概念、应用场景和优势,并阐述了 XEventBus 的设计目标。接着,我们通过分析 XEventBus 的核心组件和实现细节,介绍了订阅者方法查找、注册、事件发布和订阅者方法调用等核心功能。最后,我们还探讨了 XEventBus 的优化与拓展,包括缓存策略、优先级控制、延时处理和粘性事件等。

EventBus 的适用场景与局限性

EventBus 主要适用于组件之间的松耦合通信,特别是在 Android 应用开发中,它可以简化 Activity、Fragment、Service 之间的消息传递。然而,EventBus 也有一定的局限性,例如:

  • 对于大型项目,过多的事件订阅可能导致代码难以维护。
  • EventBus 无法保证事件的传递顺序,有时可能需要手动处理事件的执行顺序。
  • EventBus 在跨进程通信时存在局限性,需要额外的技术支持。

对 EventBus 未来发展的展望

尽管 EventBus 具有一定的局限性,但在适当的场景下,它仍然是一个非常有用的工具。随着技术的发展,我们可以期待 EventBus 的功能将不断完善,例如:

  • 提供更强大的事件过滤和路由机制,以便更精确地控制事件的传递。
  • 支持跨进程通信,使其适用于更广泛的场景。
  • 增强事件调试和追踪能力,帮助开发者更容易地定位问题。

总之,本文通过实现 XEventBus,希望能为读者提供一个 EventBus 的入门示例,以便更好地理解和应用 EventBus 这一有用的工具。

参考文章

https://www.jianshu.com/p/a5e89082d1b9

https://blog.csdn.net/u011213403/article/details/121267330

https://juejin.cn/post/6844903896700157966#heading-15

https://www.jianshu.com/p/2a8f9ac32e13

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

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

相关文章

C++ 多线程编程(三) 获取线程的返回值——future

C11标准库增加了获取线程返回值的方法&#xff0c;头文件为<future>&#xff0c;主要包括future、promise、packaged_task、async四个类。 那么&#xff0c;了解一下各个类的构成以及功能。 1 future future是一个模板类&#xff0c;它是传输线程返回值&#xff08;也…

2-Lampiao百个靶机渗透(精写-思路为主)框架漏洞利用2

特别注明&#xff1a;本文章只用于学习交流&#xff0c;不可用来从事违法犯罪活动&#xff0c;如使用者用来从事违法犯罪行为&#xff0c;一切与作者无关。 文章目录 前言一、环境重新部署二、AWVSxray联动和xraybs联动1.安装AWVSxray2.让xray和bs先联动3.AWVS和xray联动 三、p…

【Spring框架全系列】如何创建一个SpringBoot项目

&#x1f307;哈喽&#xff0c;大家好&#xff0c;我是小浪。前几篇博客我们已经介绍了什么是Spring&#xff0c;以及如何创建一个Spring项目&#xff0c;OK&#xff0c;那么单单掌握Spring是完全不够的&#xff0c;Spring的家族体系十分强大&#xff0c;我们还需要深入学习&am…

力扣---LeetCode160. 相交链表(代码详解+流程图)

文章目录 前言160. 相交链表链接&#xff1a;思路&#xff1a;方法一&#xff1a;暴力求解法1.1 时间复杂度&#xff1a;O(M*N)1.2 代码&#xff1a; 方法二&#xff1a;双指针2.1 时间复杂度&#xff1a;O(N)2.2 代码&#xff1a;2. 3流程图&#xff1a; 注意&#xff1a;补充…

13. Transformer(下)

P33 Transformer&#xff08;下&#xff09; 视频链接 P33 Transformer&#xff08;下&#xff09; 1. Decoder: Autoregressive(AT) Decoder原理&#xff1a; Encoder vs Decoder&#xff1a; Masked&#xff1a; how to stop&#xff1a; 2. Decoder: Non-autoregressive(…

网络基础——网络的发展史

作者简介&#xff1a;一名计算机萌新、前来进行学习VUE,让我们一起进步吧。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;我叫于豆豆吖的主页 目录 前言 一.网络发展史 1. ARPANET 2.TCP/IP协议 3. 互联网 4.Web浏览器 5.搜索引擎 6. 社交网…

如果你访问了某个网站,又不想让人知道怎么办?

问大家一个问题&#xff1a;如果你访问了某个网站&#xff0c;又不想让人知道怎么办&#xff1f; 你可能会说&#xff0c;把浏览器浏览历史记录清除&#xff0c;或者直接用无痕模式。 如果你只能想到这一层&#xff0c;那只能说图young&#xff01; 这么说吧&#xff0c;理论…

操作系统原理 —— 调度的概念、层次(十一)

调度的基本概念 在操作系统中的调度&#xff0c;是指操作系统从就序队列中选择一个作业&#xff0c;或者进程进行执行。 举个例子&#xff1a; 比如我们去银行窗口排队&#xff0c;排队的人就相当于就绪列表&#xff0c;窗口就相当于是操作系统&#xff0c;窗口需要服务排队…

npm的使用和命令

3.0 npm 什么是npm 是node管理包的工具 3.1 初始化包管理描述文件 package.json npm init // 会询问你每次的选项 或 npm init -y // 不询问你选项&#xff0c;默认就是确定 首先建立一个文件在路径里面全选写cmd 然后打开环境 在里面写npm init -y回车 就会在你原来空的文…

编写用户帮助/操作手册指南

背景&#xff1a; 用户操作手册是一份指导用户使用产品或服务的重要手册。 一个新系统&#xff0c;需要写用户操作手册&#xff0c;该从何下笔&#xff1f;本篇是一篇教你编写用户帮助/操作手册的指南&#xff5e; 首先&#xff0c;先来看一个反例 &#xff1a; 这个是我入职…

移动通信(17)预编码

源于某篇学位论文 利用预编码技术可以有效抑制大规模天线传输中的干扰&#xff0c;提高链路的峰值速率。大规模天线技术在提升性能的同时也存在很大的干扰问题。多天线传输中带来的多径干扰不可忽视。通常在接收端抑制干扰算法通常实现起来较为复杂&#xff0c;若采用预编码技…

安装Node.js和cnpm

一、安装Node.js 1.下载 Node.js官网下载 根据自身系统下载对应的安装包&#xff08;我这里为Windows10 64位&#xff0c;故选择下载第一个安装包&#xff09; 2、然后点击安装&#xff0c;选择自己要安装的路径&#xff0c;此处我选择的是&#xff1a;D:\Program Files\node…

中级软件设计师备考---UML

目录 面向对象的基础概念面向对象的设计原则UML的各类图设计模式对比分类 面向对象的基础概念 【只介绍一些我个人不太熟悉的概念】 继承和泛化&#xff1a;泛化和继承可以理解为是一个逆过程&#xff1a;泛化就是有子类抽象出一个父类&#xff0c;而继承就是由父类具体化一个…

程序员崩溃的N个瞬间

说到程序员&#xff0c;在外界眼里&#xff0c;他们是掌控代码的大神&#xff0c;他们是改变世界的王者。其实程序员的工作不容易&#xff0c;不信&#xff0c;就来看看程序员崩溃的各种瞬间—— 01、公司实习生找bug 02、在调试时&#xff0c;将断点设置在错误的位置 03、当我…

eventMesh 本地搭建记录

官方文档: Apache EventMesh (Incubating) | Apache EventMesh (作为整体了解 可以先看看架构) 按照官方文档需要搭建服务 eventmesh-store 文档推荐的是 rocketmq docker pull apache/rocketmq:4.9.4 部署rmq 的过程 1.nameServer docker run -d -p 9876:9876 -v pwd/d…

NECCS|全国大学生英语竞赛C类|词汇和语法|语法题|时态 非谓语动词 |19:00~20:15|完形填空·词性转化

14:35&#xff5e;14:45 15:45&#xff5e;16:2019:00&#xff5e;20:15 http://t.csdn.cn/XbsUy 目录 &#xff08;一&#xff09;时态 7. 将来进行时 8. 过去将来进行时 9. 现在完成时 10. 过去完成时​编辑 11. 将来完成时 12. 现在完成时 13. 过去完成进行时 &#xff08;…

David Silver Lecture 4: Model-Free Prediction

1 Introduction 任务&#xff1a;第三章使用动态规划方法&#xff0c;解决known的MDP问题&#xff0c;这章通过model free prediction对一个unknown的MDP估计他的value function。下一章通过Model free control的方法针对一个unknown的MDP optimise value function。 2 Monte…

密码学【java】初探究加密方式之消息摘要

文章目录 一 消息摘要1.1 消息摘要的特点1.2 消息摘要常见算法1.3 数字摘要的运用举例&#xff1a;1.4 字符串数字摘要演示1.5 其他数字摘要算法演示1.6 获取文件消息摘要 一 消息摘要 消息摘要&#xff08;Message Digest&#xff09;又称为数字摘要(Digital Digest)它是一个…

Feign组件的使用及开发中使用方式

在微服务的服务集群中服务与服务之间需要调用暴露的服务.那么就需要在服务内部发送http请求&#xff0c; 我们可以使用较为老的HttpClient实现&#xff0c;也可以使用SpringCloud提供的RestTemplate类调用对应的方法来发送对应的请求。 说明&#xff1a; 现在有两个微服务一个是…

java反序列化cc3链分析

前言 休息完五一&#xff0c;继续卷&#xff0c;原本想结束cc链的&#xff0c;但是发现cc3链好像用的方法不太一样&#xff0c;感觉也是可以记录下来&#xff0c;这里感觉cc3链有一些像cc1链的变种&#xff0c;就像是给你一些绕过的操作 在说一句&#xff0c;白日梦组长永远的神…