为什么需要控件(controls)
企业资源访问是企业应用开发过程中的重要内容,然而企业资源的多样性对于软件开发者而言是个大难题。尽管j2ee标准中对于众多资源的访问接口进行了定义,然而对于软件开发者而言,这些接口仍然是太多了。我们简单的描述一下这个问题,首先看一下j2ee1.4的架构图。
 图1 j2ee 1.4架构
上面这张图是j2ee 1.4标准中的j2ee架构图,从这张图上我们可以看到,j2ee标准中提供了丰富的组件、协议和系统服务满足企业的需求,比如ejb、jms、jdbc、web services、javamail等等,方便企业根据不同的需求通过组合调用这些组件、协议和系统服务来建设企业的业务系统,然而企业本身业务随着市场的变化在不停的调整,不同时期、不同市场环境下的不同需求导致企业内部业务系统建设的极度不均衡,不同的数据库、不同的j2ee应用中间件、不同的架构设计导致企业应用开发的复杂度成倍增加。
举个简单的例子,让我们来看看如果要使用这些技术开发一个企业级系统,开发者需要完成的工作:
在一个企业级应用系统中,业务逻辑通常会被封装成ejb,开发者需要通过jndi来访问ejb中的业务逻辑;异步的消息传递通常会使用jms服务器,开发者需要通过调用服务器支持的队列或者主题来完成消息发送和接收;更多的情况下,开发者需要通过jdbc来访问企业数据库,完成相关数据的持久化工作,下面我们简单的看看访问这些资源所需要编写的代码片断。
访问一个ejb需要完成的代码
1. //初始化ejb访问上下文 2. context context = new initialcontext(); 3. //查找jndiname对应的ejb调用入口 4. object o = ctx.lookup(“jndiname”); 5. //生成ejb访问的home接口 6. xxxhome home = (xxxhome) javax.rmi.portableremoteobject 7. .narrow(o,xxxhome.class); 8. //创建ejb对象 9. xxx xxx = home.create(); 10. //完成相关业务方法调用 11. … 12. //ejb使用完毕,清除ejb对象 13. xxx.remove(); 访问jdbc数据源要完成的代码
1. //加载数据库驱动 2. class.forname(“driver_name_of_jdbc_driver”); 3. //通过drivermanager获取数据库连接 4. connection conn = drivermanager 5. .getconnection(jdbcurl,username,password); 6. //创建statement对象 7. statement stmt = conn.createstatement(); 8. //完成相关数据的访问 9. … 10. //关闭statement对象 11. stmt.close(); 12. //关闭数据库连接 13. conn.close(); 访问jms需要完成的部分代码
1. //初始化ejb访问上下文 2. context context = new initialcontext(); 3. //通过jndi查询连接工厂 4. topicconnectionfactory factory = (topicconnectionfactory) ctx 5. .lookup(“weblogic.jws.jms. topicconnectionfactory”); 6. //使用连接工厂创建jms连接 7. topicconnection conn = factory.createtopicconnection(); 8. //创建jms会话 9. topicsession session = connection 10. .createtopicsession(false,session.auto_acknowledge); 11. //通过jndiname查找目的地(主题) 12. topic topic = (topic) ctx.lookup(“jndiname”); 13. //创建消息发生者 14. topicpublisher publisher = session.createpublisher(topic); 15. //创建要发送的文本消息 16. textmessage message = session.createtextmessage(); 17. message.settext(“hello world!”); 18. //发送文本消息 19. publisher.publish(message); 这些只是j2ee标准中不到四分之一服务的客户端调用代码片断,从中我们可以发现每种企业资源的访问方式和api都不相同,而且每一种调用代码的编写都不是一件非常轻松的事情,而j2ee中还提供了更多的企业资源接访问接口方式如web services,javamail等,对于开发者而言,要完全掌握j2ee1.4客户端调用代码对每一个开发者而言都不是一件轻松的事情。
控件(controls)架构为解决这个难题提供了行之有效的方法。在控件(controls)架构中,我们使用java控件(control)对资源访问进行封装,隐藏资源访问细节。开发者只需要继承这些java控件(control),而后提供自己的业务方法,使用java控件(control)中提供的特定注释(annotation)来注释这些业务方法,就可以完成企业资源的访问工作。这种方式不但快捷高效,而且对于资源访问者而言,他始终采用同一接口去调用不同的资源,根本不需要去了解资源访问的细节。
beehive和控件(controls)
beehive目前是apache软件基金会的开放源代码软件项目,基于apache license version 2.0发布,旨在为基于j2ee和soa的应用程序发布一个跨容器的、易于使用的编程模型和应用程序框架。
控件(controls)是beehive的重要组成部分,用于协助软件开发者基于注释机制快速访问企业资源访问,简化j2ee应用开发复杂度,同时保证软件的可配置性。
beehive的另外一个重要部分是netui,用于为web应用开发提供mvc框架和简单易用的标签库。
关于beehive的更多知识请访问参考资源部分提供的链接。
控件架构
控件架构由企业资源访问客户端、java控件和企业资源三大主要部分组成,图2显示了控件运行时架构和架构中所有组成部分之间的相互关系。
 图2 控件架构
