自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

玩轉(zhuǎn)Java注釋:自動調(diào)用監(jiān)聽器

原創(chuàng)
開發(fā) 后端
監(jiān)聽器在面向?qū)ο箝_發(fā)中是最常用的類,用于觸發(fā)啟動和停止事件。每個(gè)Java開發(fā)者都或多或少用過監(jiān)聽器,但你是否嘗試過通過Java注釋在事件總線模型上實(shí)現(xiàn)自動注冊并調(diào)用監(jiān)聽器么?本文介紹ServiceLoader注釋的使用方法。

【51CTO精選譯文】Listener,直譯為偵聽器或監(jiān)聽器,在面向?qū)ο蟮拈_發(fā)中經(jīng)常需要用到。如果你需要啟動或者停止基于Java的Web系統(tǒng)中不同部分的代碼,那么你可以使用一個(gè)簡單的ServletContentListener來監(jiān)聽容器(container)里面的啟動事件和停止事件。這個(gè)監(jiān)聽器可以使用java.util.ServiceLoader去尋找這些被偵聽事件所對應(yīng)的已注冊類。

這個(gè)方法不錯,但是如果添加一個(gè)編譯時(shí)間注釋處理器會不會更好呢?如果你用@Lifecycle(LifecycleEvent.STARTUP)注釋一個(gè)靜態(tài)方法,它將會在開機(jī)的時(shí)候被調(diào)用(在關(guān)機(jī)的時(shí)候被關(guān)掉)。處理器會產(chǎn)生類,并為了ServiceLoader而注冊它們。你也可以把同樣的機(jī)制用在任何事件總線(event-bus)模型上:在編譯的時(shí)候注冊listener,并且?guī)в凶⑨?,?dāng)事件被觸發(fā)的時(shí)候總線會自動調(diào)用他們。從本質(zhì)上講,你可以通過注釋實(shí)現(xiàn)用ServiceLoader在運(yùn)行時(shí)間自動發(fā)現(xiàn)代碼。

實(shí)際過程中,其概念如下:

1. 你用@EventListener對方法進(jìn)行注釋(可能包含一些元信息(meta-info))。

2. 注釋處理器為每個(gè)@EventListener方法生成一個(gè)EventDispatcher,包括注釋中元信息需要的過濾器。

3.  事件總線利用java.util.ServiceLoader找到EventDispatcher的實(shí)現(xiàn)。

當(dāng)EventBus.dispatch被調(diào)用時(shí),任何有興趣的、已經(jīng)用@EventListener注釋的方法都會被調(diào)用。

本文將對事件總線創(chuàng)建的幾個(gè)必要步驟進(jìn)行分析,從而闡明這一概念。事件總線不需要任何手動注冊就可以調(diào)用已注釋了的listener方法。我們將從Eventbus開始討論,然后是注釋處理器,***是一個(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)可能會刪除注釋處理器所生成的類,讓你摸不著頭腦。

以下內(nèi)容將會解釋這些工程中的代碼是如何工作的,而且為了便于說明還提供了一些程序片段。

注釋和事件

你需要的***個(gè)東西是一個(gè)@EventListener注釋,用來標(biāo)識那些偵聽事件的方法。下面是一個(gè)EventListener注釋的例子,它只能用來注釋方法。在代碼編譯后它將被拋棄,因?yàn)樗械奶幚矶际菍υ创a進(jìn)行的。

  1. @Target(ElementType.METHOD)  
  2. @Retention(RetentionPolicy.SOURCE)  
  3. public @interface EventListener {  
  4.     String name() default ".*";  
  5.     Class<?> source() default Object.class;  
  6. }  
  7.  

由于這個(gè)例子是一個(gè)事件總線模型,那么listener方法***只接受它們唯一感興趣的事件。為了便于執(zhí)行這個(gè)規(guī)則,在BusEventObject類中包含了你想過濾的名字(以@EventListener注釋里面的名字為基礎(chǔ))。為了讓過濾事件更加簡單,這個(gè)普通的EventObject類中還有一個(gè)附加的名字域。BusEventObject也作為一個(gè)標(biāo)識,可以標(biāo)識出通過EventBus分派的事件。

  1. public abstract class BusEventObject extends EventObject {  
  2.     private final String name;  
  3.     public BusEventObject(  
  4.             final Object source,  
  5.             final String name) {  
  6.         super(source);  
  7.         if(name == null || name.isEmpty()) {  
  8.             throw new IllegalArgumentException("empty or null name");  
  9.         }  
  10.         this.name = name;  
  11.     }  
  12.     public String getName() {  
  13.         return name;  
  14.     }  
  15. }  
  16.  

