2007年10月12日
#
request.getRealPath("url");//虚拟目录映射为实际目录
request.getRealPath("./");//网页所在的目录
request.getRealPath("../");//网页所在目录的上一层目录
request.getContextPath();//应用的web目录的名称
如
http://localhost:7001/bookStore/
/bookStore/ => [contextPath] (request.getContextPath())
获取Web项目的全路径
String strDirPath = request.getSession().getServletContext().getRealPath("/");
2007年9月30日
#
XDoclet起步
XDoclet是一个代码生成工具,它可以把你从Java开发过程中繁重的重复劳动中解脱出来。XDoclet可以让你的应用系统开发的更加快速,而你只要付比原先更少的努力。你可以把你手头上的冗长而又必需的代码交给它帮你完成,你可以逃脱“deployment descriptor地狱”,你还可以使你的应用系统更加易于管理。而你所要做的,只不过是在你的注释里,多加一些类javadoc属性。然后,你会惊讶于XDoclet为了做到的一切。
讨论XDoclet,有一点比较容易产生混淆,那就是XDoclet不但是一系统的代码生成应用程序,而且它本身还是一个代码生成框架。虽然每个应用系统的细节千变万化(比如EJB代码生成和Struts代码生成是不一样的,而JMX代码生成又是另一番景象),但这些代码生成的核心概念和用法却是类似的。
在这一章里,我们将会看到渗透到所有XDoclet代码生成程序当中的XDoclet框架基础概念。但在之前,我们先从一个例子入手。
2.1 XDoclet in action
每一个程序员都会认识到,他们的程序永远也不会完成。总会有另一些的功能需要添加,另一些的BUG需要修正,或者需要不断的进行重构。所以,在代码里添加注释,提醒自己(或者其他的程序员)有哪些任务需要完成已成为一个共识。
如何来跟踪这些任务是否完成了呢?理想情况下,你会收集整理出来一个TODO任务列表。在这方面,XDoclet提供了一个强大的TODO生成器,来帮助你完成这个任务。这是一个把XDoclet引入项目的好机会。
2.1.1 一个公共的任务
假设你正在开发一个使用了勺子的类。
public class Matrix {
// TODO ? 需要处理当没有勺子的情况
public void reload() {
// ...
Spoon spoon = getSpoon();
// ...
}
}
理想情况下,你在下一次阅读这段代码的时候,你会处理这个“空勺子”(null spoon)的问题。但如果你过了很久才回来看这段代码,你还会记得在这个类里还有一些工作要做吗?当然,你可以在你的源码里全局搜索TODO,甚至你的集成开发环境有一个内建的TODO列表支持。但如果你想把任务所在的类和方法也标注出来的话,XDoclet可以是另一种选择。XDoclet可以为你的项目生成一个TODO报表。
2.1.2 添加XDoclet标签
为了把你的TODO项目转换成另一种更加正式的格式,你需要对代码进行一些细微的改动。如下所示:
public class Matrix {
/** @todo 需要处理当没有勺子的情况 */
public void reload() {
// ...
}
}
这里加入了一个XDoclet需要的类javadoc标签。XDoclet会使用这些标签标记的信息,以及在这种情况下标签所处的类和方法,来生成TODO报表。
2.1.3 与Ant集成
要生成TODO报表,你需要确保在你的机器上正确安装了XDoclet。
在Ant任务里,最少要包含一个目标(例如init目标)定义<documentdoclet>任务,这是一个Ant自定义任务,例如:
<taskdef name=”documentdoclet”
classname=”xdoclet.modules.doc.DocumentDocletTask”
classname=”xdoclet.lib.path” />
这个<documentdoclet>任务是XDoclet核心代码生成应用程序中的一个。
现在,你可以在Ant构建文件中加入一个todo目标调用这个任务来生成TODO报表,如:
<target name=”todo” depends=”init”>
<documentdoclet destdir=”todo”>
<fileset dir=”${dir.src}”>
<include name=”**/*.java” />
</fileset>
<info/>
</documentdoclet>
</target>
<info>子任务会遍历你的源文件,查找todo标签,并在todo子目录下生成HTML格式的TODO报表。
2.1.4 创建一个更加职业化的TODO报表
XDoclet生成的TODO报表可以有一个更加职业化的外表。报表会列出一个概览,显示在哪个包哪个类里有todo项(以及todo项的个数)。Todo项可以跟在方法、类和域上,从报表上可以清楚的区别它们。类级别的todo项会标注class,方法级别的todo项会在方法签名上标注M。构造函数和域相关的todo项也会进行相似的标注。
这个任务看起来很简单,但考虑到你所需要做的只是在注释上添加一些格式化的@todo标签,相对于那种只有人才可以理解的无格式的松散的注释,这种标签是机器可读的,也更容易编程处理。生成的输出也更容易阅读并且更加的商业化。
2.2 任务和子任务
生成todo报表,只是XDoclet可以完成的事情当中的冰山一角。当初,XDoclet因为可以自动生成EJB繁杂的接口和布署描述文件而声名鹊起。然而,现在的XDoclet已经发展成了一个全功能的、面向属性的代码生成框架。J2EE代码生成只是XDoclet的一个应用方面,它可以完成的任务已经远远超越了J2EE和项目文档的生成。
2.2.1 XDoclet 任务
到现在为止,我们一直在讨论使用XDoclet生成代码,但事实上,更确切的说法应该是,我们使用XDoclet的一个特定的任务来生成代码,比如<ejbdoclet>。每一个XDoclet任务关注于一个特定的领域,并提供这个领域的丰富的代码生成工具。
[定义:任务(Tasks)是XDoclet里可用的代码生成应用程序的高层概念。]
在XDoclet里,目前已有如下所示的七个核心任务。
<ejbdoclet>:面向EJB领域,生成EJB、工具类和布署描述符。
<webdoclet>:面向Web开发,生成serlvet、自定义标签库和web框架文件。
<hibernatedoclet>:Hibernate持续,配置文件、Mbeans
<jdodoclet>:JDO,元数据,vender configuration
<jmxdoclet>:JMX,MBean接口,mlets,配置文件。
<doclet>:使用用户自定义模板来生成代码。
<documentdoclet>:生成项目文件(例如todo列报表)
这其中,<ejbdoclet>最常用,并且很多项目也仅仅使用XDoclet来进行EJB代码生成。<webdoclet>是其次一个常用的代码生成任务。当然,在一个项目中同时使用几个XDoclet任务是可能的(并且也是推荐的),但在这些任务之间是完全独立的,它们彼此之间并不能进行直接的交流。
2.2.2 XDoclet子任务
XDoclet的任务是领域相关的,而在某个特定领域的XDoclet任务,又由许许多多紧密耦合在一起的子任务组成的,这些子任务每个都仅仅执行一个非常特定和简单的代码生成任务。
[定义:子任务(subtasks)是由任务提供的单目标的代码生成过程]
任务提供子任务执行时的上下文,并且把这些相关的子任务组织管理了起来。任务会依赖这些子任务来生成代码。在一个任务当中调用多个子任务来协同完成各种各样比较大型的代码生成任务是非常常见的。比如,在开发EJB时,你可能想要为每一个bean生成一个home接口,一个remote接口以及ejb-jar.xml布署描述符文件。这就是在<ejbdoclet>任务的上下文环境中的三个独立的代码生成子任务。
子任务可以随意的组合排列,以满足项目代码生成的需要。某个XDoclet任务包含的子任务经常会共享功能和在源文件中使用相同的XDoclet标签。这意味着当你开始一个任务的时候,你可以很容易的集成进一个相关的子任务,而不需要很大的改动。
子任务交互
让我们以<ejbdoclet>任务为例,看一下相关的子任务之间是如何进行关联的。假设你正在开发一个CMP(容器管理持久化)实体Bean。你想要使用一些<ejbdoclet>的子任务:
•<deploymentdescriptor>:生成ejb-jar.xml布署描述符文件。
•<localhomeinterface>:生成local home接口。
•<localinterface>:生成local接口。
在执行如上子任务的时候,你需要标记出你的实体Bean的CMP域。当你发布你的bean的时候,你还需要在开发商相关的布署描述符中提供某个特定的关系数据库中的特定表和列与你的CMP实体Bean的映射关系。XDoclet可以让你在原先已存在的CMP XDoclet属性基础上再加上一些关系映射属性,然后,你就可以在任务中加入一个开发商相关的子任务(例如<jboss>或者<weblogic>)来生成布署描述符文件。XDoclet提供了几乎所有的应用服务器的支持,你只需要一些初始化的小改动,就可以进行这些应用服务器相关的代码生成了。
但那只是冰山一角。你还可以使用<entitycmp>子任务为为你的bean生成一个实体bean接口的实现子类。如果你使用<valueobject>子任务来为了你的bean生成值对象,<entityemp>子任务还会为你的值对象生成方法的实现代码。
觉得不可思议了吧。可惜XDoclet没有提供<cupofcoffee>子任务,要不然我们可以喝杯咖啡,休息一下啦。
这里不是想向你介绍<ejbdoclet>所有的子任务或者<ejbdoclet>可以完成的所有代码生成功能,而仅仅是想向你展示一下任务的子任务之间是如何工作在一起的。一旦你开始并熟悉了一个XDoclet 子任务,熟悉另一个子任务会变得非常简单- 那种每个子任务都是孤立的相比,使用这种可以相互协作的子任务,开发成本会显著的降低,效果也更加的立竿见影。
2.3 使用Ant执行任务
XDoclet“嫁”给了Ant。XDoclet任务就是Ant的自定义任务,除此以外,没有其他运行XDoclet任务的方法。所幸的是,Ant已经成为了Java构建工具事实上的标准,所以这不算什么限制。事实上,反过来,XDoclet与Ant的这种“亲密”关系使得XDoclet可以参与到任何Ant构建过程当中去。
2.3.1 声明任务
XDoclet并没有和Ant一起发布,所以如果你想要使用XDoclet的话,就需要单独的下载和安装。在使用任何一个XDoclet的任务之前,你首先需要在使用Ant的<taskdef>任务来声明它。例如:
<taskdef name=”ejbdoclet”
classname=”xdoclet.modules.ejb.EjbDocletTask”
classpathref=”xdoclet.lib.path”/>
如果你熟悉Ant的话,你就会知道这段代码是告诉Ant加载<ejbdoclet>的任务定义。当然,你也可以以你喜欢的任何方式来命名这个自定义任务,但最好还是遵守标准的命名规律以免发生混淆。classname和classpathref属性告诉Ant到哪里去找实现这个自定义任务的XDoclet类。如果你想使用其他的XDoclet任务,就必须要类似这样首先声明这个任务。
一般共通的做法是,把所有需要使用的XDoclet任务都放在Ant的一个目标里声明,这样在其他的目标里如果需要使用这些任务,只要depends这个任务就可以了。你可能已经在Ant的构建文件里包含了init目标,这就是放置XDoclet任务声明的好地方(当然如果你没有,你也可以建一个)。下面的例子就是在一个init目标里加入了<ejbdoclet>和<webdoclet>的声明:
<target name=”init”>
<taskdef name=”documentdoclet”
classname=”xdoclet.modules.doc.DocumentDocletTask”
classpathref=”xdoclet.lib.path” />
<taskdef name=”ejbdoclet”
classname=”xdoclet.modules.ejb.EjbDocletTask”
classpathref=”xdoclet.lib.path” />
<taskdef name=”webdoclet”
classname=”xdoclet.modules.web.WebDocletTask”
classpathref=”xdoclet.lib.path” />
</target>
现在,任务声明好了,XDoclet“整装待发”。
2.3.2 使用任务
你可以在任何目标里使用声明好的任务。在任务的上下文环境里,可以调动相关的子任务。让我们看一个例子,这个例子调用了<ejbdoclet>任务。不要担心看不懂语法的细节,现在你只需要关心一些基础概念就可以了。
<target name=”generateEjb” depends=”init”>
<ejbdoclet destdir=”${gen.src.dir}”>
<fileset dir=”${src.dir}”>
<include name=”**/*Bean.java”/>
</fileset>
<deploymentdescriptor destdir=”${ejb.deployment.dir}”/>
<homeinterface/>
<remoteinterface/>
<localinterface/>
<localhomeinterface/>
</ejbdoclet>
</target>
把任务想像成一个子程序运行时需要的一个配置环境(记住,子任务才是真正进行代码生成工作的)。当调用一个子任务时,子任务从任务继承上下文环境,当然,你也可以根据需要随意的覆盖这些值。在上面的例子里,因为<deploymentdescriptor>子任务生成的布署描述符文件和其他生成各种接口的子任务生成的Java源文件需要放在不同的位置,所以覆盖了destdir的属性值。布署描述符文件需要放在一个在打包EJB JAR文件的时候可以容易包含进来的地方,而生成的Java代码则需要放置在一个可以调用Java编译器进行编译的地方。需要这些子任务之间是紧密关联的,但只要你需要,你可以有足够的自主权控制任务的生成环境。
<fileset>属性同样被应用到所有的子任务。这是一个Ant的复杂类型(相对于文本和数值的简单类型),所以以子元素的方式在任务中声明。不要把它和子任务混为一谈。当然,如果你想在某个子任务中另外指定一个不同的输入文件集,你也可以在这个子任务中放置一个<fileset>子元素来覆盖它。
子任务的可配置选项远远不止这些。我们会在下一章继续介绍所有的任务和子任务,以及常用的配置选项。
2.4 用属性标注你的代码
可重用的代码生成系统需要输入来生成感兴趣的输出。一个解析器生成器也需要一个语言描述来解析生成解析器。一个商务对象代码生成器需要领域模型来知道要生成哪些商务对象。XDoclet则需要Java源文件做为输出来生成相关的类或者布署/配置文件。
然而,源文件可能并没有提供代码生成所需要的所有信息。考虑一个基于servlet的应用,当你想生成web.xml文件的时候,servlet源文件仅可以提供类名和适当的servlet接口方法。其他的信息比如URI pattern映射、servlet需要的初始化参数等信息并没有涵盖。显而易见,如果class并没有提供这些信息给你,你就需要自己手动在web.xml文件时填写这些信息。
XDoclet当然也不会知道这些信息。幸运的是,解决方法很简单。如果所需信息在源文件时没有提供,那就提供它,做法就是在源文件里加入一些XDoclet属性。XDoclet解析源文件,提取这些属性,并把它们传递给模板,模板使用这些信息生成代码。
2.4.1 剖析属性
XDoclet属性其实就是javadoc的扩展。它们在外表上和使用上都有javadoc属性一样,可以放置在javadoc文档注释里。文档注释以/**开始,*/结尾。下面是一个简单的例子:
/**
* 这是一段javadoc注释。
* 注释可以被分解成多行,每一行都以星号开始。
*/
在注释里的所有文本都被视为javadoc注释,并且都能够被XDoclet访问到。注释块一般都与Java源文件中的某个实体有关,并紧跟在这个实体的前面。没有紧跟实体的注释块将不会被处理。类(或者接口)可以有注释块,方法和域也可以有自己的注释块,比如:
/**
* 类注释块
*/
public class SomeClass {
/** 域注释块 */
private int id;
/**
* 构造函数注释块
*/
public SomeClass() {
// ...
}
/**
* 方法注释块
*/
public int getId() {
return id;
}
}
注释块分成两部分:描述部分和标签部分。当遇到第一个javadoc标签时,标签部分开始。Javadoc标签也分成两部分:标签名和标签描述。标签描述是可选的,并且可以多行。例如:
/**
* 这是描述部分
* @tag1 标签部分从这里开始
* @tag2
* @tag3 前面一个标签没有标签描述。
* 这个标签有多行标签描述。
*/
XDoclet使用参数化标签扩展了javadoc标签。在XDoclet里,你可以在javadoc标签的标签描述部分加入name=”value”参数。这个微小的改动大大增强了javadoc标签的表达能力,使得javadoc标签可以用来描述复杂的元数据。下面的代码显示了使用XDoclet属性描述实体Bean方法:
/**
* @ejb.interface-method
* @ejb.relation
* name=”blog-entries”
* role-name=”blog-has-entries”
* @ejb.value-object
* compose=”com.xdocletbook.blog.value.EntryValue”
* compose-name=”Entry”
* members=”com.xdocletbook.blog.interfaces.EntryLocal”
* members-name=”Entries”
* relation=”external”
* type=”java.util.Set”
*/
public abstract Set getEntries();
参数化标签允许组合逻辑上相关联的属性。你可以加入描述这个类的元信息,使得这个类的信息足够生成代码。另外,程序员借由阅读这样的元信息,可以很快的理解这个类是如何使用的。(如果这个例子上的元信息你看不懂,不要担心,在第4章里,我们会学到EJB相关的标签以及它们的涵意。)
另外,请注意上面的例子中,所有的标签名都以ejb开头。XDoclet使用namespace.tagname的方式给标签提供了一个命名空间。这样做除了可以跟javadoc区别开来以外,还可以把任务相关的标签组织起来,以免任务之间的标签产生混淆。
2.5 代码生成模式
XDoclet是一种基于模板的代码生成引擎。从高层视图上来看,输出文件其实就是由解析执行各式各样的模板生成出来的。如果你理解了模板以及它所执行的上下文环境,就可以确切的认识到,XDoclet可以生成什么,不可以生成什么。如果你正在评估XDoclet平台,理解这些概念是非常重要的。要不然,你可能会错过XDoclet的许多强大的功能,也可能会被XDoclet的一些限制感到迷惑。
XDoclet运行在在Ant构建文件环境中,它提供了Ant自定义任务和子任务来与XDoclet引擎交互。任务是子任务的容器,子任务负责执行代码生成。子任务调用模板。模板提供了你将生成代码的饼干模子。XDoclet解析输入的源文件,提取出源文件中的XDoclet属性元数据,再把这些数据提供给模板,驱动模板执行。除此之外,模板还可以提供合并点(merge points),允许用户插入一些模板片断(合并文件merge files)来根据需要定制代码生成。
2.5.1 模板基础
XDoclet使用代码模板来生成代码。模板(template)是你想生成文件的原型。模板里使用一些XML标签来指导模板引擎如何根据输入类以及它们的元数据来调整代码的生成。
[定义:模板(template)是生成代码或描述文件的抽象模视图。当模板被解析的时候,指定的细节信息会被填入。]
模板一般情况下会有一个执行环境。模板可能应用在一个类环境(转换生成transform generation),也有可能应用在一个全局环境(聚集生成aggregate generation)。转换生成和聚集生成是XDoclet的两种类型的任务模式,理解它们之间的区别对于理解XDoclet是非常重要的。
当你使用XDoclet生成布置描述符文件时,你使用的是聚集生成。布置描述符文件并不仅仅只与一个类相关,相反,它需要从多个类里聚集信息到一个输入文件。在这种生成模式里,解析一次模板只会生成一个输出文件,不管有多少个输入文件。
在转换生成模式里,模板遇到每一个源文件就会解析一次,根据该文件类的上下文环境生成输出。这种生成模式会为每一个输入文件生成一个输出文件。
转换生成模式的一个很好的例子是生成EJB的local和remote接口。显然,接口是和Bean类一一相关的。从每一个类里提取信息(类以及它的方法、域、接口以及XDoclet属性等信息)转换出接口。除此以外,不需要其他的信息。
从实现里提取出接口似乎有点反向。如果你手写程序的话,一般来说会先定义一个接口,然后再写一个类来关现它。但XDoclet做不到,XDoclet不可能帮你实现一个已有接口,因为它不可能帮你生成你的业务逻辑。当然,如果业务逻辑可以从接口本身得到(比如JavaBean的get/set访问器)或者使用XDoclet属性声明好,那么生成业务逻辑代码来实现一个接口也不是不可能。但一般情况下,这样做不太现实。相比而言,提供一个实现,并描述接口与这个实现之间的关联就容易多了。
聚集生成和转换生成主要区别在它们的环境信息上。即使一个代码生成任务中生成一个Java文件,一般也不常用聚集生成,因为生成一个Java类还需要一些重要信息如类所处的包以及你想生成的类名,在这种环境下是无法提供的。如果一定要使用聚集生成的话,那就需要在另一个单独的地方提供好配置信息了。
2.5.2 模板标签
在还没见到模板长啥样子之前,我们已经比较深入的认识它了。那模板文件究竟长啥样子呢?它有点像JSP文件。它们都包含文件和XML标签,生成输出文件时XML标签会被解析,然后生成文本并显示在XML标签所处的位置上。除了以XDt为命名空间打头的XML标签会被XDoclet引擎解析以外,其余的XML标签XDoclet会忽略不管。下面的代码片断显示了XDoclet模板的“经典造型”:
public class
<XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClass:classOf>
Extends Observabe {
static <XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClass:classOf>
_instance = null;
public static <XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClassOf>
getInstance() {
if (_instance == null) {
_instance =
new <XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/>
</XDtClass:classOf>();
}
return _instance;
}
}
研究一下这个模板,你会发现,它生成的是一个类定义。这个类里定义了一个静态变量instance,并且使用一个静态方法来控制这个静态文件的访问。借助Java语法,你可以很容易的推断出那些XDoclet模板标签的目录是生成类名,虽然对于这个标签如何工作你还并不是很了解。
即使你从没打算过要自己写模板,但理解模板是如何被解析运行的还是很有必要的。迟早你会调用到一个运行失败的XDoclet任务,没有产生你所期望的输出,那么最快捷的找出原因的方法就是直接检查模板文件,看看是哪里出了问题。
让我们看一下生成静态域定义的片断:
static <XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClass:classOf>
_instance = null;
在XDoclet的眼里,这段模板代码很简单,那就是:
static <tag/> _instance = null;
XDoclet解析执行标签,如果有输出的话,把输入置回到文本里去。有些标签会执行一些运算,把输出放回到一个流里。这样的标签称之为内容标签(content tags),因为它们产生内容。
另一种类型的标签称之为BODY标签。BODY标签在开始和结束标签之间存在文本。而BODY标签强大就强大在这些文本自己也可以是一断可以由外围标签解析的模板片断。比如在上面的例子里,XDtClass:classOf标签,它们的内容就是模板片断:
<XDtEjbFacade:remoteFacadeClass/>
classOf标签解析这段模板,提取出全限制的内容,然后剃除前面的包面,只输出类名。BODY标签并不总是会解析它的内容,在做这件事之前,它们会事先检查一些外部判断条件(比如检查检查你正在生成的是一个接口还是一个类)。这里标标签称之为条件标签(conditional tags)。还有一些BODY标签提供类似迭代的功能,它的内容会被解析多次。比如一个标签针对类里的每一个方法解析一次内容。
XDoclet标签提供了许多高层次的代码生成功能,但是有时候,它们可能显得不够灵活,或者表达能力满足不了你的需要。这时候,相对于另外开发一套通用功能的模板引擎相比,你可以选择扩展XDoclet模板引擎。你可以使用更具表述能力、功能更加强大的Java平台开发你自己的一套标签。
2.6 使用合并定制
代码生成系统之所以使用的不多,主要原因就在于它们往往只能生成一些死板的、不够灵活的代码。大多数代码生成系统不允许你改动它们生成的代码;如果,如果这个系统不够灵活,你所能做到的最好的扩展就是应用继承扩展生成的代码,或者使用一些共通的设计模式(比如Proxy和Adaptor)来满足你的需要。无论如此,这都不是产生你想生成的代码的好办法。代码生成器最好能做到所生成即所得WYGIWYG(what you generate is what you get),来取代你需要花费大量的时间来粉饰生成出来的并不满足要求的代码。所以,对于代码生成器来说,支持灵活的定制,是生成能够完全满足要求的代码的前提条件。
XDoclet通过合并点(merge points)支持定制??合并点是在模板文件定义里允许运行时插入定制代码的地方。有时候,合并点甚至可以影响到全局代码的生成,不但允许你添加一些定制内容,还可以从根本上改变将要生成出来的东西。
[定义:合并点(Merge points)是模板预先定义的允许你在代码生成的运行时加入定制内容的扩展点]
让我们研究一段从XDoclet源代码里摘取出来的模板代码。在为实体Bean生成主键的模板末尾,定义了这样一个合并点:
<XDtMerge:merge file=”entitypk-custom.xdt”></XDtMerge:merge>
如果你在你的merge目录下创建了一个名为entitypk-custom.xdt文件,那么这个模板文件的内容将会在这个合并点被包含进来。你的定制可以执行高层模板可以执行的所有强大功能,可以进行所有模板可以进行的运算(包括定义自定义标签,定义它们自己的合并点)。
上面的这种合并点,为所有的类环境使用了同一个文件。当然,也可以为每一个类环境使用不同的合并文件。如果你不想定制全部的类文件,或者你不想为了某些改动而重写模板的时候,这会很有用。不管动机是什么,逐类的合并点很容易识别出来:他们会在名字里包含一个XDoclet的逐类标记{0}。这里有一个生成ejb-jar.xml文件里的安全角色引用的例子:
<XDtMerge:merge file=”ejb-sec-rolerefs-{0}.xml”>
<XDtClass:forAllClassTags tagName=”ejb:security-role-ref”>
<security-role-ref>
<role-name>
<XDtClass:classTagValue
tagName=”ejb:security-roleref”
paramName=”role-name”/>
</role-name>
<role-link>
<XDtClass:classTagValue
tagName=”ejb:security-roleref”
paramName=”role-link”/>
</role-link>
</security-role-ref>
</XDtClass:forAllClassTags>
</XDtMerge:merge>
这段模板会遍历工程里的所有Bean。对于每一个Bean,XDoclet先从Bean的文件名里提取出Bean名,然后替换{0},再根据替换后的文件名去寻找合并文件。例如,如果你有一个名为BlogFacadeBean的Bean,XDoclet会尝试寻找一个名为ejb-src-rolerefs-BlogFacade.xml的合并文件。
如果找不到这个合并文件,则这个<merge>标签的内容模板会被解析。这意味着合并点不仅可以提供定制内容,还可以在一个模板文件里定义一个替换点,当定制内容不存在的时候使用替换点里的内容。不是所有的XDoclet任务都提供了有替换内容的合并点,一般来说,它们更倾向于只提供一个简单的合并点,仅仅当合并文件存在的时候解析并导入合并文件的内容。这取决于任务的开发者觉得哪种合并点更符合他的要求。
还有一点没有介绍到的是XDoclet如何定位合并文件,每一个XDoclet任务或者子任务都会提供一个mergeDir属性,这个属性用于设置你存放合并文件的目录。
一、EJB技术简介
EJB的全称是Enterprise java bean。是JAVA中的商业应用组件技术。EJB结构中的角色 EJB 组件结构是基于组件的分布式计算结构,是分布式应用系统中的组件。
一个完整的基于EJB的分布式计算结构由六个角色组成,这六个角色可以由不同的开发商提供,每个角色所作的工作必须遵循Sun公司提供的EJB规范,以保证彼此之间的兼容性。这六个角色分别是EJB组件开发者(Enterprise Bean Provider) 、应用组合者(Application Assembler)、部署者(Deployer)、EJB 服务器提供者(EJB Server Provider)、EJB 容器提供者(EJB Container Provider)、系统管理员(System Administrator):
二、EJB中各角色的分析
1、EJB组件开发者(Enterprise Bean Provider)
EJB组件开发者负责开发执行商业逻辑规则的EJB组件,开发出的EJB组件打包成ejb-jar文件。EJB组件开发者负责定义EJB的remote和home接口,编写执行商业逻辑的EJB class,提供部署EJB的部署文件(deployment descriptor)。部署文件包含EJB的名字,EJB用到的资源配置,如JDBC等。EJB组件开发者是典型的商业应用开发领域专家。
EJB组件开发者不需要精通系统级的编程,因此,不需要知道一些系统级的处理细节,如事务、同步、安全、分布式计算等。
2、应用组合者(Application Assembler)
应用组合者负责利用各种EJB组合一个完整的应用系统。应用组合者有时需要提供一些相关的程序,如在一个电子商务系统里,应用组合者需要提供JSP(Java Server Page)程序。
应用组合者必须掌握所用的EJB的home和remote接口,但不需要知道这些接口的实现。
3、部署者(Deployer)
部署者负责将ejb-jar文件部署到用户的系统环境中。系统环境包含某种EJB Server和EJB Container。部署者必须保证所有由EJB组件开发者在部署文件中声明的资源可用,例如,部署者必须配置好EJB所需的数据库资源。
部署过程分两步:部署者首先利用EJB Container提供的工具生成一些类和接口,使EJB Container能够利用这些类和接口在运行状态管理EJB。 部署者安装EJB组件和其他在上一步生成的类到EJB Container中。 部署者是某个EJB运行环境的专家。
某些情况下,部署者在部署时还需要了解EJB包含的业务方法,以便在部署完成后,写一些简单的程序测试。
4、EJB 服务器提供者(EJB Server Provider)
EJB 服务器提供者是系统领域的专家,精通分布式交易管理,分布式对象管理及其它系统级的服务。EJB 服务器提供者一般由操作系统开发商、中间件开发商或数据库开发商提供。
在目前的EJB规范中,假定EJB 服务器提供者和EJB 容器提供者来自同一个开发商,所以,没有定义EJB 服务器提供者和EJB容器提供者之间的接口标准。
5、EJB 容器提供者(EJB Container Provider)
EJB 容器提供者提供以下功能:
提供EJB部署工具为部署好的EJB组件提供运行环境 。EJB容器负责为EJB提供交易管理,安全管理等服务。
EJB 容器提供者必须是系统级的编程专家,还要具备一些应用领域的经验。EJB 容器提供者的工作主要集中在开发一个可伸缩的,具有交易管理功能的集成在EJB 服务器中的容器。EJB 容器提供者为EJB组件开发者提供了一组标准的、易用的API访问EJB 容器,使EJB组件开发者不需要了解EJB服务器中的各种技术细节。
EJB容器提供者负责提供系统监测工具用来实时监测EJB容器和运行在容器中的EJB组件状态。
6、系统管理员(System Administrator)
系统管理员负责为EJB服务器和容器提供一个企业级的计算和网络环境。
系统管理员负责利用EJB 服务器和容器提供的监测管理工具监测EJB组件的运行情况。
三、EJB的体系结构:
EJB分布式应用程序是基于对象组件模型的,低层的事务服务用了API技术。EJB技术简化了用JAVA语言编写的企业应用系统的开发,配置。EJB技术定义了一组可重用的组件:Enterprise Beans。你可以利用这些组件,象搭积木一样的建立你的分布式应用程序。当你把代码写好之后,这些组件就被组合到特定的文件中去。每个文件有一个或多个Enterprise Beans,在加上一些配置参数。最后,这些Enterprise Beans被配置到一个装了EJB容器的平台上。客户能够通过这些Beans的home接口,定位到某个beans,并产生这个beans的一个实例。这样,客户就能够调用Beans的应用方法和远程接口。
EJB服务器作为容器和低层平台的桥梁管理着EJB容器和函数。它向EJB容器提供了访问系统服务的能力。例如:数据库的管理和事务的管理,或者对于其它的Enterprise的应用服务器。所有的EJB 实例都运行在EJB容器中。容器提供了系统级的服务,控制了EJB的生命周期。EJB中的有一些易于使用的管理工具如:Security--配置描述器(The Deployment descriptor)定义了客户能够访问的不同的应用函数。容器通过只允许授权的客户访问这些函数来达到这个效果。Remote Connectivity--容器为远程链接管理着低层的通信issues,而且对Enterprise Beas的开发者和客户都隐藏了通信细节。EJB的开发者在编写应用方法的时候,就象是在条用本地的平台一样的。客户也不清楚他们调用的方法可能是在远程被处理的。Life Cycle managment--客户简单的创建一个Enterprise beans的实例,并通常取消一个实例。而容器管理着Enterprise Beans的实例,使Enterprise Beans实现最大的效能和内存利用率。容器能够这样来激活和使Enterprise Beans失效,保持众多客户共享的实例池。等等。 Trasction management-配置描述器定义了Enterprise beans 的事务处理的需求。容器管理着那些管理分布式事务处理的复杂的issues。这些事务可能要在不同的平台之间更新数据库。容器使这些事务之间互相独立,互不干扰。保证所有的更新数据库都是成功发生的,否者,就回滚到事务处理之前的状态。
EJB 组件是基于分布式事务处理的企业级应用程序的组件。所有的EJB都有如下的特点:EJB包含了处理企业数据的应用逻辑。定义了EJB的客户界面。这样的界面不受容器和服务器的影响。于是,当一个EJB被集合到一个应用程序中去时,不用更改代码和重新编译。EJB能够被定制 各种系统级的服务,例如安全和事务处理的特性,都不是属于EJB类的。而是由配置和组装应用程序的工具来实现。 有两种类型的EJB: Session beans 和 entity beans.Session beans是一种作为单用户执行的对象。作为对远程的任务请求的相应,容器产生一个Session beans 的实例。一个Session beans有一个用户.从某种程度上来说,一个Session bean 对于服务器来说就代表了它的那个用户.Session beans 也能用于事务,它能够更新共享的数据,但它不直接描绘这些共享的数据。Session beans 的生命周期是相对较短的。典型的是,只有当用户保持会话的时候,Session beans 才是活着的。一旦用户退出了,Session beans 就不再与用户相联系了。Session beans被看成是瞬时的,因为如果容器崩溃了,那么用户必须重新建立一个新的Session对象来继续会话。
Session bean典型的声明了与用户的互操作或者会话。也就是说,Session bean了在客户会话期间,通过方法的调用,掌握用户的信息。一个具有状态的Session bean称为有状态的Session bean.当用户终止与Session beans互操作的时候.会话终止了,而且,bean 也不再拥有状态值。Session bean也可能是一个无状态的 session bean.无状态的Session beans并不掌握它的客户的信息或者状态。用户能够调用beans的方法来完成一些操作。但是,beans只是在方法调用的时候才知道用户的参数变量。当方法调用完成以后,beans并不继续保持这些参数变量。这样,所有的无状态的session beans的实例都是相同的,除非它正在方法调用期间。这样,无状态的Session beans就能够支持多个用户.容器能够声明一个无状态的Session beans.能够将任何Session beans指定给任何用户.
Entity Beans对数据库中的数据提供了一种对象的视图。例如:一个Entity bean能够模拟数据库表中一行相关的数据。多个client能够共享访问同一个Entity bean.多个client也能够同时的访问同一个Entity bean.Entity beans通过事务的上下文来访问或更新下层的数据。这样,数据的完整性就能够被保证。Entity Beans能存活相对教长的时间,并且状态是持续的。只要数据库中的数据存在,Entity beans就一直存活。而不是按照应用程序或者服务进程来说的。即使EJB容器崩溃了,Entity beans也是存活的。Entity Beans生命周期能够被容器或者 Beans自己管理。如果由容器控制着保证 Entity beans持续的issus。如果由Beans自己管理,就必须写Entity beans的代码,包括访问数据库的调用。
Entity Beans是由主键(primary key 一种唯一的对象标识符)标识的。通常,主键与标识数据库中的一块数据,例如一个表中的一行,的主键是相同的。主键是client能够定位特定的数据块。
四、开发EJB
1、类介绍:
开发EJB的主要步骤一般来说,整个的开发步骤(开发,配置,组装)包括如下几个方面。开发:首先要定义三个类:Bean类本身,Bean的本地和远程接口类。 配置:配置包括产生配置描述器--这是一个XML文件、声明了Enterprise Bean的属性、绑定了bean的class文件(包括stub文件和skeleton文件)。最后将这些配置都放到一个jar文件中。还需要在配置器中定义环境属性。组装应用程序:包括将Enterprise beans安装到Server服务器中,测试各层的连接情况。程序组装器将若干个Enterprise Beans与其它的组件结合起来。组合成一个完整的应用程序。或者将若干个Enterprise beans组合成一个复杂的Enterprise Bean。管理Enterprise Bean。
我们必须定义和编写一些EJB中的基本类。如Enterprise bean类:这是Enterprise bean内部应用逻辑的实现。编写Enterprise bean的远程接口类。编写Enterprise bean的本地接口类。说明主键类,主键类只是对于Entity bean才需要的。在Enterprise bean的配置描述器中指定主键的名字。Enterprise beans提供者定义了远程接口和本地接口,实现了EJB类本身。Remote接口中提供了客户调用EJB实现的应用逻辑函数的接口。而home接口提供了产生和定位remote接口实例的方法。
在Enterprise bean本身类的实现,本地home接口,远程remote接口之间并没有正式的联系(例如继承关系)。但是,在三个类里声明的方法却必须遵守EJB里面定义的规范。例如: 你在Enterprise bean里面声明了一个应用程序的方法或者说应用逻辑。也在beans的remote接口中声明了这个方法,那么,这两个地方必须要同样的名字。Bean的实现里面必须至少有一个Create()方法:ejbCreate()。但是可以有多个带有不同参数的create()方法。 在home接口中,也必须有相同的方法定义(参数的个数相同)。EjbCreate()方法返回的一个容器管理的持久对象。它们都返回一个容器管理持久性的主键值。但是,在home的相应的Create()方法中返回值的类型是remote接口。
注意:实体bean的实现的ejbCreate方法有点不同。实体bean可以不定义ejbCreate方法。如果实体只是通过应用程序或通过数据库管理程序的途径被加到数据库中,实体bean就省略了ejbCreate方法。EjbCreate返回的值是主键类型。如果ejbCreate方法是容器管理持久性的实体bean的方法,它的返回值就是NULL类型。如果实体bean实现了Bean管理的持久性,ejbCreate方法就返回值类型就是主键类型。容器的任务是把各接口和Enterprise bean的实现类结合起来。保证在编译时和运行时,各接口和实现类是相对应的。
EJB的实现类,各接口要从不同的基类中继承下来。一个会话bean必须实现基类javax.ejb.SessionBean。而实体bean必须实现基类javax.ejb.EntiyBean。这些EJB的基类都是从javax.ejb.EnterpriseBean继承而来。而javax.ejb.EnterpriseBean又是从java.io.Serializable继承而来。每一个Enterprise Bean都必须有一个remote接口。Remote接口定义了应用程序规定客户可以调用的逻辑操作。这些是一些可以由客户调用的公共的方法,通常由Enterprise beans类来实现。注意,Enterprise bean的客户并不直接访问Bean。而是通过remote接口来访问。Enterprise bean类的remote接口扩展了javax.ejb.EJBObject类的公共java接口。而Javax.ejb.EJBObject是所有remote接口的基类。其代码如下:
package javax.ejb;
public interface EJBObject extends java.rmi.Remote{
public EJBHome getEJBHome() throws java.rmi.RemoteException;
public Object getPrimaryKey() throws java.rmi.RemoteException;
public void Remove() throws java.rmi.RemtoeException, java.rmi.RemoveException
public Handle getHandle() throws java.rmi.RemoteException;
boolean isIdentical (EJBObject p0) throws java.rmi.RemoteException;
}
getEJBHome()方法允许你取得一个相关的Home接口。对于 实体Bean,用getPrimaryKey()方法获得实体Bean的主键值。Remove()可以删除一个Enterprise bean。具体的语义在各种不同类型的enterprise beans的生命周期中,由上下文中解释的。方法getHandle()返回了一个Enterprise bean实例的持久的句柄。IsIndentical()方法允许你去比较Enterprise beans是否相同。
2、方法:
所有的remote接口中的方法必须声明为公共(public)的,并必须抛出java.rmi.RemotException异常。另外,所有的remote接口中的方法定义的参数和都必须是在RMI-IIOP中有效的。对每一个在remote接口中定义的方法,在Enterprise bean 类里面都要有相应的方法。相应的方法必须要有同样的名字,同样类型和数量的参数,同样的返回值,而且还要抛出同样的例外。 如下代码显示了一个ATM例子的会话bean的remote接口Atm,。里面声明了一个应用方法transfer()。黑体部分表示EJB规范中必须要有的内容。Remote接口必须扩展javax.ejb.EJBObject类。从客户端调用的Enterprise bean的每一个方法都必须在remote接口中声明。Transfer()方法抛出了两个意外。其中InSufficientFundsException例外是应用程序定义的意外。
Public interface Atm extends javax.ejb.EJBObject{
Public void transfer(String Source, String Target, float amount)
Throws java.rmi.RemoteException, InSufficientFundsException;
}
Home接口必须定义一个或多个的Create()方法。每一个这样的Create()方法都必须命名为Create。并且,它的参数,不管是类型还是数量都必须与bean类里面的ejbCreate()方法对应。注意,home接口中的Create()方法和bean类中ejbCreate()方法的返回值类型是不同的。实体bean的home接口还包含find()方法。 每一个Home接口都扩展了javax.ejb.EJBHome接口。如下代码显示了javax.ejb.EJBHome接口的定义:
package javax.ejb;
public interface EJBHome extends java.rmi.Remote() {
void remove(Handle handle) throws java.rmi.RemoteException,RemoveException;
void remove(Object primarykey) throws java.rmi.RemoteException,RemoveException;
EJBMetaData getEJBMetaData() throws RemoteException;
Homehandle getHomeHandle() throws RemoteException;
}
这里提供了两个remove()方法来删除Enterprise bean的实例。第一个remove方法是通过句柄来删除一个Enterprise bean的实例。第二个remove方法通过主键来删除一个Enterprise bean的实例。 在众多的Enterprise bean实例中,句柄唯一的标识一个实例。一个句柄与它引用的Enterprise bean有相同的生命期。考虑一个实体对象,客户可以通过一个句柄来重新获得相应的Enterprise bean的实例。一个句柄能够对应一个Enterprise bean对象的多个实例。例如,即使当Enterprise bean对象所在的主机崩溃了,或者Enterprise bean对象在不同的机器之间移动,句柄仍是有效的。这里的句柄是Serialized句柄,与CORBA中的字符串化的CORBA对象的引用是相似的概念。在EJBHome接口中的第二个remove操作通过其主键来决定要删除的Enterprise bean。主键可以是扩展了Java Object类的任何类型,但是,必须要实现Java的Serializable接口。主键是标识实体bean的主要的方法。通常,主键是数据库中的一个关键字,唯一的定义了由实体bean代表的数据。
方法getEJBMetaData()返回了Enterprise bean对象的metadata接口。这个接口允许客户获得Enterprise bean的metadata信息。当开发工具来编译链接应用程序的时候,或者配置工具来配置的时候,可能会用到metadata信息。Javax.ejb.EJBMetadata接口提供了获得javax.ejb.EJBHome接口,home类,remote接口,还有获得主键的方法。也提供了一个isSesson()的方法来确定在放这个home接口的对象是会话bean还是实体bean。IsStatelessSession()方法指示这个会话bean是有状态还是无状态的。如下代码显示了javax.ejb.EJBMetadata接口的定义部分的代码。
Public javax.ejb; Public interface EJBMetaData{
EJBHome getEJBHome();
Class getHomeInterfaceClass();
Class getRemoteInterfaceClasss();
Class getPrimaryKeyClass();
Boolean isSession();
Boolean isStatelesssSession();
}
对每一个Create()方法,EJB规范定义了如下的命名约定。它的返回值是会话bean的remote接口的类型。方法的名字只能是Create()。对会话bean类中的每一个ejbCreate()方法都必须有一个Create()与之对应。 对于每一个Create()方法的参数的类型和数量都必须与会话bean类中的ejbCreate()方法相对应。方法必须抛出java.rmi.RemoteException例外。 方法必须抛出javax.rmi.CreateExeption例外。 Create()方法的参数是用来初始化新的会话bean对象的。 如下代码显示了一个会话bean对象的不同的Create()方法,其中必须的部分用粗体显示:
public interface AtmHome extends javax.ejb.EJBHome{
Atm create() throws java.rmi.RemoteException,javax.ejb.CreateException;
Atm create(Profile preferredProfile)
Throws java.rmi.RemoteExeption,javax.ehrows java.rmi.RemoteException,RemoveException;
EJBMetaData getEJBMetaData() throws RemoteException;
Homehandle getHomeHandle() throws RemoteException;
}
这里提供了两个remove()方法来删除Enterprise bean的实例。第一个remove方法是通过句柄来删除一个Enterprise bean的实例。第二个remove方法通过主键来删除一个Enterprise bean的实例。在众多的Enterprise bean实例中,句柄唯一的标识一个实例。一个句柄与它引用的Enterprise bean有相同的生命期。考虑一个实体对象,客户可以通过一个句柄来重新获得相应的Enterprise bean的实例。一个句柄能够对应一个Enterprise bean对象的多个实例。例如,即使当Enterprise bean对象所在的主机崩溃了,或者Enterprise bean对象在不同的机器之间移动,句柄仍是有效的。这里的句柄是Serialized句柄,与CORBA中的字符串化的CORBA对象的引用是相似的概念。
在EJBHome接口中的第二个remove操作通过其主键来决定要删除的Enterprise bean。主键可以是扩展了Java Object类的任何类型,但是,必须要实现Java的Serializable接口。主键是标识实体bean的主要的方法。通常,主键是数据库中的一个关键字,唯一的定义了由实体bean代表的数据。方法getEJBMetaData()返回了Enterprise bean对象的metadata接口。这个接口允许客户获得Enterprise bean的metadata信息。当开发工具来编译链接应用程序的时候,或者配置工具来配置的时候,可能会用到metadata信息。Javax.ejb.EJBMetadata接口提供了获得javax.ejb.EJBHome接口,home类,remote接口,还有获得主键的方法。也提供了一个isSesson()的方法来确定在放这个home接口的对象是会话bean还是实体bean。IsStatelessSession()方法指示这个会话bean是有状态还是无状态的。如下代码显示了javax.ejb.EJBMetadata接口的定义部分的代码。
Public javax.ejb;
Public interface EJBMetaData{
EJBHome getEJBHome();
Class getHomeInterfaceClass();
Class getRemoteInterfaceClasss();
Class getPrimaryKeyClass();
Boolean isSession();
Boolean isStatelesssSession();
}
五、EJB的编程环境:
1、 使用Jbuilder
Jbuilder与EJB Container能够进行无缝连接。Jbuilder和Inprise的应用服务器包括了所有的开发和配置Enterprise Beans的工具以及所需要的库:运行和管理Enterprise Bean的容器、命名服务、 事务服务、Java数据库、开发Enterprise Beans所需要的API、一个增强的java-to-iiop编译器,支持值类型和RMI信号等等。
Jbuilder还提供了一个快速开发应用程序Enterprise Beans的工具和向导。通过简单而且直观的步骤,向导帮助你建立一个Enterprise Bean。自己设定某些缺省值,产生了bean的模板,在上面,我们可以增加我们自己的应用逻辑。Jbuilder也提供了一个EJB的接口生成向导。向导在Enterprise Bean的公共方法基础上生成了Remote接口和Home接口。Jbuilder还提供一个配置器的向导帮助我们逐步的建立XML描述器文件。并将生成的Stubs集中到一个jar文件中。
2、使用Jbuilder之外的集成环境:
如果你使用其它的除了别的集成环境(IDE)。要确定使用了集成环境IDE所带的容器工具。也要验证IDE是否支持EJB规范的相应的版本,还要确定它是否正确的支持EJB的API。
要确定JD到所支持的EJB容器的版本。可以通过检查Inprise的安装说明来确定EJB容器所支持的支持JDK的版本。
在配置Enterprise Bean的时候,你必须使用Inprise的应用服务器所提供的工具。这些工具能够编辑和修改第三方的代理商提供的Inprise配置描述器。还能够验证配置描述器,能够验证bean的源代码。
六、一个简单的HELLO例子
1、安装Apusic Application Server
Note:以下以Linux为例,来说明Apusic Application Server的安装过程。其他平台的安装,可参考Apusic Application Server安装手册。
下载JDK1.2,Apusic Application Server必须运行在JDK1.2以上环境中。可从以下站点下载最新JDK。
http://java.sun.com
下载Apusic Application Server
Apusic Application Server 试用版可从以下网址得到:
http://www.apusic.com/download/enter.jsp
在下载完成后,你可以得到一个包裹文件apusic.zip,选定安装目录,假设安装到/usr下,则用以下命令:
cd /usr
jar xvf apusic.zip
/usr下会出现一个目录apusic,Apusic Application Server的所有程序都被解压到/usr/apusic下。
将以下路径加入到CLASSPATH中
/usr/apusic/lib/apusic.jar
$JAVA_HOME/lib/tools.jar
用以下命令运行Apusic Application Server
java -Xms64m com.apusic.server.Main -root /usr/apusic
2、定义EJB远程接口(Remote Interface)
任何一个EJB都是通过Remote Interface被调用,EJB开发者首先要在Remote Interface中定义这个EJB可以被外界调用的所有方法。执行Remote Interface的类由EJB生成工具生成。
以下是HelloBean的Remote Inteface程序:
package ejb.hello;
import java.rmi.RemoteException;
import java.rmi.Remote;
import javax.ejb.*;
public interface Hello extends EJBObject, Remote {
file://this method just get "Hello World" from HelloBean.
public String getHello() throws RemoteException;
}
3、定义Home Interface
EJB容器通过EJB的Home Interface来创建EJB实例,和Remote Interface一样,执行Home Interface的类由EJB生成工具生成。
以下是HelloBean 的Home Interface程序:
package ejb.hello;
import javax.ejb.*;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.*;
/**
* This interface is extremely simple it declares only
* one create method.
*/
public interface HelloHome extends EJBHome {
public Hello create() throws CreateException,
RemoteException;
}
4、写EJB类
在EJB类中,编程者必须给出在Remote Interface中定义的远程方法的具体实现。EJB类中还包括一些 EJB规范中定义的必须实现的方法,这些方法都有比较统一的实现模版,编程者只需花费精力在具体业务方法的实现上。
以下是HelloBean的代码:
package ejb.hello;
import javax.ejb.*;
import java.util.*;
import java.rmi.*;
public class HelloBean implements SessionBean {
static final boolean verbose = true;
private transient SessionContext ctx;
// Implement the methods in the SessionBean
// interface
public void ejbActivate() {
if (verbose)
System.out.println("ejbActivate called");
}
public void ejbRemove() {
if (verbose)
System.out.println("ejbRemove called");
}
public void ejbPassivate() {
if (verbose)
System.out.println("ejbPassivate called");
}
/**
* Sets the session context.
*
* @param SessionContext
*/
public void setSessionContext(SessionContext ctx) {
if (verbose)
System.out.println("setSessionContext called");
this.ctx = ctx;
}
/**
* This method corresponds to the create method in
* the home interface HelloHome.java.
* The parameter sets of the two methods are
* identical. When the client calls
* HelloHome.create(), the container allocates an
* instance of the EJBean and calls ejbCreate().
*/
public void ejbCreate () {
if (verbose)
System.out.println("ejbCreate called");
}
/**
* **** HERE IS THE BUSINESS LOGIC *****
* the getHello just return a "Hello World" string.
*/
public String getHello()
throws RemoteException
{
return("Hello World");
}
}
5、创建ejb-jar.xml文件
ejb-jar.xml文件是EJB的部署描述文件,包含EJB的各种配置信息,如是有状态Bean(Stateful Bean) 还是无状态Bean(Stateless Bean),交易类型等。ejb-jar.xml文件的详细信息请参阅EJB规范。以下是HelloBean的配置文件:
<?xml version="1.0"?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems Inc.//DTD Enterprise JavaBeans 1.2//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_2.dtd">
<ejb-jar>
<enterprise-beans>
<session>
<ejb-name>Hello</ejb-name>
<home>ejb.hello.HelloHome</home>
<remote>ejb.hello.Hello</remote>
<ejb-class>ejb.hello.HelloBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>Hello</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
6、编译和部署
编译Java源文件并将编译后class和ejb-jar.xml打包到Hello.jar
mkdir build
mkdir build/META-INF
cp ejb-jar.xml build/META-INF
javac -d build *.java
cd build
jar cvf Hello.jar META-INF ejb
cd ..
用EJB工具生成可部署到Apusic Application Server中运行的jar文件:
java com.apusic.ejb.utils.EJBGen -d /usr/apusic/classes/Hello.jar build/Hello.jar
增加/usr/apusic/classes/Hello.jar到CLASSPATH中
将Hello.jar加入到Apusic Application Server配置文件中。在/usr/apusic/config/server.xml 加入以下几行:
<module>
<ejb>
<ejb-uri>classes/Hello.jar</ejb-uri>
<bean>
<ejb-name>Hello</ejb-name>
<jndi-name>HelloHome</jndi-name>
</bean>
</ejb>
</module>
启动服务器
java -Xms64m com.apusic.server.Main -root /usr/apusic
7、写客户端调用程序
您可以从Java Client,JSP,Servlet或别的EJB调用HelloBean。
调用EJB有以下几个步骤:
通过JNDI(Java Naming Directory Interface)得到EJB Home Interface
通过EJB Home Interface 创建EJB对象,并得到其Remote Interface
通过Remote Interface调用EJB方法
以下是一个从Java Client中调用HelloBean的例子:
package ejb.hello;
import javax.naming.Context;
import javax.naming.InitialContext;
import java.util.Hashtable;
import javax.ejb.*;
import java.rmi.RemoteException;
/**
* @author Copyright (c) 2000 by Apusic, Inc. All Rights Reserved.
*/
public class HelloClient{
public static void main(String args[]){
String url = "rmi://localhost:6888";
Context initCtx = null;
HelloHome hellohome = null;
try{
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.apusic.jndi.InitialContextFactory");
env.put(Context.PROVIDER_URL, url);
initCtx = new InitialContext(env);
}catch(Exception e){
System.out.println("Cannot get initial context: " + e.getMessage());
System.exit(1);
}
try{
hellohome = (HelloHome)initCtx.lookup("HelloHome");
Hello hello = hellohome.create();
String s = hello.getHello();
System.out.println(s);
}catch(Exception e){
System.out.println(e.getMessage());
System.exit(1);
}
}
}
运行HelloClient,可得到以下输出:
Hello World
http://yingmingpan.bokee.com/tb.b?diaryId=14817948 |
2007年9月29日
#
EJB2.0雨夜教程之一
EJB雨夜原创讲解
初识EJB
EJB是什么呢?
EJB是一个J2EE体系中的组件.再简单的说它是一个能够远程调用的javaBean.
它同普通的javaBean有两点不同.
第一点,就是远程调用.
第二点,就是事务的功能,我们在EJB中声明的事务会通过容器帮助我们来处理.
支持EJB的SERVER有以下几个:
Weblogic
Webspere
Appserver
Jboss
我选用的是weblogic+JBuilder9开发.
回过来我们继续说这个EJB的原理.
它是分布式的.这个的意思其实很简单不需要想太复杂.
就是我们把事情不是交给一个人去处理.而是把事情划分一下交给多个人处理,
而多个人处理之后我们让这个分配的人来把最后得到的结合处理.
EJB我们首先要明确它是放在服务器端的组件.
一共有三种EJB
1,SessionBean 复杂处理业务逻辑的.
2,EntityBean 处理数据
3,MessageBean 消息Bean异步,耦合的处理.
那么谁能调用EJB呢?
也或者说EJB被谁调用呢?
我们说它就是放在server的一个能够远程调用的javaBean.
所以它可以被Java的语言调用servlet,jsp等都ok的说.
而且它还可以被C++语言调用.很强大吧.
EJB的最基本的出发点就是推动Java在服务器端的编程能力.
所以呢它的功能就我个人来看太强悍了..= =
这个远程调用是通过什么呢.
Java是通过RMI-JRMP(java远程方法调用)协议来调用.EJB就是通过这个来实现的.
C++是通过Corba-iiop协议来调用的.这个协议支持两端不同语言的调用.
Corba是公共对象请求代理服务
iiop是网络内部对象协议.
下面我们来简单说一下这个分布式的调用。
客户端发送一个请求给服务器
首先是传给stub代理类它发送参数给skeleton委托类然后这个类调用我们的实现类取得结果再远路返回。
这样一个分布处理就结束了。
后面会具体的结合代码分析。
先记住这个大致的结构就好。
我们写一个EJB需要做那些工作呢?
1,写一个接口继承EJBObject 这个类作为远程接口
2,写一个接口继承EJBHome 这个类里有一个方法是create()它返回接口类型。
3,写一个Bean继承SessionBean, 这个类里包含一个create()方法和一个业务方法。
4,写一个ejb-jar.xml 这个xml是把上面的三个文件组合起来
5,写一个weblogic-ejb-jar.xml 这个xml是连接查找source的作用
(不同的server会是不同的。这里选用的是weblogic)
上面的两个xml文件需要放在META-INF目录下。
而以上这些类都需要打包在一个jar文件中然后在server部署。
这样就完成了EJB的部署
例如:
我们写个简单的计算吧。
(先声明下吧本教程的说明都是我自己的理解,
也许并不是一些文档上的描述那么正规但是很方便理解的。
如果有实在无法苟同的地方大家多多包含。这是为了便于理解的讲解)
AddCount.java这个就是我们继承EJBObject的类.
import javax.ejb.*;
import java.rmi.*;
public interface AddCount extends EJBObject
{
public int addCount(int a,int b) throws RemoteException;
}
接口的作用.在这里强调一下吧.所有实现这个接口的类都会是这个接口的类型.
同时都包含接口的方法的实现.
这个接口中的方法也就是在以后会实现的方法.我们这里要做的是一个加法的运算.
AddCountHome.java这个是继承EJBHome的类.它里面包含的这个create()返回的是AddCount类型对象.
import javax.ejb.*;
import java.rmi.*;
public interface AddCountHome extends EJBHome
{
public AddCount create() throws RemoteException,CreateException;
}
下面这个是我们的Bean.这个类继承了SessionBean
import javax.ejb.*;
public class AddCountBean implements SessionBean
{
public void ejbCreate()
{
}
public void setSessionContext(SessionContext ctx)
throws EJBException,
java.rmi.RemoteException
{
}
public void ejbRemove()
throws EJBException,
java.rmi.RemoteException
{
}
public void ejbActivate()
throws EJBException,
java.rmi.RemoteException
{
}
public void ejbPassivate()
throws EJBException,
java.rmi.RemoteException
{
}
public int addCount(double a,double b)
{
return a+b;
}
}
这个里面我们实现了业务的方法addCount(){a+b;}
同时需要指出一下它的其他方法(这些仅仅简单指出后面的教程有详细说明)
首先是ejbCreate()这个方法实际上是对我们的远程接口的实现类的初始化.
setSessionContext(SessionContext ctx)设置context.容器是在这个方法之后产生的实例.
ejbRemove()毫无疑问是一个实例结束移除.
ejbActivate()激活方法.它的作用是激活钝化.
ejbPassivate()钝化方法.当实例的内容长时间不进行处理的时候就会钝化.也就是闲置的意思.
以上这几个是SessionBean的基本方法.希望大家可以自己慢慢理解.
之后也会反复说到这些的.
下面我们写xml文件
首先是ejb-jar.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
<ejb-jar>
<enterprise-beans>
<session>
<display-name>first</display-name>
<ejb-name>add</ejb-name>
<home>AddCountHome</home>
<remote>AddCount</remote>
<ejb-class>AddCountBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>add</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
下面这个是weblogic-ejb-jar.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE weblogic-ejb-jar PUBLIC '-//BEA Systems, Inc.//DTD WebLogic 8.1.0 EJB//EN' 'http://www.bea.com/servers/wls810/dtd/weblogic-ejb-jar.dtd'>
<weblogic-ejb-jar>
<weblogic-enterprise-bean>
<ejb-name>
add
</ejb-name>
<jndi-name>rainnight</jndi-name>
</weblogic-enterprise-bean>
</weblogic-ejb-jar>
我们接下来写测试类
import java.util.*;
import java.naming.*;
import java.rim.*;
public class AddTest{
public static void main(String bb[])throws Exception{
Hashtable hash = new Hashtable();
hash.put(Context.INITIAL_COUNTEXT_FACTORY,
"weblogic.jndi.WLInitialContextFactory");
hash.put(Context.PROVIDER_URL,
"t3://localhost:7001");
Object obj = context.lookup("rainnight");
AddCountHome home = (AddCountHome)PortableRemoteObject(obj,AddCountHome.class);
AddCount local = home.create();
System.out.println(local.add(1,1));
}
}
测试类的hash是初始化我们的weblogic的xml信息.
这里的t3://localhost:7001是weblogic的特殊协议指向的是ip位置.
然后lookup("rainnight")查找jndi,而实际上也就是通过这个jndi找到我们的ejb组件.
通过得到的对象我们对比一下是不是Home类型.
然后我们执行home.create()产生AddCount的实例
最后调用方法就ok了.
下面是如何执行这个的方法.
第一步,jdk,weblogic.需要快些下载安装啦..
第二步,配置环境变量.基本的java的环境变量如何配置我就不说了.
这里需要特别指出要把java目录下的lib中的tools.jar加载到我们的class_path中.
然后我们再把weblogic的目录下的server目录下的lib中的weblogic.jar找到也加载进来.
这样我们编译需要的东西就ok了.
第三步,编译java类.
第四步,打包.jar cvf AddCount.jar *.class META-INF/*.xml
第五步,java weblogic.appc AddCount.jar
第六步,部署到weblogic中.
第七步,运行Test类.
jar
功能说明:
Java归档工具
语法:
jar [ 命令选项 ] [manifest] destination input-file [input-files]
补充说明:
jar工具是个java应用程序,可将多个文件合并为单个JAR归档文件。jar是个多用途的存档及压缩工具,它基于ZIP和ZLIB压缩格式。然而, 设计jar的主要目的是便于将java applet或应用程序打包成单个归档文件。将applet或应用程序的组件(.class 文件、图像和声音)合并成单个归档文件时,可以用java代理(如浏览器)在一次HTTP事务处理过程中对它们进行下载,而不是对每个组件都要求一个新连 接。这大大缩短了下载时间。jar还能压缩文件,从而进一步提高了下载速度。此外,它允许applet的作者对文件中的各个项进行签名,因而可认证其来 源。jar工具的语法基本上与tar命令的语法相同。
命令选项
-c 在标准输出上创建新归档或空归档。
-t 在标准输出上列出内容表。
-x[file] 从标准输入提取所有文件,或只提取指定的文件。如果省略了file,则提取所有文件;否则只提取指定文件。
-f 第二个参数指定要处理的jar文件。在-c(创建)情形中,第二个参数指的是要创建的jar文件的名称(不是在标准输出上)。在-t(表(或-x(抽取)这两种情形中,第二个参数指定要列出或抽取的jar文件。
-v 在标准错误输出设备上生成长格式的输出结果。
-m 包括指定的现有清单文件中的清单信息。用法举例:“jar cmf myManifestFile myJarFile *.class”
-0 只储存,不进行 ZIP 压缩。
-M 不创建项目的清单文件。
-u 通过添加文件或更改清单来更新现有的 JAR 文件。例如:“jar -uf foo.jar foo.class”将文件 foo.class 添加到现有的JAR文件foo.jar中,而“jar umf manifest foo.jar”则用manifest中的信息更新foo.jar的清单。
-C 在执行 jar 命令期间更改目录。例如:“jar -uf foo.jar -C classes *”将classes目录内的所有文件加到foo.jar中,但不添加类目录本身。
程序示例
1:将当前目录下所有CLASS文件打包成新的JAR文件:
jar cf file.jar *.class
2:显示一个JAR文件中的文件列表
jar tf file.jar
3:将当前目录下的所有文件增加到一个已经存在的JAR文件中
jar cvf file.jar *
2007年9月4日
#
为什么要区分J2EE容器和J2EE应用系统?
我们知道,J2EE应用系统只有部署在J2EE容器中才能运行,那么为什么划分为J2EE容器和J2EE应用系统? 通过对J2EE容器运行机制的分析(见我的电子教材“EJB实用原理”),我们可以发现:实际上J2EE容器分离了一般应用系统的一些通用功能,例如事务机制、安全机制以及对象池或线程池等性能优化机制。
这些功能机制是每个应用系统几乎都需要的,因此可以从具体应用系统中分离出来,形成一个通用的框架平台,而且,这些功能机制的设计开发有一定难度,同时运行的稳定性和快速性都非常重要,必须经过长时间调试和运行经验积累而成,因此,形成了专门的J2EE容器服务器产品,如Tomcat JBoss、Websphere、WebLogic等。
从J2EE系统划分为J2EE容器和J2EE应用系统两个方面,我们已经看到一种分散关注的思路(separation of concerns)。
分散关注
将通用需求功能从不相关类之中分离出来;同时,能够使得很多类共享一个行为,一旦行为发生变化,不必修改很多类,只要修改这个行为就可以。
AOP就是这种实现分散关注的编程方法,它将“关注”封装在“方面”中。
AOP是什么?
AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向方面编程。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。
举例:假设有在一个应用系统中,有一个共享的数据必须被并发同时访问,首先,将这个数据封装在数据对象中,称为Data Class,同时,将有多个访问类,专门用于在同一时刻访问这同一个数据对象。
为了完成上述并发访问同一资源的功能,需要引入锁Lock的概念,也就是说,某个时刻,当有一个访问类访问这个数据对象时,这个数据对象必须上锁Locked,用完后就立即解锁unLocked,再供其它访问类访问。
使用传统的编程习惯,我们会创建一个抽象类,所有的访问类继承这个抽象父类,如下:
abstract class Worker{
abstract void locked();
abstract void accessDataObject();
abstract void unlocked();
}
|
缺点:
- accessDataObject()方法需要有“锁”状态之类的相关代码。
- Java只提供了单继承,因此具体访问类只能继承这个父类,如果具体访问类还要继承其它父类,比如另外一个如Worker的父类,将无法方便实现。
- 重用被打折扣,具体访问类因为也包含“锁”状态之类的相关代码,只能被重用在相关有“锁”的场合,重用范围很窄。
仔细研究这个应用的“锁”,它其实有下列特性:
- “锁”功能不是具体访问类的首要或主要功能,访问类主要功能是访问数据对象,例如读取数据或更改动作。
- “锁”行为其实是和具体访问类的主要功能可以独立、区分开来的。
- “锁”功能其实是这个系统的一个纵向切面,涉及许多类、许多类的方法。如下图:
因此,一个新的程序结构应该是关注系统的纵向切面,例如这个应用的“锁”功能,这个新的程序结构就是aspect(方面)
在这个应用中,“锁”方面(aspect)应该有以下职责:
提供一些必备的功能,对被访问对象实现加锁或解锁功能。以保证所有在修改数据对象的操作之前能够调用lock()加锁,在它使用完成后,调用unlock()解锁。
AOP应用范围
很明显,AOP非常适合开发J2EE容器服务器,目前JBoss 4.0正是使用AOP框架进行开发。
具体功能如下:
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging 调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务
AOP有必要吗?
当然,上述应用范例在没有使用AOP情况下,也得到了解决,例如JBoss 3.XXX也提供了上述应用功能,但是没有使用AOP。
但是,使用AOP可以让我们从一个更高的抽象概念来理解软件系统,AOP也许提供一种有价值的工具。可以这么说:因为使用AOP结构,现在JBoss 4.0的源码要比JBoss 3.X容易理解多了,这对于一个大型复杂系统来说是非常重要的。
从另外一个方面说,好像不是所有的人都需要关心AOP,它可能是一种架构设计的选择,如果选择J2EE系统,AOP关注的上述通用方面都已经被J2EE容器实现了,J2EE应用系统开发者可能需要更多地关注行业应用方面aspect。
AOP具体实现
AOP是一个概念,并没有设定具体语言的实现,它能克服那些只有单继承特性语言的缺点(如Java),目前AOP具体实现有以下几个项目:
AspectJ (TM): 创建于Xerox PARC. 有近十年历史,成熟
缺点:过于复杂;破坏封装;需要专门的Java编译器。
动态AOP:使用JDK的动态代理API或字节码Bytecode处理技术。
基于动态代理API的具体项目有:
JBoss 4.0 JBoss 4.0服务器
nanning 这是以中国南宁命名的一个项目,搞不清楚为什么和中国相关?是中国人发起的?
基于字节码的项目有:
aspectwerkz
spring ?
在以后其它文章中,我将继续对AOP概念进行分析,和大家一起学习进步。
转载 板桥里人 http://www.jdon.com 2004/01/09