- 资源访问客户端指需要访问企业资源的一段java代码,他和支持其调用的java控件处在同一个java虚拟机中,资源访问客户端采用本地java方法调用控件支持的业务方法。
- 企业资源根据不同企业的不同环境,可能和资源访问客户端处在同一个java虚拟机中,也可能位于一个单独的java虚拟机中。
- java控件为资源访问客户端提供企业资源访问接口和属性访问接口,方便资源访问客户端控制java控件的相关属性,同时负责通过相对固定的方式访问企业资源。
- 资源访问端可以通过java控件提供的接口直接访问企业资源中的数据,或者通过向java控件注册的事件进行触发。
- 资源访问时可以使用jsr-175标准中规定的元数据来定义控件实例、类或者方法所需要的参数,也可以使用其他方式(比如配置描述符文件)来提供这些参数,控件框架中提供了独立的配置模型实现配置参数和控件的绑定,可能需要配置的参数包括jndi名称、资源访问协议、消息格式等经常需要变化的参数。
- java控件本身通过资源访问代理访问企业资源,资源访问代理可能是ejb home接口、jms连接和会话、web服务访问代理等,控件负责这些代理的创建和生命周期管理。控件根据不同的资源类型自动选择通过本地方法或者远程调用实现企业资源访问。
java控件对象模型
一个java控件由一个公共接口、控件实现类和为控件实现类提供辅助功能的控件属性访问包装器类组成,他们互相协作,共同完成企业资源访问的封装。
- 公共接口
公共接口定义了控件和资源访问客户端之间的交互行为,包括业务接口和事件。
- 控件实现类
控件实现类提供公共接口的具体实现,包括了企业资源访问接口的创建、状态管理、接口调用。
- 控件属性包装器类
控件属性包装器类辅助控件实现类完成工作,提供控件实现类的属性访问接口、控件实现类实例动态属性的保存、事件监听路由以及上下文环境和内部控件的初始化工作。
他们三者之间的关系如 图3所示。
 图3 控件组成部分及相互关系
公共接口、控件实现类由开发者提供,而控件属性包装器类则是由编译工具根据公共接口、控件实现类自动生成的。
下面的图4描述了控件组成部分完成控件框架(见图2)支持功能时各部分的具体工作。
 图4 控件组成部分工作图
