I have a dream

Java&computer science
posts - 6, comments - 1, trackbacks - 0, articles - 0

2006年5月11日

深入Struts 1.1

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项


最新推荐

Java 应用开发源动力 - 下载免费软件,快速启动开发


级别: 初级

王和全 ,

2003 年 8 月 02 日

作为基于MVC模式的Web应用最经典框架,Struts已经正式推出了1.1版本,该版本在以往版本的基础上,提供了许多激动人心的新功能。本文就将带你走进Struts 1.1去深入地了解这些功能。

说明:希望本文的读者能有一定的Struts使用基础。

Model 2

Struts是基于Model 2之上的,而Model 2是经典的MVC(模型-视图-控制器)模型的Web应用变体,这个改变主要是由于网络应用的特性--HTTP协议的无状态性引起的。Model 2的目的和MVC一样,也是利用控制器来分离模型和视图,达到一种层间松散耦合的效果,提高系统灵活性、复用性和可维护性。在多数情况下,你可以将Model 2与MVC等同起来。

下图表示一个基于Java技术的典型网络应用,从中可以看出Model 2中的各个部分是如何对应于Java中各种现有技术的。



在利用Model 2之前,我们是把所有的表示逻辑和业务逻辑都集中在一起(比如大杂烩似的JSP),有时也称这种应用模式为Model 1,Model 1的主要缺点就是紧耦合,复用性差以及维护成本高。





回页首


Struts 1.1 和Model 2

既然Struts 1.1是基于Model 2之上,那它的底层机制也就是MVC,下面是Struts 1.1中的MVC实现示意图:



图解说明:其中不同颜色代表MVC的不同部分:红色(控制器)、紫色(模型)和绿色(视图)

首先,控制器(ActionServlet)进行初始化工作,读取配置文件(struts-config.xml),为不同的Struts模块初始化相应的ModuleConfig对象。比如配置文件中的Action映射定义都保存在ActionConfig集合中。相应地有ControlConfig集合、FormBeanConfig集合、ForwardConfig集合和MessageResourcesConfig集合等。

提示:模块是在Struts 1.1中新提出的概念,在稍后的内容中我们将详细介绍,你现在可以简单地把模块看作是一个子系统,它们共同组成整个应用,同时又各自独立。Struts 1.1中所有的处理都是在特定模块环境中进行的。模块的提出主要是为了解决Struts 1.0中单配置文件的问题。

控制器接收HTTP请求,并从ActionConfig中找出对应于该请求的Action子类,如果没有对应的Action,控制器直接将请求转发给JSP或者静态页面。否则控制器将请求分发至具体Action类进行处理。

在控制器调用具体Action的execute方法之前,ActionForm对象将利用HTTP请求中的参数来填充自己(可选步骤,需要在配置文件中指定)。具体的ActionForm对象应该是ActionForm的子类对象,它其实就是一个JavaBean。此外,还可以在ActionForm类中调用validate方法来检查请求参数的合法性,并且可以返回一个包含所有错误信息的ActionErrors对象。如果执行成功,ActionForm自动将这些参数信息以JavaBean(一般称之为form bean)的方式保存在Servlet Context中,这样它们就可以被其它Action对象或者JSP调用。

Struts将这些ActionForm的配置信息都放在FormBeanConfig集合中,通过它们Struts能够知道针对某个客户请求是否需要创建相应的ActionForm实例。

Action很简单,一般只包含一个execute方法,它负责执行相应的业务逻辑,如果需要,它也进行相应的数据检查。执行完成之后,返回一个ActionForward对象,控制器通过该ActionForward对象来进行转发工作。我们主张将获取数据和执行业务逻辑的功能放到具体的JavaBean当中,而Action只负责完成与控制有关的功能。遵循该原则,所以在上图中我将Action对象归为控制器部分。

提示:其实在Struts 1.1中,ActionMapping的作用完全可以由ActionConfig来替代,只不过由于它是公共API的一部分以及兼容性的问题得以保留。ActionMapping通过继承ActionConfig来获得与其一致的功能,你可以等同地看待它们。同理,其它例如ActionForward与ForwardConfig的关系也是如此。

下图给出了客户端从发出请求到获得响应整个过程的图解说明。



下面我们就来详细地讨论一下其中的每个部分,在这之前,先来了解一下模块的概念。





回页首


模块

我们知道,在Struts 1.0中,我们只能在web.xml中为ActionServlet指定一个配置文件,这对于我们这些网上的教学例子来说当然没什么问题,但是在实际的应用开发过程中,可能会有些麻烦。因为许多开发人员都可能同时需要修改配置文件,但是配置文件只能同时被一个人修改,这样肯定会造成一定程度上的资源争夺,势必会影响开发效率和引起开发人员的抱怨。

在Struts 1.1中,为了解决这个并行开发的问题,提出了两种解决方案:

  1. 多个配置文件的支持
  2. 模块的支持

支持多个配置文件,是指你能够为ActionServlet同时指定多个xml配置文件,文件之间以逗号分隔,比如Struts提供的MailReader演示例子中就采用该种方法。

												
														  <!-- Action Servlet Configuration -->
  <servlet>
	<servlet-name>action</servlet-name>
	<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
	<init-param>
		<param-name>config</param-name>
		<param-value>/WEB-INF/struts-config.xml, /WEB-INF/struts-config-registration.xml</param-value>
	</init-param> 
	<load-on-startup>1</load-on-startup>
  </servlet>


												
										

这种方法可以很好地解决修改冲突的问题,不同的开发人员可以在不同的配置文件中设置自己的Action、ActionForm等等(当然不是说每个开发人员都需要自己的配置文件,可以按照系统的功能模块进行划分)。但是,这里还是存在一个潜在的问题,就是可能不同的配置文件之间会产生冲突,因为在ActionServlet初始化的时候这几个文件最终还是需要合并到一起的。比如,在struts-config.xml中配置了一个名为success的<forward>,而在struts-config-registration.xml中也配置了一个同样的<forward>,那么执行起来就会产生冲突。

为了彻底解决这种冲突,Struts 1.1中引进了模块(Module)的概念。一个模块就是一个独立的子系统,你可以在其中进行任意所需的配置,同时又不必担心和其它的配置文件产生冲突。因为前面我们讲过,ActionServlet是将不同的模块信息保存在不同的ModuleConfig对象中的。要使用模块的功能,需要进行以下的准备工作:

1、为每个模块准备一个配置文件

2、配置web.xml文件,通知控制器

决定采用多个模块以后,你需要将这些信息告诉控制器,这需要在web.xml文件进行配置。下面是一个典型的多模块配置:

												
														<init-param>
	<param-name>config</param-name>
	<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
	<param-name>config/customer</param-name> 
	<param-value>/WEB-INF/struts-config-customer.xml</param-value>
</init-param>
<init-param> 
	<param-name>config/order</param-name>
	<param-value>/WEB-INF/struts-config-order.xml</param-value>
</init-param>


												
										

