Expresso使用说明
修订历史记录
日期 | 版本 | 说明 | 作者 |
2005-07-04 | 1.0 | 初建 | 唐家平 |
|
|
|
|
|
|
|
|
5.4 创建一些需要的控制器(Controller)对象 5
6.3 初始化Expresso为Java Application环境 12
8 Expresso和Spring+Hiberate组合结构的比较 13
1 简介
Expresso是jcorporate提供的一Java开源项目,项目组主要的收入来源是出售该项目的服务支持(目前每年的服务支持费是$999.00US)。
Expresso基于Struts,也就是说Struts是它的核心思想。
随Expresso的安装包里,expresso/doc提供了完整的文档。最新版本的软件包可以从如下网址获得:
http://www.jcorporate.com
2 引入框架开发的目的
总结起来,目的大概如下三条:
1. 规范化开发结构;
2. 提供大多数项目均需要的服务功能,如日志、持久层管理等;
3. 提供开发工具和方法,以加快开发速度。
没有结构不成方圆,程序设计从最初的机器语言发展为结构化的高级语言,从面向过程发展为面向对象,其内在原因就是由于开发的规模在急剧扩大。我们可以采用各种方法实现我们的软件项目,可是没有组织好的上千万行代码将带来的结果是可读性极低、可扩展性极差。“项目的开发象一头巨兽陷在泥潭,越陷越深。”
软件需求变幻无穷,计划没有变化快,但是我们还是要寻找出不变的东西,并将它和变化的东西分离开来,框架就是这一思想的成果。
3 安装与配置
3.1 有两种安装方法
解压下载来的expresso5.0.5.zip或expresso5-0-5-src.zip或expresso5.0.5.war均能得到一个Expresso的应用,也就是开发环境;
解压下载来的expresso5-0-5-complete.zip将得到一个带有Tomcat服务的完全版本。
启动服务后,测试URL:http://localhost:8080/expresso/frame.jsp。
3.2 配置expresso-config.xml
Expresso的主要配置信息文件,它在服务启动时被ConfigManager对象读入,应用里的任何对象均可通过ConfigManager对象对之进行操作。ConfigManager是一个单一实例模式的对象,甚至在配置了几个数据上下文(连接池)项目。
信息包括:将日志写在哪个目录;"class-handlers"部分指明Expresso和应用使用的变量和所实现的类的对应关系,即相对于程序设计里的变量申明;"context"部分通常是数据库的配置。
一旦expresso-config.xml被读入,安装信息便从每个对应数据库中读入,每个连接池也就初始化好了。
数据库的使用通常是:保持使用“Default”为Expresso自带的数据库,另外增加一数据库上下文配置为用户提供数据存放,通过登陆时切换数据库。在配置文件里使用<hasSetupTables>false</hasSetupTables>进行区分。
登陆切换数据库Html语句如下:
<input type="HIDDEN" name="dbContext" value="default" >
3.3 国际/本地化问题
本地化(中文)问题与MessagesBundle_*.properties文件有关,可用修改MessagesBundle_zh.properties文件使Expresso的界面中文化。可以使用native2ascii命令完成编码转换。
项目界面的中文化问题建议采用重新编写自己项目View部分的jsp,并通过编写继承的Controller或重定义Action实现。这样,就避免破坏原Expresso结构。也就是说,借用Expresso功能时,我们不借用“界面”部分,当然,该部分对界面的开发有着很高的借鉴意义。
4 Expresso功能列表
1. 提供应用的组织和配置;
2. 提供数据库访问和数据对象工具包;
3. 支持MVC结构和创建几种类型的Controller对象的组件包;
4. 提供任务队列操作;
5. 提供容易快速创建Servlet和JSP的方法;
6. 提供定义那些发生在Web应用之间的事件机制;
7. 提供一定数量的其他有用的功能;
8. 日志功能;
9. 用户安全功能;
10. 初始化机制;
11. 数据库数据缓冲管理;
5 最佳实践
一个典型的Web应用通常是由一组数据(Database)对象(包括Model和持久化数据库),控制(Controller)对象、工作任务(Job)对象和一个单一的计划(Schema)对象将它们紧紧地联系在一起。在这种情况下,应用可以使用一个普通的“View handler”进行测试。以下的提供的源码是一个股票买卖的例子,Orange Trader,2002年4月由Peter Pilgrim编写。
5.1 创建包
通常需要创建如下包:
² dbobj: 包含数据库对象;
² controller: 包含控制器对象;
² job: 包含工作任务对象,可选;
5.2 创建一个Schema对象
Schema:a Web Application Context object
创建一个Schema对象,放在包的根;Schemo对象为Expresso提供全部的引用,用以安全管理等(注册后),这是设计模式的FACADE模式思想。
必需做的工作:
1. 加入所有的和应用相关的controllers, dbobjects or jobs;
2. 设置MessageBundle路径,getMessageBundlePath()返回国际化项目资源所在路径;
最容易的方法是拷贝一个现成的Schema类进行修改,如:
<OrangeTraderSchema.java>
import com.jcorporate.expresso.core.db.*;
import com.jcorporate.expresso.core.dbobj.Schema;
public class OrangeTraderSchema extends Schema
{
public OrangeTraderSchema() throws DBException
{
super();
try {
addDBObject( "com.xenonsoft.orangetrader.ot1.dbobj.StockTrade" );
addController( "com.xenonsoft.orangetrader.ot1.controller.StockController" );
}
catch ( Throwable t ) {
throw new DBException( t.getMessage() );
}
}
public String getMessageBundlePath() {
return "com/xenonsoft/orangetrader";
}
}
5.3 创建一些需要的数据(DB)对象
通常情况下,所有数据对象类均放在dbobj包里,并使用Schema类注册这些数据对象,以便DBTool和DBCreate能够使用它们。
数据对象是为Expresso的持久层而设计的,相当于EJB里的Entity JavaBean(若使用EJB,可对应移植),是表在关系数据库里的直接映射。使用数据对象的优势在于:缓冲机制、值校验、引用完整性和数据安全;可以避免编写SQL语句,而这些SQL语句由于不同的数据库系统有不同的版本;数据对象和业务逻辑是相关的,通过引用完整性和复杂的表间关系可以达到和存储过程一样的效果。这样就实现了“与数据库无关”。
创建的方法有很多,一个DBObject里必需的实现constructors、setupFields、getThisDBObj()方法。
< StockTrade.java >
import com.jcorporate.expresso.core.dbobj.*;
import com.jcorporate.expresso.core.db.*;
public class StockTrade extends SecuredDBObject
{
public StockTrade() throws DBException
{ super(); }
public StockTrade(DBConnection theConnection) throws DBException
{ super(theConnection); }
protected synchronized void setupFields()
throws DBException
{
setTargetTable("STOCKTRADE");
setDescription("Stock Trade I");
addField("ST_ID", "int", 0, false, "Text Channel Autoincrement ID");
addField("ST_TITLE", "varchar", 40, false, "Stock Title");
addField("ST_TRADER", "varchar", 40, false, "Owner Trader");
addField("ST_STATUS", "varchar", 10, true, "Trade Status" );
addField("ST_PRICE", "float", 0, true, "Current Price" );
addField("ST_ASK", "float", 0, true, "Asking Price" );
addField("ST_BID", "float", 0, true, "Bid Price" );
addField("ST_CHANGE", "float", 0, true, "Percentage Change" );
addKey("ST_ID");
}
public DBObject getThisDBObj() throws DBException {
return new StockTrade();
}
}
5.4 创建一些需要的控制器(Controller)对象
一个控制器就是应用从当前状态转换成下一状态的访问点。这就是Finite State Machine的思想。最典型的例子就是ATM自动取款机,和你交互的取款机就是一个控制器,每一次交互就对应控制器的一个功能单元(State)。
控制器对象类通常放在Controller包里。
控制器对象封装了应用的大部分业务逻辑(数据对象也有业务逻辑),象征着整个应用的业务层。你可以单独地去设计你的表示层,充分考虑输入和输出。
控制器中有着“run+状态名(首字母大写)+State”的方法会在调用相应状态名时被自动调用,该方法还应该申明为void和private或protected。
Controller和View的接口:
Controller的输入:
命令行参数;From_bean方式
Controller的输出:
通常采用Block对象集合传递参数,Block又可包含Input、Output、Action对象。
< StockController.java >
import java.util.*
import com.jcorporate.expresso.core.controller.*;
import com.jcorporate.expresso.core.controller.session.*;
import com.jcorporate.expresso.core.dbobj.*;
import com.jcorporate.expresso.core.db.*;
import com.xenonsoft.orangetrader.ot1.dbobj.StockTrade;
public class StockController extends DBController
{
public StockController()
{
super();
addState( new State("displayStocks", "displayStocks"));
addState( new State("promptBuyStock", "Prompt Buy Stock"));
addState( new State("promptSellStock", "Prompt Sell Stock"));
addState( new State("buyStock", "Buy Stock"));
addState( new State("sellStock", "Sell Stock"));
setInitialState("displayStocks"); //一定别忘了
}
public String getTitle() {
return "Stock Controller II";
}
protected ControllerResponse runDisplayStocksState(
ControllerRequest myRequest, ControllerResponse myResponse )
throws ControllerException
{
String myName=thisClass+"displayStocks() ";
System.out.println( myName );
try {
// Create a block called "StockList" which is the root of the controller
// response.
Block blockList = new Block("StockList" );
// We retrieve all the stocks from the database. For each row
// tuple form the database we create an Output for it.
StockTrade stockInit = new StockTrade();
stockInit.setDBName( myRequest.getDBName() );
ArrayList list = stockInit.searchAndRetrieveList( "ST_TITLE" );
int N=list.size();
for ( int k=0; k<N; ++k ) {
StockTrade stock = (StockTrade)list.get(k);
// Create a Block for each row tuple
Block blockRow = new Block("Stock"+k );
blockList.add( blockRow );
// Create an Output for the DBObject row tuple and add to
// the row Block
Output out1 = new Output();
out1.setName( "Detail" );
out1.setAttribute( "Title", stock.getField("ST_TITLE") );
out1.setAttribute( "Trader", stock.getField("ST_TRADER") );
out1.setAttribute( "Price", stock.getField("ST_PRICE") );
out1.setAttribute( "Ask", stock.getField("ST_ASK") );
out1.setAttribute( "Bid", stock.getField("ST_BID") );
out1.setAttribute( "Change", stock.getField("ST_CHANGE") );
out1.setAttribute( "Status", stock.getField("ST_STATUS") );
blockRow.add( out1 );
// out1.setAttribute( "", stock.getField("ST_") );
// Add a "buy" transition
// NOTE: this state requires a request parameter "buyPage"
// to specifies the JSP of the prompt buy view
Transition buyTransition = new Transition("Buy", getClass().getName() );
buyTransition.setName( "Buy" );
buyTransition.addParam("state", "promptBuyStock");
buyTransition.addParam("stock", stock.getField("ST_ID") );
blockRow.add(buyTransition);
// Add a "sell" transition
// NOTE: this state requires a request parameter "sellURL"
// to specifies the JSP of the prompt sell view
Transition sellTransition = new Transition("Sell", getClass().getName() );
sellTransition.setName( "Sell" );
sellTransition.addParam("state", "promptSellStock");
sellTransition.addParam("stock", stock.getField("ST_ID") );
blockRow.add(sellTransition);
}
// Finally add the block to the list
myResponse.addBlock( blockList );
}
catch (DBException dbe) {
throw new ControllerException(
myName+ " database exception: "+dbe.getMessage() );
}
myResponse.setStyle("displayStocks" );
return myResponse;
}
//……More code
}
构造器里不能少的是调用父类方法super()和setInitialState();而super.newState(newState)则是应用安全规则所必需的。
以下为显示页面
< orangetrader/ot2 /index.jsp>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" %>
<%@ taglib uri="/xenon" prefix="xenon" %>
<%@ taglib uri="/xspresso" prefix="xspresso" %>
<%@ page import="com.xenonsoft.orangetrader.ot1.dbobj.StockTrade" %>
<%@ page import="com.xenonsoft.orangetrader.ot1.controller.StockController" %>
<table width="100%" border="1" cellspacing="1" cellpadding="2" >
<tr>
<th> <b>Title</b> </th>
<th> <b>Trader</b> </th>
<th> <b>Price</b> </th>
<th> <b>Ask</b> </th>
<th> <b>Bid</b> </th>
<th> <b>Change</b> </th>
<th> <b>Status</b> </th>
<th colspan="2"> <b>Command</b> </th>
</tr>
<xspresso:block blockPath="StockList" id="blockStocks" >
<xenon:loop name="blockStocks" property="nested"
loopId="block" className="com.jcorporate.expresso.core.controller.Block"
counterId="counter" >
<tr bgcolor="<%= ( counter.intValue() % 2 == 0 ? "#F0F0F0" : "#E0E0E0" ) %>" >
<xspresso:output outputPath="Detail" blockName="block" >
<td> <xspresso:outputAttribute property="Title" /> </td>
<td> <xspresso:outputAttribute property="Trader" /> </td>
<td> <xspresso:outputAttribute property="Price" /> </td>
<td> <xspresso:outputAttribute property="Ask" /> </td>
<td> <xspresso:outputAttribute property="Bid" /> </td>
<td> <xspresso:outputAttribute property="Change" /> </td>
<td> <xspresso:outputAttribute property="Status" /> </td>
</xspresso:output>
<form action="<%= contextPath %>/StockController2.do?cmd=button" method="POST" >
<td>
<xspresso:transition transitionName="Buy" blockName="block"
htmlClass="orange-form-submit-orange" />
</td>
<td>
<xspresso:transition transitionName="Sell" blockName="block"
htmlClass="orange-form-submit-orange" />
</td>
</form>
</tr>
</xenon:loop>
</xspresso:block>
</table>
5.5 创建一些需要的工作任务(Job)对象
Job对象通常被用在那些不需要用户交互而独立运行的任务,或者是把一耗时长的任务封装到一个线程里处理。Job对象和用户间通常采用消息通讯。
Job对象通常放在job包里。
每一Job对象均对应一个JobHandler线程,JobHandler线程能被配置成在连接数据库后自动执行。
(本例不涉及Job对象)
5.6 安装Expresso应用
5.6.1 在config目录里创建XML配置文件
struts-config.xml
该文件是Expresso框架在Struts组件里自身的URL's到"Action"对象的映射关系,一般情况下,用户不必调整此文件。用户的Struts对应关系由用户编写XXX-config.xml完成(XXX表示应用的名字),具有这样命名格式的配置文件在struts-config.xml被读取时一同被读取,编写方法可借鉴struts-config.xml,更多内容请阅读Struts文档。
以下为orange-struts-config.xml文件部分内容:
<action path="/StockController2" type="com.xenonsoft.orangetrader.ot2.controller.StockController" scope="request" validate="false">
<forward name="displayStocks" path="/orangetrader/ot2/index.jsp" />
<forward name="promptBuy" path="/orangetrader/ot2/buy.jsp" />
<forward name="promptSell" path="/orangetrader/ot2/sell.jsp" />
</action>
其实质表示:
1. 动作的输入为/StockController2.do?state= displayStocks;
2. 在执行/orangetrader/ot2/index.jsp之前插入了StockController的一段代码,相当于传统的Jsp头部的嵌入式脚本;
3. StockController和Jsp之间的数据采用Block对象传递;
4. 在页面中的服务器脚本交由标签处理。
expressoLogging.xml
定义Expresso使用Log4j各对象产生日志的等级,用户应用使用Log4j各对象产生的日志等级由XXX Logging.xml定义,XXX表示应用的名字。
expressoLogging.xml文件由LogManager进行读入管理,ConfigManager是第一个调用LogManager的对象。
5.6.2 注册Schema和创建/初始化数据库
启动Expresso的Tomcat服务程序,使用浏览器打开Expresso主页:http://localhost:8080/expresso/frame.jsp,使用Admin登陆,选择“Applications”页,使用Component Manager去添加注册Schema。
上例如下设置:
Schema Class File: com.xenonsoft.orangetrader.OrangeTraderSchema
Schema Description: OrangeTrader Demo
Component Code: orangetrader
在Setup也,使用Create/Verify Database Structure & Perform Initial Setup来创建数据库表。
使用浏览器输入URL:http://localhost:8080/应用目录/ 就可以开始你的应用了。
6 Expresso专题
6.1 数据对象
继承自SecuredDBObject类的数据对象无需加入任何代码便有了安全属性,也就是说,可以用此来控制某个用户是否可用该数据对象。
在SetupFields里可以使用:
使用setDBName(String)方法可以指定数据库;
addDetail方法可以实现表间Master/Detail关系,并应用主从表规则如自动删除、键置更新等;
还可以通过setLookupObject方法实现一个数据对象对应多个表的情况。
通过Schema注册数据对象的不同方法,可以将表映射到不同机器的数据库中。
6.2 控制器对象
Controller和DBController的不同就象DBObject之于SecuredDBObject,均是安全性的不同。一个控制器可类似地对应一个use case,每个State便是use case的细节用例。
控制器能被各种各样的客户端程序调用: Servlet,JSP,Applet和Application。
控制器是居留内存的,不安全线程代码是一个可怕恶梦,请注意使用synchronized(针对静态数据成员)或Hashtable来处理线程安全性问题。
控制器活动图:
状态:椭圆表示;
输出:带名字的箭头表示;
每个控制器状态都是安全线程模式的,也就是说:不可能指望在状态间共享内存变量。
你可用通过在expresso-config.xml插入如下行而修改成自己的登陆界面(而不是通过修改Expresso自带的Jsp文件):
<class-handler name="login"
classHandler="com.jcorporate.myproject.controller.MyLoginController"/>
6.3 初始化Expresso为Java Application环境
让Expresso不是Web Application方式执行也是可能的,如下就是一个例子:com.jcorporate.expresso.core.utility.JobHandler,这时Struts将不能使用,但是ConfigManager、CacheManager、DB connection pools和logging等仍能使用。
6.4 DBTool和DBCreate
DBTool是一个脱离Web环境的Java Application,DBCreate/DBTool可以通过用户定义的DBObject在数据库里创建表,也可以反过来,从数据表产生Java bean(DBObject)。
6.5 数据库连接池
支持多连接池,可同时提供多个分布在不同机器、不同类型的数据库连接池,也就是说,可以实现例如将Sybase的数据导入Oracle数据库的功能。
7 使用Expresso框架后工作划分
7.1 分析和设计人员(包括数据库设计)
² 系统分析
² 需求分析
² 软件设计
1. 控制器和视图之间的数据交换接口
2. MVC设计图
3. 由控制器延伸业务相关类图
4. 由控制器延伸业务相关序列图
5. 数据字典
6. 表间关系、触发器和存储过程
7. 不再需要Web Application的结构设计
7.2 美工、界面设计人员
² 熟悉标签库
² 输入界面
² 输出界面
7.3 程序员
² 编写控制器对象
² 延伸出来的业务相关Java Bean
² 数据对象(按照数据库设计人员的数据字典编写表的创建脚本代码)
8 Expresso和Spring+Hiberate组合结构的比较
1. Expresso最大的亮点就是引入了作为业界MVC标准的Struts,并以之为核心思想,实现了一个表示层-业务层-持久层的应用框架。
2. Expresso没有使用第三方持久层组件,持久层和连接池为自主开发,效率值得考证。事实上,持久层的强大功能也是Expresso的一大亮点,她将“与数据库无关”理论实现得相当完美。
3. Expresso整个系统所引入的东西较多(多于200个文件),包括很多JSP、Java Bean、配置XML、数据库表等,造成本身的结构都很复杂,给学习者带来较大的困难,且怎么取舍那么多界面元素的.jsp还是个问题。
4. Expresso本身就是一个Web应用,不象许多第三方组件商,仅提供.jar包,因此使用该框架进行开发,就使用方法方面带来了差异。与其说是使用Expresso工具为我们的项目应用服务,还不如说是扩展开发Expresso。Expresso带来的是一些现成的工具,同时带来了一种开发模式。开发模式和开发方式比较固定,自由度较低,层级功能是很难单个拆分的,如去掉Expresso的用户安全系统可能就比较难了。
5. Spring等其他框架,使用时更象是使用一种第三方工具,使用过程通过实例创建和方法调用完成。由于使用时就只引入几个有限的Jar文件,感觉起来封装性较好,使用时更自由,当然也就没有安全模块这样的公共模块了。
6. Spring、iBatis等其他第三方组件,通常直接采用HashMap对象传递数据,Expresso里则采用Block对象。
9 开发Expresso建议
为保持Expresso的完整性,请遵守不要修改系统所带来的*.jsp、*.java文件,配置文件xml内容尽量只增加不修改的原则。
项目的目录结构视Expresso为一个组件,尽量不要将项目的目录放入Expresso目录结构中,以免查找困难。
project
|
+-- src - 源文件
|
+-- lib – 包文件
|
+-- docs – 文档
|
+-- build -编译后的发布结构
|
+-- web--expresso - Expresso的jsps
|
+---WEB-INF - 发布的lib、Classes,包含Expresso的
|
+---project jsps - 项目的jsp,按模块分
10 参考资料
Expresso Developer's Guide
Best Practice with Expresso Framework: Using a framework to create a web application
DBMaint A Step-by-Step Example