第一个例子—hello world!
下面我们通过一个简单的hello world!的例子来介绍如何开发一个具备基本功能的控件。这个控件的唯一作用是提供名为“gethellostring”的业务接口,当开发者调用该接口时返回“hello world!”字符串。
在开始开发工作之前,我们必须安装一些必需的软件。
- 下载、安装jdk5.0或者以上版本
jdk5.x是java开发包的最新版本,请访问java.sun.com网站上下载安装版本,下载后安装到c:/jdk目录下,文章的后续内容中将使用%java_home%变量来引用这个目录。
- 下载、安装beehive1.0
beehive是apache组织的一个开源项目,请访问www.apache.org网站上下载最新的可执行版本,下载后解压缩到c:/beehive目录下,文章的后续内容中将使用%beehive_home%变量来引用这个目录。
现在我们开始创建helloworld!例子。在c:/创建目录”helloworld” (下面的例子中将使用%helloworld_home%引用这个目录)。
创建控件接口(helloworldcontrol.java),它的全部源代码见清单1。
清单1 srcorgvivianjeehiventrolsxampleshelloworldcontrol.java
1. package org.vivianj.beehive.controls.examples; 2. 3. import org.apache.beehive.controls.api.bean.controlinterface; 4. 5. 6. public interface helloworldcontrol { 7. /* 声明需要封装的业务方法gethellostring */ 8. public string gethellostring(string dest); 9. } helloworldcontrol接口声明了一个业务方法gethellostring,它接收传入的string类型参数,返回内容也是string类型。这段代码中唯一让人困惑的是第5行的。是控件(controls)架构中提供的注释,在接口声明前面使用注释表明接下来定义的这个接口是一个java控件的公共接口。
接下来我们要创建控件实现类(helloworlcontrolimpl.java),它的全部代码见清单2。
清单2 srcorgvivianjeehiventrolsxampleshelloworlcontrolimpl.java
1. package org.vivianj.beehive.controls.examples; 2. 3. import org.apache.beehive.controls.api.bean.controlimplementation; 4. 5. 6. public class helloworldcontrolimpl 7. implements helloworldcontrol,java.io.serializable { 8. 9. /* 实现接口中声明的业务方法gethellostring */ 10. public string gethellostring(string dest) { 11. return “hello world!”; 12. } 13. } helloworldcontrolimpl类继承了控件公共接口helloworldcontrol(参见清单1),提供了gethellostring方法的具体实现。在代码的第5行出现了@ controlimplementation。@ controlimplementation注释是控件(controls)架构中提供的注释,在类声明前面使用controlimplementation表明接下来定义的这个类是一个java控件的接口实现类。
编译控件
下面的步骤是windows平台下编译控件的主要步骤。
- 在%helloworld_home%目录下创建build.properties文件。文件内容如清单3。
清单3 build.properties
#请将beehive.home的属性修改为您安装beehive的目录
beehive.home=c:/beehive
- 在%helloworld_home%目录下创建build.xml文件,文件内容如清单4。
清单4 build.xml
1. <project name="controls-blank" default="usage" basedir="."> 2. 3. <property environment="os"/> 4. <property file="build.properties"/> 5. <import file="/beehive-imports.xml"/> 6. <import file="/ant/beehive-tools.xml"/> 7. 8. <property name="source.dir" location="/src"/> 9. <property name="build.dir" location="/build"/> 10. <property name="build.classes" location="/classes"/> 11. <property name="build.beansrc" location="/beansrc"/> 12. <property name="build.jar" value="mycontrols.jar"/> 13. 14. <path id="build.classpath"> 15. <pathelement path=""/> 16. <path refid="controls.dependency.path"/> 17. </path> 18. 19. <target name="clean" description="delete all generated files"> 20. <delete dir=""/> 21. <delete file="velocity.log"/> 22. </target> 23. 24. <target name="build" description="build control sources"> 25. <mkdir dir=""/> 26. <mkdir dir=""/> 27. 28. <build-controls srcdir="" 29. destdir="" 30. tempdir="" 31. classpathref="build.classpath"/> 32. 33. <control-jar destfile="/" basedir="" /> 34. </target> 35. 36. <target name="usage" description="print the usage for this build.xml"> 37. <echo message=""/> 38. <echo message=""/> 39. <echo message="controls project build file"/> 40. <echo message=""/> 41. <echo message=""/> 42. <echo message="----------------------------------------------------------------"/> 43. <echo message="| standard targets |"/> 44. <echo message="----------------------------------------------------------------"/> 45. <echo message="clean - delete all generated files"/> 46. <echo message="build - build control source files"/> 47. <echo message="----------------------------------------------------------------"/> 48. </target> 49. 50. </project> - 调整系统环境变量
打开命令行界面,进入%helloworld_home%目录,将%java_home%/bin目录和%ant_home%/bin目录加入到path环境中。随后调用build.xml中提供的build任务可以完成整个web应用的编译工作。
在windows环境下编译时使用的命令如下,在其他环境下运行编译命令请参考不同操作系统的帮助文档进行修改。
- 设置环境变量
c:helloworld> set path=%java_home%/bin;%ant_home%/bin 命令行中的%java_home%指的是jdk的安装目录,%ant_home%指的是ant的安装目录,请根据您的环境替换它们的内容。
- 运行编译任务
使用ant运行编译任务。 c:helloworld> set path=%java_home%/bin;%ant_home%/bin 使用junit测试控件
junit是最常用的java单元测试框架,为了测试新创建的控件,我们建立一个junit testcase来测试它。在开发testcase之前,我们需要准备些软件。
- 下载、安装junit3.8.2
请访问www.junit.org下载junit3.8.2版本,将下载的zip文件解压缩到c:/junit目录下。
本文中的所有讲述均针对junit3.8.2版本,由于junit4.x版本了的开发方式和junit3.8.2版本存在差异,如果您选择下载junit4.x版本,请根据实际情况进行调整。
- 安装beehiveforjunit.jar
控件是一种比较特殊的框架,需要对junit进行扩展才能完成单元测试,在资源区中可以找到下载beehiveforjunit.jar的超链接。下载后,请将该文件拷贝到%helloworld_home%ib目录下。
本文所带例子helloworld.zip中的lib目录下也可以找到。
现在我们来创建控件的测试类(helloworldcontroltest),它的全部代码见清单5。
清单5 testorgvivianjeehiventrolsxamplesnittesthelloworldcontroltest.java
1. package org.vivianj.beehive.controls.examples.unittest; 2. 3. import junit.framework.testcase; 4. 5. import org.apache.beehive.controls.api.bean.control; 6. import org.apache.beehive.test.tools.milton.junit.miltontestcase; 7. import org.vivianj.beehive.controls.examples.helloworldcontrolbean; 8. 9. /** 10. * helloworldcontroltest用于 11. */ 12. public class helloworldcontroltest extends miltontestcase { 13. 14. /* 使用声明是实例化方式创建控件实例 */ 15. 16. public helloworldcontrolbean _bean; 17. 18. /* 必须创建构造器,否则控件调用时无法获取上下文环境 */ 19. public helloworldcontroltest(string name) throws exception 20. { 21. super(name); 22. } 23. 24. public void testgethellostring() { 25. /* 调用helloworldcontrol控件的业务方法,获取返回字符串 */ 26. string s = _bean.gethellostring("world"); 27. 28. /* 使用junit的比对语句完成期望结果和控件调用返回结果值的比对 */ 29. assertequals(s, "hello world!"); 30. } 31. 32. public static void main(string[] args){ 33. junit.textui.testrunner.run(helloworldcontroltest.class); 34. } 35. }
下面的步骤是windows平台下编译控件测试代码的主要步骤。
- 修改%helloworld_home%目录下的build.properties文件。修改后的文件内容如清单6。
清单6 build.properties
1. # 请将beehive.home的属性修改为您安装beehive的目录 2. beehive.home=e:/masteringbeehive/apache-beehive-1.0.1 3. 4. # 请将junit.home的属性修改为您安装junit的目录 5. junit.home=f:/opensource/junit3.8.2 - 修改%helloworld_home%目录的build.xml文件。
增加新的path元素” test.classpath”,增加新的任务”build_tests”和”run_tests”。修改后的文件内容如清单7。
清单7 build.xml
1. <project name="controls_helloworld" default="usage" basedir="."> 2. 3. <property environment="os" /> 4. <property file="build.properties" /> 5. <import file="/beehive-imports.xml" /> 6. <import file="/ant/beehive-tools.xml" /> 7. 8. <property name="source.dir" location="/src" /> 9. <property name="test.source.dir" location="/test" /> 10. <property name="build.dir" location="/build" /> 11. <property name="build.classes" location="/bin" /> 12. <property name="build.beansrc" location="/beansrc" /> 13. <property name="build.jar" value="mycontrols.jar" /> 14. 15. <path id="build.classpath"> 16. <pathelement path="" /> 17. <path refid="controls.dependency.path" /> 18. </path> 19. 20. <path id="test.classpath"> 21. <pathelement path="./build/mycontrols.jar" /> 22. <pathelement path="/junit.jar" /> 23. <pathelement path="./lib/beehiveforjunit.jar" /> 24. <fileset dir="/lib/common"> 25. <include name="**/*.jar"/> 26. </fileset> 27. <path refid="build.classpath" /> 28. </path> 29. 30. <target name="clean" description="delete all generated files"> 31. <delete dir="" /> 32. <delete dir="" /> 33. <delete file="velocity.log" /> 34. </target> 35. 36. <target name="build" description="build control sources"> 37. <mkdir dir="" /> 38. <mkdir dir="" /> 39. 40. <build-controls srcdir="" destdir="" tempdir="" classpathref="build.classpath" /> 41. 42. <control-jar destfile="/" basedir="" /> 43. </target> 44. 45. <target name="build_tests" description="build control tests"> 46. <mkdir dir="" /> 47. <mkdir dir="" /> 48. 49. <build-controls srcdir="" destdir="" tempdir="" classpathref="test.classpath" /> 50. </target> 51. 52. <target name="run_tests" description="run control tests"> 53. <java classname="org.vivianj.beehive.controls.examples.unittest.helloworldcontroltest" classpathref="test.classpath" /> 54. </target> 55. 56. <target name="usage" description="print the usage for this build.xml"> 57. <echo message="" /> 58. <echo message="" /> 59. <echo message="controls project template build file" /> 60. <echo message="" /> 61. <echo message="" /> 62. <echo message="----------------------------------------------------------------" /> 63. <echo message="| standard targets |" /> 64. <echo message="----------------------------------------------------------------" /> 65. <echo message="clean - delete all generated files" /> 66. <echo message="build - build control sources" /> 67. <echo message="build_tests - build control tests" /> 68. <echo message="run_tests - runs control tests" /> 69. <echo message="----------------------------------------------------------------" /> 70. </target> 71. 72. </project> - 编译控件测试类
我们可以在命令行中进入%helloworld_home%目录下,设置系统环境后运行下面的脚本编译新创建的测试类。 c:helloworld> ant build_tests
- 运行测试类
我们可以在命令行中进入%helloworld_home%目录下,设置系统环境后运行下面的脚本运行新创建的测试类。 c:helloworld> ant run_tests
测试类成功执行后,命令行窗口中的显示内容如图5所示。
 图5 控件测试类执行效果