要配置多个模块,你需要在原有的一个<init-param>(在Struts 1.1中将其对应的模块称为缺省模块)的基础之上,增加模块对应的<init-param>。其中<param-name>表示为config/XXX的形式,其中XXX为对应的模块名,<param-value>中还是指定模块对应的配置文件。上面这个例子说明该应用有三个模块,分别是缺省模块、customer和order,它们分别对应不同的配置文件。

3、准备各个模块所需的ActionForm、Action和JSP等资源

但是要注意的是,模块的出现也同时带来了一个问题,即如何在不同模块间进行转发?有两种方法可以实现模块间的转发,一种就是在<forward>(全局或者本地)中定义,另外一种就是利用org.apache.struts.actions.SwitchAction。

下面就是一个全局的例子:

												
														    ... 
    <struts-config>
	... 
	<global-forwards>
		<forward name="toModuleB"
			contextRelative="true"  
			path="/moduleB/index.do" 
		redirect="true"/>   
	... 
	</global-forwards>  
	...   
    </struts-config>

												
										

可以看出,只需要在原有的path属性前加上模块名,同时将contextRelative属性置为true即可。此外,你也可以在<action>中定义一个类似的本地<forward>。

												
														  <action-mappings>
	<!-- Action mapping for profile form -->
	<action path="/login" 
	type="com.ncu.test.LoginAction"  
	name="loginForm"     
	scope="request"      
	input="tile.userLogin"
	validate="true">     
	<forward name="success" contextRelative="true" path="/moduleA/login.do"/> 
	</action> 
  </action-mappings>

												
										

如果你已经处在其他模块,需要转回到缺省模块,那应该类似下面这样定义,即模块名为空。

												
														<forward name="success" contextRelative="true" path="/login.do"/>


												
										

此外,你也可以使用org.apache.struts.actions.SwitchAction,例如:

												
														    ...
    <action-mappings> 
	<action path="/toModule" 
	type="org.apache.struts.actions.SwitchAction"/>  
	...    
    </action-mappings>  
    ...

												
										





回页首


ActionServlet

我们首先来了解MVC中的控制器。在Struts 1.1中缺省采用ActionServlet类来充当控制器。当然如果ActionServlet不能满足你的需求,你也可以通过继承它来实现自己的类。这可以在/WEB-INF/web.xml中来具体指定。

要掌握ActionServlet,就必须了解它所扮演的角色。首先,ActionServlet表示MVC结构中的控制器部分,它需要完成控制器所需的前端控制及转发请求等职责。其次,ActionServlet被实现为一个专门处理HTTP请求的Servlet,它同时具有servlet的特点。在Struts 1.1中它主要完成以下功能:

  • 接收客户端请求
  • 根据客户端的URI将请求映射到一个相应的Action类
  • 从请求中获取数据填充Form Bean(如果需要)
  • 调用Action类的execute()方法获取数据或者执行业务逻辑
  • 选择正确的视图响应客户

此外,ActionServlet还负责初始化和清除应用配置信息的任务。ActionServlet的初始化工作在init方法中完成,它可以分为两个部分:初始化ActionServlet自身的一些信息以及每个模块的配置信息。前者主要通过initInternal、initOther和initServlet三个方法来完成。

我们可以在/WEB-INF/web.xml中指定具体的控制器以及初始参数,由于版本的变化以及Struts 1.1中模块概念的引进,一些初始参数被废弃或者移入到/WEB-INF/struts-config.xml中定义。下面列出所有被废弃的参数,相应地在web.xml文件中也不鼓励再使用。

  • application
  • bufferSize
  • content
  • debug
  • factory
  • formBean
  • forward
  • locale
  • mapping
  • maxFileSize
  • multipartClass
  • nocache
  • null
  • tempDir

ActionServlet根据不同的模块来初始化ModuleConfig类,并在其中以XXXconfig集合的方式保存该模块的各种配置信息,比如ActionConfig,FormBeanConfig等。

初始化工作完成之后,ActionServlet准备接收客户请求。针对每个请求,方法process(HttpServletRequest request, HttpServletResponse response)将被调用。该方法指定具体的模块,然后调用该模块的RequestProcessor的process方法。

												
														protected void process(HttpServletRequest request, 
		HttpServletResponse response) 
		throws IOException, ServletException {

	RequestUtils.selectModule(request, getServletContext());        
	getRequestProcessor(getModuleConfig(request)).process(request, response);
}

												
										

RequestProcessor包含了Struts控制器的所有处理逻辑,它调用不同的processXXX方法来完成不同的处理。下表列出其中几个主要的方法:

方法 功能
processPath 获取客户端的请求路径
processMapping 利用路径来获得相应的ActionMapping
processActionForm 初始化ActionForm(如果需要)并存入正确的scope中
processActionCreate 初始化Action
processActionPerform 调用Action的execute方法
processForwardConfig 处理Action返回的ActionForward




回页首


ActionForm

对于ActionForm你可以从以下几个方面来理解它:

  1. ActionForm表示HTTP窗体中的数据,可以将其看作是模型和视图的中介,它负责保存视图中的数据供模型或者视图使用。Struts 1.1文档中把它比作HTTP和Action之间的防火墙,这体现了ActionForm具有的过滤保护的作用,只有通过ActionForm验证的数据才能够发送到Action处理。
  2. ActionForm是与一个或多个ActionConfig关联的JavaBean,在相应的action的execute方法被调用之前,ActionForm会自动利用请求参数来填充自己(初始化属性)。
  3. ActionForm是一个抽象类,你必须通过继承来实现自己的类。

ActionForm首先利用属性的getter和setter方法来实现初始化,初始化完毕后,ActionForm的validate方法被调用,你可以在其中来检查请求参数的正确性和有效性,并且可以将错误信息以ActionErrors的形式返回到输入窗体。否则,ActionForm将被作为参数传给action的execute方法以供使用。

ActionForm bean的生命周期可以设置为session(缺省)和request,当设置为session时,记得在reset方法中将所有的属性重新设置为初始值。

由于ActionForm对应于HTTP窗体,所以随着页面的增多,你的ActionForm将会急速增加。而且可能同一类型页面字段将会在不同的ActionForm中出现,并且在每个ActionForm中都存在相同的验证代码。为了解决这个问题,你可以为整个应用实现一个ActionForm或者至少一个模块对应于一个ActionForm。

但是,聚合的代价就是复用性很差,而且难维护。针对这个问题,在Struts 1.1中提出了DynaActionForm的概念。

DynaActionForm类

DynaActionForm的目的就是减少ActionForm的数目,利用它你不必创建一个个具体的ActionForm类,而是在配置文件中配置出所需的虚拟ActionForm。例如,在下表中通过指定<form-bean>的type为"org.apache.struts.action.DynaActionForm"来创建一个动态的ActionForm--loginForm。

												
														<form-beans>
	<form-bean name="loginForm" type="org.apache.struts.action.DynaActionForm">  
		<form-property name="actionClass" type="java.lang.String"/>
		<form-property name="username" type="java.lang.String"/>
		<form-property name="password" type="java.lang.String"/> 
	</form-bean> 
</form-beans>


												
										

