劉小可(河南省科學技術信息研究院,河南 鄭州 450003)
基于Resin的通用依賴注入模式
劉小可
(河南省科學技術信息研究院,河南鄭州450003)
本文描述四種CanDI設計模式:服務,資源,啟動和擴展模式,以服務模式為基礎的應用程序采用封裝方式管理數據,資源模式是用戶和應用程序之間的配置接口,啟動模式用于初始化應用,擴展模式用于用戶定制復雜的應用程序行為。
CanDI依賴注入;注解;模式
CanDI作為Caucho公司對下一代通用依賴注入技術標準的CDI獨立實現,主要包含四種Java注入模式,其設計目標在于采用聲明式注入方式改善Java類代碼,使之展現出更加清晰的依賴關系,生命周期,輸出,定義等。工程師可以在讀取類的同時了解它們的表現方式,無需讀取XML的配置文件,即可知道系統程序的行為是什么。
類型安全的自定義注解綁定是CanDI實現的技術關鍵,在注入點可以利用生動的注解名稱對資源或者服務進行清晰描述。注解本身就是Java的真實類,它們在JavaDoc中定義并由編譯器負責解釋。少量生動的注解可以減少代碼編寫負擔,并節省開發時間。

表1 基于CanDI的應用模式
服務模式主要是為滿足封裝多客戶端的狀態和管理需要,本文展示一個用于多Servlet、PHP和JSP腳本的單服務例子。采用服務模式的應用通常使用@Inject注解并綁定為單例模式。
資源模式主要通過XML文件為資源配置驅動類和屬性,這部分內容使用MyResource作為一個通用資源API,我們可以把它當作是某個DataSource或者Entity-Manager,應用程序主要需要綁定@Red和@Blue。資源API是通用的,我們需要在具體應用程序的代碼中定義描述其意圖。綁定注解所用的詞匯一般是簡單、清晰、易于理解的形容詞,并且數量較少。作為BlueResourceBean這樣的驅動類就可以在XML文件中進行配置,同樣如果需要配置數據庫也是這么做。
許多應用需要啟動模式的初始化功能,我們可以使用CanDI提供的啟動模式定義啟動類,并且不需要額外的XML配置,這是因為CanDI可以在classpath中自動尋找Bean,我們可以使用@Startup注解和@PostConstruct方法來對要創建的啟動Bean進行標識。
擴展服務主要用于系統內部使用,它可以提升許多應用的靈活性。擴展模式不需要使用其他配置,只需要使用CanDI型的插件即可完成查找裝配。本文使用MyResource API作為插件,捕捉所有使用CanDI Instance接口的實現和@Any注解類。
許多應用主要使用服務和資源模式,CanDI類中最重要的三個注解就是@Inject、@Qualifier和@Singleton,通過有效使用這三種注解,可以提升應用中服務和資源的可讀性、可維護性,如表2所示。

表2 服務和資源模式中主要的CanDI類
提供腳本方式訪問服務和資源的應用可以使用@Named注解進行聲明,這樣在腳本可以直接使用其對應的名字來進行操作,如表3所示。

表3 CanDI的腳本支持類
啟動模式使用兩個注解:@Startup注解用于標記容器啟動時需要創建的Bean,@PostConstruct注解用于需要初始化的方法,如表4所示。

表4 啟動模式的CanDI類
擴展模式使用兩個CanDI類使其易于通過classpath查找插件。Instance

表5 擴展模式的CanDI類
程序例子架構:見圖1。

圖1 程序例子的技術架構
涉及的程序文件
服務模式:見表6。
配置和擴展模式:見表7。
啟動模式:見表8。
由于應用的服務通常是特定的,一般服務接口設計做成獨立的服務。在CanDI中,@Inject注解向客戶端類注入特定的服務。服務的聲明和使用都使用聲明方式將服務限定在@Singleton范圍,同時在客戶端使用@Inject注解將服務注入。CanDI的服務注解類自身通過這種描述方式提升可讀性和靈活性。
例子:GetServlet.java
package example;
import javax.inject.Inject;
...
public class GetServlet extends HttpServlet{
private@Inject MyService_service;
...
}
用戶可以通過類似于MyService這樣的接口訪問服務。服務接口的實現是類似于MyServiceBean的具體類。CanDI的接口API屬于Java標準接口,因此不依賴于具體的CanDI注解或者引用。
例子:MyService.java
package example;
public interface MyService{
public void setMessage(String message);
public String getMessage();
}

表6 服務模式涉及的程序文件表

表7 配置和擴展模式涉及的程序文件

