
 @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í)采用一種透明的方式,原因有二:
  出于這些原因,我將引入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è)插件組成:
  采用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)題我將留給讀者去思考。
新聞熱點(diǎn)
疑難解答
圖片精選