文档章节

EventBus用法及源码解析

潇湘剑雨
 潇湘剑雨
发布于 2017/11/15 13:16
字数 4363
阅读 65
收藏 1

EventBus用法及源码解析

  • 目录介绍
  • 1.EventBus简介
  • 1.1 EventBus的三要素
  • 1.2 EventBus的四种ThreadMode(线程模型)
  • 1.3 EventBus怎么调用
  • 2.EventBus使用
  • 2.1 最简单的使用
  • 3.EventBus注册源码解析
  • 3.1 EventBus.getDefault()获取对象
  • 3.2 register(this)注册源码解析
  • 3.2.1 首先看register(this)源码
  • 3.2.2 接下来看findSubscriberMethods(subscriberClass)里面的源码
  • 3.2.3 接下来看findUsingInfo(subscriberClass)源码
  • 3.3 查找完所有的订阅方法后便开始对所有的订阅方法进行注册
  • 3.3.1 subscribe(subscriber, subscriberMethod);
  • 3.3.2 订阅者的注册过程
  • 3.3.3 流程图
  • 4.EventBus事件分发解析
  • 4.1 从post方法入手
  • 4.2 什么是PostingThreadState?
  • 4.3 PostingThreadState怎么获得?
  • 4.4 来看看postSingleEvent方法里做了什么
  • 4.5 接下来看看postSingleEventForEventType方法
  • 4.6 接下来看看postToSubscription方法
  • 4.7 整个流程图
  • 4.8 总结一下整个事件分发的过程
  • 5.EventBus取消注册解析
  • 5.1 unregister(this)方法入手
  • 5.2 再来看看unsubscribeByEventType(subscriber, eventType)
  • 5.3 取消注册流程图
  • 5.4 总结一下取消注册的过程
  • 6.总结一下EventBus的工作原理
  • 6.1 订阅逻辑
  • 6.2 事件发送逻辑
  • 6.3 取消逻辑
  • 6.4 利与弊
  • 7.其他介绍
  • 7.1 参考文档
  • 7.2 其他

0.本人写的综合案例

1.EventBus简介

  • 1.1 EventBus的三要素
    • Event:事件 可以是任意类型的对象。
    • Subscriber:事件订阅者 在EventBus3.0之前,消息处理的方法只能限定于onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,他们分别代表四种线程模型。 在EventBus3.0之后,事件处理的方法可以随便取名,但是需要添加一个注解@Subscribe,并且要指定线程模型(默认为POSTING),四种线程模型下面会讲到。
    • Publisher:事件发布者 可以在任意线程任意位置发送事件,直接调用EventBus的post(Object)方法。可以自己实例化EventBus对象,但一般使用EventBus.getDefault()就好了,根据post函数参数的类型,会自动调用订阅相应类型事件的函数。
  • 1.2 EventBus的四种ThreadMode(线程模型)
    • POSTING(默认): 如果使用事件处理函数指定了线程模型为POSTING,那么该事件在哪个线程发布出来的,事件处理函数就会在这个线程中运行,也就是说发布事件和接收事件在同一个线程。在线程模型为POSTING的事件处理函数中尽量避免执行耗时操作,因为它会阻塞事件的传递,甚至有可能会引起ANR。
    • MAIN: 事件的处理会在UI线程中执行。事件处理时间不能太长,长了会ANR的。
    • BACKGROUND: 如果事件是在UI线程中发布出来的,那么该事件处理函数就会在新的线程中运行,如果事件本来就是子线程中发布出来的,那么该事件处理函数直接在发布事件的线程中执行。在此事件处理函数中禁止进行UI更新操作。
    • ASYNC: 无论事件在哪个线程发布,该事件处理函数都会在新建的子线程中执行,同样,此事件处理函数中禁止进行UI更新操作。
  • 1.3 EventBus怎么调用** 代码如下: EventBus.getDefault().post(param);
    • 调用原理简单理解为:
    • 一句话,你也可以叫发布,只要把这个param发布出去,EventBus会在它内部存储的方法中,进行扫描,找到参数匹配的,就使用反射进行调用。
    • 撇开专业术语:其实EventBus就是在内部存储了一堆onEvent开头的方法,然后post的时候,根据post传入的参数,去找到匹配的方法,反射调用之。
    • 它内部使用了Map进行存储,键就是参数的Class类型。知道是这个类型,那么你觉得根据post传入的参数进行查找还是个事么?