动态的ActionForm的使用方法跟普通的ActionForm相同,但是要注意一点。普通的ActionForm对象需要为每个属性提供getter和setter方法,以上面的例子而言,我们需要提供getUsername() 和 setUsername()方法取得和设置username属性,同样地有一对方法用于取得和设置password属性和actionClass属性。

如果使用DynaActionForm,它将属性保存在一个HashMap类对象中,同时提供相应的get(name) 和 set(name)方法,其中参数name是要访问的属性名。例如要访问DynaActionForm中username的值,可以采用类似的代码:

												
														String username = (String)form.get("username");


												
										

由于值存放于一个HashMap对象,所以要记得对get()方法返回的Object对象做强制性类型转换。正是由于这点区别,如果你在Action中非常频繁地使用ActionForm对象,建议还是使用普通的ActionForm对象。

在Struts 1.1中,除了DynaActionForm以外,还提供了表单输入自动验证的功能,在包org.apache.struts.validator中提供了许多有用的类,其中最常见的就是DynaValidatorForm类。

DynaValidatorForm类

DynaValidatorForm是DynaActionForm的子类,它能够提供动态ActionForm和自动表单输入验证的功能。和使用DynaActionForm类似,你必须首先在配置文件中进行配置:

												
														<form-beans>
	<form-bean name="loginForm" type="org.apache.struts.validator.DynaValidatorForm"> 
		<form-property name="actionClass" type="java.lang.String"/>     
		<form-property name="username" type="java.lang.String"/> 
		<form-property name="password" type="java.lang.String"/>  
	</form-bean>
</form-beans>


												
										

同时要定义验证的插件:

												
														  <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
	<set-property property="pathnames"  
	value="/WEB-INF/validator-rules.xml,  
	/WEB-INF/validation.xml"/>
  </plug-in>


												
										

其中的validator.xml和validator-rules.xml分别表示验证定义和验证规则的内容(可以合并在一起),比如针对上例中的DynaValidatorForm,我们有如下验证定义(validator.xml):

												
														<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE form-validation PUBLIC  
"-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN"  
"http://jakarta.apache.org/commons/dtds/validator_1_0.dtd">
<!--    Validation Rules    $Id: validation.xml-->

<form-validation>  
<!-- ========== Default Language Form Definitions ===================== -->
<formset>  
	<form name="loginForm">     
		<field property="username" depends="required, minlength,maxlength"> 
			<arg0   key="prompt.username"/>          
			<arg1   key="${var:minlength}" name="minlength" resource="false"/>       
			<arg2   key="${var:maxlength}" name="maxlength" resource="false"/>              
			<var>                
				<var-name>maxlength</var-name>    
				<var-value>16</var-value>         
			</var>          
			<var>      
				<var-name>minlength</var-name>     
				<var-value>3</var-value>         
			</var>       
		</field>     
		<field property="password" depends="required, minlength,maxlength" bundle="alternate">          
			<arg0   key="prompt.password"/>   
			<arg1   key="${var:minlength}" name="minlength" resource="false"/>          
			<arg2   key="${var:maxlength}" name="maxlength" resource="false"/>  
			<var>              
				<var-name>maxlength</var-name>     
				<var-value>16</var-value>        
			</var>          
			<var>      
				<var-name>minlength</var-name> 
				<var-value>3</var-value>       
			</var>        
		</field>    
	</form>   
</formset>
</form-validation>

												
										

从上述定义中,我们可以看到对于字段username有三项验证:required, minlength, maxlength,意思是该字段不能为空,而且长度在3和16之间。而validator-rules.xml文件则可以采用Struts提供的缺省文件。注意在<form-bean>中定义的form是如何与validation.xml中的form关联起来的。最后,要启动自动验证功能,还需要将Action配置的validate属性设置为true。

												
														<action path="/login"  
type="com.ncu.test.LoginAction"
name="loginForm"          
scope="request"         
input="tile.userLogin"validate="true">


												
										

此时,Struts将根据xml配置文件中的定义来检验表单输入,并将不符合要求的错误信息输出到页面。但是你可能会想:这个功能虽然好,可是什么检验都跑到服务器端执行,效率方面和用户易用性方面是不是有些问题?你可能会怀念起那简单的JavaScript客户端验证。

不用担心,在Struts 1.1中也支持JavaScript客户端验证。如果你选择了客户端验证,当某个表单被提交以后,Struts 1.1启动客户端验证,如果浏览器不支持JavaScript验证,则服务器端验证被启动,这种双重验证机制能够最大限度地满足各种开发者的需要。JavaScript验证代码也是在validator-rules.xml文件中定义的。要启动客户端验证,你必须在相应的JSP文件中做如下设置:

  1. 为<html:form>增加onsubmit属性
  2. 设置Javascript支持

下表中列出了一JSP文件的示例代码,红字部分为Javascript验证所需代码。

												
														<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<table bgcolor="#9AFF9A" cellspacing="0" cellpadding="10" border="1" width="100%">
	<tr>
	<td> 
	<table cellspacing="0" cellpadding="0" border="0" width="100%"> 
	<tr bgcolor="#696969"> 
		<td align="center">     
		<font color="#FFFFFF">Panel 3: Profile</font>  
		</td>
		</tr> 
	<tr>  
		<td><br> 
		<html:errors/>  
		<html:form action="/login.do" focus="username"  onsubmit="return validateLoginForm(this);">  
		<html:hidden property="actionClass"/>   
		<center>      
		<table>      
			<tr>        
			<td>UserName:</td>   
			<td><html:text property="username" size="20"/></td> 
			</tr> 
			<tr>  
			<td>Password:</td>   
			<td><html:password property="password" size="20"/></td>    
			</tr>  
			<tr>  
			<td colspan=2><html:submit property="submitProperty" value="Submit"/></td>     
		</table>   
		</center>  
		</html:form> 
		<html:javascript formName="loginForm" dynamicJavascript="true" staticJavascript="false"/>  
	
	<script language="Javascript1.1" src="staticJavascript.jsp"></script>  
	</td> 
	</tr> 
	</table>
	</td>
	</tr>
</table>

												
										

其中onsubmit的值为"return validateLoginForm(this);",它的语法为:

return validate + struts-config.xml中定义的form-bean名称 + (this);

staticJavascript.jsp的内容为:

												
														<%@ page language="java" %>