注釋處理器

為了開始寫注釋處理器,你首先應(yīng)該熟悉javax.annotation.processing 和 javax.lang.model的包組。一般來說,你可以直接掠過執(zhí)行處理器接口,進(jìn)入抽象類javax.annotation.processing.AbstractProcessor。AbstractProcessor需要一些關(guān)于實(shí)現(xiàn)的信息,這些信息用來提供注釋。例子中的EventListenerAnnotationProcessor代碼聲明如下所示:

  1. @SupportedSourceVersion(SourceVersion.RELEASE_5)  
  2. @SupportedAnnotationTypes(EventListenerAnnotationProcessor.ANNOTATION_TYPE)  
  3. public class EventListenerAnnotationProcessor extends AbstractProcessor {  
  4.  

@SupportedSourceVersion告訴AbstractProcessor你只想要用java5或者更高版本寫的源文件;而@SupportedAnnotationTypes告訴AbstractProcessor哪個(gè)注釋是你感興趣的(EventListener.class.getName()不會作為一個(gè)注釋值起作用,因?yàn)榫幾g器不能計(jì)算這種表達(dá)式的值)。

  1. public static final String ANNOTATION_TYPE = "eventbus.EventListener";  
  2.  

為了簡單起見,注釋處理器被分成兩個(gè)主要的類(EventListenerAnnotationProcessor 和EventDispatcherGenerator)以及一個(gè)通用工具類(ServiceRegistration)。為了便于編譯器注釋工具執(zhí)行EventListenerAnnotationProcessor,你需要用一個(gè)服務(wù)文件來注冊它(編譯器也使用ServiceLoader)。

eventbus.processor.EventListenerAnnotationProcessor

服務(wù)注冊文件(META-INF/services/javax.annotation.processing.Processor)是根據(jù)ServiceLoader一定能找到的接口來命名的。

EventListenerAnnotationProcessor.process()方法的***個(gè)行動就是找到這輪編譯中所有的@EventListener方法。

  1. final Elements elements = processingEnv.getElementUtils();  
  2. final TypeElement annotation = elements.getTypeElement(ANNOTATION_TYPE);  
  3. final Set<? extends Element> methods =  
  4.         roundEnv.getElementsAnnotatedWith(annotation);  
  5.  

Element對象很像編譯器以及注釋處理器的反射對象(reflection objects)。TypeElement就像是類,而ExecutableElement跟構(gòu)造器或者方法類似。RoundEnvironment(代表本輪注釋處理)將會返回到被@EventListener 注釋的Element。

EventDispatcherGenerator

EventDispatcherGenerator是一個(gè)非常簡單的代碼生成器。你可能更喜歡用模板(比如FreeMarker 或者Velocity)來生成你的源代碼,但是這個(gè)例子中的代碼是用PrintWriter寫的。每個(gè)代表@EventListener注釋方法的ExecutableElementEvent被傳遞到DispatcherGenerator.generate,它可以給EventDispatcher寫出源代碼。

  1. for(final Element m : methods) {  
  2.     // ensure that the element is a method  
  3.     if(m.getKind() == ElementKind.METHOD) {  
  4.         final ExecutableElement method = (ExecutableElement)m;  
  5.         results.add(generator.generate(method));  
  6.     }  
  7. }  
  8.  

該EventDispatcherGenerator需要為每個(gè)方法產(chǎn)生一個(gè)Java源文件。一個(gè)注釋處理器用ProcessingEnvironment提供的過濾目標(biāo)來創(chuàng)建用于編寫代碼的源文件。

  1. final JavaFileObject file = processingEnvironment.getFiler().createSourceFile(  
  2.         className, // ie: com.mydomain.example.OnMessageDispatcher  
  3.         method);     // ie: com.mydomain.example.Listener.onMessage(MessageEvent)  
  4.  

