
@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)
疑難解答
圖片精選