<%-- set document type to Javascript (addresses a bug in Netscape according to a web resource --%>
<%@ page contentType="application/x-javascript" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<html:javascript dynamicJavascript="false" staticJavascript="true"/>



												
										

如果validator-rules.xml中定义的基本验证功能不能满足你的需求,你可以自己添加所需的验证类型。





回页首


Action

我们通过继承Action类来实现具体的执行类。具体Action类的功能一般都在execute(以前是perform方法)方法中完成,其中主要涉及到以下几个方面:

  1. 辅助ActionForm进行一些表单数据的检查。
  2. 执行必要的业务逻辑,比如存取数据库,调用实体bean等。
  3. 更新服务器端的bean数据,后续对象中可能会用到这些数据,比如在JSP中利用bean:write来获得这些数据。
  4. 根据处理结果决定程序的去处,并以ActionForward对象的形式返回给ActionServlet。

提示:由于在Action和ActionForm中都可以实现验证方法,那么如何来安排它们之间的分工呢?一般来说,我们秉着MVC分离的原则,也就是视图级的验证工作放在ActionForm来完成,比如输入不能为空,email格式是否正确,利用ValidatorForm可以很轻松地完成这些工作。而与具体业务相关的验证则放入Action中,这样就可以获得最大ActionForm重用性的可能。

前面我们提到过,我们主张将业务逻辑执行分离到单独的JavaBean中,而Action只负责错误处理和流程控制。而且考虑到重用性的原因,在执行业务逻辑的JavaBean中不要引用任何与Web应用相关的对象,比如HttpServletRequest,HttpServletResponse等对象,而应该将其转化为普通的Java对象。关于这一点,可以参考Petstore中WAF框架的实现思路。

此外,你可能还注意到execute与perform的一个区别:execute方法简单地掷出Exception异常,而perform方法则掷出ServletException和IOException异常。这不是说Struts 1.1在异常处理功能方面弱化了,而是为了配合Struts 1.1中一个很好的功能--宣称式异常处理机制。





回页首


宣称式异常处理

和EJB中的宣称式事务处理概念类似,宣称式异常处理其实就是可配置的异常处理,你可以在配置文件中指定由谁来处理Action类中掷出的某种异常。你可以按照以下步骤来完成该功能:

  1. 实现org.apache.struts.action.ExceptionHandler的子类,覆盖execute方法,在该方法中处理异常并且返回一个ActionForward对象
  2. 在配置文件中配置异常处理对象,你可以配置一个全局的处理类或者单独为每个Action配置处理类

下表就定义了一个全局的处理类CustomizedExceptionHandler,它被用来处理所有的异常。

												
														<global-exceptions> 
<exception 
	handler="com.yourcorp.CustomizedExceptionHandler" 
	key="global.error.message" 
	path="/error.jsp"    
	scope="request"    
	type="java.lang.Exception"/>
</global-exceptions>


												
										

其中具体的参数含义,可以参考ExceptionHandler.java源文件。





回页首


taglib

讲完了模型和控制器,接下来我们要涉及的是视图。视图的角色主要是由JSP来完成,从JSP的规范中可以看出,在视图层可以"折腾"的技术不是很多,主要的就是自定义标记库的应用。Struts 1.1在原有的四个标记库的基础上新增了两个标记库--Tiles和Nested。

其中Tiles除了替代Template的基本模板功能外,还增加了布局定义、虚拟页面定义和动态页面生成等功能。Tiles强大的模板功能能够使页面获得最大的重用性和灵活性,此外可以结合Tiles配置文件中的页面定义和Action的转发逻辑,即你可以将一个Action转发到一个在Tiles配置文件中定义的虚拟页面,从而减少页面的数量。比如,下表中的Action定义了一个转发路径,它的终点是tile.userMain,而后者是你在Tiles配置文件中定义的一个页面。

												
														<!-- ========== Action Mapping Definitions ============================== -->
<action-mappings>  
<!-- Action mapping for profile form --> 
	<action path="/login"   
		type="com.ncu.test.LoginAction"      
		name="loginForm"    
		scope="request"     
		input="tile.userLogin"
		validate="true">     
		<forward name="success" path="tile.userMain"/>   
	</action> 
</action-mappings>

												
										

Tiles配置文件:tiles-defs.xml

												
														<!DOCTYPE tiles-definitions PUBLIC 
"-//Apache Software Foundation//DTD Tiles Configuration//EN"       "http://jakarta.apache.org/struts/dtds/tiles-config.dtd">
<tiles-definitions>  
<!-- =======================================================  --> 
<!-- Master definitions                                       -->
<!-- =======================================================  --> 
<!-- Page layout used as root for all pages. --> 

<definition name="rootLayout" path="/tiles-layouts/rootLayout.jsp"> 
	<put name="titleString" value="CHANGE-ME"/>   
	<put name="topMenu" value="/tiles-components/topMenu.jsp"/> 
	<put name="leftMenu" value="/tiles-components/panel1.jsp"/>  
	<put name="body" value="CHANGE-ME"/>   
	<put name="footer" value="/tiles-components/footer.jsp"/> 
</definition> 

<!-- =======================================================  --> 
<!-- Page definitions 					-->  
<!-- =======================================================  --> 

<!-- User Login page --> 
<definition name="tile.userLogin" extends="rootLayout"> 
	<put name="titleString" value="User Login"/>  
	<put name="body" value="/src/userLogin.jsp"/> 
</definition>  
<!-- User Main page --> 
<definition name="tile.userMain" extends="rootLayout"> 
	<put name="titleString" value="User Main"/>  
	<put name="body" value="/src/userMain.jsp"/> 
</definition>
</tiles-definitions>

												
										

而Nested标记库的作用是让以上这些基本标记库能够嵌套使用,发挥更大的作用。





回页首


Commons Logging 接口

所谓的Commons Logging接口,是指将日志功能的使用与日志具体实现分开,通过配置文件来指定具体使用的日志实现。这样你就可以在Struts 1.1中通过统一的接口来使用日志功能,而不去管具体是利用的哪种日志实现,有点于类似JDBC的功能。Struts 1.1中支持的日志实现包括:Log4J,JDK Logging API, LogKit,NoOpLog和SimpleLog。

你可以按照如下的方式来使用Commons Logging接口(可以参照Struts源文中的许多类实现):

												
														package com.foo;
// ...
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
//...
	public class Foo {    
	// ...    
	private static Log log = LogFactory.getLog(Foo.class);
	// ...    
	public void setBar(Bar bar) {       
		if (log.isTraceEnabled()) {         
			log.trace("Setting bar to " + bar);   
		}      
	this.bar = bar;   
	}
// ...
}

												
										

而开启日志功能最简单的办法就是在WEB-INF/classes目录下添加以下两个文件:

commons-logging.properties文件:

												
														# Note: The Tiles framework now uses the commons-logging package to output different information or debug statements. 
Please refer to this package documentation to enable it. The simplest way to enable logging is to create two files in 
WEB-INF/classes:
# commons-logging.properties
# org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
# simplelog.properties
# # Logging detail level,
# # Must be one of ("trace", "debug", "info", "warn", "error", or "fatal").
#org.apache.commons.logging.simplelog.defaultlog=trace
org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog


												
										

simplelog.properties文件:

												
														# Logging detail level,
# Must be one of ("trace", "debug", "info", "warn", "error", or "fatal").
org.apache.commons.logging.simplelog.defaultlog=fatal


												
										

这里我们采用的日志实现是SimpleLog,你可以在simplelog.properties文件指定日志明细的级别:trace,debug,info,warn,error和fatal,从trace到fatal错误级别越来越高,同时输出的日志信息也越来越少。而这些级别是和org.apache.commons.logging.log接口中的方法一一对应的。这些级别是向后包含的,也就是前面的级别包含后面级别的信息。

posted @ 2006-05-11 17:16 I have a dream 阅读(304) | 评论 (0)编辑 收藏

2005年12月19日

使用Java Swing 创建一个XML编辑器

chris 发表于2003-07-17 评价:5/1 评论数:0 点击数:3226 [收藏]
摘要:



本文Matrix永久镜像:http://www.matrix.org.cn/resource/article/0/392.html
说明:本文可能由Matrix原创,也可能由Matrix的会员整理,或者由
Matrix的Crawler在全球知名Java或者其他技术相关站点抓取并永久
保留镜像,Matrix会保留所有原来的出处URL,并在显著地方作出说明,
如果你发觉出处URL有误,请联系Matrix改正.
出自:yesky

我想您一定对XML有所了解,说不定您现在还跃跃欲试想写一段XML文本呢,可是现在能找到的跨平台的、免费的XML编辑器太少了。所以在本文中,我想介绍一下或者说带您一步一步的开发一个简单的XML编辑器,当然我们要用到一些最常见的Java 2 Swing组件,不过这些都是免费的,有些是JDK中的,有些是可以从网上下载的。我想通过本文,你就可以创建一个属于你自己的XML编辑器。

  先让我介绍一下本文辑写的思路。首先我想简要的讨论一下XML和为什么树型结构比较适合用来显示XML,然后我们来看一看JAXP API如何建立所需要的XML类的环境;然后我们将了解用来显示一个图形树的JTree Swing组件;最后,我们将创建一个继承JTree组件的可以重复使用的类,可以用来分析一个XML文档,并把数据显示在一个Jtree中。

  说到XML(eXtensible Markup Languge),人们往往把它当成是一种新的用于Web浏览器中的标记语言,就象HTML或CSS一样。其实,XML是一种数据表示语言,它允许你使用一种非常有效的方法来描述你的数据。XML能够使你定义诸如“these three words constitutes a heading”这样的语句。XML允许你声明任何类型的数据,而不是用来把这些数据显示在网页中。

  请看一看下面的XML实例:

<article>
<header>
<title> 使用Java Swing 创建一个XML编辑器
<subtitle> 第一部分</subtitle>
</title>
<author> Wayne </author>
<header>
<content> 这是正文</content>
</article>


  请注意,这些元素和标准的HTML语句是不同的,但是它们看上去比较象HTML,这是因为XML和HTML都是来源于SGML语言。不同的是HTML有预定义的标签集,而XML的语法则有许多灵活性,它允许你使用表意的标记如<author>来括在数据两边。你还要注意,所有的元素都从属于根元素(上例中为<article>),有些元素则还有自己的子元素,如<subtitle>就是<title>的子元素。这样的数据组织方式有三个好处:数据能够更加表意,数据更加易维护而且数据更加容易作为一个树的结构表现出来,这就是我们为什么使用JTree对象来显示XML数据的原因。如果你想对XML有更深的了解,请参阅天极网上的相关教程。

  JAXP是一个用于处理XML的Java API,它能够使应用程序分析并且转化XML文档,它的功能有点象JDBC API,都是把函数功能抽象成一个个方法。你可以去Apache网站找到最新的Xerces分析器,其中含有最新的JAXP,下载下来以后把它放在你的类目录中。

下面让我们看一下如何使用JTree Swing组件。

  我们都知道,在自然界中,一棵树通常都有一个非常粗的树干,树干上有许多树枝分叉。每个树杈和树杈之间都有一定的联系,因为它们都有同一个来源:树干。这种继承的关系并不只在树枝中有,人类谱系也遵循相同的规律。从父母,到子女再到子女的子女,就这样直到数不清为止。同样,在数据存储中,树的概念也是一种使用同人类家谱树一样方法储存数据的方法。树的每一个树杈称为一个节点,每个有子节点的节点称为父节点,所有的子节点的公共的父节点被称为根节点。一个JTree组件就是一个简单的树数据结构的可视化表现形式。

  几乎所有的XML编辑器都包括一个可视化的树结构,能让你编辑XML文档中的元素。我们马上就会构建一个编辑器,不过在此之前,先让我们再了解一下JTree组件。一个节点在一棵树的某个位置储存数据,为了存储数据,必须知道任何一个父节点和它们的子节点。javax.swing.tree包定义了一些非常有用的接口,提供了一种通用的方法构建和操作一个树结构。

  TreeNode方法,用于访问树的节点的信息

  MutableTreeNode方法 用在一个可变的树上(能够添加或删除子节点)

  TreeModel方法 用于创建和管理与树有关的数据模型。

  接下来,我们将创建一个继承JTree的类,提供分析XML文档和用可视化JTree组件把节点显示出来的功能。

  创建XTree组件

  XTree类由一个构造函数和三个方法组成,为了简单起见我们的组件只能构建一个Xtree,在树创建好之后不能进行处理它的节点。下面让我们来看一个这个类。

  域:

  private DefaultMutableTreeNode treeNode 这个成员变量储存TreeNode对象用于存储JTree的模型。 

  DefaultMutableTreeNode类是在javax.swing.tree中被定义的,默认提供了MutableTreeNode接口的一个实现。

  private DocumentBuilderFactory dbf

  private DocumentBuilder db

  private Document doc 这三个成员变量是JAXP的一部分,用来分析XML文本并转化成DOM(Document Object Model) 对象。

  构造函数

  public XTree( String text )

  这个构造函数通过使用传送到构造器中的XML文本创建一个XTree对象。在初始化一些与JTree超类和DOM分析对象有关的基本显示属性后,构造函数生成一个TreeModel 对象用来创建一个实际可视的树。通过把DOM对象传送到createTreeNode()方法来创建一个根节点,createTreeNode()方法返回一个DefaultMutableTreeNode类型的对象。这个对象然后被用来创建树的TreeModel。

  方法

   private DefaultMutableTreeNode createTreeNode( Node root )

  这个方法采用一个DOM 节点,然后在子节点中递归直到所有的接点都被添加到DefaultMutableTreeNode中。这是一个递归方法,为了找到根节点下的每一个子节点,它每次都要调用自己。JTree然后就可以使用DefaultMutableTreeNode对象了,因为它已经是树型了。

   private String getNodeType( Node node )

  这个方法,被createTreeNode()用来联系一个字符串和某一种类型的节点。

   private Node parseXml()

  这个方法,用来分析XML文本字符串,它返回Node类型的对象,能够被传送到createTreeNode()方法中。
下面我给出了java代码,供大家分析研究。

// 到入W3C的DOM 类
import org.w3c.dom.*;
// JAXP的用于DOM I/O的类
import javax.xml.parsers.*;
// 标准Java类
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
public class XTree extends JTree
{
/**
* 这个成员变量储存TreeNode对象用于存储JTree的模型。
*DefaultMutableTreeNode类是在javax.swing.tree中被定义的
*默认提供了MutableTreeNode接口的一个实现。
*/
private DefaultMutableTreeNode treeNode;
/**
* 这三个成员变量是JAXP的一部分,用来分析XML文本并转化成DOM(Document Object Model) 对象。
*/
private DocumentBuilderFactory dbf;
private DocumentBuilder db;
private Document doc;

 /**
 * 这个构造函数通过使用传送到构造器中的XML文本创建一个XTree对象

 * @参数 text是一个XML格式的XML文本

 * @异常 ParserConfigurationException 如果构造函数非正常的设置分析器,就会抛出异常

 */

public XTree( String text ) throws ParserConfigurationException
{
super();

// 设置Tree渲染的基本属性
getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION );
setShowsRootHandles( true );
setEditable( false ); // 允许树可以编辑

// 通过初始化对象的DOM来分析对象
dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating( false );
db = dbf.newDocumentBuilder();

// 采用DOM根节点并且把它转化成JTree的树模型
treeNode = createTreeNode( parseXml( text ) );
setModel( new DefaultTreeModel( treeNode ) );
} file://中止XTree()


  /**

  * 这个方法采用一个DOM 节点,然后在子节点中递归直到所有的接点都被添加到DefaultMutableTreeNode中。

  * 这是一个递归方法,为了找到根节点下的每一个子节点,它每次都要调用自己。

  * JTree然后就可以使用DefaultMutableTreeNode对象了,因为它已经是树型了。

  *

  * @参数 root org.w3c.Node.Node

  *

  * @返回值 返回一个基于根节点DefaultMutableTreeNode对象

  */

private DefaultMutableTreeNode createTreeNode( Node root )
{
DefaultMutableTreeNode treeNode = null;
String type, name, value;
NamedNodeMap attribs;
Node attribNode;

// 从根节点中取得数据
type = getNodeType( root );
name = root.getNodeName();
value = root.getNodeValue();

treeNode = new DefaultMutableTreeNode( root.getNodeType() == Node.TEXT_NODE ? value : name );

// 显示属性
attribs = root.getAttributes();
if( attribs != null )
{
for( int i = 0; i < attribs.getLength(); i++ )
{
attribNode = attribs.item(i);
name = attribNode.getNodeName().trim();
value = attribNode.getNodeValue().trim();

if ( value != null )
{
if ( value.length() > 0 )
{
treeNode.add( new DefaultMutableTreeNode( "[Attribute] --> " + name + "=\"" + value + "\"" ) );
} file://end if ( value.length() > 0 )
} file://end if ( value != null )
} file://end for( int i = 0; i < attribs.getLength(); i++ )
} file://end if( attribs != null )

// 如果存在子节点,递归
if( root.hasChildNodes() )
{
NodeList children;
int numChildren;
Node node;
String data;

children = root.getChildNodes();
// 如果子节点非空的话,只递归
if( children != null )
{
numChildren = children.getLength();

for (int i=0; i < numChildren; i++)
{
node = children.item(i);
if( node != null )
{
if( node.getNodeType() == Node.ELEMENT_NODE )
{
treeNode.add( createTreeNode(node) );
} file://end if( node.getNodeType() == Node.ELEMENT_NODE )

data = node.getNodeValue();

if( data != null )
{
data = data.trim();
if ( !data.equals("\n") && !data.equals("\r\n") && data.length() > 0 )
{
treeNode.add(createTreeNode(node));
} file://end if ( !data.equals("\n") && !data.equals("\r\n") && data.length() > 0 )
} file://end if( data != null )
} file://end if( node != null )
} file://end for (int i=0; i < numChildren; i++)
} file://end if( children != null )
} file://end if( root.hasChildNodes() )
return treeNode;
} file://end createTreeNode( Node root )


  /**

  * 这个方法,被createTreeNode()用来联系一个字符串和某一种类型的节点。

  *

  * @参数 node org.w3c.Node.Node

  *

  * @返回值 返回显示节点类的字符串

  */

private String getNodeType( Node node )
{
String type;

switch( node.getNodeType() )
{
case Node.ELEMENT_NODE:
{
type = "Element";
break;
}
case Node.ATTRIBUTE_NODE:
{
type = "Attribute";
break;
}
case Node.TEXT_NODE:
{
type = "Text";
break;
}
case Node.CDATA_SECTION_NODE:
{
type = "CData section";
break;
}
case Node.ENTITY_REFERENCE_NODE:
{
type = "Entity reference";
break;
}
case Node.ENTITY_NODE:
{
type = "Entity";
break;
}
case Node.PROCESSING_INSTRUCTION_NODE:
{
type = "Processing instruction";
break;
}
case Node.COMMENT_NODE:
{
type = "Comment";
break;
}
case Node.DOCUMENT_NODE:
{
type = "Document";
break;
}
case Node.DOCUMENT_TYPE_NODE:
{
type = "Document type";
break;
}
case Node.DOCUMENT_FRAGMENT_NODE:
{
type = "Document fragment";
break;
}
case Node.NOTATION_NODE:
{
type = "Notation";
break;
}
default:
{
type = "???";
break;
}
}// 结束 switch( node.getNodeType() )
return type;
} file://结束 getNodeType()


  /**

  * 这个方法,用来分析XML文本字符串,它返回Node类型的对象,能够被传送到createTreeNode()方法中。

  *

  * @参数 text 一个显示XML文档的字符串

  * @返回值 返回一个org.w3c.Node.Node对象

  */

private Node parseXml( String text )
{
ByteArrayInputStream byteStream;

byteStream = new ByteArrayInputStream( text.getBytes() );

try
{
doc = db.parse( byteStream );
}
catch ( Exception e )
{
e.printStackTrace();
System.exit(0);
}
return ( Node )doc.getDocumentElement();
} file://结束 parseXml()

} file://结束 class XTree





  代码2 XTreeTester.java

