劉小可(河南省科學(xué)技術(shù)信息研究院,河南 鄭州 450003)
基于Resin的通用依賴注入模式
劉小可
(河南省科學(xué)技術(shù)信息研究院,河南鄭州450003)
本文描述四種CanDI設(shè)計(jì)模式:服務(wù),資源,啟動(dòng)和擴(kuò)展模式,以服務(wù)模式為基礎(chǔ)的應(yīng)用程序采用封裝方式管理數(shù)據(jù),資源模式是用戶和應(yīng)用程序之間的配置接口,啟動(dòng)模式用于初始化應(yīng)用,擴(kuò)展模式用于用戶定制復(fù)雜的應(yīng)用程序行為。
CanDI依賴注入;注解;模式
CanDI作為Caucho公司對(duì)下一代通用依賴注入技術(shù)標(biāo)準(zhǔn)的CDI獨(dú)立實(shí)現(xiàn),主要包含四種Java注入模式,其設(shè)計(jì)目標(biāo)在于采用聲明式注入方式改善Java類代碼,使之展現(xiàn)出更加清晰的依賴關(guān)系,生命周期,輸出,定義等。工程師可以在讀取類的同時(shí)了解它們的表現(xiàn)方式,無(wú)需讀取XML的配置文件,即可知道系統(tǒng)程序的行為是什么。
類型安全的自定義注解綁定是CanDI實(shí)現(xiàn)的技術(shù)關(guān)鍵,在注入點(diǎn)可以利用生動(dòng)的注解名稱對(duì)資源或者服務(wù)進(jìn)行清晰描述。注解本身就是Java的真實(shí)類,它們?cè)贘avaDoc中定義并由編譯器負(fù)責(zé)解釋。少量生動(dòng)的注解可以減少代碼編寫負(fù)擔(dān),并節(jié)省開發(fā)時(shí)間。
表1 基于CanDI的應(yīng)用模式
服務(wù)模式主要是為滿足封裝多客戶端的狀態(tài)和管理需要,本文展示一個(gè)用于多Servlet、PHP和JSP腳本的單服務(wù)例子。采用服務(wù)模式的應(yīng)用通常使用@Inject注解并綁定為單例模式。
資源模式主要通過XML文件為資源配置驅(qū)動(dòng)類和屬性,這部分內(nèi)容使用MyResource作為一個(gè)通用資源API,我們可以把它當(dāng)作是某個(gè)DataSource或者Entity-Manager,應(yīng)用程序主要需要綁定@Red和@Blue。資源API是通用的,我們需要在具體應(yīng)用程序的代碼中定義描述其意圖。綁定注解所用的詞匯一般是簡(jiǎn)單、清晰、易于理解的形容詞,并且數(shù)量較少。作為BlueResourceBean這樣的驅(qū)動(dòng)類就可以在XML文件中進(jìn)行配置,同樣如果需要配置數(shù)據(jù)庫(kù)也是這么做。
許多應(yīng)用需要啟動(dòng)模式的初始化功能,我們可以使用CanDI提供的啟動(dòng)模式定義啟動(dòng)類,并且不需要額外的XML配置,這是因?yàn)镃anDI可以在classpath中自動(dòng)尋找Bean,我們可以使用@Startup注解和@PostConstruct方法來(lái)對(duì)要?jiǎng)?chuàng)建的啟動(dòng)Bean進(jìn)行標(biāo)識(shí)。
擴(kuò)展服務(wù)主要用于系統(tǒng)內(nèi)部使用,它可以提升許多應(yīng)用的靈活性。擴(kuò)展模式不需要使用其他配置,只需要使用CanDI型的插件即可完成查找裝配。本文使用MyResource API作為插件,捕捉所有使用CanDI Instance接口的實(shí)現(xiàn)和@Any注解類。
許多應(yīng)用主要使用服務(wù)和資源模式,CanDI類中最重要的三個(gè)注解就是@Inject、@Qualifier和@Singleton,通過有效使用這三種注解,可以提升應(yīng)用中服務(wù)和資源的可讀性、可維護(hù)性,如表2所示。
表2 服務(wù)和資源模式中主要的CanDI類
提供腳本方式訪問服務(wù)和資源的應(yīng)用可以使用@Named注解進(jìn)行聲明,這樣在腳本可以直接使用其對(duì)應(yīng)的名字來(lái)進(jìn)行操作,如表3所示。
表3 CanDI的腳本支持類
啟動(dòng)模式使用兩個(gè)注解:@Startup注解用于標(biāo)記容器啟動(dòng)時(shí)需要?jiǎng)?chuàng)建的Bean,@PostConstruct注解用于需要初始化的方法,如表4所示。
表4 啟動(dòng)模式的CanDI類
擴(kuò)展模式使用兩個(gè)CanDI類使其易于通過classpath查找插件。Instance
表5 擴(kuò)展模式的CanDI類
程序例子架構(gòu):見圖1。
圖1 程序例子的技術(shù)架構(gòu)
涉及的程序文件
服務(wù)模式:見表6。
配置和擴(kuò)展模式:見表7。
啟動(dòng)模式:見表8。
由于應(yīng)用的服務(wù)通常是特定的,一般服務(wù)接口設(shè)計(jì)做成獨(dú)立的服務(wù)。在CanDI中,@Inject注解向客戶端類注入特定的服務(wù)。服務(wù)的聲明和使用都使用聲明方式將服務(wù)限定在@Singleton范圍,同時(shí)在客戶端使用@Inject注解將服務(wù)注入。CanDI的服務(wù)注解類自身通過這種描述方式提升可讀性和靈活性。
例子:GetServlet.java
package example;
import javax.inject.Inject;
...
public class GetServlet extends HttpServlet{
private@Inject MyService_service;
...
}
用戶可以通過類似于MyService這樣的接口訪問服務(wù)。服務(wù)接口的實(shí)現(xiàn)是類似于MyServiceBean的具體類。CanDI的接口API屬于Java標(biāo)準(zhǔn)接口,因此不依賴于具體的CanDI注解或者引用。
例子:MyService.java
package example;
public interface MyService{
public void setMessage(String message);
public String getMessage();
}
表6 服務(wù)模式涉及的程序文件表
表7 配置和擴(kuò)展模式涉及的程序文件
表8 啟動(dòng)模式涉及的程序文件
所有與類部署有關(guān)的信息都包含在類本身,原因在于CanDI通過classpath查找發(fā)現(xiàn)服務(wù)的具體實(shí)現(xiàn)類。換句話說,服務(wù)的部署來(lái)自于自身定義。服務(wù)做成單例時(shí),則被@Singleton注解限定。其他注解是可選的,主要用于描述服務(wù)的注冊(cè)或者行為。例如,本文使用@Named注解標(biāo)簽,這是因?yàn)閠est.jsp和test.php中用到了名字引用。
為了集成JSP的EL表達(dá)式語(yǔ)言和PHP編程的需要,腳本Bean需要使用CanDI Bean中的@Named注解。由于CanDI使用服務(wù)類型和綁定注解的方式進(jìn)行匹配,因此非腳本Bean不需要聲明@Named。
例子:MyServiceBean.java
package example;
import javax.inject.Singleton;
import javax.inject.Named;
import javax.enterprise.inject.Default;
@Singleton
@Named("myService")
@Default
public class MyServiceBean implements MyService{
private String_message="default";
public void setMessage(String message){
_message=message;
}
public String getMessage(){
return_message;
}
}
在PHP和JSP中使用服務(wù)
CanDI的設(shè)計(jì)與PHP和JSP等腳本語(yǔ)言緊密結(jié)合。腳本語(yǔ)言可以使用名字字符串加載CanDI服務(wù)和資源,腳本語(yǔ)言不具備依賴注入的強(qiáng)類型要求,由此,CanDI服務(wù)中由@Named注解聲明的名字字符串就是Bean自身。PHP和JSP代碼通過Bean的名字完成對(duì)其引用,在PHP中,可以使用java_bean函數(shù)調(diào)用對(duì)應(yīng)的類:
例子:test.php
<?php
$myService=java_bean("myService");
echo$myService->getMessage();
?>
PHP使用函數(shù)訪問CanDI服務(wù)和資源,JSP和JSF通過JSP表達(dá)式語(yǔ)言訪問CanDI的Bean。EL表達(dá)式可以使用任何帶有@Named注解的CanDI Bean:
例子:test.jsp
message:${myService.message}
像數(shù)據(jù)庫(kù)、應(yīng)用程序中的多角色隊(duì)列在生成Data-Source和BlockingQueue時(shí)需要對(duì)這些資源進(jìn)行描述配置。當(dāng)需要生成特定的或可以使用@Inject注解的服務(wù)時(shí),通常創(chuàng)建個(gè)性化的@Qualifier注解以定義和驗(yàn)證資源。
CanDI鼓勵(lì)以綁定注解的方式使用少量形容詞描述資源,典型的中等應(yīng)用像郵件列表管理或許一半都需要個(gè)性化綁定的形容詞,多與少依賴于特定資源的數(shù)量。每一個(gè)數(shù)據(jù)庫(kù)、隊(duì)列、郵件和JPA實(shí)體管理器會(huì)生成對(duì)應(yīng)的特定名字。如果用戶需要個(gè)性化配置內(nèi)部資源,那么需要附加綁定類型。如果應(yīng)用系統(tǒng)是單數(shù)據(jù)庫(kù),只需要一個(gè)綁定注解即可,或者只使用@Inject。
綁定注解的目的是在客戶端代碼中實(shí)現(xiàn)自定義。如果應(yīng)用系統(tǒng)使用@ShoppingCart和@ProductCatalog注解數(shù)據(jù)庫(kù),客戶端代碼就會(huì)綁定他們的描述,代碼聲明它在需要可讀時(shí)的依賴,另外使CanDI與其配置提供自身需要的資源。
本文在XML中配置有一個(gè)@Red資源,用戶可以通過這種方式對(duì)個(gè)性化資源進(jìn)行配置。資源客戶端Set-Servlet在使用形容詞注解聲明成員:
例子:SetServlet.java
public class SetServlet extends HttpServlet{
private@Red MyResource_red;
private@Blue MyResource_blue;
...
}
XML文件很短卻很有用,只需要個(gè)性化表達(dá),而不需要綁定或者做其他連接。數(shù)據(jù)庫(kù)和JMS隊(duì)列需要配置數(shù)據(jù)庫(kù)驅(qū)動(dòng)和增加形容詞綁定。如果希望配置對(duì)用戶有用,應(yīng)用程序的資源也可以配置在XML中,對(duì)于服務(wù)特定的內(nèi)部類則不必配置。在例子中,用戶可以對(duì)資源中的data成員變量進(jìn)行配置,并且可以對(duì)實(shí)現(xiàn)類進(jìn)行選擇。
Bean的XML配置需要三塊信息:驅(qū)動(dòng)類、用于綁定注解的描述以及需要個(gè)性化的數(shù)據(jù)。驅(qū)動(dòng)類是最重要的,CanDI通過XML標(biāo)簽的方式使用類,通過XML名字空間的方式使用包。在查詢XML文件時(shí),驅(qū)動(dòng)類最先體現(xiàn)出它的重要性。在例子中
...
例子:BlueResourceBean實(shí)例的配置
在CanDI中,綁定注解也是一個(gè)XML標(biāo)簽,通過類名稱和所在的包表示。類和注解使用大寫首字母的名字來(lái)匹配類名字,XML屬性使用小寫。這種區(qū)分類和成員的方式提高了XML的可讀性。
例子:@Blue配置
資源的屬性使用標(biāo)準(zhǔn)Bean樣式的名字,
xmlns:example="urn:java:example">
例子:resin-web.xml
綁定類型應(yīng)該使用可描述的形容詞,這樣注入的描述看起來(lái)比較清晰。其他人看到代碼就會(huì)立刻理解哪些是使用的資源。由于它的重要性以及這只是少數(shù)的個(gè)性化注解,本文的@Blue綁定注解就是使用CanDI的@Qualifier注解標(biāo)記的普通Java注解。花費(fèi)時(shí)間選擇一個(gè)好的形容詞名字很重要。
package example;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import java.lang.annotation.*;
import javax.inject.Qualifier;
@Qualifier
@Documented
@Target({TYPE,METHOD,F(xiàn)IELD,PARAMETER})
@Retention(RUNTIME)
public@interface Blue{
}
例子:Blue.java
資源的實(shí)現(xiàn)很直觀。單例模式的資源,使用@Singleton注解,比如某個(gè)服務(wù)。默認(rèn)情況下,在每個(gè)注入點(diǎn)CanDI都會(huì)注入Bean的新實(shí)例。
package example;
public class BlueResourceBean{
private String_data;
public void setData(String data){
_data=data;
}
}
例子:BlueResourceBean.java
@Startup注解將Bean標(biāo)記為在服務(wù)器啟動(dòng)時(shí)初始化。啟動(dòng)Bean會(huì)在CanDI查詢classpath時(shí)被找到,啟動(dòng)類控制自身的初始化。站在另外一個(gè)角度,有啟動(dòng)類就足夠了,它不需要在XML中配置啟動(dòng)。啟動(dòng)Bean使用@PostConstruct注解的初始化方法啟動(dòng)初始化代碼。
package example;
import javax.annotation.PostConstruct;
import javax.ejb.Startup;
import javax.inject.Inject;
@Startup
public class MyStartupBean{
private@Inject MyService_service;
@PostConstruct
public void init(){
_service.setMessage(this+":initial value");
}
}
例子:MyStartupBean.java
擴(kuò)展架構(gòu)可以使應(yīng)用系統(tǒng)更加靈活且可配置。例如,過濾器、blueprint、客戶action,這種模式可以顯著提升應(yīng)用系統(tǒng)的性能。擴(kuò)展模式使用CanDI的檢索系統(tǒng)來(lái)查找所有擴(kuò)展接口的實(shí)現(xiàn)。和@Any綁定注解一起Instance會(huì)枚舉所有資源的實(shí)現(xiàn)。
CanDI的Instance接口有兩個(gè)用途:使用get()方法返回特定的編程實(shí)例;列出所有實(shí)例的擴(kuò)展可能。Instance實(shí)現(xiàn)了JDK中Iterable接口,這樣就可以在for循環(huán)中使用。每一個(gè)返回實(shí)例都遵守標(biāo)準(zhǔn)的CanDI范圍規(guī)則,同時(shí)返回@Singleton單例模式的單值或者創(chuàng)建默認(rèn)的新實(shí)例。
@Any注解與Instance一起工作用于選定所有符合條件的實(shí)例。綁定類型默認(rèn)是@Inject:
package example;
import javax.inject.Inject;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.Instance;
...
public class GetServlet extends HttpServlet{
@Inject@Any Instance
public void service(HttpServletRequest request,
HttpServletResponse response)
throws IOException,ServletException{
PrintWriter out=response.getWriter();
for(MyResource resource:_resources){
out.println("resource:"+resource);
}
}
}
例子:GetServlet.java
CanDI作為Caucho公司對(duì)下一代通用依賴注入技術(shù)標(biāo)準(zhǔn)的CDI獨(dú)立實(shí)現(xiàn),已經(jīng)成為Resin4應(yīng)用服務(wù)器的基礎(chǔ),許多功能已經(jīng)超越了JavaEE6 API,并與諸如JUnit、Struts2、Wicket、iBATIS、Quartz以及Spring等流行第三方框架完成了整合,同時(shí)CanDI支持所有EJB和POJO Bean注解,希望POJO bean注解能夠成為未來(lái)JavaEE7的標(biāo)準(zhǔn)規(guī)范之一。
本文主要介紹了在采用CanDI依賴注入技術(shù)開發(fā)企業(yè)級(jí)應(yīng)用系統(tǒng)時(shí)用到的各類注入模式,在實(shí)際研發(fā)過程中,工程師除了需要掌握服務(wù)、資源、啟動(dòng)、擴(kuò)展這幾種模式的特點(diǎn)和使用情景,還需要在特定環(huán)境中完成資源的配置、具體程序的注入等工作,某些時(shí)候?yàn)榱藬U(kuò)展需要,也可能用到第三方類來(lái)完成應(yīng)用程序裝配。
General Dependency Injection Mode Based on Resin
Liu Xiaoke
(Henan Academy of Science and Technology Information,Zhengzhou Henan 450003)
In this paper,we describe four candi design patterns:service,resource configuration,startup and plugin/extension pattern.Based on the service pattern of the application package management data,The resource configuration pattern is the configuration interface between the user and the application,startuppattern used to initialize the application,plugin/extensionpattern for user customization of complex application behavior.
CanDI;dependency injection;annotation;pattern
TP311.52
A
1003-5168(2016)05-0087-05
2016-5-20
劉小可(1981.11-),男,碩士研究生,工程師,研究方向:軟件工程、公共管理。