刘小可(河南省科学技术信息研究院,河南 郑州 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-),男,硕士研究生,工程师,研究方向:软件工程、公共管理。