import javax.xml.parsers.*;

// GUI类
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

file://标准 Java类
import java.io.*;


public class XTreeTester extends JFrame
{
// XTree对象,用来在JTree中显示XML
private XTree xTree;
// JScrollPane是JTree的容器
private JScrollPane jScroll;
private WindowListener winClosing;

// 设置框架的宽和高
private static final int FRAME_WIDTH = 400;
private static final int FRAME_HEIGHT = 300;


  /*

  * 构造器构造一个框架包含JScrollPane,

  * 把一个基于XML字符串的XTree对象传到构造函数中

  */

public XTreeTester( String title, String xml ) throws ParserConfigurationException
{
super( title );

Toolkit toolkit;
Dimension dim, minimumSize;
int screenHeight, screenWidth;

// 初始化基本的布局属性
setBackground( Color.lightGray );
getContentPane().setLayout( new BorderLayout() );
toolkit = Toolkit.getDefaultToolkit();
dim = toolkit.getScreenSize();
screenHeight = dim.height;
screenWidth = dim.width;
setBounds( (screenWidth-FRAME_WIDTH)/2, (screenHeight-FRAME_HEIGHT)/2, FRAME_WIDTH, FRAME_HEIGHT );

// 构建XTree对象
xTree = new XTree( xml );

file://把XTree封装到JScroll中,以便在JFrame可以使它在屏幕中上下滚动.
jScroll = new JScrollPane();
jScroll.getViewport().add( xTree );

// 添加滚动条到框架中
getContentPane().add( jScroll, BorderLayout.CENTER );
validate();
setVisible(true);
// 添加WindowListener用来关闭窗口
winClosing = new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
exit();
}
};
addWindowListener(winClosing);
}


  // 程序从这里开始执行。必须把一个以xml为扩展名的XML文件传送到这个方法中,其格式为java XTreeTester yourxmlfilename.xml