2.EventBus使用

  • 2.1 最简单的使用
  • 2.1.1 自定义一个事件类
public class MessageEvent {
}
  • 2.1.2 在需要订阅事件的地方注册事件
EventBus.getDefault().register(this);
  • 2.1.3 发送事件
EventBus.getDefault().post(messageEvent);
  • 2.1.4 处理事件
    • 3.0之后, 消息处理的方法可以随便取名 问题: (threadMode = ThreadMode.MAIN)是做什么用的?? 需要添加一个注解@Subscribe,并且要指定线程模型 如果没有添加,那就是默认为POSTING
@Subscribe(threadMode = ThreadMode.MAIN)
public void Hhhh(MessageEvent messageEvent) {
...
}
  • 2.1.5 取消事件订阅
EventBus.getDefault().unregister(this);

3.EventBus注册源码解析

  • 3.1 EventBus.getDefault()获取对象
  • a.先看源码:
/** Convenience singleton for apps using a process-wide EventBus instance. */
public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }
    return defaultInstance;
}
  • b.分析 单例模式, 使用了双重判断的方式,防止并发的问题,还能极大的提高效率
  • 3.2 register(this)注册源码解析
  • 3.2.1.首先看register(this)源码
public void register(Object subscriber) {
    //首先获取订阅者的类对象
    Class<?> subscriberClass = subscriber.getClass();
    //用 subscriberMethodFinder 提供的方法,找到在 subscriber 这个类里面订阅的内容。
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        //
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}
  • 3.2.2.接下来看findSubscriberMethods(subscriberClass)里面的源码
    • 该方法的作用其实就是从订阅类中获取所有的订阅方法信息
    • findSubscriberMethods找出一个SubscriberMethod的集合,也就是传进来的订阅者所有的订阅的方法,接下来遍历订阅者的订阅方法来完成订阅者的订阅操作。对于SubscriberMethod(订阅方法)类中,主要就是用保存订阅方法的Method对象、线程模式、事件类型、优先级、是否是粘性事件等属性。
    • 源码分析:首先从缓存中查找,如果找到了就立马返回。如果缓存中没有的话,则根据 ignoreGeneratedIndex 选择如何查找订阅方法,ignoreGeneratedIndex属性表示是否忽略注解器生成的MyEventBusIndex。最后,找到订阅方法后,放入缓存,以免下次继续查找。ignoreGeneratedIndex 默认就是false,可以通过EventBusBuilder来设置它的值。我们在项目中经常通过EventBus单例模式来获取默认的EventBus对象,也就是ignoreGeneratedIndex为false的情况,这种情况调用了findUsingInfo方法
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    //首先从缓存中读取
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
    //是否忽略注解器生成的MyEventBusIndex类
    if (ignoreGeneratedIndex) {
        //利用反射来获取订阅类中的订阅方法信息
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        //从注解器生成的MyEventBusIndex类中获得订阅类的订阅方法信息
        subscriberMethods = findUsingInfo(subscriberClass);
    }

    //在获得subscriberMethods以后,如果订阅者中不存在@Subscribe注解并且为public的订阅方法,则会抛出异常。
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        //保存进缓存
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}
//METHOD_CACHE,是一个map集合,键是class类型
Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
  • 3.2.3 接下来看findUsingInfo(subscriberClass)源码
    • 通过getSubscriberInfo方法来获取订阅者信息。在我们开始查找订阅方法的时候并没有忽略注解器为我们生成的索引MyEventBusIndex,如果我们通过EventBusBuilder配置了MyEventBusIndex,便会获取到subscriberInfo,调用subscriberInfo的getSubscriberMethods方法便可以得到订阅方法相关的信息,这个时候就不在需要通过注解进行获取订阅方法。如果没有配置MyEventBusIndex,便会执行findUsingReflectionInSingleClass方法,将订阅方法保存到findState中。最后再通过getMethodsAndRelease方法对findState做回收处理并反回订阅方法的List集合。
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        //获取订阅者信息,没有配置MyEventBusIndex返回null
        findState.subscriberInfo = getSubscriberInfo(findState);
        if (findState.subscriberInfo != null) {
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            for (SubscriberMethod subscriberMethod : array) {
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            //通过反射来查找订阅方法
            findUsingReflectionInSingleClass(findState);
        }
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}
  • 3.3 查找完所有的订阅方法后便开始对所有的订阅方法进行注册
  • 3.3.1 回到 register(this)这个方法 Image.png
  • 3.3.2 订阅者的注册过程
    • 订阅的代码主要就做了两件事,第一件事是将我们的订阅方法和订阅者封装到subscriptionsByEventType和typesBySubscriber中,subscriptionsByEventType是我们投递订阅事件的时候,就是根据我们的EventType找到我们的订阅事件,从而去分发事件,处理事件的;typesBySubscriber在调用unregister(this)的时候,根据订阅者找到EventType,又根据EventType找到订阅事件,从而对订阅者进行解绑。第二件事,如果是粘性事件的话,就立马投递、执行。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    //获取订阅方法的参数类型 
    Class<?> eventType = subscriberMethod.eventType;
    //根据订阅者和订阅方法构造一个订阅事件
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    //获取当前订阅事件中Subscription的List集合
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    //该事件对应的Subscription的List集合不存在,则重新创建并保存在subscriptionsByEventType中
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
    //订阅者已经注册则抛出EventBusException
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }

    //遍历订阅事件,找到比subscriptions中订阅事件小的位置,然后插进去
    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }

     //通过订阅者获取该订阅者所订阅事件的集合
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }

      //将当前的订阅事件添加到subscribedEvents中
    subscribedEvents.add(eventType);
    if (subscriberMethod.sticky) {
        if (eventInheritance) {
        //粘性事件的处理
            Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
            for (Map.Entry<Class<?>, Object> entry : entries) {
                Class<?> candidateEventType = entry.getKey();
                if (eventType.isAssignableFrom(candidateEventType)) {
                    Object stickyEvent = entry.getValue();
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            Object stickyEvent = stickyEvents.get(eventType);
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}
  • 3.3.3 流程图 Image.png

4.EventBus事件分发解析

  • 4.1 从post方法入手 首先从PostingThreadState对象中取出事件队列,然后再将当前的事件插入到事件队列当中。最后将队列中的事件依次交由postSingleEvent方法进行处理,并移除该事件。
/** Posts the given event to the event bus. */
public void post(Object event) {
    //获取当前线程的postingState
    PostingThreadState postingState = currentPostingThreadState.get();
    //取得当前线程的事件队列
    List<Object> eventQueue = postingState.eventQueue;
    //将该事件添加到当前的事件队列中等待分发
    eventQueue.add(event);
    if (!postingState.isPosting) {
        //判断是否是在主线程post
        postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
        postingState.isPosting = true;
        if (postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            while (!eventQueue.isEmpty()) {
                //分发事件
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}
  • 4.2 什么是PostingThreadState? PostingThreadState中包含了当前线程的事件队列,就是当前线程所有分发的事件都保存在eventQueue事件队列中以及订阅者订阅事件等信息,有了这些信息我们就可以从事件队列中取出事件分发给对应的订阅者
final static class PostingThreadState {
    final List<Object> eventQueue = new ArrayList<Object>();//当前线程的事件队列
    boolean isPosting;//是否有事件正在分发
    boolean isMainThread;//post的线程是否是主线程
    Subscription subscription;//订阅者
    Object event;//订阅事件
    boolean canceled;//是否取消
}
  • 4.3 PostingThreadState怎么获得?
    • ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,而这段数据是不会与其他线程共享的。
    • 可以看出currentPostingThreadState的实现是一个包含了PostingThreadState的ThreadLocal对象,这样可以保证取到的都是自己线程对应的数据。
    • 我们有了PostingThreadState获取到了当前线程的事件队列,接下来就是事件分发,我们来看postSingleEvent(eventQueue.remove(0), postingState);
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
    @Override
    protected PostingThreadState initialValue() {
        return new PostingThreadState();
    }
};
  • 4.4 来看看postSingleEvent方法里做了什么 eventInheritance表示是否向上查找事件的父类,它的默认值为true,可以通过在EventBusBuilder中来进行配置。当eventInheritance为true时,则通过lookupAllEventTypes找到所有的父类事件并存在List中,然后通过postSingleEventForEventType方法对事件逐一处理,接下来看看postSingleEventForEventType方法
事件分发
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    //得到事件类型
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;

    //是否触发订阅了该事件(eventClass)的父类,以及接口的类的响应方法.
    if (eventInheritance) {
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    if (!subscriptionFound) {
        if (logNoSubscriberMessages) {
            Log.d(TAG, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
            post(new NoSubscriberEvent(this, event));
        }
    }
}
  • 4.5 接下来看看postSingleEventForEventType方法 同步取出该事件对应的Subscription集合并遍历该集合将事件event和对应Subscription传递给postingState并调用postToSubscription方法对事件进行处理,接下来看看postToSubscription方法:
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
        //根据事件类型获取所有的订阅者
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    //向每个订阅者分发事件
    if (subscriptions != null && !subscriptions.isEmpty()) {
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted = false;
            try {
                //对事件进行处理
                postToSubscription(subscription, event, postingState.isMainThread);
                aborted = postingState.canceled;
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
}
  • 4.6 接下来看看postToSubscription方法
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING://默认的 ThreadMode,表示在执行 Post 操作的线程直接调用订阅者的事件响应方法,
        //不论该线程是否为主线程(UI 线程)。
            invokeSubscriber(subscription, event);
            break;
        case MAIN://在主线程中执行响应方法。
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case BACKGROUND://在后台线程中执行响应方法。
            if (isMainThread) {
                backgroundPoster.enqueue(subscription, event);
            } else {
                invokeSubscriber(subscription, event);
            }
            break;
        case ASYNC://不论发布线程是否为主线程,都使用一个空闲线程来处理。
            asyncPoster.enqueue(subscription, event);
            break;
        default:
            throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
    }
}
  • 4.7 整个流程图 511825-20160330182550879-762466444.png
  • 4.8 总结一下整个事件分发的过程
    • 首先获取当前线程的PostingThreadState对象从而获取到当前线程的事件队列
    • 通过事件类型获取到所有订阅者集合
    • 通过反射执行订阅者中的订阅方法

5.EventBus取消注册解析

  • 5.1 unregister(this)方法入手
/** Unregisters the given subscriber from all event classes. */
public synchronized void unregister(Object subscriber) {
    //获取订阅者的所有订阅的事件类型
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        for (Class<?> eventType : subscribedTypes) {
            //从事件类型的订阅者集合中移除订阅者
            unsubscribeByEventType(subscriber, eventType);
        }
        typesBySubscriber.remove(subscriber);
    } else {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}
  • 5.2 再来看看unsubscribeByEventType(subscriber, eventType)
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    //获取事件类型的所有订阅者
    List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    //遍历订阅者集合,将解除的订阅者移除
    if (subscriptions != null) {
        int size = subscriptions.size();
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
            if (subscription.subscriber == subscriber) {
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}
  • 5.3 取消注册流程图 Image.png
  • 5.4 总结一下取消注册的过程
    • 1、首先获取订阅者的所有订阅事件
    • 2、遍历订阅事件
    • 3、根据订阅事件获取所有的订阅了该事件的订阅者集合
    • 4、将该订阅者移除
    • 5、将步骤1中的集合中的订阅者移除

6.总结一下EventBus的工作原理

  • 6.1 订阅逻辑
    • 1、首先用register()方法注册一个订阅者
    • 2、获取该订阅者的所有订阅的方法
    • 3、根据该订阅者的所有订阅的事件类型,将订阅者存入到每个以 事件类型为key 以所有订阅者为values的map集合中
    • 4、然后将订阅事件添加到以订阅者为key 以订阅者所有订阅事件为values的map集合中
    • 5、如果是订阅了粘滞事件的订阅者,从粘滞事件缓存区获取之前发送过的粘滞事件,响应这些粘滞事件。
  • 6.2 事件发送逻辑
    • 1、首先获取当前线程的事件队列
    • 2、将要发送的事件添加到事件队列中
    • 3、根据发送事件类型获取所有的订阅者
    • 4、根据响应方法的执行模式,在相应线程通过反射执行订阅者的订阅方法
  • 6.3 取消逻辑
    • 1、首先通过unregister方法拿到要取消的订阅者
    • 2、得到该订阅者的所有订阅事件类型
    • 3、遍历事件类型,根据每个事件类型获取到所有的订阅者集合,并从集合中删除该订阅者
    • 4、将订阅者从步骤2的集合中移除
  • 6.4 利与弊
    • EventBus好处比较明显,它能够解耦和,将业务和视图分离,代码实现比较容易。而且3.0后,我们可以通过apt预编译找到订阅者,避免了运行期间的反射处理解析,大大提高了效率。当然EventBus也会带来一些隐患和弊端,如果滥用的话会导致逻辑的分散并造成维护起来的困难。另外大量采用EventBus代码的可读性也会变差。

7.其他介绍

© 著作权归作者所有

共有 人打赏支持
潇湘剑雨
粉丝 10
博文 108
码字总数 385128
作品 0
朝阳
私信 提问
guava eventbus源码解析

说在前面 本文转自“天河聊技术”微信公众号 事件驱动模型设计是一种优雅的程序设计方式,实现有很多,原理都是发布与订阅,观察者设计模式实现,java自带的实现、spring ioc的事件驱动模型,...

天河2018
2018/07/07
0
0
Guava库学习:学习Guava EventBus(一)EventBus

在软件开发过程中,对象信息的分享以及相互直接的协作是必须的,困难在于确保对象之间的沟通是有效完成的,而不是拥有成本高度耦合的组件。当对象对其他组件的责任有太多的细节时,它被认为是...

Realfighter
2014/12/29
0
0
EventBus3.1.1 源码分析

EventBus源码分析 EventBus 是一个事件总线框架,解决了组件之间通信的问题。使用了观察者模式。使代码更加简洁 简单使用 1.引入依赖 2.定义事件 3.注册与反注册 4.在某个地方发送事件 5.在注...

sun_____xin
2018/07/20
0
0
为JFinal添加event消息事件

在之前使用spring mvc的时候,在复杂的下单和支付中有一部分功能使用的Spring事件驱动模型去完成!具体优点不啰嗦了,发现涛哥有篇文章讲得比较详细:[详解Spring事件驱动模型][1] 最初准备基...

如梦技术
2015/04/28
0
20
SimpleNews 项目的重构之旅(3) -EventBus 接入

通过需求使用 EventBus 之前就接触过 EventBus ,只是没有在项目中使用过,练习地址 WPEventBusDemo ,今天在项目中接入 EventBus 。 最开始的目的是为了做一个完全退出机制,看了网上很多用...

無名小子的杂货铺
2017/06/02
0
0

没有更多内容

加载失败,请刷新页面

加载更多

vue 对对象的属性进行修改时,不能渲染页面 vue.$set()

我在vue里的方法里给一个对象添加某个属性时,我console.log出来的是已经更改的object ,但是页面始终没有变化 原因如下: **受现代 JavaScript 的限制 (而且 Object.observe 也已经被废弃),...

Js_Mei
44分钟前
0
0
开始看《Java学习笔记》

虽然书买了很久,但一直没看。这其中也写过一些Java程序,但都是基于IDE的帮助和对C#的理解来写的,感觉不踏实。 林信良的书写得蛮好的,能够帮助打好基础,看得出作者是比较用心的。 第1章概...

max佩恩
昨天
12
0
Redux 三大原则

1.单一数据源 在传统的MVC架构中,我们可以根据需要创建无数个Model,而Model之间可以互相监听、触发事件甚至循环或嵌套触发事件,这些在Redux中都是不被允许的。 因为在Redux的思想里,一个...

wenxingjun
昨天
8
0
跟我学Spring Cloud(Finchley版)-12-微服务容错三板斧

至此,我们已实现服务发现、负载均衡,同时,使用Feign也实现了良好的远程调用——我们的代码是可读、可维护的。理论上,我们现在已经能构建一个不错的分布式应用了,但微服务之间是通过网络...

周立_ITMuch
昨天
4
0
XML

学习目标  能够说出XML的作用  能够编写XML文档声明  能够编写符合语法的XML  能够通过DTD约束编写XML文档  能够通过Schema约束编写XML文档  能够通过Dom4j解析XML文档 第1章 xm...

stars永恒
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部