国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 開(kāi)發(fā) > 綜合 > 正文

在Eclipse RCP中實(shí)現(xiàn)反轉(zhuǎn)控制(IoC)

2024-07-21 02:14:41
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友
  eclipse富客戶(hù)平臺(tái)(rcp)是一個(gè)功能強(qiáng)大的軟件平臺(tái),它基于插件間的互連與協(xié)作,允許開(kāi)發(fā)人員構(gòu)建通用的應(yīng)用程序。rcp使開(kāi)發(fā)人員可以集中精力進(jìn)行應(yīng)用程序業(yè)務(wù)代碼的開(kāi)發(fā),而不需要花費(fèi)時(shí)間重新發(fā)明輪子編寫(xiě)應(yīng)用程序管理的邏輯。

  反轉(zhuǎn)控制(inversion of control, ioc)和依賴(lài)注入(dependency injection, di)是兩種編程模式,可用于減少程序間的耦合。它們遵循一個(gè)簡(jiǎn)單的原則:你不要?jiǎng)?chuàng)建你的對(duì)象;你描述它們應(yīng)當(dāng)如何被創(chuàng)建。你不要實(shí)例化你的部件所需要對(duì)象或直接定位你的部件所需要的服務(wù);相反,你描述哪個(gè)部件需要哪些服務(wù),其它人(通常是一個(gè)容器)負(fù)責(zé)將它們連接到一起。這也被認(rèn)為是好萊塢法則:don't call us--we'll call you。

  本文將描述一個(gè)簡(jiǎn)單的方式在eclipse rcp應(yīng)用程序中使用依賴(lài)注入。為了避免污染eclipse 平臺(tái)的基礎(chǔ)結(jié)構(gòu)以及透明地在rcp之上添加ioc框架,我們將結(jié)合使用運(yùn)行時(shí)字節(jié)碼操作技術(shù)(使用 objectweb asm庫(kù))、java類(lèi)加載代理(使用java.lang.instrument包)以及java annotation。

  什么是eclipse富客戶(hù)平臺(tái)?

  用一句話(huà)來(lái)講,富客戶(hù)平臺(tái)是一個(gè)類(lèi)庫(kù)、軟件框架的集合,它是一個(gè)用于構(gòu)建單機(jī)和連網(wǎng)應(yīng)用程序的運(yùn)行時(shí)環(huán)境。

  盡管eclipse被認(rèn)為是構(gòu)建集成開(kāi)發(fā)環(huán)境(ide)的框架,從3.0開(kāi)始,eclipse整個(gè)產(chǎn)品進(jìn)行了重構(gòu),分割成各種不同的部件,它些部件可以用于構(gòu)建任意的應(yīng)用程序。其中的一個(gè)子集構(gòu)成了富客戶(hù)平臺(tái),它包含以下元素:基本的運(yùn)行時(shí)環(huán)境、用戶(hù)界面組件(swt和jface)、插件以及 osgi層。圖1顯示了eclipse平臺(tái)的主要部件。