表8 啟動模式涉及的程序文件
所有與類部署有關的信息都包含在類本身,原因在于CanDI通過classpath查找發現服務的具體實現類。換句話說,服務的部署來自于自身定義。服務做成單例時,則被@Singleton注解限定。其他注解是可選的,主要用于描述服務的注冊或者行為。例如,本文使用@Named注解標簽,這是因為test.jsp和test.php中用到了名字引用。
為了集成JSP的EL表達式語言和PHP編程的需要,腳本Bean需要使用CanDI Bean中的@Named注解。由于CanDI使用服務類型和綁定注解的方式進行匹配,因此非腳本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中使用服務
CanDI的設計與PHP和JSP等腳本語言緊密結合。腳本語言可以使用名字字符串加載CanDI服務和資源,腳本語言不具備依賴注入的強類型要求,由此,CanDI服務中由@Named注解聲明的名字字符串就是Bean自身。PHP和JSP代碼通過Bean的名字完成對其引用,在PHP中,可以使用java_bean函數調用對應的類:
例子:test.php
<?php
$myService=java_bean("myService");
echo$myService->getMessage();
?>
PHP使用函數訪問CanDI服務和資源,JSP和JSF通過JSP表達式語言訪問CanDI的Bean。EL表達式可以使用任何帶有@Named注解的CanDI Bean:
例子:test.jsp
message:${myService.message}
像數據庫、應用程序中的多角色隊列在生成Data-Source和BlockingQueue時需要對這些資源進行描述配置。當需要生成特定的或可以使用@Inject注解的服務時,通常創建個性化的@Qualifier注解以定義和驗證資源。
CanDI鼓勵以綁定注解的方式使用少量形容詞描述資源,典型的中等應用像郵件列表管理或許一半都需要個性化綁定的形容詞,多與少依賴于特定資源的數量。每一個數據庫、隊列、郵件和JPA實體管理器會生成對應的特定名字。如果用戶需要個性化配置內部資源,那么需要附加綁定類型。如果應用系統是單數據庫,只需要一個綁定注解即可,或者只使用@Inject。
綁定注解的目的是在客戶端代碼中實現自定義。如果應用系統使用@ShoppingCart和@ProductCatalog注解數據庫,客戶端代碼就會綁定他們的描述,代碼聲明它在需要可讀時的依賴,另外使CanDI與其配置提供自身需要的資源。
本文在XML中配置有一個@Red資源,用戶可以通過這種方式對個性化資源進行配置。資源客戶端Set-Servlet在使用形容詞注解聲明成員:
例子:SetServlet.java
public class SetServlet extends HttpServlet{
private@Red MyResource_red;
private@Blue MyResource_blue;
...
}
XML文件很短卻很有用,只需要個性化表達,而不需要綁定或者做其他連接。數據庫和JMS隊列需要配置數據庫驅動和增加形容詞綁定。如果希望配置對用戶有用,應用程序的資源也可以配置在XML中,對于服務特定的內部類則不必配置。在例子中,用戶可以對資源中的data成員變量進行配置,并且可以對實現類進行選擇。
Bean的XML配置需要三塊信息:驅動類、用于綁定注解的描述以及需要個性化的數據。驅動類是最重要的,CanDI通過XML標簽的方式使用類,通過XML名字空間的方式使用包。在查詢XML文件時,驅動類最先體現出它的重要性。在例子中
...
例子:BlueResourceBean實例的配置
在CanDI中,綁定注解也是一個XML標簽,通過類名稱和所在的包表示。類和注解使用大寫首字母的名字來匹配類名字,XML屬性使用小寫。這種區分類和成員的方式提高了XML的可讀性。
例子:@Blue配置
資源的屬性使用標準Bean樣式的名字,
xmlns:example="urn:java:example">
例子:resin-web.xml
綁定類型應該使用可描述的形容詞,這樣注入的描述看起來比較清晰。其他人看到代碼就會立刻理解哪些是使用的資源。由于它的重要性以及這只是少數的個性化注解,本文的@Blue綁定注解就是使用CanDI的@Qualifier注解標記的普通Java注解。花費時間選擇一個好的形容詞名字很重要。
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,FIELD,PARAMETER})
@Retention(RUNTIME)
public@interface Blue{
}
例子:Blue.java
資源的實現很直觀。單例模式的資源,使用@Singleton注解,比如某個服務。默認情況下,在每個注入點CanDI都會注入Bean的新實例。
package example;
public class BlueResourceBean{
private String_data;
public void setData(String data){
_data=data;
}
}
例子:BlueResourceBean.java
@Startup注解將Bean標記為在服務器啟動時初始化。啟動Bean會在CanDI查詢classpath時被找到,啟動類控制自身的初始化。站在另外一個角度,有啟動類就足夠了,它不需要在XML中配置啟動。啟動Bean使用@PostConstruct注解的初始化方法啟動初始化代碼。
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
擴展架構可以使應用系統更加靈活且可配置。例如,過濾器、blueprint、客戶action,這種模式可以顯著提升應用系統的性能。擴展模式使用CanDI的檢索系統來查找所有擴展接口的實現。和@Any綁定注解一起Instance會枚舉所有資源的實現。
CanDI的Instance接口有兩個用途:使用get()方法返回特定的編程實例;列出所有實例的擴展可能。Instance實現了JDK中Iterable接口,這樣就可以在for循環中使用。每一個返回實例都遵守標準的CanDI范圍規則,同時返回@Singleton單例模式的單值或者創建默認的新實例。
@Any注解與Instance一起工作用于選定所有符合條件的實例。綁定類型默認是@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公司對下一代通用依賴注入技術標準的CDI獨立實現,已經成為Resin4應用服務器的基礎,許多功能已經超越了JavaEE6 API,并與諸如JUnit、Struts2、Wicket、iBATIS、Quartz以及Spring等流行第三方框架完成了整合,同時CanDI支持所有EJB和POJO Bean注解,希望POJO bean注解能夠成為未來JavaEE7的標準規范之一。
本文主要介紹了在采用CanDI依賴注入技術開發企業級應用系統時用到的各類注入模式,在實際研發過程中,工程師除了需要掌握服務、資源、啟動、擴展這幾種模式的特點和使用情景,還需要在特定環境中完成資源的配置、具體程序的注入等工作,某些時候為了擴展需要,也可能用到第三方類來完成應用程序裝配。
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-),男,碩士研究生,工程師,研究方向:軟件工程、公共管理。