public static void main( String[] args )
{
String fileName = "";
BufferedReader reader;
String line;
StringBuffer xmlText;
XTreeTester xTreeTester;

// 创建一个基于特定XML文件的文档对象
try
{
if( args.length > 0 )
{
fileName = args[0];

if ( fileName.substring( fileName.indexOf( '.' ) ).equals( ".xml" ) )
{
reader = new BufferedReader( new FileReader( fileName ) );
xmlText = new StringBuffer();

while ( ( line = reader.readLine() ) != null )
{
xmlText.append( line );
}

// 分析完文档对象后将重写文件
reader.close();

// 构造 GUI 组件
xTreeTester = new XTreeTester( "XTree 测试", xmlText.toString() );
}
else
{
help();
}
}
else
{
help();
}
}
catch( FileNotFoundException fnfEx )
{
System.out.println( "没有发现"+ fileName + "文件。" );
exit();
}
catch( Exception ex )
{
ex.printStackTrace();
exit();
}
}


  file://帮助信息

private static void help()
{
System.out.println( "\n使用方法:java XTreeTester yourxmlfilename.xml" );
System.exit(0);
}

// 退出
private static void exit()
{
System.out.println( "\n谢谢使用 XTree" );
System.exit(0);
}

}

posted @ 2005-12-19 12:54 I have a dream 阅读(489) | 评论 (0)编辑 收藏

