玩轉(zhuǎn)Java注釋:自動(dòng)調(diào)用監(jiān)聽(tīng)器
原創(chuàng)【51CTO精選譯文】Listener,直譯為偵聽(tīng)器或監(jiān)聽(tīng)器,在面向?qū)ο蟮拈_(kāi)發(fā)中經(jīng)常需要用到。如果你需要啟動(dòng)或者停止基于Java的Web系統(tǒng)中不同部分的代碼,那么你可以使用一個(gè)簡(jiǎn)單的ServletContentListener來(lái)監(jiān)聽(tīng)容器(container)里面的啟動(dòng)事件和停止事件。這個(gè)監(jiān)聽(tīng)器可以使用java.util.ServiceLoader去尋找這些被偵聽(tīng)事件所對(duì)應(yīng)的已注冊(cè)類(lèi)。
這個(gè)方法不錯(cuò),但是如果添加一個(gè)編譯時(shí)間注釋處理器會(huì)不會(huì)更好呢?如果你用@Lifecycle(LifecycleEvent.STARTUP)注釋一個(gè)靜態(tài)方法,它將會(huì)在開(kāi)機(jī)的時(shí)候被調(diào)用(在關(guān)機(jī)的時(shí)候被關(guān)掉)。處理器會(huì)產(chǎn)生類(lèi),并為了ServiceLoader而注冊(cè)它們。你也可以把同樣的機(jī)制用在任何事件總線(event-bus)模型上:在編譯的時(shí)候注冊(cè)listener,并且?guī)в凶⑨?,?dāng)事件被觸發(fā)的時(shí)候總線會(huì)自動(dòng)調(diào)用他們。從本質(zhì)上講,你可以通過(guò)注釋實(shí)現(xiàn)用ServiceLoader在運(yùn)行時(shí)間自動(dòng)發(fā)現(xiàn)代碼。
實(shí)際過(guò)程中,其概念如下:
1. 你用@EventListener對(duì)方法進(jìn)行注釋(可能包含一些元信息(meta-info))。
2. 注釋處理器為每個(gè)@EventListener方法生成一個(gè)EventDispatcher,包括注釋中元信息需要的過(guò)濾器。
3. 事件總線利用java.util.ServiceLoader找到EventDispatcher的實(shí)現(xiàn)。
當(dāng)EventBus.dispatch被調(diào)用時(shí),任何有興趣的、已經(jīng)用@EventListener注釋的方法都會(huì)被調(diào)用。
本文將對(duì)事件總線創(chuàng)建的幾個(gè)必要步驟進(jìn)行分析,從而闡明這一概念。事件總線不需要任何手動(dòng)注冊(cè)就可以調(diào)用已注釋了的listener方法。我們將從Eventbus開(kāi)始討論,然后是注釋處理器,***是一個(gè)用法實(shí)例。
組織你的代碼
這個(gè)例子的代碼包含兩個(gè)單獨(dú)的IDE工程
◆EventBus ——包含了事件總線以及注釋處理器
◆EventBusExample ——包含了一個(gè)使用事件總線的例子
當(dāng)操作注釋處理器的時(shí)候,你應(yīng)該在IDE選項(xiàng)中關(guān)閉"Compile on Save"(或者其他等同的選項(xiàng))。這些選項(xiàng)可能會(huì)刪除注釋處理器所生成的類(lèi),讓你摸不著頭腦。
以下內(nèi)容將會(huì)解釋這些工程中的代碼是如何工作的,而且為了便于說(shuō)明還提供了一些程序片段。
注釋和事件
你需要的***個(gè)東西是一個(gè)@EventListener注釋,用來(lái)標(biāo)識(shí)那些偵聽(tīng)事件的方法。下面是一個(gè)EventListener注釋的例子,它只能用來(lái)注釋方法。在代碼編譯后它將被拋棄,因?yàn)樗械奶幚矶际菍?duì)源代碼進(jìn)行的。
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.SOURCE)
- public @interface EventListener {
- String name() default ".*";
- Class<?> source() default Object.class;
- }
由于這個(gè)例子是一個(gè)事件總線模型,那么listener方法***只接受它們唯一感興趣的事件。為了便于執(zhí)行這個(gè)規(guī)則,在BusEventObject類(lèi)中包含了你想過(guò)濾的名字(以@EventListener注釋里面的名字為基礎(chǔ))。為了讓過(guò)濾事件更加簡(jiǎn)單,這個(gè)普通的EventObject類(lèi)中還有一個(gè)附加的名字域。BusEventObject也作為一個(gè)標(biāo)識(shí),可以標(biāo)識(shí)出通過(guò)EventBus分派的事件。
- public abstract class BusEventObject extends EventObject {
- private final String name;
- public BusEventObject(
- final Object source,
- final String name) {
- super(source);
- if(name == null || name.isEmpty()) {
- throw new IllegalArgumentException("empty or null name");
- }
- this.name = name;
- }
- public String getName() {
- return name;
- }
- }
注釋處理器
為了開(kāi)始寫(xiě)注釋處理器,你首先應(yīng)該熟悉javax.annotation.processing 和 javax.lang.model的包組。一般來(lái)說(shuō),你可以直接掠過(guò)執(zhí)行處理器接口,進(jìn)入抽象類(lèi)javax.annotation.processing.AbstractProcessor。AbstractProcessor需要一些關(guān)于實(shí)現(xiàn)的信息,這些信息用來(lái)提供注釋。例子中的EventListenerAnnotationProcessor代碼聲明如下所示:
- @SupportedSourceVersion(SourceVersion.RELEASE_5)
- @SupportedAnnotationTypes(EventListenerAnnotationProcessor.ANNOTATION_TYPE)
- public class EventListenerAnnotationProcessor extends AbstractProcessor {
@SupportedSourceVersion告訴AbstractProcessor你只想要用java5或者更高版本寫(xiě)的源文件;而@SupportedAnnotationTypes告訴AbstractProcessor哪個(gè)注釋是你感興趣的(EventListener.class.getName()不會(huì)作為一個(gè)注釋值起作用,因?yàn)榫幾g器不能計(jì)算這種表達(dá)式的值)。
- public static final String ANNOTATION_TYPE = "eventbus.EventListener";
為了簡(jiǎn)單起見(jiàn),注釋處理器被分成兩個(gè)主要的類(lèi)(EventListenerAnnotationProcessor 和EventDispatcherGenerator)以及一個(gè)通用工具類(lèi)(ServiceRegistration)。為了便于編譯器注釋工具執(zhí)行EventListenerAnnotationProcessor,你需要用一個(gè)服務(wù)文件來(lái)注冊(cè)它(編譯器也使用ServiceLoader)。
eventbus.processor.EventListenerAnnotationProcessor
服務(wù)注冊(cè)文件(META-INF/services/javax.annotation.processing.Processor)是根據(jù)ServiceLoader一定能找到的接口來(lái)命名的。
EventListenerAnnotationProcessor.process()方法的***個(gè)行動(dòng)就是找到這輪編譯中所有的@EventListener方法。
- final Elements elements = processingEnv.getElementUtils();
- final TypeElement annotation = elements.getTypeElement(ANNOTATION_TYPE);
- final Set<? extends Element> methods =
- roundEnv.getElementsAnnotatedWith(annotation);
Element對(duì)象很像編譯器以及注釋處理器的反射對(duì)象(reflection objects)。TypeElement就像是類(lèi),而ExecutableElement跟構(gòu)造器或者方法類(lèi)似。RoundEnvironment(代表本輪注釋處理)將會(huì)返回到被@EventListener 注釋的Element。
EventDispatcherGenerator
EventDispatcherGenerator是一個(gè)非常簡(jiǎn)單的代碼生成器。你可能更喜歡用模板(比如FreeMarker 或者Velocity)來(lái)生成你的源代碼,但是這個(gè)例子中的代碼是用PrintWriter寫(xiě)的。每個(gè)代表@EventListener注釋方法的ExecutableElementEvent被傳遞到DispatcherGenerator.generate,它可以給EventDispatcher寫(xiě)出源代碼。
- for(final Element m : methods) {
- // ensure that the element is a method
- if(m.getKind() == ElementKind.METHOD) {
- final ExecutableElement method = (ExecutableElement)m;
- results.add(generator.generate(method));
- }
- }
該EventDispatcherGenerator需要為每個(gè)方法產(chǎn)生一個(gè)Java源文件。一個(gè)注釋處理器用ProcessingEnvironment提供的過(guò)濾目標(biāo)來(lái)創(chuàng)建用于編寫(xiě)代碼的源文件。
- final JavaFileObject file = processingEnvironment.getFiler().createSourceFile(
- className, // ie: com.mydomain.example.OnMessageDispatcher
- method); // ie: com.mydomain.example.Listener.onMessage(MessageEvent)
在這個(gè)例子中,給定的過(guò)濾器ExecutableElement代表了已經(jīng)注釋的方法(createSourceFile中的第二個(gè)觀點(diǎn))。這會(huì)告訴環(huán)境你正在生成跟那個(gè)方法相關(guān)的源代碼,雖然不是必須的,但是比較有用。然后代碼用JavaFileObject來(lái)打開(kāi)一個(gè)書(shū)寫(xiě)器,并開(kāi)始生成源代碼。
- final Writer writer = file.openWriter();
- final PrintWriter pw = new PrintWriter(writer);
- pw.append("package ").append(packageName).println(';');
在@EventListener注釋中為方法指定值,從而在調(diào)用注釋方法之前產(chǎn)生一個(gè)if 語(yǔ)句,這個(gè)if語(yǔ)句可以過(guò)濾BusEventObjects。EventDispatcherGenerator把if 語(yǔ)句寫(xiě)進(jìn)源代碼,從而決定是否把事件對(duì)象分派到@EventListener方法中去。
- public final class EventBus {
- private static final EventDispatcher[] DISPATCHERS;
- static {
- final ServiceLoader<EventDispatcher> loader =
- ServiceLoader.load(EventDispatcher.class);
- final List<EventDispatcher> list = new ArrayList<EventDispatcher>();
- for(final EventDispatcher dispatcher : loader) {
- list.add(dispatcher);
- }
- DISPATCHERS = list.toArray(new EventDispatcher[list.size()]);
- }
- private EventBus() {
- }
- public static void dispatch(final BusEventObject object) {
- if(object == null) {
- throw new IllegalArgumentException("null event object");
- }
- for(final EventDispatcher dispatcher : DISPATCHERS) {
- dispatcher.dispatch(object);
- }
- }
- public static interface EventDispatcher {
- void dispatch(BusEventObject object);
- }
- }
#p#
對(duì)EventDispatcher進(jìn)行注冊(cè)
對(duì)于生成EventDispatcher來(lái)說(shuō),***的工作就是在一個(gè)服務(wù)文件中把它們?nèi)苛谐?,這樣在EventBus初始化的時(shí)候ServiceLoader能夠找得到它們。這個(gè)過(guò)程有幾個(gè)技巧。注釋處理器會(huì)給你一個(gè)列表,上面只列出了目前編譯中的方法。如果開(kāi)發(fā)人員不想馬上編譯他們的所有代碼,那么處理器代碼需要跟蹤已經(jīng)編譯好了的和那些正在編譯的方法。這便是ServiceRegistration類(lèi)所要做的工作。
首先,你需要告訴ServiceRegistration來(lái)讀在源路徑或者類(lèi)輸出目錄中現(xiàn)存的服務(wù)文件。接下來(lái),你添加新編譯的EventDispatcher類(lèi),然后把新的服務(wù)文件寫(xiě)到類(lèi)的輸出目錄中。
- final AnnotationHelper annotation = new AnnotationHelper(
- method,
- EventListenerAnnotationProcessor.ANNOTATION_TYPE,
- environment.getElementUtils());
- final String nameFilter = (String)annotation.getValue("name");
- final TypeElement sourceFilter = (TypeElement)environment.getTypeUtils().
- asElement((TypeMirror)annotation.getValue("source"));
- pw.println("\tpublic void dispatch(eventbus.BusEventObject event) {");
- pw.print("\t\tif(event instanceof ");
- pw.println(eventType.getQualifiedName());
- pw.println("\t\t\t\t&& nameFilter.matcher(event.getName()).matches()");
- pw.append("\t\t\t\t&& event.getSource() instanceof ").
- append(sourceFilter.getQualifiedName()).println(") {");
把所有的東西放在一起
EventBus工程的結(jié)果是一個(gè)簡(jiǎn)單的JAR文件,既有編譯時(shí)間又有運(yùn)行時(shí)間代碼(盡管你可以把它分解成兩個(gè)JAR文件)?,F(xiàn)在你需要寫(xiě)一個(gè)BusEventObject的子類(lèi),它可以通過(guò)EventBus被分派到listener。你還需要一個(gè)@EventListener方法,來(lái)接受你的新事件類(lèi)的實(shí)例。***:你需要一個(gè)類(lèi)來(lái)分派事件(從一個(gè)源文件)。
為了驗(yàn)證@EventListener方法生成了一個(gè)EventDispatcher,你需要讓編譯器知道運(yùn)行EventListenerAnnotationProcessor。這個(gè)過(guò)程根據(jù)IDE的不同而不同,但是驗(yàn)證了JAR文件是在類(lèi)路徑上或者在你工程庫(kù)里已經(jīng)足夠了。在一些IDE里,你需要對(duì)注釋處理器進(jìn)行手動(dòng)注冊(cè)。而這個(gè)例子中,MessageEvent類(lèi)將通過(guò)事件總線來(lái)分派:
- public class MessageEvent extends BusEventObject {
- private final String message;
- // constructor, etc.
- public String getMessage() {
- return message;
- }
- }
你需要一個(gè)@EventListener來(lái)接受MessageEvent對(duì)象,并進(jìn)行一些處理。請(qǐng)記住,你可以在任何類(lèi)中進(jìn)行這樣的操作,只要注釋處理器有機(jī)會(huì)看代碼。比如本例,代碼打開(kāi)了一個(gè)帶有消息的JoptionPane:
- @EventListener
- public static void onMessage(final MessageEvent event) {
- JOptionPane.showMessageDialog(
- null,
- event.getMessage(),
- "Message Event",
- JOptionPane.INFORMATION_MESSAGE);
- }
這個(gè)listener MessageListener是一個(gè)包羅萬(wàn)象的listener,可以接受所有通過(guò)事件總線分派過(guò)來(lái)的MessageEvent對(duì)象。
剩下唯一需要做的事情就是通過(guò)EventBus來(lái)分派MessageEvent:
- EventBus.dispatch(new MessageEvent(
- this,
- "message",
- "Hello World!"));
MessageEvent構(gòu)造器接管了事件的源,事件名字,以及消息。這個(gè)事件將去向任何由@EventListener注釋的方法,并接受MessageEvent作為他們的參數(shù)。
輸出文件在哪里?
#t#當(dāng)你完成編譯代碼以后,看看創(chuàng)建目錄。在每個(gè)帶有@EventListener方法的類(lèi)文件旁邊,那里應(yīng)該有*EventDispatcher Java源文件以及生成的類(lèi)文件。如果那些文件不在那里,請(qǐng)確保你已經(jīng)設(shè)置了你的創(chuàng)建目錄環(huán)境,以便于EventListenerAnnotationProcessor對(duì)于編譯器以及注釋處理工具是可見(jiàn)的(你可以回去參考"組織你的代碼"那一節(jié))。
我希望,你會(huì)發(fā)現(xiàn)使用ServiceLoader注釋讓你的生活更輕松。
原文:Implement Automatic Discovery in Your Java Code with Annotations 作者:Jason Morris