??xml version="1.0" encoding="utf-8" standalone="yes"?>
request.getRealPath("../");//|页所在目录的上一层目?br />
request.getContextPath();//应用的web目录的名U?br />
?a href="http://localhost:7001/bookStore/">http://localhost:7001/bookStore/
/bookStore/ => [contextPath] (request.getContextPath())
获取Web目的全路径
String strDirPath = request.getSession().getServletContext().getRealPath("/");
]]>
XDoclet是一个代码生成工P它可以把你从Java开发过E中J重的重复劳动中解脱出来。XDoclet可以让你的应用系l开发的更加快速,而你只要付比原先更少的努力。你可以把你手头上的冗长而又必需的代码交l它帮你完成Q你可以逃脱“deployment descriptor地狱”Q你q可以你的应用pȝ更加易于理。而你所要做的,只不q是在你的注释里Q多加一些类javadoc属性。然后,你会惊讶于XDocletZ做到的一切?br />
讨论XDocletQ有一Ҏ较容易生淆,那就是XDoclet不但是一pȝ的代码生成应用程序,而且它本w还是一个代码生成框架。虽然每个应用系l的l节千变万化Q比如EJB代码生成和Struts代码生成是不一LQ而JMX代码生成又是另一番景象)Q但q些代码生成的核心概念和用法却是cM的?br />
在这一章里Q我们将会看到渗透到所有XDoclet代码生成E序当中的XDoclet框架基础概念。但在之前,我们先从一个例子入手?br />
2.1 XDoclet in action
每一个程序员都会认识刎ͼ他们的程序永q也不会完成。M有另一些的功能需要添加,另一些的BUG需要修正,或者需要不断的q行重构。所以,在代码里d注释Q提醒自己(或者其他的E序员)有哪些Q务需要完成已成ؓ一个共识?br />
如何来跟t这些Q务是否完成了呢?理想情况下,你会攉整理出来一个TODOd列表。在q方面,XDoclet提供了一个强大的TODO生成器,来帮助你完成q个d。这是一个把XDoclet引入目的好Z?br />
2.1.1 一个公qd
假设你正在开发一个用了勺子的类?br />
public class Matrix {
// TODO ? 需要处理当没有勺子的情?br />
public void reload() {
// ...
Spoon spoon = getSpoon();
// ...
}
}
理想情况下,你在下一ơ阅读这D代码的时候,你会处理q个“I勺?#8221;(null spoon)的问题。但如果你过了很久才回来看这D代码,你还会记得在q个c里q有一些工作要做吗Q当Ӟ你可以在你的源码里全局搜烦TODOQ甚至你的集成开发环境有一个内建的TODO列表支持。但如果你想把Q务所在的cdҎ也标注出来的话,XDoclet可以是另一U选择。XDoclet可以Z的项目生成一个TODO报表?br />
2.1.2 dXDoclet标签
Z把你的TODO目转换成另一U更加正式的格式Q你需要对代码q行一些细微的改动。如下所C:
public class Matrix {
/** @todo 需要处理当没有勺子的情?nbsp;*/
public void reload() {
// ...
}
}
q里加入了一个XDoclet需要的cjavadoc标签。XDoclet会用这些标{标记的信息Q以及在q种情况下标{所处的cdҎQ来生成TODO报表?br />
2.1.3 与Ant集成
要生成TODO报表Q你需要确保在你的机器上正安装了XDoclet?br />
在Antd里,最要包含一个目标(例如init目标Q定?lt;documentdoclet>dQ这是一个Ant自定义Q务,例如Q?br />
<taskdef name=”documentdoclet”
classname=”xdoclet.modules.doc.DocumentDocletTask”
classname=”xdoclet.lib.path” />
q个<documentdoclet>d是XDoclet核心代码生成应用E序中的一个?br />
现在Q你可以在Ant构徏文g中加入一个todo目标调用q个d来生成TODO报表Q如Q?br />
<target name=”todo” depends=”init”>
<documentdoclet destdir=”todo”>
<fileset dir=”${dir.src}”>
<include name=”**/*.java” />
</fileset>
<info/>
</documentdoclet>
</target>
<info>子Q务会遍历你的源文Ӟ查找todo标签Qƈ在todo子目录下生成HTML格式的TODO报表?br />
2.1.4 创徏一个更加职业化的TODO报表
XDoclet生成的TODO报表可以有一个更加职业化的外表。报表会列出一个概览,昄在哪个包哪个c里有todo(以及todo的个数Q。Todo可以跟在方法、类和域上,从报表上可以清楚的区别它们。类U别的todo会标注classQ方法别的todo会在方法签名上标注M。构造函数和域相关的todo也会进行相似的标注?br />
q个d看v来很单,但考虑C所需要做的只是在注释上添加一些格式化的@todo标签Q相对于那种只有人才可以理解的无格式的松散的注释Q这U标{是机器可读的,也更Ҏ~程处理。生成的输出也更Ҏ阅读q且更加的商业化?br />
2.2 d和子d
生成todo报表Q只是XDoclet可以完成的事情当中的冰山一角。当初,XDoclet因ؓ可以自动生成EJBJ杂的接口和布v描述文g而声名鹊赗然而,现在的XDoclet已经发展成了一个全功能的、面向属性的代码生成框架。J2EE代码生成只是XDoclet的一个应用方面,它可以完成的d已经q远越了J2EE和项目文的生成?br />
2.2.1 XDoclet d
到现在ؓ止,我们一直在讨论使用XDoclet生成代码Q但事实上,更确切的说法应该是,我们使用XDoclet的一个特定的d来生成代码,比如<ejbdoclet>。每一个XDocletdx于一个特定的领域Qƈ提供q个领域的丰富的代码生成工具?br />
[定义QQ务(TasksQ是XDoclet里可用的代码生成应用E序的高层概c]
在XDoclet里,目前已有如下所C的七个核心d?br />
<ejbdoclet>Q面向EJB领域Q生成EJB、工L和布|描q符?br />
<webdoclet>Q面向Web开发,生成serlvet、自定义标签库和web框架文g?br />
<hibernatedoclet>QHibernate持箋Q配|文件、Mbeans
<jdodoclet>QJDOQ元数据Qvender configuration
<jmxdoclet>QJMXQMBean接口QmletsQ配|文件?br />
<doclet>Q用用戯定义模板来生成代码?br />
<documentdoclet>Q生成项目文Ӟ例如todo列报表)
q其中,<ejbdoclet>最常用Qƈ且很多项目也仅仅使用XDoclet来进行EJB代码生成?lt;webdoclet>是其ơ一个常用的代码生成d。当Ӟ在一个项目中同时使用几个XDocletd是可能的Qƈ且也是推荐的Q,但在q些d之间是完全独立的Q它们彼此之间ƈ不能q行直接的交?br />
2.2.2 XDoclet子Q?br />
XDoclet的Q务是领域相关的,而在某个特定领域的XDocletdQ又p许多多紧密耦合在一L子Q务组成的Q这些子d每个都仅仅执行一个非常特定和单的代码生成d?br />
[定义Q子dQsubtasksQ是׃Q务提供的单目标的代码生成q程]
d提供子Q务执行时的上下文Qƈ且把q些相关的子dl织理了v来。Q务会依赖q些子Q务来生成代码。在一个Q务当中调用多个子d来协同完成各U各h较大型的代码生成d是非常常见的。比如,在开发EJBӞ你可能想要ؓ每一个bean生成一个home接口Q一个remote接口以及ejb-jar.xml布v描述W文件。这是?lt;ejbdoclet>d的上下文环境中的三个独立的代码生成子d?br />
子Q务可以随意的l合排列Q以满目代码生成的需要。某个XDocletd包含的子dl常会共享功能和在源文g中用相同的XDoclet标签。这意味着当你开始一个Q务的时候,你可以很Ҏ的集成进一个相关的子Q务,而不需要很大的改动?br />
子Q务交?br />
让我们以<ejbdoclet>dZQ看一下相关的子Q务之间是如何q行兌的。假设你正在开发一个CMPQ容器管理持久化Q实体Bean。你惌使用一?lt;ejbdoclet>的子dQ?br />
•<deploymentdescriptor>Q生成ejb-jar.xml布v描述W文件?br />
•<localhomeinterface>Q生成local home接口?br />
•<localinterface>Q生成local接口?br />
在执行如上子d的时候,你需要标记出你的实体Bean的CMP域。当你发布你的bean的时候,你还需要在开发商相关的布|描q符中提供某个特定的关系数据库中的特定表和列与你的CMP实体Bean的映关pRXDoclet可以让你在原先已存在的CMP XDoclet属性基上再加上一些关pL属性,然后Q你可以在d中加入一个开发商相关的子dQ例?lt;jboss>或?lt;weblogic>Q来生成布v描述W文件。XDoclet提供了几乎所有的应用服务器的支持Q你只需要一些初始化的小改动Q就可以q行q些应用服务器相关的代码生成了?br />
但那只是冰山一角。你q可以?lt;entitycmp>子Q务ؓZ的bean生成一个实体bean接口的实现子cR如果你使用<valueobject>子Q务来Z你的bean生成值对象,<entityemp>子Q务还会ؓ你的值对象生成方法的实现代码?br />
觉得不可思议了吧。可惜XDoclet没有提供<cupofcoffee>子Q务,要不然我们可以喝杯咖啡,休息一下啦?br />
q里不是惛_你介l?lt;ejbdoclet>所有的子Q务或?lt;ejbdoclet>可以完成的所有代码生成功能,而仅仅是惛_你展CZ下Q务的子Q务之间是如何工作在一L。一旦你开始ƈ熟悉了一个XDoclet 子Q务,熟悉另一个子d会变得非常简? 那种每个子Q务都是孤立的相比Q用这U可以相互协作的子Q务,开发成本会显著的降低,效果也更加的立竿见媄?nbsp;
2.3 使用Ant执行d
XDoclet“?#8221;l了Ant。XDocletd是Ant的自定义dQ除此以外,没有其他q行XDocletd的方法。所q的是,Ant已经成ؓ了Java构徏工具事实上的标准Q所以这不算什么限制。事实上Q反q来QXDoclet与Ant的这U?#8220;亲密”关系使得XDoclet可以参与CQ何Ant构徏q程当中厅R?br />
2.3.1 声明d
XDocletq没有和Ant一起发布,所以如果你惌使用XDoclet的话Q就需要单独的下蝲和安装。在使用M一个XDoclet的Q务之前,你首先需要在使用Ant?lt;taskdef>d来声明它。例如:
<taskdef name=”ejbdoclet”
classname=”xdoclet.modules.ejb.EjbDocletTask”
classpathref=”xdoclet.lib.path”/>
如果你熟悉Ant的话Q你׃知道q段代码是告诉Ant加蝲<ejbdoclet>的Q务定义。当Ӟ你也可以以你喜欢的Q何方式来命名q个自定义Q务,但最好还是遵守标准的命名规律以免发生h。classname和classpathref属性告诉Ant到哪里去扑֮现这个自定义d的XDocletcR如果你想用其他的XDocletdQ就必须要类D样首先声明这个Q务?br />
一般共通的做法是,把所有需要用的XDocletd都放在Ant的一个目标里声明Q这样在其他的目标里如果需要用这些Q务,只要dependsq个d可以了。你可能已经在Ant的构建文仉包含了init目标Q这是攄XDocletd声明的好地方Q当然如果你没有Q你也可以徏一个)。下面的例子是在一个init目标里加入了<ejbdoclet>?lt;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>
现在QQ务声明好了,XDoclet“整装待发”?br />
2.3.2 使用d
你可以在M目标里用声明好的Q务。在d的上下文环境里,可以调动相关的子d。让我们看一个例子,q个例子调用?lt;ejbdoclet>d。不要担心看不懂语法的细节,现在你只需要关心一些基概念可以了?br />
<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>
把Q务想像成一个子E序q行旉要的一个配|环境(CQ子d才是真正q行代码生成工作的)。当调用一个子dӞ子Q务从dl承上下文环境,当然Q你也可以根据需要随意的覆盖q些倹{在上面的例子里Q因?lt;deploymentdescriptor>子Q务生成的布v描述W文件和其他生成各种接口的子d生成的Java源文仉要放在不同的位置Q所以覆盖了destdir的属性倹{布|描q符文g需要放在一个在打包EJB JAR文g的时候可以容易包含进来的地方Q而生成的Java代码则需要放|在一个可以调用Java~译器进行编译的地方。需要这些子d之间是紧密关联的Q但只要你需要,你可以有_的自L控制d的生成环境?br />
<fileset>属性同栯应用到所有的子Q务。这是一个Ant的复杂类型(相对于文本和数值的单类型)Q所以以子元素的方式在Q务中声明。不要把它和子Q务Z谈。当Ӟ如果你想在某个子d中另外指定一个不同的输入文g集,你也可以在这个子d中放|一?lt;fileset>子元素来覆盖它?br />
子Q务的可配|选项q远不止q些。我们会在下一章l介l所有的d和子dQ以及常用的配置选项?br />
2.4 用属性标注你的代?br />
可重用的代码生成pȝ需要输入来生成感兴的输出。一个解析器生成器也需要一个语a描述来解析生成解析器。一个商务对象代码生成器需要领域模型来知道要生成哪些商务对象。XDoclet则需要Java源文件做出来生成相关的类或者布|?配置文g?br />
然而,源文件可能ƈ没有提供代码生成所需要的所有信息。考虑一个基于servlet的应用,当你想生成web.xml文g的时候,servlet源文件仅可以提供cd和适当的servlet接口Ҏ。其他的信息比如URI pattern映射、servlet需要的初始化参数等信息q没有涵盖。显而易见,如果classq没有提供这些信息给你,你就需要自己手动在web.xml文g时填写这些信息?br />
XDoclet当然也不会知道这些信息。幸q的是,解决Ҏ很简单。如果所需信息在源文g时没有提供,那就提供它,做法是在源文g里加入一些XDoclet属性。XDoclet解析源文Ӟ提取q些属性,q把它们传递给模板Q模板用这些信息生成代码?br />
2.4.1 剖析属?br />
XDoclet属性其实就是javadoc的扩展。它们在外表上和使用上都有javadoc属性一P可以攄在javadoc文注释里。文档注释以/**开始,*/l尾。下面是一个简单的例子Q?br />
/**
* q是一Djavadoc注释?br />
* 注释可以被分解成多行Q每一行都以星号开始?br />
*/
在注释里的所有文本都被视为javadoc注释Qƈ且都能够被XDoclet讉K到。注释块一般都与Java源文件中的某个实体有养Iq紧跟在q个实体的前面。没有紧跟实体的注释块将不会被处理。类Q或者接口)可以有注释块Q方法和域也可以有自q注释块,比如Q?br />
/**
* cL释块
*/
public class SomeClass {
/** 域注释块 */
private int id;
/**
* 构造函数注释块
*/
public SomeClass() {
// ...
}
/**
* Ҏ注释?br />
*/
public int getId() {
return id;
}
}
注释块分成两部分Q描q部分和标签部分。当遇到W一个javadoc标签Ӟ标签部分开始。Javadoc标签也分成两部分Q标{和标{描q。标{描q是可选的Qƈ且可以多行。例如:
/**
* q是描述部分
* @tag1 标签部分从这里开?br />
* @tag2
* @tag3 前面一个标{没有标{描q?br />
* q个标签有多行标{描q?br />
*/
XDoclet使用参数化标{扩展了javadoc标签。在XDoclet里,你可以在javadoc标签的标{描q部分加入name=”value”参数。这个微的改动大大增强了javadoc标签的表达能力,使得javadoc标签可以用来描述复杂的元数据。下面的代码昄了用XDoclet属性描q实体BeanҎQ?br />
/**
* @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();
参数化标{օ许组合逻辑上相兌的属性。你可以加入描述q个cȝ元信息,使得q个cȝ信息_生成代码。另外,E序员借由阅读q样的元信息Q可以很快的理解q个cL如何使用的。(如果q个例子上的元信息你看不懂,不要担心Q在W?章里Q我们会学到EJB相关的标{以及它们的涉|。)
另外Q请注意上面的例子中Q所有的标签名都以ejb开头。XDoclet使用namespace.tagname的方式给标签提供了一个命名空间。这样做除了可以跟javadoc区别开来以外,q可以把d相关的标{lv来,以免d之间的标{生淆?nbsp;
2.5 代码生成模式
XDoclet是一U基于模板的代码生成引擎。从高层视图上来看,输出文g其实是p析执行各式各L模板生成出来的。如果你理解了模板以及它所执行的上下文环境Q就可以切的认识到QXDoclet可以生成什么,不可以生成什么。如果你正在评估XDocletq_Q理解这些概忉|非常重要的。要不然Q你可能会错qXDoclet的许多强大的功能Q也可能会被XDoclet的一些限制感到迷惑?br />
XDocletq行在在Ant构徏文g环境中,它提供了Ant自定义Q务和子Q务来与XDoclet引擎交互。Q务是子Q务的容器Q子d负责执行代码生成。子d调用模板。模板提供了你将生成代码的饼q模子。XDoclet解析输入的源文gQ提取出源文件中的XDoclet属性元数据Q再把这些数据提供给模板Q驱动模板执行。除此之外,模板q可以提供合q点(merge points)Q允许用h入一些模板片?合ƈ文gmerge files)来根据需要定制代码生成?br />
2.5.1 模板基础
XDoclet使用代码模板来生成代码。模?template)是你想生成文件的原型。模杉K使用一些XML标签来指导模板引擎如何根据输入类以及它们的元数据来调整代码的生成?br />
[定义Q模?template)是生成代码或描述文g的抽象模视图。当模板被解析的时候,指定的细节信息会被填入。]
模板一般情况下会有一个执行环境。模板可能应用在一个类环境(转换生成transform generation)Q也有可能应用在一个全局环境(聚集生成aggregate generation)。{换生成和聚集生成是XDoclet的两U类型的d模式Q理解它们之间的区别对于理解XDoclet是非帔R要的?br />
当你使用XDoclet生成布置描述W文件时Q你使用的是聚集生成。布|描q符文gq不仅仅只与一个类相关Q相反,它需要从多个c里聚集信息C个输入文件。在q种生成模式里,解析一ơ模板只会生成一个输出文Ӟ不管有多个输入文g?br />
在{换生成模式里Q模杉K到每一个源文g׃解析一ơ,Ҏ该文件类的上下文环境生成输出。这U生成模式会为每一个输入文件生成一个输出文件?br />
转换生成模式的一个很好的例子是生成EJB的local和remote接口。显Ӟ接口是和BeancM一相关的。从每一个类里提取信?cM及它的方法、域、接口以及XDoclet属性等信息)转换出接口。除此以外,不需要其他的信息?br />
从实现里提取出接口似乎有点反向。如果你手写E序的话Q一般来说会先定义一个接口,然后再写一个类来关现它。但XDoclet做不刎ͼXDoclet不可能帮你实C个已有接口,因ؓ它不可能帮你生成你的业务逻辑。当Ӟ如果业务逻辑可以从接口本w得?比如JavaBean的get/set讉K?或者用XDoclet属性声明好Q那么生成业务逻辑代码来实C个接口也不是不可能。但一般情况下Q这样做不太现实。相比而言Q提供一个实玎ͼq描q接口与q个实现之间的关联就Ҏ多了?br />
聚集生成和{换生成主要区别在它们的环境信息上。即使一个代码生成Q务中生成一个Java文gQ一般也不常用聚集生成,因ؓ生成一个Javac还需要一些重要信息如cL处的包以及你想生成的cdQ在q种环境下是无法提供的。如果一定要使用聚集生成的话Q那需要在另一个单独的地方提供好配|信息了?br />
2.5.2 模板标签
在还没见到模杉K啥样子之前,我们已经比较深入的认识它了。那模板文gI竟长啥样子呢?它有点像JSP文g。它们都包含文g和XML标签Q生成输出文件时XML标签会被解析Q然后生成文本ƈ昄在XML标签所处的位置上。除了以XDt为命名空间打头的XML标签会被XDoclet引擎解析以外Q其余的XML标签XDoclet会忽略不。下面的代码片断昄了XDoclet模板?#8220;l典造型”Q?br />
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>();
?br />
return _instance;
}
}
研究一下这个模板,你会发现Q它生成的是一个类定义。这个类里定义了一个静态变量instanceQƈ且用一个静态方法来控制q个静态文件的讉K。借助Java语法Q你可以很容易的推断出那些XDoclet模板标签的目录是生成cdQ虽然对于这个标{֦何工作你qƈ不是很了解?br />
即你从没打过要自己写模板Q但理解模板是如何被解析q行的还是很有必要的。迟早你会调用到一个运行失败的XDocletdQ没有生你所期望的输出,那么最快捷的找出原因的Ҏ是直接查模板文Ӟ看看是哪里出了问题?br />
让我们看一下生成静态域定义的片断:
static <XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClass:classOf>
_instance = null;
在XDoclet的眼里,q段模板代码很简单,那就是:
static <tag/> _instance = null;
XDoclet解析执行标签Q如果有输出的话Q把输入|回到文本里厅R有些标{会执行一些运,把输出放回到一个流里。这L标签UC为内Ҏ{?content tags)Q因为它们生内宏V?br />
另一U类型的标签UC为BODY标签。BODY标签在开始和l束标签之间存在文本。而BODY标签强大强大在q些文本自己也可以是一断可以由外围标签解析的模板片断。比如在上面的例子里QXDtClass:classOf标签Q它们的内容是模板片断Q?br />
<XDtEjbFacade:remoteFacadeClass/>
classOf标签解析q段模板Q提取出全限制的内容Q然后剃除前面的包面Q只输出cd。BODY标签q不L会解析它的内容,在做qg事之前,它们会事先检查一些外部判断条?比如查检查你正在生成的是一个接口还是一个类)。这里标标签UC为条件标{?conditional tags)。还有一些BODY标签提供cMq代的功能,它的内容会被解析多次。比如一个标{N对类里的每一个方法解析一ơ内宏V?br />
XDoclet标签提供了许多高层次的代码生成功能,但是有时候,它们可能昑־不够灉|Q或者表达能力满不了你的需要。这时候,相对于另外开发一套通用功能的模板引擎相比,你可以选择扩展XDoclet模板引擎。你可以使用更具表述能力、功能更加强大的Javaq_开发你自己的一套标{?nbsp;
2.6 使用合ƈ定制
代码生成pȝ之所以用的不多Q主要原因就在于它们往往只能生成一些死板的、不够灵zȝ代码。大多数代码生成pȝ不允怽改动它们生成的代码;如果Q如果这个系l不够灵z,你所能做到的最好的扩展是应用l承扩展生成的代码,或者用一些共通的设计模式(比如Proxy和Adaptor)来满你的需要。无论如此,q都不是产生你想生成的代码的好办法。代码生成器最好能做到所生成x得WYGIWYG(what you generate is what you get)Q来取代你需要花费大量的旉来粉饰生成出来的q不满要求的代码。所以,对于代码生成器来_支持灉|的定Ӟ是生成能够完全满求的代码的前提条件?br />
XDoclet通过合ƈ?merge points)支持定制??合ƈҎ在模板文件定义里允许q行时插入定制代码的地方。有时候,合ƈ点甚臛_以媄响到全局代码的生成,不但允许你添加一些定制内容,q可以从Ҏ上改变将要生成出来的东西?br />
[定义Q合q点(Merge points)是模杉K先定义的允许你在代码生成的运行时加入定制内容的扩展点]
让我们研I一D从XDoclet源代码里摘取出来的模板代码。在为实体Bean生成主键的模板末,定义了这样一个合q点Q?br />
<XDtMerge:merge file=”entitypk-custom.xdt”></XDtMerge:merge>
如果你在你的merge目录下创Z一个名为entitypk-custom.xdt文gQ那么这个模板文件的内容会在这个合q点被包含进来。你的定制可以执行高层模板可以执行的所有强大功能,可以q行所有模板可以进行的q算(包括定义自定义标{,定义它们自己的合q点)?br />
上面的这U合q点Qؓ所有的cȝ境用了同一个文件。当Ӟ也可以ؓ每一个类环境使用不同的合q文件。如果你不想定制全部的类文gQ或者你不想Z某些改动而重写模板的时候,q会很有用。不动机是什么,逐类的合q点很容易识别出来:他们会在名字里包含一个XDoclet的逐类标记{0}。这里有一个生成ejb-jar.xml文g里的安全角色引用的例子:
<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>
q段模板会遍历工E里的所有Bean。对于每一个BeanQXDoclet先从Bean的文件名里提取出Bean名,然后替换{0}Q再Ҏ替换后的文g名去L合ƈ文g。例如,如果你有一个名为BlogFacadeBean的BeanQXDoclet会尝试寻找一个名为ejb-src-rolerefs-BlogFacade.xml的合q文件?br />
如果找不到这个合q文Ӟ则这?lt;merge>标签的内Ҏ板会被解析。这意味着合ƈ点不仅可以提供定制内容,q可以在一个模板文仉定义一个替换点Q当定制内容不存在的时候用替换点里的内容。不是所有的XDocletd都提供了有替换内容的合ƈ点,一般来_它们更們于只提供一个简单的合ƈ点,仅仅当合q文件存在的时候解析ƈ导入合ƈ文g的内宏V这取决于Q务的开发者觉得哪U合q点更符合他的要求?br />
q有一Ҏ有介l到的是XDoclet如何定位合ƈ文gQ每一个XDocletd或者子d都会提供一个mergeDir属性,q个属性用于设|你存放合ƈ文g的目录?nbsp;
]]>
一、EJB技术简?
EJB的全U是Enterprise java bean。是JAVA中的商业应用lg技术。EJBl构中的角色 EJB lgl构是基于组件的分布式计结构,是分布式应用pȝ中的lg?br />
一个完整的ZEJB的分布式计算l构由六个角色组成,q六个角色可以由不同的开发商提供Q每个角色所作的工作必须遵@Sun公司提供的EJB规范Q以保证彼此之间的兼Ҏ。这六个角色分别是EJBlg开发?Enterprise Bean Provider) 、应用组合?Application Assembler)、部|?Deployer)、EJB 服务器提供?EJB Server Provider)、EJB 容器提供?EJB Container Provider)、系l管理员(System Administrator)Q?br />
二、EJB中各角色的分?br />
1、EJBlg开发?Enterprise Bean Provider)
EJBlg开发者负责开发执行商业逻辑规则的EJBlgQ开发出的EJBlg打包成ejb-jar文g。EJBlg开发者负责定义EJB的remote和home接口Q编写执行商业逻辑的EJB class,提供部vEJB的部|文?deployment descriptor)。部|文件包含EJB的名字,EJB用到的资源配|,如JDBC{。EJBlg开发者是典型的商业应用开发领域专家?br />
EJBlg开发者不需要精通系l的编E,因此Q不需要知道一些系l的处理细节,如事务、同步、安全、分布式计算{?br />
2、应用组合?Application Assembler)
应用l合者负责利用各UEJBl合一个完整的应用pȝ。应用组合者有旉要提供一些相关的E序Q如在一个电子商务系l里Q应用组合者需要提供JSP(Java Server Page)E序?br />
应用l合者必L握所用的EJB的home和remote接口Q但不需要知道这些接口的实现?br />
3、部|?Deployer)
部v者负责将ejb-jar文g部v到用Lpȝ环境中。系l环境包含某UEJB Server和EJB Container。部|者必M证所有由EJBlg开发者在部v文g中声明的资源可用Q例如,部v者必配|好EJB所需的数据库资源?br />
部vq程分两步:部v者首先利用EJB Container提供的工L成一些类和接口,使EJB Container能够利用q些cd接口在运行状态管理EJB?部v者安装EJBlg和其他在上一步生成的cdEJB Container中?部v者是某个EJBq行环境的专家?br />
某些情况下,部v者在部v时还需要了解EJB包含的业务方法,以便在部|完成后Q写一些简单的E序试?br />
4、EJB 服务器提供?EJB Server Provider)
EJB 服务器提供者是pȝ领域的专Ӟ_N分布式交易理Q分布式对象理及其它系l的服务。EJB 服务器提供者一般由操作pȝ开发商、中间g开发商或数据库开发商提供?br />
在目前的EJB规范中,假定EJB 服务器提供者和EJB 容器提供者来自同一个开发商Q所以,没有定义EJB 服务器提供者和EJB容器提供者之间的接口标准?br />
5、EJB 容器提供?EJB Container Provider)
EJB 容器提供者提供以下功能:
提供EJB部v工具为部|好的EJBlg提供q行环境 。EJB容器负责为EJB提供交易理Q安全管理等服务?br />
EJB 容器提供者必LpȝU的~程专家Q还要具备一些应用领域的l验。EJB 容器提供者的工作主要集中在开发一个可伸羃的,h交易理功能的集成在EJB 服务器中的容器。EJB 容器提供者ؓEJBlg开发者提供了一l标准的、易用的API讉KEJB 容器QEJBlg开发者不需要了解EJB服务器中的各U技术细节?br />
EJB容器提供者负责提供系l监工L来实时监EJB容器和运行在容器中的EJBlg状态?br />
6、系l管理员(System Administrator)
pȝ理员负责ؓEJB服务器和容器提供一个企业的计和|络环境?br />
pȝ理员负责利用EJB 服务器和容器提供的监管理工LEJBlg的运行情c?br />
三、EJB的体pȝ构:
EJB分布式应用程序是Z对象lg模型的,低层的事务服务用了API技术。EJB技术简化了用JAVA语言~写的企业应用系l的开发,配置。EJB技术定义了一l可重用的组ӞEnterprise Beans。你可以利用q些lgQ象搭积木一L建立你的分布式应用程序。当你把代码写好之后Q这些组件就被组合到特定的文件中厅R每个文件有一个或多个Enterprise BeansQ在加上一些配|参数。最后,q些Enterprise Beans被配|到一个装了EJB容器的^C。客戯够通过q些Beans的home接口Q定位到某个beansQƈ产生q个beans的一个实例。这P客户p够调用Beans的应用方法和q程接口?br />
EJB服务器作为容器和低层q_的桥梁管理着EJB容器和函数。它向EJB容器提供了访问系l服务的能力。例如:数据库的理和事务的理Q或者对于其它的Enterprise的应用服务器。所有的EJB 实例都运行在EJB容器中。容器提供了pȝU的服务Q控制了EJB的生命周期。EJB中的有一些易于用的理工具如:Security--配置描述器(The Deployment descriptorQ定义了客户能够讉K的不同的应用函数。容器通过只允许授权的客户讉Kq些函数来达到这个效果。Remote Connectivity--容器E链接管理着低层的通信issuesQ而且对Enterprise Beas的开发者和客户都隐藏了通信l节。EJB的开发者在~写应用Ҏ的时候,p是在条用本地的^CL。客户也不清楚他们调用的Ҏ可能是在q程被处理的。Life Cycle managment--客户单的创徏一个Enterprise beans的实例,q常取消一个实例。而容器管理着Enterprise Beans的实例,使Enterprise Beans实现最大的效能和内存利用率。容器能够这hȀzd使Enterprise Beans失效Q保持众多客户共享的实例池。等{。 Trasction management-配置描述器定义了Enterprise beans 的事务处理的需求。容器管理着那些理分布式事务处理的复杂的issues。这些事务可能要在不同的q_之间更新数据库。容器ɘq些事务之间互相独立Q互不干扰。保证所有的更新数据库都是成功发生的Q否者,回滚到事务处理之前的状态?br />
EJB lg是基于分布式事务处理的企业应用E序的组件。所有的EJB都有如下的特点:EJB包含了处理企业数据的应用逻辑。定义了EJB的客L面。这L界面不受容器和服务器的媄响。于是,当一个EJB被集合到一个应用程序中LQ不用更改代码和重新~译。EJB能够被定?各种pȝU的服务Q例如安全和事务处理的特性,都不是属于EJBcȝ。而是由配|和l装应用E序的工h实现?有两U类型的EJB: Session beans ?entity beans.Session beans是一U作为单用户执行的对象。作为对q程的Q务请求的相应Q容器生一个Session beans 的实例。一个Session beans有一个用?从某U程度上来说Q一个Session bean 对于服务器来说就代表了它的那个用?Session beans 也能用于事务Q它能够更新׃n的数据,但它不直接描l这些共享的数据。Session beans 的生命周期是相对较短的。典型的是,只有当用户保持会话的时候,Session beans 才是zȝ的。一旦用户退ZQSession beans ׃再与用户相联pM。Session beans被看成是瞬时的,因ؓ如果容器崩溃了,那么用户必须重新建立一个新的Session对象来l会话?br />
Session bean典型的声明了与用L互操作或者会话。也是_Session bean了在客户会话期间Q通过Ҏ的调用,掌握用户的信息。一个具有状态的Session beanUCؓ有状态的Session bean.当用L止与Session beans互操作的时?会话l止了,而且Qbean 也不再拥有状态倹{Session bean也可能是一个无状态的 session bean.无状态的Session beansq不掌握它的客户的信息或者状态。用戯够调用beans的方法来完成一些操作。但是,beans只是在方法调用的时候才知道用户的参数变量。当Ҏ调用完成以后Qbeansq不l箋保持q些参数变量。这P所有的无状态的session beans的实例都是相同的Q除非它正在Ҏ调用期间。这P无状态的Session beansp够支持多个用?容器能够声明一个无状态的Session beans.能够Q何Session beans指定lQ何用?
Entity BeansҎ据库中的数据提供了一U对象的视图。例如:一个Entity bean能够模拟数据库表中一行相关的数据。多个client能够׃n讉K同一个Entity bean.多个client也能够同时的讉K同一个Entity bean.Entity beans通过事务的上下文来访问或更新下层的数据。这P数据的完整性就能够被保证。Entity Beans能存zȝҎ长的旉Qƈ且状态是持箋的。只要数据库中的数据存在QEntity beans׃直存zR而不是按照应用程序或者服务进E来说的。即使EJB容器崩溃了,Entity beans也是存活的。Entity Beans生命周期能够被容器或?Beans自己理。如果由容器控制着保证 Entity beans持箋的issus。如果由Beans自己理Q就必须写Entity beans的代码,包括讉K数据库的调用?br />
Entity Beans是由主键Qprimary key 一U唯一的对象标识符Q标识的。通常Q主键与标识数据库中的一块数据,例如一个表中的一行,的主键是相同的。主键是client能够定位特定的数据块?br />
四、开发EJB
1、类介绍Q?br />
开发EJB的主要步骤一般来_整个的开发步骤(开发,配置Q组装)包括如下几个斚w。开发:首先要定义三个类QBeancLw,Bean的本地和q程接口cR?配置Q配|包括生配|描q器--q是一个XML文g、声明了Enterprise Bean的属性、绑定了bean的class文gQ包括stub文g和skeleton文gQ。最后将q些配置都放C个jar文g中。还需要在配置器中定义环境属性。组装应用程序:包括Enterprise beans安装到Server服务器中Q测试各层的q接情况。程序组装器若q个Enterprise Beans与其它的lgl合h。组合成一个完整的应用E序。或者将若干个Enterprise beansl合成一个复杂的Enterprise Bean。管理Enterprise Bean?
我们必须定义和编写一些EJB中的基本cR如Enterprise beanc:q是Enterprise bean内部应用逻辑的实现。编写Enterprise bean的远E接口类。编写Enterprise bean的本地接口类。说明主键类Q主键类只是对于Entity bean才需要的。在Enterprise bean的配|描q器中指定主键的名字。Enterprise beans提供者定义了q程接口和本地接口,实现了EJBcLw。Remote接口中提供了客户调用EJB实现的应用逻辑函数的接口。而home接口提供了生和定位remote接口实例的方法?
在Enterprise bean本ncȝ实现Q本地home接口Q远Eremote接口之间q没有正式的联系Q例如承关p)。但是,在三个类里声明的Ҏ却必遵守EJB里面定义的规范。例如: 你在Enterprise bean里面声明了一个应用程序的Ҏ或者说应用逻辑。也在beans的remote接口中声明了q个ҎQ那么,q两个地方必要同样的名字。Bean的实现里面必至有一个Create()ҎQejbCreate()。但是可以有多个带有不同参数的create()Ҏ。 在home接口中,也必L相同的方法定义(参数的个数相同)。EjbCreate()Ҏq回的一个容器管理的持久对象。它们都q回一个容器管理持久性的主键倹{但是,在home的相应的Create()Ҏ中返回值的cd是remote接口?br />
注意Q实体bean的实现的ejbCreateҎ有点不同。实体bean可以不定义ejbCreateҎ。如果实体只是通过应用E序或通过数据库管理程序的途径被加到数据库中,实体beanq略了ejbCreateҎ。EjbCreateq回的值是主键cd。如果ejbCreateҎ是容器管理持久性的实体bean的方法,它的q回值就是NULLcd。如果实体bean实现了Bean理的持久性,ejbCreateҎp回值类型就是主键类型。容器的d是把各接口和Enterprise bean的实现类l合h。保证在~译时和q行Ӟ各接口和实现cL相对应的?
EJB的实现类Q各接口要从不同的基cMl承下来。一个会话bean必须实现基类javax.ejb.SessionBean。而实体bean必须实现基类javax.ejb.EntiyBean。这些EJB的基c都是从javax.ejb.EnterpriseBeanl承而来。而javax.ejb.EnterpriseBean又是从java.io.Serializablel承而来。每一个Enterprise Bean都必L一个remote接口。Remote接口定义了应用程序规定客户可以调用的逻辑操作。这些是一些可以由客户调用的公qҎQ通常由Enterprise beanscL实现。注意,Enterprise bean的客户ƈ不直接访问Bean。而是通过remote接口来访问。Enterprise beancȝremote接口扩展了javax.ejb.EJBObjectcȝ公共java接口。而Javax.ejb.EJBObject是所有remote接口的基cR其代码如下Q?
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接口。对?实体BeanQ用getPrimaryKeyQ)Ҏ获得实体Bean的主键倹{RemoveQ)可以删除一个Enterprise bean。具体的语义在各U不同类型的enterprise beans的生命周期中Q由上下文中解释的。方法getHandleQ)q回了一个Enterprise bean实例的持久的句柄。IsIndentical()Ҏ允许你去比较Enterprise beans是否相同?br />
2、方法:
所有的remote接口中的Ҏ必须声明为公共(publicQ的Qƈ必须抛出java.rmi.RemotException异常。另外,所有的remote接口中的Ҏ定义的参数和都必L在RMI-IIOP中有效的。对每一个在remote接口中定义的ҎQ在Enterprise bean c里面都要有相应的方法。相应的Ҏ必须要有同样的名字,同样cd和数量的参数Q同Lq回|而且q要抛出同样的例外?如下代码昄了一个ATM例子的会话bean的remote接口AtmQ。里面声明了一个应用方法transferQ)。黑体部分表CEJB规范中必要有的内容。Remote接口必须扩展javax.ejb.EJBObjectcR从客户端调用的Enterprise bean的每一个方法都必须在remote接口中声明。TransferQ)Ҏ抛出了两个意外。其中InSufficientFundsException例外是应用程序定义的意外?
Public interface Atm extends javax.ejb.EJBObject{
Public void transfer(String Source, String Target, float amount)
Throws java.rmi.RemoteException, InSufficientFundsException;
}
Home接口必须定义一个或多个的Create()Ҏ。每一个这LCreate()Ҏ都必d名ؓCreate。ƈ且,它的参数Q不是cdq是数量都必Mbeanc里面的ejbCreate()Ҏ对应。注意,home接口中的Create()Ҏ和beancMejbCreate()Ҏ的返回值类型是不同的。实体bean的home接口q包含findQ)Ҏ?每一个Home接口都扩展了javax.ejb.EJBHome接口。如下代码显CZjavax.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;
}
q里提供了两个removeQ)Ҏ来删除Enterprise bean的实例。第一个removeҎ是通过句柄来删除一个Enterprise bean的实例。第二个removeҎ通过主键来删除一个Enterprise bean的实例?在众多的Enterprise bean实例中,句柄唯一的标识一个实例。一个句柄与它引用的Enterprise bean有相同的生命期。考虑一个实体对象,客户可以通过一个句柄来重新获得相应的Enterprise bean的实例。一个句柄能够对应一个Enterprise bean对象的多个实例。例如,即当Enterprise bean对象所在的L崩溃了,或者Enterprise bean对象在不同的机器之间UdQ句柄仍是有效的。这里的句柄是Serialized句柄Q与CORBA中的字符串化的CORBA对象的引用是怼的概c在EJBHome接口中的W二个remove操作通过其主键来军_要删除的Enterprise bean。主键可以是扩展了Java ObjectcȝMcdQ但是,必须要实现Java的Serializable接口。主键是标识实体bean的主要的Ҏ。通常Q主键是数据库中的一个关键字Q唯一的定义了由实体bean代表的数据?
ҎgetEJBMetaDataQ)q回了Enterprise bean对象的metadata接口。这个接口允许客戯得Enterprise bean的metadata信息。当开发工h~译链接应用E序的时候,或者配|工h配置的时候,可能会用到metadata信息。Javax.ejb.EJBMetadata接口提供了获得javax.ejb.EJBHome接口Qhomec,remote接口Q还有获得主键的Ҏ。也提供了一个isSesson()的方法来定在放q个home接口的对象是会话beanq是实体bean。IsStatelessSession()Ҏ指示q个会话bean是有状态还是无状态的。如下代码显CZjavax.ejb.EJBMetadata接口的定义部分的代码?br />
Public javax.ejb; Public interface EJBMetaData{
EJBHome getEJBHome();
Class getHomeInterfaceClass();
Class getRemoteInterfaceClasss();
Class getPrimaryKeyClass();
Boolean isSession();
Boolean isStatelesssSession();
}
Ҏ一个CreateQ)ҎQEJB规范定义了如下的命名U定。它的返回值是会话bean的remote接口的类型。方法的名字只能是Create()。对会话beancM的每一个ejbCreate()Ҏ都必L一个Create()与之对应?对于每一个Create()Ҏ的参数的cd和数量都必须与会话beancM的ejbCreate()Ҏ相对应。方法必L出java.rmi.RemoteException例外?Ҏ必须抛出javax.rmi.CreateExeption例外?CreateQ)Ҏ的参数是用来初始化新的会话bean对象的?如下代码昄了一个会话bean对象的不同的Create()ҎQ其中必ȝ部分用粗体显C:
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;
}
q里提供了两个removeQ)Ҏ来删除Enterprise bean的实例。第一个removeҎ是通过句柄来删除一个Enterprise bean的实例。第二个removeҎ通过主键来删除一个Enterprise bean的实例。在众多的Enterprise bean实例中,句柄唯一的标识一个实例。一个句柄与它引用的Enterprise bean有相同的生命期。考虑一个实体对象,客户可以通过一个句柄来重新获得相应的Enterprise bean的实例。一个句柄能够对应一个Enterprise bean对象的多个实例。例如,即当Enterprise bean对象所在的L崩溃了,或者Enterprise bean对象在不同的机器之间UdQ句柄仍是有效的。这里的句柄是Serialized句柄Q与CORBA中的字符串化的CORBA对象的引用是怼的概c?
在EJBHome接口中的W二个remove操作通过其主键来军_要删除的Enterprise bean。主键可以是扩展了Java ObjectcȝMcdQ但是,必须要实现Java的Serializable接口。主键是标识实体bean的主要的Ҏ。通常Q主键是数据库中的一个关键字Q唯一的定义了由实体bean代表的数据。方法getEJBMetaDataQ)q回了Enterprise bean对象的metadata接口。这个接口允许客戯得Enterprise bean的metadata信息。当开发工h~译链接应用E序的时候,或者配|工h配置的时候,可能会用到metadata信息。Javax.ejb.EJBMetadata接口提供了获得javax.ejb.EJBHome接口Qhomec,remote接口Q还有获得主键的Ҏ。也提供了一个isSesson()的方法来定在放q个home接口的对象是会话beanq是实体bean。IsStatelessSession()Ҏ指示q个会话bean是有状态还是无状态的。如下代码显CZjavax.ejb.EJBMetadata接口的定义部分的代码?br />
Public javax.ejb;
Public interface EJBMetaData{
EJBHome getEJBHome();
Class getHomeInterfaceClass();
Class getRemoteInterfaceClasss();
Class getPrimaryKeyClass();
Boolean isSession();
Boolean isStatelesssSession();
}
五、EJB的编E环境:
1?使用Jbuilder
Jbuilder与EJB Container能够q行无缝q接。Jbuilder和Inprise的应用服务器包括了所有的开发和配置Enterprise Beans的工具以及所需要的库:q行和管理Enterprise Bean的容器、命名服务?事务服务、Java数据库、开发Enterprise Beans所需要的API、一个增强的java-to-iiop~译器,支持值类型和RMI信号{等?
Jbuilderq提供了一个快速开发应用程序Enterprise Beans的工具和向导。通过单而且直观的步骤,向导帮助你徏立一个Enterprise Bean。自p定某些缺省|产生了bean的模板,在上面,我们可以增加我们自己的应用逻辑。Jbuilder也提供了一个EJB的接口生成向对{向导在Enterprise Bean的公共方法基上生成了Remote接口和Home接口。Jbuilderq提供一个配|器的向导帮助我们逐步的徏立XML描述器文件。ƈ生成的Stubs集中C个jar文g中?br />
2、用Jbuilder之外的集成环境:
如果你用其它的除了别的集成环境QIDEQ。要定使用了集成环境IDE所带的容器工具。也要验证IDE是否支持EJB规范的相应的版本Q还要确定它是否正确的支持EJB的API?
要确定JD到所支持的EJB容器的版本。可以通过查Inprise的安装说明来定EJB容器所支持的支持JDK的版本?
在配|Enterprise Bean的时候,你必M用Inprise的应用服务器所提供的工兗这些工兯够编辑和修改W三方的代理商提供的Inprise配置描述器。还能够验证配置描述器,能够验证bean的源代码?
六、一个简单的HELLO例子
1、安装Apusic Application Server
Note:以下以LinuxZQ来说明Apusic Application Server的安装过E。其他^台的安装Q可参考Apusic Application Server安装手册?
下蝲JDK1.2QApusic Application Server必须q行在JDK1.2以上环境中。可从以下站点下载最新JDK?
http://java.sun.com
下蝲Apusic Application Server
Apusic Application Server 试用版可从以下网址得到Q?
http://www.apusic.com/download/enter.jsp
在下载完成后Q你可以得到一个包Ҏ件apusic.zipQ选定安装目录Q假讑֮装到/usr下,则用以下命oQ?br />
cd /usr
jar xvf apusic.zip
/usr下会出现一个目录apusicQApusic 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、定义EJBq程接口(Remote Interface)
M一个EJB都是通过Remote Interface被调用,EJB开发者首先要在Remote Interface中定义这个EJB可以被外界调用的所有方法。执行Remote Interface的类由EJB生成工具生成?
以下是HelloBean的Remote IntefaceE序Q?
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实例Q和Remote Interface一P执行Home Interface的类由EJB生成工具生成?
以下是HelloBean 的Home InterfaceE序Q?
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、写EJBc?br />
在EJBcMQ编E者必ȝ出在Remote Interface中定义的q程Ҏ的具体实现。EJBcMq包括一?EJB规范中定义的必须实现的方法,q些Ҏ都有比较l一的实现模版,~程者只需p_֊在具体业务方法的实现上?
以下是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文g
ejb-jar.xml文g是EJB的部|描q文Ӟ包含EJB的各U配|信息,如是有状态Bean(Stateful Bean) q是无状态Bean(Stateless Bean)Q交易类型等。ejb-jar.xml文g的详l信息请参阅EJB规范。以下是HelloBean的配|文Ӟ
Q?xml version="1.0"?Q?br />
Q?DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems Inc.//DTD Enterprise JavaBeans 1.2//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_2.dtd"Q?br />
Qejb-jarQ?br />
Qenterprise-beansQ?br />
QsessionQ?br />
Qejb-nameQHelloQ?ejb-nameQ?br />
QhomeQejb.hello.HelloHomeQ?homeQ?br />
QremoteQejb.hello.HelloQ?remoteQ?br />
Qejb-classQejb.hello.HelloBeanQ?ejb-classQ?br />
Qsession-typeQStatelessQ?session-typeQ?br />
Qtransaction-typeQContainerQ?transaction-typeQ?br />
Q?sessionQ?
Q?enterprise-beansQ?br />
Qassembly-descriptorQ?br />
Qcontainer-transactionQ?br />
QmethodQ?br />
Qejb-nameQHelloQ?ejb-nameQ?br />
Qmethod-nameQ?Q?method-nameQ?br />
Q?methodQ?br />
Qtrans-attributeQRequiredQ?trans-attributeQ?br />
Q?container-transactionQ?
Q?assembly-descriptorQ?br />
Q?ejb-jarQ?br />
6、编译和部v
~译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文g:
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配置文g中。在/usr/apusic/config/server.xml 加入以下几行Q?
QmoduleQ?br />
QejbQ?br />
Qejb-uriQclasses/Hello.jarQ?ejb-uriQ?br />
QbeanQ?br />
Qejb-nameQHelloQ?ejb-nameQ?br />
Qjndi-nameQHelloHomeQ?jndi-nameQ?br />
Q?beanQ?br />
Q?ejbQ?br />
Q?moduleQ?br />
启动服务?
java -Xms64m com.apusic.server.Main -root /usr/apusic
7、写客户端调用程?br />
您可以从Java ClientQJSPQServlet或别的EJB调用HelloBean?
调用EJB有以下几个步骤:
通过JNDI(Java Naming Directory Interface)得到EJB Home Interface
通过EJB Home Interface 创徏EJB对象Qƈ得到其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);
}
}
}
q行HelloClientQ可得到以下输出Q?
Hello World
http://yingmingpan.bokee.com/tb.b?diaryId=14817948
]]>
初识EJB
EJB是什么呢?
EJB是一个J2EE体系中的lg.再简单的说它是一个能够远E调用的javaBean.
它同普通的javaBean有两点不?
W一?是q程调用.
W二?是事务的功?我们在EJB中声明的事务会通过容器帮助我们来处?
支持EJB的SERVER有以下几?
Weblogic
Webspere
Appserver
Jboss
我选用的是weblogic+JBuilder9开?
回过来我们l说q个EJB的原?
它是分布式的.q个的意思其实很单不需要想太复?
是我们把事情不是交l一个hd?而是把事情划分一下交l多个h处理,
而多个h处理之后我们让这个分配的人来把最后得到的l合处理.
EJB我们首先要明它是放在服务器端的lg.
一共有三种EJB
1,SessionBean 复杂处理业务逻辑?
2,EntityBean 处理数据
3,MessageBean 消息Bean异步,耦合的处?
那么谁能调用EJB?
也或者说EJB被谁调用?
我们说它是攑֜server的一个能够远E调用的javaBean.
所以它可以被Java的语a调用servlet,jsp{都ok的说.
而且它还可以被C++语言调用.很强大吧.
EJB的最基本的出发点是推动Java在服务器端的~程能力.
所以呢它的功能我个h来看太强悍了..= =
q个q程调用是通过什么呢.
Java是通过RMI-JRMP(javaq程Ҏ调用)协议来调?EJB是通过q个来实现的.
C++是通过Corba-iiop协议来调用的.q个协议支持两端不同语言的调?
Corba是公共对象请求代理服?br />
iiop是网l内部对象协?
下面我们来简单说一下这个分布式的调用?br />
客户端发送一个请求给服务?br />
首先是传lstub代理cd发送参数给skeleton委托cȝ后这个类调用我们的实现类取得l果再远路返回?br />
q样一个分布处理就l束了?br />
后面会具体的l合代码分析?br />
先记住这个大致的l构好?br />
我们写一个EJB需要做那些工作呢?
1Q写一个接口承EJBObject q个cME接?br />
2Q写一个接口承EJBHome q个c里有一个方法是createQ)它返回接口类型?br />
3Q写一个Beanl承SessionBeanQ?nbsp; q个c里包含一个createQ)Ҏ和一个业务方法?br />
4Q写一个ejb-jar.xml q个xml是把上面的三个文件组合v?br />
5Q写一个weblogic-ejb-jar.xml q个xml是连接查找source的作?br />
Q不同的server会是不同的。这里选用的是weblogicQ?br />
上面的两个xml文g需要放在META-INF目录下?br />
而以上这些类都需要打包在一个jar文g中然后在server部v?br />
q样完成了EJB的部|?br />
例如Q?br />
我们写个单的计算吧?br />
(先声明下吧本教程的说明都是我自己的理?
也许q不是一些文档上的描q那么正规但是很方便理解的?br />
如果有实在无法苟同的地方大家多多包含。这是ؓ了便于理解的讲解)
AddCount.javaq个是我们l承EJBObject的类.
import javax.ejb.*;
import java.rmi.*;
public interface AddCount extends EJBObject
{
public int addCount(int a,int b) throws RemoteException;
}
接口的作?在这里强调一下吧.所有实现这个接口的c都会是q个接口的类?
同时都包含接口的Ҏ的实?
q个接口中的Ҏ也就是在以后会实现的Ҏ.我们q里要做的是一个加法的q算.
AddCountHome.javaq个是承EJBHome的类.它里面包含的q个create()q回的是AddCountcd对象.
import javax.ejb.*;
import java.rmi.*;
public interface AddCountHome extends EJBHome
{
public AddCount create() throws RemoteException,CreateException;
}
下面q个是我们的Bean.q个cȝ承了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;
}
}
q个里面我们实现了业务的ҎaddCount(){a+b;}
同时需要指Z下它的其他方?q些仅仅单指出后面的教程有详l说?
首先是ejbCreate()q个Ҏ实际上是Ҏ们的q程接口的实现类的初始化.
setSessionContext(SessionContext ctx)讄context.容器是在q个Ҏ之后产生的实?
ejbRemove()毫无疑问是一个实例结束移?
ejbActivate()ȀzL?它的作用是激z钝?
ejbPassivate()钝化Ҏ.当实例的内容长时间不q行处理的时候就会钝?也就是闲|的意?
以上q几个是SessionBean的基本方?希望大家可以自己慢慢理解.
之后也会反复说到q些?
下面我们写xml文g
首先是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>
下面q个是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>
我们接下来写试c?br />
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));
}
}
试cȝhash是初始化我们的weblogic的xml信息.
q里的t3://localhost:7001是weblogic的特D协议指向的是ip位置.
然后lookup("rainnight")查找jndi,而实际上也就是通过q个jndi扑ֈ我们的ejblg.
通过得到的对象我们对比一下是不是Homecd.
然后我们执行home.create()产生AddCount的实?br />
最后调用方法就ok?
下面是如何执行这个的Ҏ.
W一?jdk,weblogic.需要快些下载安装啦..
W二?配置环境变量.基本的java的环境变量如何配|我׃说了.
q里需要特别指把java目录下的lib中的tools.jar加蝲到我们的class_path?
然后我们再把weblogic的目录下的server目录下的lib中的weblogic.jar扑ֈ也加载进?
q样我们~译需要的东西ok?
W三?~译javac?
W四?打包.jar cvf AddCount.jar *.class META-INF/*.xml
W五?java weblogic.appc AddCount.jar
W六?部v到weblogic?
W七?q行Testc?
]]>
功能说明Q?
Java归工具
语法Q?
jar [ 命o选项 ] [manifest] destination input-file [input-files]
补充说明Q?
jar工具是个java应用E序Q可多个文件合qؓ单个JAR归档文g。jar是个多用途的存档及压~工P它基于ZIP和ZLIB压羃格式。然而, 设计jar的主要目的是便于java applet或应用程序打包成单个归文g。将applet或应用程序的lg(.class 文g、图像和声音)合ƈ成单个归档文件时Q可以用java代理(如浏览器)在一ơHTTP事务处理q程中对它们q行下蝲Q而不是对每个lg都要求一个新q?接。这大大~短了下载时间。jarq能压羃文gQ从而进一步提高了下蝲速度。此外,它允许applet的作者对文g中的各个进行签名,因而可认证其来 源。jar工具的语法基本上与tar命o的语法相同?/span>
命o选项
-c 在标准输Z创徏新归档或I归?/span>
-t 在标准输Z列出内容表?
-x[file] 从标准输入提取所有文Ӟ或只提取指定的文件。如果省略了fileQ则提取所有文Ӟ否则只提取指定文件?
-f W二个参数指定要处理的jar文g。在-c(创徏)情Ş中,W二个参数指的是要创建的jar文g的名U?不是在标准输Z)。在-t(??x(抽取)q两U情形中Q第二个参数指定要列出或抽取的jar文g?/span>
-v 在标准错误输备上生成长格式的输出l果?
-m 包括指定的现有清单文件中的清单信息。用法D例:“jar cmf myManifestFile myJarFile *.class”
-0 只储存,不进?ZIP 压羃?
-M 不创建项目的清单文g?
-u 通过d文g或更Ҏ单来更新现有?JAR 文g。例如:“jar -uf foo.jar foo.class”文?foo.class d到现有的JAR文gfoo.jar中,?#8220;jar umf manifest foo.jar”则用manifest中的信息更新foo.jar的清单?
-C 在执?jar 命o期间更改目录。例如:“jar -uf foo.jar -C classes *”classes目录内的所有文件加到foo.jar中,但不dcȝ录本w?
E序CZ
1:当前目录下所有CLASS文g打包成新的JAR文gQ?
jar cf file.jar *.class
2:昄一个JAR文g中的文g列表
jar tf file.jar
3:当前目录下的所有文件增加到一个已l存在的JAR文g?
jar cvf file.jar *
我们知道QJ2EE应用pȝ只有部v在J2EE容器中才能运行,那么Z么划分ؓJ2EE容器和J2EE应用pȝQ?通过对J2EE容器q行机制的分析(见我的电子教?#8220;EJB实用原理”Q,我们可以发现Q实际上J2EE容器分离了一般应用系l的一些通用功能Q例如事务机制、安全机制以及对象池或线E池{性能优化机制?/p>
q些功能机制是每个应用系l几乎都需要的Q因此可以从具体应用pȝ中分d来,形成一个通用的框架^収ͼ而且Q这些功能机制的设计开发有一定难度,同时q行的稳定性和快速性都非常重要Q必ȝq长旉调试和运行经验积累而成Q因此,形成了专门的J2EE容器服务器品,如Tomcat JBoss、Websphere、WebLogic{?/p>
从J2EEpȝ划分为J2EE容器和J2EE应用pȝ两个斚wQ我们已l看CU分散关注的思\Qseparation of concernsQ?br />
分散x
通用需求功能从不相关类之中分离出来Q同Ӟ能够使得很多cd享一个行为,一旦行为发生变化,不必修改很多c,只要修改q个行ؓ可以?br />
AOP是q种实现分散x的编E方法,它将“x”装?#8220;斚w”中?/p>
AOP是什么?
AOP是OOP的gl,是Aspect Oriented Programming的羃写,意思是面向斚w~程。AOP实际是GoF设计模式的gl,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这U目标的一U实现?/p>
举例Q假设有在一个应用系l中Q有一个共享的数据必须被ƈ发同时访问,首先Q将q个数据装在数据对象中Q称为Data ClassQ同Ӟ有多个讉Kc,专门用于在同一时刻讉Kq同一个数据对象?/p>
Z完成上述q发讉K同一资源的功能,需要引入锁Lock的概念,也就是说Q某个时刻,当有一个访问类讉Kq个数据对象Ӟq个数据对象必须上锁LockedQ用完后q卌锁unLockedQ再供其它访问类讉K?/p>
使用传统的编E习惯,我们会创Z个抽象类Q所有的讉Kcȝ承这个抽象父c,如下Q?/p>
abstract class Worker{ abstract void locked(); } |
因此Q一个新的程序结构应该是xpȝ的纵向切面,例如q个应用?#8220;?#8221;功能Q这个新的程序结构就是aspectQ方面)
在这个应用中Q?#8220;?#8221;斚wQaspectQ应该有以下职责Q?/p>
提供一些必备的功能Q对被访问对象实现加锁或解锁功能。以保证所有在修改数据对象的操作之前能够调用lock()加锁Q在它用完成后Q调用unlock()解锁?/p>
AOP应用范围
很明显,AOP非常适合开发J2EE容器服务器,目前JBoss 4.0正是使用AOP框架q行开发?br />
具体功能如下Q?br />
Authentication 权限
Caching ~存
Context passing 内容传?br />
Error handling 错误处理
Lazy loading 懒加?br />
Debugging 调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久?br />
Resource pooling 资源?br />
Synchronization 同步
Transactions 事务
AOP有必要吗Q?/strong>
当然Q上q应用范例在没有使用AOP情况下,也得C解决Q例如JBoss 3.XXX也提供了上述应用功能Q但是没有用AOP?/p>
但是Q用AOP可以让我们从一个更高的抽象概念来理解Y件系l,AOP也许提供一U有价值的工具。可以这么说Q因Z用AOPl构Q现在JBoss 4.0的源码要比JBoss 3.XҎ理解多了Q这对于一个大型复杂系l来说是非常重要的?/p>
从另外一个方面说Q好像不是所有的人都需要关心AOPQ它可能是一U架构设计的选择Q如果选择J2EEpȝQAOPx的上q通用斚w都已l被J2EE容器实现了,J2EE应用pȝ开发者可能需要更多地x行业应用斚waspect?/p>
AOP具体实现
AOP是一个概念,q没有设定具体语a的实玎ͼ它能克服那些只有单承特性语a的缺点(如JavaQ,目前AOP具体实现有以下几个项目:
AspectJ (TM)Q 创徏于Xerox PARC. 有近十年历史Q成?br /> ~点Q过于复杂;破坏装Q需要专门的Java~译器?/p>
动态AOPQ用JDK的动态代理API或字节码Bytecode处理技术?br />
Z动态代理API的具体项目有Q?br />
JBoss 4.0 JBoss 4.0服务?br />
nanning q是以中国南宁命名的一个项目,搞不清楚Z么和中国相关Q是中国人发LQ?br />
Z字节码的目有:
aspectwerkz
spring Q?/a>
在以后其它文章中Q我l对AOP概念q行分析Q和大家一起学习进步?br />
转蝲 板桥里h http://www.jdon.com 2004/01/09