在這個(gè)例子中,給定的過濾器ExecutableElement代表了已經(jīng)注釋的方法(createSourceFile中的第二個(gè)觀點(diǎn))。這會告訴環(huán)境你正在生成跟那個(gè)方法相關(guān)的源代碼,雖然不是必須的,但是比較有用。然后代碼用JavaFileObject來打開一個(gè)書寫器,并開始生成源代碼。

  1. final Writer writer = file.openWriter();  
  2. final PrintWriter pw = new PrintWriter(writer);  
  3. pw.append("package ").append(packageName).println(';');  
  4.  

在@EventListener注釋中為方法指定值,從而在調(diào)用注釋方法之前產(chǎn)生一個(gè)if 語句,這個(gè)if語句可以過濾BusEventObjects。EventDispatcherGenerator把if 語句寫進(jìn)源代碼,從而決定是否把事件對象分派到@EventListener方法中去。

  1. public final class EventBus {  
  2.     private static final EventDispatcher[] DISPATCHERS;  
  3.     static {  
  4.         final ServiceLoader<EventDispatcher> loader =  
  5.                 ServiceLoader.load(EventDispatcher.class);  
  6.         final List<EventDispatcher> list = new ArrayList<EventDispatcher>();  
  7.         for(final EventDispatcher dispatcher : loader) {  
  8.             list.add(dispatcher);  
  9.         }  
  10.         DISPATCHERS = list.toArray(new EventDispatcher[list.size()]);  
  11.     }  
  12.     private EventBus() {  
  13.     }  
  14.     public static void dispatch(final BusEventObject object) {  
  15.         if(object == null) {  
  16.             throw new IllegalArgumentException("null event object");  
  17.         }  
  18.         for(final EventDispatcher dispatcher : DISPATCHERS) {  
  19.             dispatcher.dispatch(object);  
  20.         }  
  21.     }   
  22.     public static interface EventDispatcher {  
  23.    
  24.         void dispatch(BusEventObject object);  
  25.     }  
  26. }  
  27.  

#p#

對EventDispatcher進(jìn)行注冊

對于生成EventDispatcher來說,***的工作就是在一個(gè)服務(wù)文件中把它們?nèi)苛谐?,這樣在EventBus初始化的時(shí)候ServiceLoader能夠找得到它們。這個(gè)過程有幾個(gè)技巧。注釋處理器會給你一個(gè)列表,上面只列出了目前編譯中的方法。如果開發(fā)人員不想馬上編譯他們的所有代碼,那么處理器代碼需要跟蹤已經(jīng)編譯好了的和那些正在編譯的方法。這便是ServiceRegistration類所要做的工作。

首先,你需要告訴ServiceRegistration來讀在源路徑或者類輸出目錄中現(xiàn)存的服務(wù)文件。接下來,你添加新編譯的EventDispatcher類,然后把新的服務(wù)文件寫到類的輸出目錄中。

  1. final AnnotationHelper annotation = new AnnotationHelper(  
  2.                 method,  
  3.                 EventListenerAnnotationProcessor.ANNOTATION_TYPE,  
  4.                 environment.getElementUtils());  
  5.    
  6. final String nameFilter = (String)annotation.getValue("name");  
  7. final TypeElement sourceFilter = (TypeElement)environment.getTypeUtils().  
  8.         asElement((TypeMirror)annotation.getValue("source"));  
  9.    
  10. pw.println("\tpublic void dispatch(eventbus.BusEventObject event) {");  
  11.    
  12. pw.print("\t\tif(event instanceof ");  
  13. pw.println(eventType.getQualifiedName());  
  14. pw.println("\t\t\t\t&& nameFilter.matcher(event.getName()).matches()");  
  15. pw.append("\t\t\t\t&& event.getSource() instanceof ").  
  16.         append(sourceFilter.getQualifiedName()).println(") {");  
  17.  

把所有的東西放在一起

EventBus工程的結(jié)果是一個(gè)簡單的JAR文件,既有編譯時(shí)間又有運(yùn)行時(shí)間代碼(盡管你可以把它分解成兩個(gè)JAR文件)。現(xiàn)在你需要寫一個(gè)BusEventObject的子類,它可以通過EventBus被分派到listener。你還需要一個(gè)@EventListener方法,來接受你的新事件類的實(shí)例。***:你需要一個(gè)類來分派事件(從一個(gè)源文件)。