圖1. eclipse平臺(tái)的主要部件

  整個(gè)eclipse平臺(tái)是基于插件和擴(kuò)展點(diǎn)。一個(gè)插件是一個(gè)可以獨(dú)立開(kāi)發(fā)和發(fā)布的最小的功能單元。它通常打包成一個(gè)jar文件,通過(guò)添加功能(例如,一個(gè)編輯器、一個(gè)工具欄按鈕、或一個(gè)編譯器)來(lái)擴(kuò)展平臺(tái)。整個(gè)平臺(tái)是一個(gè)相互連接和通信的插件的集合。一個(gè)擴(kuò)展點(diǎn)是一個(gè)互相連接的端點(diǎn),其它插件可以用它提供額外的功能(在eclipse中稱(chēng)為擴(kuò)展)。擴(kuò)展和擴(kuò)展點(diǎn)定義在xml配置文件中,xml文件與插件捆綁在一起。

  插件模式加強(qiáng)了關(guān)注分離的概念,插件間的強(qiáng)連接和通訊需要通過(guò)配線(xiàn)進(jìn)行設(shè)置它們之間的依賴(lài)。典型的例子源自需要定位應(yīng)用程序所需要的單子服務(wù),例如數(shù)據(jù)庫(kù)連接池、日志處理或用戶(hù)保存的首選項(xiàng)。反轉(zhuǎn)控制和依賴(lài)注入是消除這種依賴(lài)的可行解決方案。

  反轉(zhuǎn)控制和依賴(lài)注入

  反轉(zhuǎn)控制是一種編程模式,它關(guān)注服務(wù)(或應(yīng)用程序部件)是如何定義的以及他們應(yīng)該如何定位他們依賴(lài)的其它服務(wù)。通常,通過(guò)一個(gè)容器或定位框架來(lái)獲得定義和定位的分離,容器或定位框架負(fù)責(zé):
  • 保存可用服務(wù)的集合
  • 提供一種方式將各種部件與它們依賴(lài)的服務(wù)綁定在一起
  • 為應(yīng)用程序代碼提供一種方式來(lái)請(qǐng)求已配置的對(duì)象(例如,一個(gè)所有依賴(lài)都滿(mǎn)足的對(duì)象), 這種方式可以確保該對(duì)象需要的所有相關(guān)的服務(wù)都可用。
  現(xiàn)有的框架實(shí)際上使用以下三種基本技術(shù)的框架執(zhí)行服務(wù)和部件間的綁定:
  • 類(lèi)型1 (基于接口): 可服務(wù)的對(duì)象需要實(shí)現(xiàn)一個(gè)專(zhuān)門(mén)的接口,該接口提供了一個(gè)對(duì)象,可以從用這個(gè)對(duì)象查找依賴(lài)(其它服務(wù))。早期的容器excalibur使用這種模式。
  • 類(lèi)型2 (基于setter): 通過(guò)javabean的屬性(setter方法)為可服務(wù)對(duì)象指定服務(wù)。hivemind和spring采用這種方式。
  • 類(lèi)型3 (基于構(gòu)造函數(shù)): 通過(guò)構(gòu)造函數(shù)的參數(shù)為可服務(wù)對(duì)象指定服務(wù)。picocontainer只使用這種方式。hivemind和spring也使用這種方式。
  我們將采用第二種方式的一個(gè)變種,通過(guò)標(biāo)記方式來(lái)提供服務(wù)(下面示例程序的源代碼可以在資源部分得到)。 聲明一個(gè)依賴(lài)可以表示為:
 @injected public void aservicingmethod(service s1, anotherservice s2) {   // 將s1和s2保存到類(lèi)變量,需要時(shí)可以使用 }
  反轉(zhuǎn)控制容器將查找injected注釋?zhuān)褂谜?qǐng)求的參數(shù)調(diào)用該方法。我們想將ioc引入eclipse平臺(tái),服務(wù)和可服務(wù)對(duì)象將打包放入eclipse插件中。插件定義一個(gè)擴(kuò)展點(diǎn) (名稱(chēng)為com.onjava.servicelocator.servicefactory),它可以向程序提供服務(wù)工廠。當(dāng)可服務(wù)對(duì)象需要配置時(shí),插件向一個(gè)工廠請(qǐng)求一個(gè)服務(wù)實(shí)例。servicelocator類(lèi)將完成所有的工作,下面的代碼描述該類(lèi)(我們省略了分析擴(kuò)展點(diǎn)的部分,因?yàn)樗容^直觀):
    /**      * injects the requested dependencies into the parameter object. it scans      * the serviceable object looking for methods tagged with the      * {@link injected} annotation.parameter types are extracted from the      * matching method. an instance of each type is created from the registered      * factories (see {@link iservicefactory}). when instances for all the      * parameter types have been created the method is invoked and the next one      * is examined.      *       * @param serviceable      *            the object to be serviced      * @throws serviceexception      */     public static void service(object serviceable) throws serviceexception {         servicelocator sl = getinstance();         if (sl.isalreadyserviced(serviceable)) {             // prevent multiple initializations due to             // constructor hierarchies             system.out.println("object " + serviceable                     + " has already been configured ");             return;         }         system.out.println("configuring " + serviceable);         // parse the class for the requested services         for (method m : serviceable.getclass().getmethods()) {             boolean skip = false;             injected ann = m.getannotation(injected.class);             if (ann != null) {                 object[] services = new object[m.getparametertypes().length];                 int i = 0;                 for (class<?> class : m.getparametertypes()) {                     iservicefactory factory = sl.getfactory(class, ann                             .optional());                     if (factory == null) {                         skip = true;                         break;                     }                     object service = factory.getserviceinstance();                     // sanity check: verify that the returned                     // service's class is the expected one                     // from the method                     assert (service.getclass().equals(class) || class                             .isassignablefrom(service.getclass()));                     services[i++] = service;                 }                 try {                     if (!skip)                         m.invoke(serviceable, services);                 } catch (illegalaccessexception iae) {                     if (!ann.optional())                         throw new serviceexception(                                 "unable to initialize services on "                                         + serviceable + ": " + iae.getmessage(), iae);                 } catch (invocationtargetexception ite) {                     if (!ann.optional())                         throw new serviceexception(                                 "unable to initialize services on "                                         + serviceable + ": " + ite.getmessage(), ite);                 }             }         }         sl.setasserviced(serviceable);     }


  由于服務(wù)工廠返回的服務(wù)可能也是可服務(wù)對(duì)象,這種策略允許定義服務(wù)的層次結(jié)構(gòu)(然而目前不支持循環(huán)依賴(lài))。

  asm和java.lang.instrument代理

  前節(jié)所述的各種注入策略通常依靠容器提供一個(gè)入口點(diǎn),應(yīng)用程序使用入口點(diǎn)請(qǐng)求已正確配置的對(duì)象。然而,我們希望當(dāng)開(kāi)發(fā)ioc插件時(shí)采用一種透明的方式,原因有二:

  • rcp采用了復(fù)雜的類(lèi)加載器和實(shí)例化策略(想一下createexecutableextension()) 來(lái)維護(hù)插件的隔離和強(qiáng)制可見(jiàn)性限制。我們不希望修改或替換這些策略而引入我們的基于容器的實(shí)例化規(guī)則。
  • 顯式地引用這樣一個(gè)入口點(diǎn)(service locator插件中定義的service()方法) 將強(qiáng)迫應(yīng)用程序采用一種顯式地模式和邏輯來(lái)獲取已初始化的部件。這表示應(yīng)用程序代碼出現(xiàn)了library lock-in。我們希望定義可以協(xié)作的插件,但不需要顯示地引用它的基代碼。

  出于這些原因,我將引入java轉(zhuǎn)換代理,它定義在 java.lang.instrument 包中,j2se 5.0及更高版本支持。一個(gè)轉(zhuǎn)換代理是一個(gè)實(shí)現(xiàn)了 java.lang.instrument.classfiletransformer接口的對(duì)象,該接口只定義了一個(gè) transform()方法。當(dāng)一個(gè)轉(zhuǎn)換實(shí)例注冊(cè)到j(luò)vm時(shí),每當(dāng)jvm創(chuàng)建一個(gè)類(lèi)的對(duì)象時(shí)都會(huì)調(diào)用它。這個(gè)轉(zhuǎn)換器可以訪問(wèn)類(lèi)的字節(jié)碼,在它被jvm加載之前可以修改類(lèi)的表示形式。

  可以使用jvm命令行參數(shù)注冊(cè)轉(zhuǎn)換代理,形式為-javaagent:jarpath[=options],其中jarpath是包含代碼類(lèi)的jar文件的路徑, options是代理的參數(shù)字符串。代理jar文件使用一個(gè)特殊的manifest屬性指定實(shí)際的代理類(lèi),該類(lèi)必須定義一個(gè) public static void premain(string options, instrumentation inst)方法。代理的premain()方法將在應(yīng)用程序的main()執(zhí)行之前被調(diào)用,并且可以通過(guò)傳入的java.lang.instrument.instrumentation對(duì)象實(shí)例注冊(cè)一個(gè)轉(zhuǎn)換器。

  在我們的例子中,我們定義一個(gè)代理執(zhí)行字節(jié)碼操作,透明地添加對(duì)ioc容器(service locator 插件)的調(diào)用。代理根據(jù)是否出現(xiàn)serviceable注釋來(lái)標(biāo)識(shí)可服務(wù)的對(duì)象。接著它將修改所有的構(gòu)造函數(shù),添加對(duì)ioc容器的回調(diào),這樣就可以在實(shí)例化時(shí)配置和初始化對(duì)象。

  假設(shè)我們有一個(gè)對(duì)象依賴(lài)于外部服務(wù)(injected注釋):

@serviceable public class serviceableobject {   public serviceableobject() {     system.out.println("initializing...");   }   @injected public void aservicingmethod(service s1, anotherservice s2) {     // ... omissis ...   } }

  當(dāng)代理修改之后,它的字節(jié)碼與下面的類(lèi)正常編譯的結(jié)果一樣:

@serviceable public class serviceableobject {   public serviceableobject() {     servicelocator.service(this);     system.out.println("initializing...");   }   @injected public void aservicingmethod(service s1, anotherservice s2) {     // ... omissis ...   } }

  采用這種方式,我們就能夠正確地配置可服務(wù)對(duì)象,并且不需要開(kāi)發(fā)人員對(duì)依賴(lài)的容器進(jìn)行硬編碼。開(kāi)發(fā)人員只需要用serviceable注釋標(biāo)記可服務(wù)對(duì)象。代理的代碼如下:

 public class ioctransformer implements classfiletransformer {     public byte[] transform(classloader loader, string classname,             class<?> classbeingredefined, protectiondomain protectiondomain,             byte[] classfilebuffer) throws illegalclassformatexception {         system.out.println("loading " + classname);         classreader creader = new classreader(classfilebuffer);         // parse the class file         constructorvisitor cv = new constructorvisitor();         classannotationvisitor cav = new classannotationvisitor(cv);         creader.accept(cav, true);         if (cv.getconstructors().size() > 0) {             system.out.println("enhancing " + classname);             // generate the enhanced-constructor class             classwriter cw = new classwriter(false);             classconstructorwriter writer = new classconstructorwriter(cv                     .getconstructors(), cw);             creader.accept(writer, false);             return cw.tobytearray();         } else             return null;     }     public static void premain(string agentargs, instrumentation inst) {         inst.addtransformer(new ioctransformer());     } }

  constructorvisitor、classannotationvisitor、 classwriter以及classconstructorwriter使用objectweb asm庫(kù)執(zhí)行字節(jié)碼操作。

  asm使用visitor模式以事件流的方式處理類(lèi)數(shù)據(jù)(包括指令序列)。當(dāng)解碼一個(gè)已有的類(lèi)時(shí), asm為我們生成一個(gè)事件流,調(diào)用我們的方法來(lái)處理這些事件。當(dāng)生成一個(gè)新類(lèi)時(shí),過(guò)程相反:我們生成一個(gè)事件流,asm庫(kù)將其轉(zhuǎn)換成一個(gè)類(lèi)。注意,這里描述的方法不依賴(lài)于特定的字節(jié)碼庫(kù)(這里我們使用的是asm);其它的解決方法,例如bcel或javassist也是這樣工作的。

  我們不再深入研究asm的內(nèi)部結(jié)構(gòu)。知道constructorvisitor和 classannotationvisitor對(duì)象用于查找標(biāo)記為serviceable類(lèi),并收集它們的構(gòu)造函數(shù)已經(jīng)足夠了。他們的源代碼如下:

         public class classannotationvisitor extends classadapter {     private boolean matches = false;     public classannotationvisitor(classvisitor cv) {         super(cv);     }     @override     public annotationvisitor visitannotation(string desc, boolean visible) {         if (visible && desc.equals("lcom/onjava/servicelocator/annot/serviceable;")) {             matches = true;         }         return super.visitannotation(desc, visible);     }     @override     public methodvisitor visitmethod(int access, string name, string desc,             string signature, string[] exceptions) {         if (matches)             return super.visitmethod(access, name, desc, signature, exceptions);         else {             return null;         }     } } public class constructorvisitor extends emptyvisitor {     private set<method> constructors;     public constructorvisitor() {         constructors = new hashset<method>();     }     public set<method> getconstructors() {         return constructors;     }     @override     public methodvisitor visitmethod(int access, string name, string desc,             string signature, string[] exceptions) {         type t = type.getreturntype(desc);         if (name.indexof("<init>") != -1 && t.equals(type.void_type)) {             constructors.add(new method(name, desc));         }         return super.visitmethod(access, name, desc, signature, exceptions);     } }

  一個(gè)classconstructorwriter的實(shí)例將修改收集的每個(gè)構(gòu)造函數(shù),注入對(duì)service locator插件的調(diào)用:

com.onjava.servicelocator.servicelocator.service(this);

  asm需要下面的指令以完成工作:

// mv is an asm method visitor, // a class which allows method manipulation mv.visitvarinsn(aload, 0); mv.visitmethodinsn(     invokestatic,      "com/onjava/servicelocator/servicelocator",      "service",      "(ljava/lang/object;)v");

  第一個(gè)指令將this對(duì)象引用加載到棧,第二指令將使用它。它二個(gè)指令調(diào)用servicelocator的靜態(tài)方法。

  eclipse rcp應(yīng)用程序示例

  現(xiàn)在我們具有了構(gòu)建應(yīng)用程序的所有元素。我們的例子可用于顯示用戶(hù)感興趣的名言警句。它由四個(gè)插件組成:

  • service locator插件,提供ioc框架
  • fortuneservice插件,提供服務(wù)管理fortune cookie
  • fortuneinterface插件,發(fā)布訪問(wèn)服務(wù)所需的公共接口
  • fortuneclient插件,提供eclipse應(yīng)用程序,以eclipse視圖中顯示名言警句。

  采用ioc設(shè)計(jì),使服務(wù)的實(shí)現(xiàn)與客戶(hù)分離;服務(wù)實(shí)例可以修改,對(duì)客戶(hù)沒(méi)有影響。圖2顯示了插件間的依賴(lài)關(guān)系。


圖2. 插件間的依賴(lài)關(guān)系: servicelocator和接口定義使服務(wù)和客戶(hù)分離。

  如前面所述,service locator將客戶(hù)和服務(wù)綁定到一起。fortuneinterface只定義了公共接口 ifortunecookie,客戶(hù)可以用它訪問(wèn)cookie消息:

public interface ifortunecookie {         public string getmessage(); }

  fortuneservice提供了一個(gè)簡(jiǎn)單的服務(wù)工廠,用于創(chuàng)建ifortunecookie的實(shí)現(xiàn):

public class fortuneservicefactory implements iservicefactory {     public object getserviceinstance() throws serviceexception {         return new fortunecookieimpl();     }     // ... omissis ... }

  工廠注冊(cè)到service locator插件的擴(kuò)展點(diǎn),在plugin.xml文件:

<?xml version="1.0" encoding="utf-8"?> <?eclipse version="3.0"?> <plugin> <extension   point="com.onjava.servicelocator.servicefactory">   <servicefactory     class="com.onjava.fortuneservice.fortuneservicefactory"     id="com.onjava.fortuneservice.fortuneservicefactory"     name="fortune service factory"     resourceclass="com.onjava.fortuneservice.ifortunecookie"/> </extension> </plugin>

  resourceclass屬性定義了該工廠所提供的服務(wù)的類(lèi)。在fortuneclient插件中, eclipse視圖使用該服務(wù):

 @serviceable public class view extends viewpart {     public static final string id = "fortuneclient.view";     private ifortunecookie cookie;     @injected(optional = false)     public void setdate(ifortunecookie cookie) {         this.cookie = cookie;     }     public void createpartcontrol(composite parent) {         label l = new label(parent, swt.wrap);         l.settext("your fortune cookie is:/n" + cookie.getmessage());     }     public void setfocus() {     } }

  注意這里出現(xiàn)了serviceable和injected注釋?zhuān)糜诙x依賴(lài)的外部服務(wù),并且沒(méi)有引用任何服務(wù)代碼。最終結(jié)果是,createpartcontrol() 可以自由地使用cookie對(duì)象,可以確保它被正確地初始化。示例程序如圖3所示


圖3. 示例程序

  結(jié)論

  本文我討論了如何結(jié)合使用一個(gè)強(qiáng)大的編程模式--它簡(jiǎn)化了代碼依賴(lài)的處理(反轉(zhuǎn)控制),與java客戶(hù)端程序(eclipse rcp)。即使我沒(méi)有處理影響這個(gè)問(wèn)題的更多細(xì)節(jié),我已經(jīng)演示了一個(gè)簡(jiǎn)單的應(yīng)用程序的服務(wù)和客戶(hù)是如何解耦的。我還描述了當(dāng)開(kāi)發(fā)客戶(hù)和服務(wù)時(shí), eclipse插件技術(shù)是如何實(shí)現(xiàn)關(guān)注分離的。然而,還有許多有趣的因素仍然需要去探究,例如,當(dāng)服務(wù)不再需要時(shí)的清理策略,或使用mock-up服務(wù)對(duì)客戶(hù)端插件進(jìn)行單元測(cè)試,這些問(wèn)題我將留給讀者去思考。

發(fā)表評(píng)論 共有條評(píng)論
用戶(hù)名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 分宜县| 南丹县| 荆门市| 隆子县| 泾源县| 永城市| 颍上县| 横峰县| 大石桥市| 山丹县| 黔东| 梓潼县| 临沭县| 吴川市| 兴和县| 郸城县| 莱阳市| 商河县| 白银市| 汉沽区| 临夏县| 老河口市| 台北县| 凤山市| 南召县| 景洪市| 安阳市| 五峰| 河南省| 大新县| 莱阳市| 龙海市| 石狮市| 依安县| 通辽市| 攀枝花市| 临泉县| 钦州市| 涞水县| 清流县| 宜黄县|