实例简单分析
从上面的例子中,我们可以看到,除了控件变量声明比较特殊之外,其他的并没有什么差异。我们来简单的分析一下控件变量声明和实例化过程。
15.
16.public helloworldcontrolbean _bean;
上面这两行代码是junit单元测试中用于实例化控件的代码。
15行中的是控件架构中提供的注释,表明接下来的这行变量声明是控件变量的声明。
16行中,我们声明了一个变量,他的类型是helloworldcontrolbean。请注意,我们开发例子时创建的公共接口是helloworldcontrol,接口实现类是helloworlcontrolimpl。那么helloworldcontrolbean是什么?又是从哪里来的呢?答案是helloworldcontrolbean是“helloworld!”例子中控件的属性包装器类,它是控件编译过程中自动生成的,自动命名规则是“%控件公共接口名称%”+”bean”。
所有控件都可以直接使用类似的代码、声明式实例化方式进行实例化,控件架构在控件调用时自动生成被注释控件的实例。 结束语 j2ee架构中提供超过10种、可用于实际应用开发环境的技术和资源如ejb、webservices等,虽然这些技术和资源能够为我们提供更加广泛的选择范围,然而要学会如何使用客户端去访问这些不同形式的技术却成为开发者的难题。
控件(controls)架构为解决这个难题提供了一种可行的方式。在控件架构中,我们使用控件(control)来封装企业资源访问的具体细节,为开发者提供统一的访问方式。
本文中首先简单的介绍了控件的基本原理,然后通过”hello world!”的例子演示了开发、测试控件的基本步骤。当然,这个简单的例子还不足以体现控件架构的巨大威力。想了解更多的关于控件的知识,请关注作者后续的文章。
|