為了驗(yàn)證@EventListener方法生成了一個(gè)EventDispatcher,你需要讓編譯器知道運(yùn)行EventListenerAnnotationProcessor。這個(gè)過程根據(jù)IDE的不同而不同,但是驗(yàn)證了JAR文件是在類路徑上或者在你工程庫里已經(jīng)足夠了。在一些IDE里,你需要對注釋處理器進(jìn)行手動注冊。而這個(gè)例子中,MessageEvent類將通過事件總線來分派:

  1. public class MessageEvent extends BusEventObject {  
  2.     private final String message;  
  3.     // constructor, etc.  
  4.     public String getMessage() {  
  5.         return message;  
  6.     }  
  7. }  
  8.  

你需要一個(gè)@EventListener來接受MessageEvent對象,并進(jìn)行一些處理。請記住,你可以在任何類中進(jìn)行這樣的操作,只要注釋處理器有機(jī)會看代碼。比如本例,代碼打開了一個(gè)帶有消息的JoptionPane:

  1. @EventListener 
  2. public static void onMessage(final MessageEvent event) {  
  3.     JOptionPane.showMessageDialog(  
  4.             null,  
  5.             event.getMessage(),  
  6.             "Message Event",  
  7.             JOptionPane.INFORMATION_MESSAGE);  
  8. }  
  9.  

這個(gè)listener MessageListener是一個(gè)包羅萬象的listener,可以接受所有通過事件總線分派過來的MessageEvent對象。

剩下唯一需要做的事情就是通過EventBus來分派MessageEvent:

  1. EventBus.dispatch(new MessageEvent(  
  2.         this,  
  3.         "message",  
  4.         "Hello World!"));  
  5.  

MessageEvent構(gòu)造器接管了事件的源,事件名字,以及消息。這個(gè)事件將去向任何由@EventListener注釋的方法,并接受MessageEvent作為他們的參數(shù)。

輸出文件在哪里?

#t#當(dāng)你完成編譯代碼以后,看看創(chuàng)建目錄。在每個(gè)帶有@EventListener方法的類文件旁邊,那里應(yīng)該有*EventDispatcher Java源文件以及生成的類文件。如果那些文件不在那里,請確保你已經(jīng)設(shè)置了你的創(chuàng)建目錄環(huán)境,以便于EventListenerAnnotationProcessor對于編譯器以及注釋處理工具是可見的(你可以回去參考"組織你的代碼"那一節(jié))。

我希望,你會發(fā)現(xiàn)使用ServiceLoader注釋讓你的生活更輕松。

原文:Implement Automatic Discovery in Your Java Code with Annotations  作者:Jason Morris

責(zé)任編輯:yangsai 來源: 51CTO.com
相關(guān)推薦

2011-03-21 16:21:49

Oracle監(jiān)聽口令監(jiān)聽器

2009-07-08 17:39:23

Servlet監(jiān)聽器

2009-09-27 17:46:22

Hibernate監(jiān)聽

2011-05-16 10:14:11

Hibernate

2009-11-09 10:03:09

WCF通道監(jiān)聽器

2010-02-22 15:06:31

WCF信道監(jiān)聽器

2023-01-06 08:55:00

2010-04-23 18:00:31

2011-06-01 14:55:24

Android Service 監(jiān)聽器

2009-07-06 13:48:53

Servlet監(jiān)聽器

2009-01-03 13:37:26

Oracle監(jiān)聽器Oracle服務(wù)器Oracle網(wǎng)絡(luò)配置

2009-11-18 18:28:27

Oracle監(jiān)聽器

2009-06-22 09:23:18

事件監(jiān)聽器

2012-02-03 13:27:16

2010-04-19 15:38:10

2010-08-09 11:06:01

Flex事件機(jī)制

2010-04-23 10:13:18

Oracle監(jiān)聽

2020-04-20 11:09:49

過濾器監(jiān)聽器 Web

2020-12-15 10:46:29

事件監(jiān)聽器Spring Boot

2014-07-14 13:03:26

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號