2005年12月16日

好像自己以前很少关注编程比赛方面的东西,但是今年偶然碰到个Google的,就因为这个公司,我注册了,但是还是没有勇气去参见,因为我知道自己的编程水平。帐号就借给别人用了。
昨天看到别人说的程序员的几种状态,发现自己现在处于的位置是想入门但还没有入门。为什么这样说呢?自己学计算机也有四年多了,理论知识掌握了倒是有一大套的,但是动手能力还是太差,都怪自己以前太懒,不想编程。最近自己已经开始着手在这方面训练自己了,每天都要读一堆或写一堆代码。我想说,Google编程比赛,明年见!

posted @ 2005-12-16 12:00 I have a dream 阅读(675) | 评论 (0)编辑 收藏

 Bruce Tate有令人惊奇的预见成功技术的记录。他是早期开发者中能预见Spring框架出现的一位;他在EJB3专家组放弃老的方法一年前的时候就预见了EJB2技术的消亡。在他的新书《Beyond Java》中,Bruce关注了语言和技术,这些将来有可能会在一些开发领域对Java的优势发出挑战。在这篇文章中,Bruce提及了四种新出现的重要技术。
  
Java是一种杰出的产业开发语言,这是因为它带来了伟大的统一和对事实上以前并不存在的重要标准的关注。但是和所有语言一样,Java将来也会褪色。依据我做的超越Java的研究,一个重复出现的主题是有越来越多的人相信Java已不再足够的有效率。以下一组技术可以使你更有效率。他们不是对所有的工程都适合,但当被应用于适合的工程时,他们是优秀的工具。
  
  1.动态语言
  
  动态语言可以比像C++或Java这样的静态语言更加有效率。他们可以让你用更少的语言表达更多的意思。这里,我会关注现在新出现最流行的动态语言Ruby。用Ruby的"Hello, World"和Java的作一个比较:
  puts "Hello, world."
  
  这显然既简单又明了。你不需要写一些其他的代码去做这件事。下面是用Java语言的描述:
  
  class HelloWorld { public static void main(String[] args) {  System.out.println("Hello World!") }}
  
  在Java中,类型是静态的。这就代表了编译器要检查所有的类型;你必须建立和编译一个完整的程序。在Ruby中,类型是动态的,所以你不需要去声明他们,你可以马上编写和运行他们。下面是用两种语言描述的Fibonacci 序列:
  
  First, Ruby:x1, x2 = 0, 1          //110.times do puts x2  x1, x2 = x2, x1+x2     //4end
  
  注意到在第一、四行同时声明两个变量,让你可以简洁地表达两种不同的声明形式。
  
  另外,注意到10是一个对象,它支持方法,如:times。再另外,在do和end之间是一个代码块。
  
  Ruby代码块可以让你把代码块传入方法。。这种技术导致了难以置信的效率和简洁的代码。
  
  接下来,看一看用Java实现的代码:
  class Fib {
    public static void main (String args[]) {
     int x1 = 0;
     int x2 = 1;
     int total = 1;
     for (int i=0; i<10; i++) {
      System.out.println(total);
      total = x1+x2;
      x1 = x2;
      x2 = total;
     }
    }
  }
  
  你需要去声明所有的变量,以及详细地写出来你用for循环实现的迭代。每个变量都是独立的,所以你必须有一个临时变量用于存放total。
  相比,动态语言更为简洁。按照一个普通的规则,如果你可以写更少的代码而不牺牲可读性,这些代码将导致更高的效率。(但是你不可以牺牲可读性来达到这一步,我们可以以Perl举例作为结尾。)更为重要的是,动态语言在Java开发者想要去解决的重要问题上表现得更好,如: 元编程。 Hibernate 使用元编程技术使对象持久化。
  Spring使用元编程来为Java对象增加服务,而免除你为他们构建额外支持的烦恼。在Rails框架上,当红的Ruby利用了自己能力来构建某种已存的最有效率的应用开发框架。
  令人惊讶的是,许多Java开发者采用了Ruby。Ant和Tomcat的发明者James Duncan Davidson正在Rails上使用Ruby,以及Java的畅销书作者之一,JSP专家组的成员David Geary正在写一本关于Rails上的Rub的书y。许多在Java社区里有着聪明思想的人都转向使用像Ruby一样的动态语言。这是因为这种新出现的语言能更好的解决最有兴趣的问题。动态语言将不会完全取代Java,但是他们会适合于解决小的,轻量级的问题。?

  2.Continuation 服务
  Web编程绝对是个灾难。在Java诞生十年后,我们仍旧不能构建一个使返回按钮正确的框架。Web应用是无国界的,所以Web应用会发展得更好。但是很难去构建无国界的应用,而我们现有的框架不能给与我们足够的帮助。你使用大多数Java的Web框架时,从根本上说,你构建了许多不相关的使用servlets或JSP技术的应用。然后通过手工保存对象来把他们集成起来,这些对象就是你需要的,用来暂时存储对象的会话。
  Continuation是语言的构造器,它可以使你快速存储某个线程的状态,过后执行这个线程。基于 Continuation的web框架总体上是通过模拟一个监控状态的应用来使web开发变得更为容易。当你的应用需要从用户那取得数据时,这种框架使用continuation来自动保存应用程序的状态。如果用户按下返回按钮或者通过浏览器的历史纪录回到以前的页面,应用程序可以重新读取一个continuation。
  基于continuation最好的框架是用动态语言来开发的。到目前为止,最健壮的框架是Seaside。他是基于一种Smalltalk的Squeak语言的框架。Seaside支持很好的调试功能,你可以实时检查、调试以及在浏览器里改动你的代码。Borges、Iowa和Wee都是基于Ruby且支持continuation的框架。
  Java不支持continuations,但是一些在特殊限制下构造的Java框架支持模拟continuations。这些框架具有用其他语言编写的continuations框架的某些特征。
  ·流行的框架是用了一些高级的技术,如:字节码增强、反射以及特殊类的装载器。这些技术用Java部分地实现了continuations。
  ·Cocoon 2在Rhino JavaScript 虚拟机中增加了continuations,用来模拟监控状态的应用。
  ·Spring Webflow使用了状态机来提供对返回按钮良好的支持,以及其他一些continuation服务的特征。
  ·Lakeshore使用了悬挂的线程来模拟continuations。这种方法不像其他方法一样有可扩展性,同时还缺乏对返回按钮完整的支持,但是这些预计在将来都会具有。
  每个月都会有新的框架出现。我认为在未来的三年内,我们都会使用支持基于continuations的方法的web开发框架,这种框架是由一种语言或者其他语言编写的。

  3.惯例超越配置
  Java开发者经常探索用于改进配置的方法。新的框架越来越多的使用Java 5批注来进行配置。其他的框架是用一种不同的方法。Rails中的Ruby常用惯例来推断需要在其他框架进行配置的联系。例如:在结束的时候,一个叫BlogController且有一个show方法的Rails控制器,会自动在blog_controller目录里提交一个叫show.rhtml的视图。Rails还使用命名惯例来绑定数据库表里持久化的类。默认情况下,Perosn类会与用英语的复数people与表进行匹配。新的框架将会支持惯例,而不是配置。

  4.元编程
  就像前面提到的一样,在Rails编程框架上的Ruby里,存在着许多hype的。我认为这种hype是正确的。在Rails上的Ruby让你比起java,可以在一个更抽象的层次上编写你的程序。有了Rails上的Ruby,你可以创建域对象,这种对象可以发现相关联的数据库表的内容。例如:你可以写这样简单的模型对象:
  class Person < ActiveRecord::Baseend
  这种类表面看起来相当的受限制。但是一旦你执行它,Rails就会展现它的神奇。这种实现了持久化Rails的活动纪录框架与相关的数据库关联,以及为了表定义扫描系统表,还发现数据库里列项。然后,活动记录为数据库中的每一列增加一个属性,为数据库中id列名在类中增加一个独一无二的标示符。你可以用下面的类去编写代码:
        person=Person.newperson.name='Bruce Tate'person.email='bruce.tate@j2life.nospam.com'person.save
  数据库的列名和行为都会在运行时后加入Person类。你可以很容易的扩展Person类:
  class Person < ActiveRecord::Base has_many :carsend
  通过Ruby中一个belongs_to的简单方法和:department标示符,我实现了所有我想做的。活动记录隐式调用了Ruby的元编程来添加了所有的方法和变量,这些方法和变量用来管理一个任何一个部门之间的一对多关系。Rails用户使用域语言来管理像继承这样的关系,另外可以用Ruby语言在一个更抽象的层次上工作。Rails无缝扩展了Ruby语言。
 

 Rails会不会是下一代伟大的框架?有可能。要做一个选择的话,Rails应该会是在使用Ruby或是其他动态编程语言的元编程框架潮流中的第一个。或者,你可能看到Rails会作为某些松散对齐技术的中枢,它是以元编程作为基础的。在任何情况下,你都会更有效率。
  

  总结
  在《超越Java》这本书中,我表达了Java还不会淘汰意思,但是在最近的十年,我们目睹了在Java领域之外引人注目的创新。这四种技术会在不远的将来起到重要的作用。请密切关注他们。

