EventBus3.0源码解析
本文主要介绍EventBus3.0的源码
EventBus.getDefault().register(this);
2、响应事件方法
@Subscribe(threadMode = ThreadMode.BACKGROUND, sticky = true, priority = 100)
public void jiaoTest(String str) {
System.out.println("响应方法:" + str);
}
参数解析:
EventBus.getDefault().post("Test");
4、解除注册
EventBus.getDefault().unregister(this);
以上就是EventBus的使用过程,用起来非常简单方便,非常实用。
二、注册源码解析
对应以上的注册方式,我们就从EventBus.getDefault().register(this);入手,首先查看EventBus.getDefault()
看看EventBus是如何初始化的;
/** 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;
}
可以看出来,EventBus是单例模式存在的,一个项目中只能有一个EventBus这样有利于管理订阅者和订阅方法,这会在下面的介绍中体现出来。
接下来看register(this)
public void register(Object subscriber) {
//订阅者
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
可以看出首先获取订阅者的类对象Class<?> subscriberClass = subscriber.getClass();
在看这段代码之前,我们首先要了解SubscriberMethod和subscriberMethodFinder.findSubscriberMethods方法到底做了什么
首先来看SubscriberMethod
public class SubscriberMethod {
final Method method;//方法
final ThreadMode threadMode;//执行线程
final Class<?> eventType;//接收的事件类型
final int priority;//优先级
final boolean sticky;
/** Used for efficient comparison */
String methodString;
....
}
可以看出SubscriberMethod其实就是一个订阅方法的实体类,里面保存了订阅方法信息
接着看subscriberMethodFinder.findSubscriberMethods
该方法的作用其实就是从订阅类中获取所有的订阅方法信息;
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);
}
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;
}
}
我们看到,该方法首先从缓存中获取订阅类的订阅方法信息,如果没有则通过两种方式来获取
1、通过EventBusAnnotationProcessor(注解处理器)生成的MyEventBusIndex中获取
2、利用反射来读取订阅类中订阅方法信息
EventBusAnnotationProcessor是什么东东?(此处参考:文/达达达达sky(简书作者)原文链接:http://www.jianshu.com/p/f057c460c77e)
在3.0版本中,EventBus提供了一个EventBusAnnotationProcessor注解处理器来在编译期通过读取@Subscribe()注解并解析,
处理其中所包含的信息,然后生成java类来保存所有订阅者关于订阅的信息,这样就比在运行时使用反射来获得这些订阅者的
信息速度要快.我们可以参考EventBus项目里的EventBusPerformance这个例子,编译后我们可以在build文件夹里找到这个类
,MyEventBusIndex 类,当然类名是可以自定义的.我们大致看一下生成的MyEventBusIndex类是什么样的:
/**
* This class is generated by EventBus, do not edit.
*/
public class MyEventBusIndex implements SubscriberInfoIndex {
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX; static {
SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>(); putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscriberClassEventBusAsync.class,
true, new SubscriberMethodInfo[]{
new SubscriberMethodInfo("onEventAsync", TestEvent.class, ThreadMode.ASYNC),
})); putIndex(new SimpleSubscriberInfo(TestRunnerActivity.class, true, new SubscriberMethodInfo[]{
new SubscriberMethodInfo("onEventMainThread", TestFinishedEvent.class, ThreadMode.MAIN),
}));
} private static void putIndex(SubscriberInfo info) {
SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
} @Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
if (info != null) {
return info;
} else {
return null;
}
}
}
可以看出是使用一个静态HashMap即:SUBSCRIBER_INDEX来保存订阅类的信息,其中包括了订阅类的class对象,
是否需要检查父类,以及订阅方法的信息SubscriberMethodInfo的数组,SubscriberMethodInfo中又保存了,订阅方法的方法名,
订阅的事件类型,触发线程,是否接收sticky事件以及优先级priority.这其中就保存了register()的所有需要的信息;
我们重点研究一下通过反射来获取订阅方法信息即:findUsingReflection(subscriberClass);
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
findUsingReflectionInSingleClass(findState);
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
FindState其实就是一个里面保存了订阅者和订阅方法信息的一个实体类,包括订阅类中所有订阅的事件类型和所有的订阅方法等。
我们看到会首先创建一个FindState对象并执行findUsingReflectionInSingleClass(findState);来获取订阅类的方法信息
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
//通过反射获取到订阅类中的所有方法
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
//遍历所有方法,忽略private类型的,最后如果是公有,并且不是
//java编译器 生成的方法名,那么就是我们要的了。
for (Method method : methods) {
int modifiers = method.getModifiers();
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
//保证只有一个事件参数
if (parameterTypes.length == 1) {
//得到注解
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class<?> eventType = parameterTypes[0];
//校验是否添加该方法
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
//添加方法
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException("@Subscribe method " + methodName +
"must have exactly 1 parameter but has " + parameterTypes.length);
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
}
}
}
可以看到,首先会得到订阅类的class对象并通过反射获取订阅类中的所有方法信息,然后通过筛选获取到订阅方法集合。
程序执行到此我们就获取到了订阅类中的所有的订阅方法信息,接下来我们就要对订阅方法进行注册;
subscribe(subscriber, subscriberMethod);//参数:1订阅者2订阅方法集
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
//获取订阅方法的参数类型
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//根据订阅的事件类型获取所有的订阅者
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
//将订阅者添加到subscriptionsByEventType集合中
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
} //根据优先级,将订阅者插入到指定的位置
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);
}
//将该事件类型添加到typesBySubscriber中
subscribedEvents.add(eventType); //如果接收sticky事件,立即分发sticky事件
if (subscriberMethod.sticky) {
if (eventInheritance) {
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
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);
}
}
}
上面这段代码涉及到几个对象我来介绍一下:
Subscription
//订阅者信息
final class Subscription {
final Object subscriber;//订阅者
final SubscriberMethod subscriberMethod;//订阅方法
}
subscriptionsByEventType
key订阅方法类型 values 所有订阅了该类型的订阅者集合
Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
typesBySubscriber
key订阅者 values订阅事件集合
Map<Object, List<Class<?>>> typesBySubscriber;
了解了这几个对象,上面的代码就很容易看懂了,
1、首先获取订阅方法的参数类型即订阅事件类型
2、根据订阅事件类型获取该事件类型的所有订阅者
3、将该订阅者添加到该事件类型的订阅者集合中即:subscriptionsByEventType
4、获取订阅者所有的订阅事件类型
5、将该事件类型添加到该订阅者的订阅事件类型集中即:typesBySubscriber
至此,就完成了订阅类中订阅方法的注册,我们来看一下整个流程
三、事件分发解析
接下来我们来分析EventBus的事件分发机制即:EventBus.getDefault().post("Test");
我们从post方法入手
/** 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;
}
}
}
什么是PostingThreadState?
final static class PostingThreadState {
final List<Object> eventQueue = new ArrayList<Object>();//当前线程的事件队列
boolean isPosting;//是否有事件正在分发
boolean isMainThread;//post的线程是否是主线程
Subscription subscription;//订阅者
Object event;//订阅事件
boolean canceled;//是否取消
}
PostingThreadState中包含了当前线程的事件队列,就是当前线程所有分发的事件都保存在eventQueue事件队列中
以及订阅者订阅事件等信息,有了这些信息我们就可以从事件队列中取出事件分发给对应的订阅者。
PostingThreadState怎么获得?
ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,而这段数据是不会与其他线程共享的。
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};
可以看出currentPostingThreadState
的实现是一个包含了PostingThreadState
的ThreadLocal
对象,这样可以保证取到的都是
自己线程对应的数据。
我们有了PostingThreadState获取到了当前线程的事件队列,接下来就是事件分发,我们来看
postSingleEvent(eventQueue.remove(0), postingState);
事件分发
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));
}
}
}
通过以上代码我们可以发现,真正的事件分发是通过postSingleEventForEventType(event, postingState, eventClass);发出去的我们来看:
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;
}
可以看到首先根据事件类型获取到所有的订阅者,然后循环向每个订阅者发送事件,通过
postToSubscription(subscription, event, postingState.isMainThread);发送出去
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);
}
}
以上的四种threadMode可以看代码注释简单了解一下,通过一下代码我们来看一下订阅方法最后是通过invokeSubscriber(subscription, event);来执行的
//最终通过反射调用订阅者的订阅函数 并把event作为参数传入
void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
真相大白;最后是通过反射的方式,调用了订阅类中的订阅方法。我们来总结一下整个事件分发的过程
1、首先获取当前线程的PostingThreadState对象从而获取到当前线程的事件队列
2、通过事件类型获取到所有订阅者集合
3、通过反射执行订阅者中的订阅方法
是不是很简单。
我们来看一下整个事件分发的流程图
四、取消注册解析
我们简单看一下取消注册的源码EventBus.getDefault().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());
}
}
再来看一下:unsubscribeByEventType(subscriber, eventType);
/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
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--;
}
}
}
}
总结一下:
1、首先获取订阅者的所有订阅事件
2、遍历订阅事件
3、根据订阅事件获取所有的订阅了该事件的订阅者集合
4、将该订阅者移除
5、将步骤1中的集合中的订阅者移除
这样就完成了取消订阅的全过程;
最后我们从开发者的角度来总结一下EventBus的工作原理
订阅逻辑
1、首先用register()方法注册一个订阅者
2、获取该订阅者的所有订阅的方法
3、根据该订阅者的所有订阅的事件类型,将订阅者存入到每个以 事件类型为key 以所有订阅者为values的map集合中
4、然后将订阅事件添加到以订阅者为key 以订阅者所有订阅事件为values的map集合中
4.1、如果是订阅了粘滞事件的订阅者,从粘滞事件缓存区获取之前发送过的粘滞事件,响应这些粘滞事件。
事件发送逻辑
1、首先获取当前线程的事件队列
2、将要发送的事件添加到事件队列中
3、根据发送事件类型获取所有的订阅者
4、根据响应方法的执行模式,在相应线程通过反射执行订阅者的订阅方法
取消逻辑
1、首先通过unregister方法拿到要取消的订阅者
2、得到该订阅者的所有订阅事件类型
3、遍历事件类型,根据每个事件类型获取到所有的订阅者集合,并从集合中删除该订阅者
4、将订阅者从步骤2的集合中移除
EventBus3.0源码解析的更多相关文章
- Android事件总线(二)EventBus3.0源码解析
1.构造函数 当我们要调用EventBus的功能时,比如注册或者发送事件,总会调用EventBus.getDefault()来获取EventBus实例: public static EventBus ...
- solr&lucene3.6.0源码解析(四)
本文要描述的是solr的查询插件,该查询插件目的用于生成Lucene的查询Query,类似于查询条件表达式,与solr查询插件相关UML类图如下: 如果我们强行将上面的类图纳入某种设计模式语言的话,本 ...
- solr&lucene3.6.0源码解析(三)
solr索引操作(包括新增 更新 删除 提交 合并等)相关UML图如下 从上面的类图我们可以发现,其中体现了工厂方法模式及责任链模式的运用 UpdateRequestProcessor相当于责任链模式 ...
- Heritrix 3.1.0 源码解析(三十七)
今天有兴趣重新看了一下heritrix3.1.0系统里面的线程池源码,heritrix系统没有采用java的cocurrency包里面的并发框架,而是采用了线程组ThreadGroup类来实现线程池的 ...
- solr&lucene3.6.0源码解析(二)
上文描述了solr3.6.0怎么采用maven管理的方式在eclipse中搭建开发环境,在solr中,为了提高搜索性能,采用了缓存机制,这里描述的是LRU缓存,这里用到了 LinkedHashMap类 ...
- solr&lucene3.6.0源码解析(一)
本文作为系列的第一篇,主要描述的是solr3.6.0开发环境的搭建 首先我们需要从官方网站下载solr的相关文件,下载地址为http://archive.apache.org/dist/luc ...
- apache mina2.0源码解析(一)
apache mina是一个基于java nio的网络通信框架,为TCP UDP ARP等协议提供了一致的编程模型:其源码结构展示了优秀的设计案例,可以为我们的编程事业提供参考. 依照惯例,首先搭建a ...
- Retrofit2.0源码解析
欢迎访问我的个人博客 ,原文链接:http://wensibo.net/2017/09/05/retrofit/ ,未经允许不得转载! 今天是九月的第四天了,学校也正式开学,趁着大学最后一年的这大好时 ...
- 【原创】backbone1.1.0源码解析之View
作为MVC框架,M(odel) V(iew) C(ontroler)之间的联系是必不可少的,今天要说的就是View(视图) 通常我们在写逻辑代码也好或者是在ui组件也好,都需要跟dom打交道,我们 ...
随机推荐
- 关于skip_name_resolve参数的总结
作为MySQL调优的一部分,很多人都推荐开启skip_name_resolve.这个参数是禁止域名解析的(当然,也包括主机名).很多童鞋会好奇,这背后的原理是什么,什么情况下开启这个参数比较合适. 基 ...
- 我的Node.js处女作
前言 很高兴我的node.js处女作开发完成了,目前还在优化完善阶段,经历两周的紧张沟通和开发,工作总算搞一段落.选用node.js 一是因为这次的业务逻辑相对来说简单想拿node练练手,二就是相对来 ...
- PHP之call user func()函数
在实际开发中通常会遇到这样的问题,决定调用某个函数是通过传入的参数决定的,例如: $functionName=$_post['functionName']; 接着我们需要访问一个叫$functionN ...
- 【原创】新手入门一篇就够:从零开发移动端IM
一.前言 IM发展至今,已是非常重要的互联网应用形态之一,尤其移动互联网时代,它正以无与论比的优势降低了沟通成本和沟通代价,对各种应用形态产生了深远影响. 做为IM开发者或即将成为IM开发者的技术人员 ...
- CloudNotes云端个人笔记系统系列文章汇总
[CloudNotes版本更新信息与下载地址:http://cloudnotes.cloudapp.net/webapi/Home/Release] [CloudNotes RESTful API帮助 ...
- 你的程序支持复杂的时间调度嘛?如约而来的 java 版本
你的程序支持复杂的时间调度嘛? 这篇文章介绍了时间适配器的c#版本,是给客户端用的,服务器自然也要有一套对应的做法,java版本的 [年][月][日][星期][时间] [*][*][*][*][*] ...
- [Web API] Web API 2 深入系列(7) Model绑定(下)
目录 ModelBinder ModelBinderProvider 不同类型的Model绑定 简单类型 复杂类型 其他类型 ModelBinder ModelBinder是Model绑定的核心. p ...
- Design Patterns Simplified - Part 2 (Singleton)【设计模式简述--第二部分(单例模式)】
原文链接: http://www.c-sharpcorner.com/UploadFile/19b1bd/design-patterns-simplified-part-2-singleton/ De ...
- 【原创】kafka admin源代码分析
admin包定义了命令行的一些实现 一.AdminOperationException.scala 一个异常类,表示执行admin命令时候抛出的异常 二.AdminUtils.scala admin一 ...
- C#正则表达式Regex常用匹配
使用Regex类需要引用命名空间:using System.Text.RegularExpressions; 利用Regex类实现验证 示例1:注释的代码所起的作用是相同的,不过一个是静态方法,一个是 ...