本文是关于Jakarta Struts 1.1的文章中的前半部分。文中一部分是出自Sue Spielman的书《The Struts Framework: Practical Guide for Java Programmers (Morgan-Kaufmann) / Struts框架Java程序员实用指南》。这本书是市场上全面的详细的介绍Struts 1.1的首批图书。你可以通过Sue的邮箱(sspielman@switchbacksoftware.com)与他联系。
去年,Struts框架(一个Jakarta的开元项目)事实上已经变成了建立Web应用的实际标准。Struts已经证明自己是一种基于MVC模式架构的可靠的框架,能够被用于各种系统。事实上,我是被迫要提出一个理由来解释为什么我和我的开发团队要为项目花费一个开发定制MVC模式框架的周期。这有点令人无法理解。
Struts v1.1是这个框架的最新版本。写本文时Struts v1.1还是beta2版,不久就会推出最终版。要你现在开始学习使用 v1.1版,似乎没什么理由,可为什么不呢;其实有很多理由说明我们因该使用这一版。
本文一共有两部分。在第一部分这里,我们概括地介绍一下Jakarta通用库的集成与多应用支持。第二部分里将着重介绍嵌套的标记库、插件API以及异常处理声明。新版本对框架已经进行了一些重要的改进,并且你有可能希望用这一版本开始新的开发。事实上,我强烈建议这么做。保持v1.0.2版的向后兼容性是Struts项目参与者永远的目标,并且他们在v1.1这一版本中完成得非常好。本文最后将指出一些从v1.0.2版到v1.1版进行升级时需要注意的问题。
本文并不是Struts的入门读物。我们假设你已经熟悉了这一体系架构中的各个组件。如果你需要先看一看入门介绍,请看我写的对于Struts框架的三部分介绍(http://www.onjava.com/pub/a/onjava/2001/09/11/jsp_servlets.html)。
更新 在整个v1.1版本的开发中对Struts已经有了令人激动的特性和改进。我们会轮流介绍它们,但是之前我们先来看看都有哪些更新。
Jakarta通用库的使用,例如BeanUtil与Logging;
DynaActionForms可不用编写任何代码创建动态的动作表单;
多应用支持允许定义多个struts-config.xml配置文件;
嵌套的标记库使在JSP页中可以很容易的访问嵌套的对象体系;
Tiles与Validator提供了高级的模板以及表单验证表达式;
插件API可以增强ActionServlet的功能;
异常处理声明使Actions不必关心捕获到的所有异常。
下面让我们逐个的看一看每个特性。
Jakarta通用库的集成 Struts工具包可以解决在创建Web应用时不断出现的问题。这个包中的大部分类不依赖于控制器Servlet框架或定制的标记库,因此它们可被用于一般的Java应用编程。在Struts 1.1版本中,这个包中的许多类被移植到Jakarta的公共项目中。这些类包括Bean工具,Colections,以及Digester包。Struts中提供的logging实际上来自于公共项目.
org.apache.commons.beanutils组件封装了Java的Reflection与Introspection应用编程接口(API)。使用beanutils包中的类,可在未知方法名编译的情况下动态的调用getter与setter方法。这些类用Struts的定制标记库调用,因此可以为你的应用定义附加的定制标记,你也许想要进一步了解这个包中的可用内容。表1列出了这些。
表1:Struts 1.1中的通用包
BeanUtils 通过反射组装JavaBeans属性
ConvertUtils 将字符串值转换为指定类的对象
MappedPropertyDescriptor 描述与映射属性
MethodUtils 集中于大体的方法上而不是属性等细节上
PropertyUtils 使用Java Reflection应用编程接口(API)对一般属性进行setter(设置值)与getter(获取值)操作
Digester包提供了基于XML文档的规则。这么做对读取配置文件非常重要,便于正确的初始化对象。这个包可是你非常简单的完成这些事否则就需要你更深入地了解DOM或SAX的处理。当XML中的模式被验证后,这些可通过指定了规则的对象映射模块来完成。这包括嵌入你自己模式匹配的引擎的能力,合法命名空间的处理,以及用于多应用并且封装了规则的RuleSets。如果你明确的读取XML文件别需要映射为适当的Java对象,Digester包是十分有用的。
查看本文来源
在第一篇文章《Jakarta Struts简介》中,我大致分析了Struts框架,讨论了它所能完成的功能,还浏览了组成Struts的各个组成部分。在第二篇文章《学习Jakarta Struts》中,我开始详细描述如何利用Struts来构建一个简单应用的过程步骤。而本篇文章将会向大家演示如何将ApplicationResource文件中的文本信息,通过Struts标签在JSP页面中显示出来。
Action类是连接Struts架构和应用中业务逻辑代码的桥梁。所以你应该尽可能让Action类小巧简单,因为真实应用中的逻辑处理应该是由单独分离出来的逻辑层来完成的。如果你正在从事n层应用的开发,你当然希望层与层之间的接口越简单越好。而事实上,Action类中的主要方法"perform()"(1.1中为execute())却有点暗示应该在本方法中做点什么的意思。我们知道,每个Action类都需要从 org.apache.struts.action.Action 继承而来。在小型应用中,我们的Action类很可能就只要继承org.apache.struts.action.Action就足够了;而在某些特定的复杂应用中,我就从我们所实现的Action类中总结出来了一些通用特性。因此,在我看来,构造一个基类将这些通用特性的代码实现出来,让应用中所用到的所有Action类不直接继承org.apache.struts.action.Action,而继承这个完成了一些通用特性的基类以实现代码重用,是一个相当不错的设计。我在StrutsSample中就应用了这种方法,构造了这样的一个基类,该基类的方法在完成复杂逻辑的和简单转发请求的Action类中都可以使用。
package com.oreilly.actions;
import java.io.IOException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.util.Enumeration;
import java.util.Properties;
import java.rmi.RemoteException;
import javax.ejb.EJBHome;
import javax.ejb.CreateException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
这个类就是使用Struts开发时,所有Action类都要继承的基类。它把一些通常在实际应用中最有可能被用到的东西都考虑进来了。就这篇文章而言, 类中一些与Struts并不是太紧密相关的方法将只做注释而不会完整的实现,而从事开发工作的你,有兴趣的话,请完成这些方法并应用这个类,将为你在实际项目中的开发快马加鞭。注意,因为所有的Action类都要从org.apache.struts.action.Action 继承而来,所以我们的这个类同样。
public abstract class AbstStrutsActionBase extends Action {
/ * 定义一些在struts-config.xml中记录在案的
* 全局应用中皆可可通用的forward标识*/
protected static final String SUCCESS = "success";
protected static final String FAILURE = "failure";
protected static final String ERROR = "error";
protected static final String LOGIN = "login";
protected static final String CONFIRM = "confirm";
protected Context jndiContext = null;
/**
* 默认构造方法
*/
public AbstStrutsActionBase() {
}
/**
下面这个查找EJB实例的方法将不会完整实现。
一般来说,Action类应该调用实现了应用的商务逻辑的EJB会话bean(或仅仅普通JavaBean)。在大型项目中,开发人员必须划清层与层之间的界限。在Action类中,我们应该拿到获取含有JNDI信息的环境的实例,然后通过EJB的JNDI名字去查询获取它的home接口。过程并不简单,所以下面这个代码片断只是个给出了必要实现的小例子。
参数类型String,传入的要查询JNDI的名字
返回类型Object,即查找到的home接口
如果查找失败,抛出NamingException异常
如果获取资源信息失败,抛出MissingResourceException异常
*/
public Object lookup(String jndiName)
throws NamingException, MissingResourceException {
// 为调用EJB对象,通过构建记录JNDI信息的Properties对象
// 来获得初始环境信息
if (jndiContext == null) {
ResourceBundle resource =
ResourceBundle.getBundle("strutssample.properties");
Properties properties = new Properties();
properties.setProperty(
Context.INITIAL_CONTEXT_FACTORY,
resource.getString(Context.INITIAL_CONTEXT_FACTORY));
properties.setProperty(
Context.PROVIDER_URL,
resource.getString(Context.PROVIDER_URL));
properties.setProperty(
Context.SECURITY_PRINCIPAL,
resource.getString(Context.SECURITY_PRINCIPAL));
properties.setProperty(
Context.SECURITY_CREDENTIALS,
resource.getString(Context.SECURITY_CREDENTIALS));
jndiContext = new InitialContext(properties);
}
注意:在真正的产品中,我们应该在此处考虑代码的健壮性,将代码加入到try/catch块内,并记录所有错误或重要信息到系统log中。而本例中,我们仅仅把异常往外抛,并假定一定会找到EJB对象的home接口并返回。
return (jndiContext.lookup(jndiName));
}
由于Action类将是由Struts来调用的。所以它的主要方法应该是一个抽象方法,而由每个继承的子类来具体实现,或者在其中做一些所有Action都会做的通用机制,例如记录log信息。在本例中,我们一切从简,将其抽象之。
参数mapping:其类型为ActionMapping,将在本Action做跳转选择用
参数actionForm:由Struts根据本次HTTP请求数据填充完成的ActionForm对象(可选,如果存在请求数据的话)
参数request:此Action所有处理的本次HTTP请求(对象)
参数response:此Action输出数据所要用到的HTTP响应(对象)
如果有I/O错误出现,则本方法抛出IOException异常
如果处理时发生servlet异常,则本方法抛出ServletException异常
本方法处理完请求后按照处理逻辑返回相应的页面导向(对象)
public abstract ActionForward perform(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException;
}
或者让这个抽象方法更有用一点,那就在里面干点什么吧,比如像下面这样在其中记录log。
{
ActionForward forward = null;
// 只是简单的记录一些提示信息到servlet log
getServlet().log(
"AbstStrutsActionBase.perform() [Action Class: "
+ this.getClass().getName()
+ " ]");
getServlet().log(
"AbstStrutsActionBase.perform() [Form Class : "
+ (form == null ? "null" : form.getClass().getName())
+ " ]");
}
然后,我们再编写的每个Action类都应该从AbstStrutsActionBase继承,并依照处理逻辑编写各自的perform方法。让我们用LoginAction为例,看看具体应该怎么应用吧。
package com.oreilly.actions;
import java.io.IOException;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForward;
import com.oreilly.forms.LoginForm;
/*
LoginAction 将演示一个Action将如何被Struts架构所调用
在这个例子中,我们只是简单的演示perform方法是如何调用、执行并返回的
*/
public class LoginAction extends AbstStrutsActionBase {
接下来这个是验证用户的方法,本例中没有具体实现。但一个典型的应用方案是调用JavaBean或者EJB来完成。用来查找EJB的lookup方法(在基类中完成的)应该在本方法中被调用,其返回一个依据后台数据库验证用户的接口。
参数类型String,要验证的用户名
查看本文来源