posted @ 2005-12-16 10:39 I have a dream 阅读(318) | 评论 (0)编辑 收藏

2005年12月15日

这是大约一个多月前给自己定的Reading List:              完成状况         Deadline       
1、Distributed System:Concepts and Design   by CDK                          1月4日
2、网络分布计算和软件工程   by 冯老板                                               1月4日
3、设计模式:可复用的软件基础    by GoF                           *1           1月4日
4、Java与模式                                 by      阎宏                           *2            12月23日    
5、编程珠玑           by Jon Bentley                                                              待议
6、Pattern-Oriented Software Architecture Volume1                                2月1日
7、Pattern-Oriented Software Architecture Volume2                                 5月1日
8、Pattern-Oriented Software Architecture Volume3                                 5月1日
10、形式语言与自动机                                                                                 12月25日
11、计算机算法分析与设计     by 王晓东                                                  12月28日
12、 算法导论                                      MIT                                                   待议
13、英语语音                                                                                                1月29日
14、精通英语习惯用语                                                                                3月1日
15、你为谁工作?                                                                                         已完成


posted @ 2005-12-15 10:47 I have a dream 阅读(310) | 评论 (1)编辑 收藏

2005年12月14日

现在有几个blog,但都是混杂在一起,这个blog将作为我的纯技术blog,我将在这里记录我的学习、实践的过程。
昨天被公司辞了,原因我不是很清楚,不过我感觉应该是我现在去的时间太少了,虽说是兼职,但是我每周只能去两天。最近三周更是因为老板要开讨论班,这样每周最多只能去一天多几个小时。公司老板自然是为了公司着想了,我这样时去时不去的,他(非技术人员)肯定觉得我做不好工作。又加上公司最近又请了个专职的,辞掉我就更理所当然了。
总结一下去这个公司这几个月的时间,坦然地说,我刚去的时候对Struts等也是一知半解,但很快上手并适应了。随后逐渐的发现自己在这个公司对自己的技术水平没有太大的提高了,公司现在做的就是修改代码,做平行的工作。还有最重要的一点是这个公司在技术方面没有猛人,没有人可以请教交流技术。公司现在总共做技术的假如算我的话有3个,老大是学信管研究生毕业的,他在金融方面懂得多,但在技术、项目管理方面水平实在不敢恭维。还有一个刚来的专职,水平也很一般。唯有暑假的时候有个北大的doctor,我们相处得很愉快,从他那里学到了不少东西。总之,在这个公司我技术方面不会有什么提高。
另外一个很重要的地方就是,这个公司的团队是十分融洽的,虽然我跟他们做的不一样,还是感觉跟他们很合得来,从他们那里学到了不少的金融方面的知识,还有老板,从他身上学到了比知识更重要的东西。所以我要感谢这个公司,希望他们的发展越来越好!
既然不再去公司了,现在又决定不玩wow了,所以这段时间的首要任务是准备考试,然后呢就是苦练技术,现在发现一个人要专注于一件事情。我呢以后要将重心转移到技术方面。还希望各位能够多多指教,大家互相探讨。
在此,谢谢我的MM,她一直在支持我,照顾我!

posted @ 2005-12-14 19:26 I have a dream 阅读(261) | 评论 (0)编辑 收藏