现在如今眼目下,开源程序库越来越多,程序员们很多时候都不需要自己造轮子,就可以找到称心如意的开源库进行使用。虽然我们在使用各种各样的开源代码时,并不需要知道这些代码是如何实现的。但是了解它们的实现方法,不仅可以提升我们自己本身的编程能力和编程技巧,同时也为我们学习某一特定的技术点提供了可以模仿的例子。
Guava简介
如同boost之于c++,guava也几乎成为了java编程中不可或缺的一部分。guava中涵盖了很多有意思的东西,比如在java中使用函数式编程;新的数据结构,如bimap等等。总之,guava让写java程序成为一件更美好的事情。guava中还有很多很多有意思的东西,可以去guava的官方网站上探个究竟。guava
本文试图通过分析guava的eventbus的源代码,学习如何在java中使用annotation编程。
EventBus简介
Publisher-Subscriber这种设计模式在GoF中早就详细的解释。也是一种最常用不过的设计模式。而EventBus则是对于Publisher和Subscriber的一种实现,如果你还在使用JDK中的Observer,则不妨看看TW大大的一片博客《你应该更新的java知识之observer》,使用EventBus替代Observer似乎成了一种必须。
EventBus使用方法
MessageScreen.java:
public class MessageScreen {
@Subscribe
public void printMessage(String message) {
System.out.println(message);
}
}
调用代码:
EventBus eventBus = new EventBus();eventBus.register(new MessageScreen());
eventBus.post("Hello Screen");
显示结果:
Hello Screen
代码解释:
调用eventBus中得register时,会向eventBus中注册一个Listener。这里的listener就是MessageScreen。
listener中被@Subscribe标示的方法为EventHandler,当eventBus使用post发布Event时,这个方法就会被调用。对应前面的例子,EventHandler是printMessage这个方法。Event是“Hello Screen”。
EventHandler中的参数类型为EventType,这里是String。当eventBus使用post时,会更具event的Type不同发送到相应的handler进行处理。
于是,一切看似神奇的事情都是发生在@Subscribe这个标记上。那Annotation究竟是干什么用得呢?怎么的一个标明了@Subscribe的方法就可以变成了一个subscriber了呢?
Annotation编程
Annotation是一种元数据,它提供了关于程序的信息,但是这个信息并不属于被Annotation标注的程序的本身的一部分。它不会影响被标注的程序的执行。
以上面的MessageListener为例,displayMessage这个方法被标注了@Subscribe,这个标注信息是在EventBus中的register中用到的,而不是在MessageListener中用到的。
Annotation编程可以分为大致两个部分,第一个部分是annotation定义,第二个部分是获取被annotation标记的代码,然后针对这部分代码进行一些特定的操作。下面就使用EventBus讲解这两个部分的工作是如何完成的。
定义Annotation:@Subscribe
Subscribe的定义:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Subscribe {
}
@interface表示定义了一个annotation。@Retention和@Target是predefined annotation。
@Retention表示Subscribe这个Annotation是怎么被保存的。@Retention(RententionPolicy.RUNTIME)说明Subscribe这个annotation在运行时可以被使用。
@Target表示的是这个Subscribe这个annotation能被标注到哪里。@Target(ElementType.METHOD)说明Subscribe是用于标记方法的。对应一开始EventBus的例子,就是printMessage这个方法。
获取被Annotation标记的代码:EventBus中如何知道@Subscribe的方法的呢?
EventBus的代码:
public void register(Object object) {
Multimap<Class<?>, EventHandler> methodsInListener =
finder.findAllHandlers(object);
handlersByType.putAll(methodsInListener);
}
在EventBus进行register时,会通过一个finder找到register的object中被标注了@Subscribe的方法。并且按照EventType进行分类,放在handlersByType里。这样当EventBus的post新的Event时,就可以根据EventType调用相应的EventHandler。
AnnotatedHandlerFinder的代码:
public Multimap<Class<?>, EventHandler> findAllHandlers(Object listener) {
Multimap<Class<?>, EventHandler> methodsInListener = HashMultimap.create();
Class<?> clazz = listener.getClass();
for (Method method : getAnnotatedMethods(clazz)) {
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?> eventType = parameterTypes[0];
EventHandler handler = makeHandler(listener, method);
methodsInListener.put(eventType, handler);
}
return methodsInListener;
}
在findAllHandlers这个函数中,将首先调用getAnnotatedMethods得到listener中所有被标记了@Subscribe的方法。然后将listener和被标记了@Subscribe的方法本身放在一个叫EventHandler的数据结构中,同时记录了method的第一个也是唯一一个参数类型作为EventType作为post时按类型分发消息使用。接着再看是如何得到被@Subscribe标记的方法的:
private static ImmutableList<Method> getAnnotatedMethods(Class<?> clazz) {
for (Method method : clazz.getMethods()) {
if (method.isAnnotationPresent(Subscribe.class)) {
... ...// add to list
}
}
}
getAnnotatedMethods首先获得了listener的所有方法。然后再遍历查询是否有方法是否被@Subscribe标示了:method.isAnnotationPresent(Subscribe.class)。如果被@Subscribe标示了,则添加到返回的list中。这其实也告诉了我们,这种方法的另一个好处,一个类中不再只能有一个update作为Observer的方法。而是可以有多个被@Subscribe标示的EventHandler,他们在同一个类时,就通过方法名和EventType进行区分。
当EventBus中的post被调用时,中间会有一系列的入队列,出队列的操作。最后调用,Eventhandler的handleEvent方法。
EventHandler.java
public void handleEvent(Object event) {
method.invoke(target,new Object[]{ event});
}
handleEvent中method是在register中被标记了@Subscribe的方法,第一个参数target就是在register中加入的listener,通过调用method的invoke方法。这样就调用了被@Subscribe的方法。
总结
1.Annotation编程在很多地方都有用到,比如大家都再熟悉不过的Junit4中的@Test,Spring中的@Component等等。所以,学习Annotation,说不定可以让我们写出这些使用方便的程序。
2.在EventBus的完整实现中,其实还包含了其他很多的技术,像多线程、cache以及Guava其他的一些功能。比如getAnnotatedMethods这个方法并不是真正的去查找Annotated Methods,真正的查找是getAnnotatedMethodsInternal这个方法,期间就使用了cache。这里想专注于Annotation本身的使用,就不展开讨论了。
3.在判断是否含有@Subscribe时,AnnotatedHandlerFinder实际上是检查了所有父类的方法。因为父类本身的Annotation是不会被子类继承的,如果子类重写了父类的方法。那么调用子类的isAnnotationPresent则会返回false。
4. 如何想要了解更多的关于annotation的细节知识,可以访问下oracle的官方文档:oracle annotation