基于Stripes框架进行Java Web开发



基于Stripes框架进行Java Web开发

Mark Eagle是美国乔治亚州亚特兰大市MATRIX Resources有限公司的一位资深软件工程师,拥有Sun公司的SCP和SCWCD认证。Mark本人非常喜欢使用开源软件进行软件开发,并且多次积极参与过Hibernate,Spring及Proxool等著名开源工程。

一、简介

Stripes是一个开源的,基于Action的Java web框架。传统型Java web开发强调通过松耦合的设计达到灵活开发之目的,这导致了产生多种配置文件,额外对象以及其它零碎的资源。显然,这些问题提升了众多开发人员的学习曲线,从而降低了开发效率。基于这些原因,一些Java开发者求助于另外的非Java开发框架—例如Ruby on Rails或者Django。而象Stripes这样的新型Java web开发框架,从支持流线式开发框架的成功中获取了大量现成的经验。本文中,我们将通过一个具体的案例来探讨Stripes是如何区别于其它基于Action的Java web框架(例如Struts),而同时又实现了例如Ruby on Rails框架所具备的简单性。

下图1展示了基于Stripes框架开发的典型应用程序中事件及组件间的通常流程。

图1:典型的Stripes流程

如你所见,这也正是我们从一个典型的MVC框架下所想得到的东西。在Stripes及其它基于行为的框架之间的一个主要区别在于,它没有提供外部配置文件。很快我们将会看到,在配置方面,Stripes借助于注解和约定(convention)等技术进一步简化了开发过程。

二、构建你的第一个Stripes行为

下面,让我们通过创建一个“Hello World”示例来理解Stripes框架是如何把诸多内容结合到一起的。其中,HelloWorldAction类负责提示用户输入自己的姓名,然后把它们显示于一个单独的视图中。首先,我们来编写控制器Java类。

public class HelloWorldAction implements ActionBean {
@ValidateNestedProperties({
@Validate(field = “firstName”, required = true,
on = {“hello”}),
@Validate(field = “age”, required = true, minvalue = 13,
on = {“hello”})
})
private Person person;
private ActionBeanContext context;
@DefaultHandler
public Resolution index() {
return new ForwardResolution(“Hello.jsp”);
}
public Resolution hello() {
return new ForwardResolution(“SayHello.jsp”);
}
public void setPerson(String person) {this.person = person;}
public String getPerson() { return person;}
public void setContext(ActionBeanContext c) {this.context = c; }
public ActionBeanContext getContext() {return context; }
}

在此,这个控制器类十分类似于一个实现了一个称为ActionBean的Stripes特定接口的POJO(普通Java对象)。注意,所有的Stripes行为都需要实现这个接口,以便允许StripesDispatcher servlet把一个ActionBeanContext对象注入到当前提供服务的行为中。该ActionBeanContext对象允许你存取servlet API对象—例如请求,响应和servlet上下文。大多数情况下,在一个Stripes应用程序中没有必要存取这些低级别的API对象。该ActionBeanContext类还允许你取得关于当前行为的状态信息,而且还可以从当前行为中添加参考消息和错误消息。注意,ActionBeanContext域及其存取器(accessor)可以存储在一个基类中,因为所有的Stripes行为都会要求这样的实现。

对于该控制器类的其它内容,我相信任何一位Java开发者都应该熟悉。Person对象及相应的存取器将用于把所有人的姓名信息读写到我们的视图中。尽管这是一个简单的嵌套对象,但是Stripes还允许使用Java集合,泛型和index属性等实现更为复杂的数据绑定。既然Stripes可以处理复杂的数据绑定,那么,你的域(domain)对象也可以重用于需要它们的其它层中。例如,我们可以很容易地实现经由Stripes收集一个域(domain)对象中的信息,并且使用其它POJO框架(例如Hibernate或EJB 3)进行永久性存储。

另外,还应注意,在此,一个简单的Stripes校验注解已被添加到person域中以确保当调用hello方法时用户输入了姓名信息。如果该用户没有输入这些要求的域,那么他们将被返回到源页面并且显示出一个与这个校验相关的错误消息。仅当要求hello事件时才检查这个校验—这是通过注解属性(on={“hello”})指定的。Stripes还将基于校验类型和域名友好地生成一个缺省的错误消息。例如,如果当提交表单时没有提供要求的Person对象的firstName域,那么用户将看到下列结果:

Person First Name is a required field.

这条消息是系统通过把对象图形组件当作Person.firstName而构建的,从而使之更具可读性。如果必要的话,可以重载这些校验错误消息以便提供更多的定制。

#p#

还有一个称为age的整型(integer)域,这是Person对象的一个属性。Stripes将根据请求参数值首先针对Person.age整数域执行类型转换,并绑定Person对象中的相应值。在把该值绑定到Person对象的age域上后,Stripes将进行校验以保证该Integer值小于13。如果用户输入了一个字符串而不是一个整数,那么该用户将看到如下的这条消息:

The value (Mark) entered in field Person Age must be a valid number.

如果用户输入了一个整数但是该值小于13,那么用户将看到这条消息:

The minimum allowed value for Age is 13.

在此,我们仍然不需要对于这些错误消息提供任何外部配置。提供这一校验功能的注解是内联于你的域的,这使得开发者能够容易地定位校验,理解校验将会怎样发生,以及对该校验进行维护性修改。

在这个Stripes行为中,还存在两个可以调用的方法(称为“事件”)。一个事件是通过一个ActionBean类中的一个方法使用下列签名形式加以描述的:

public Resolution eventName

注意,相应的index方法被加上了@DefaultHandler注解标记。因为在这个行为中存在多个事件,所以需要把它们其中之一指定为缺省的事件。如果调用这个行为的URL没有指定一个事件,那么Stripes将查找一个标记有@DefaultHandler注解的事件并执行它。

三、构建视图

现在,让我们在“Hello World”示例中添加我们的视图逻辑部分。默认情况下,Stripes支持JSP作为标准视图技术,然而,你还可以使用其它视图技术—例如FreeMarker。在此,基本没有什么要介绍的新内容—除了Stripes标签库外。初始的视图,称为Hello.jsp,允许用户输入并提交他们的名字。

<%@ taglib prefix=”stripes”

uri=”http://stripes.sourceforge.net/stripes.tld” %>

<html>

<head>

<title>Stripes Hello World</title>

</head>

<body>

<stripes:errors/>

<stripes:form

beanclass=”com.

myco.

web.

stripes.

action.

example.

HelloWorldAction”>

Say hello to: <br>

First name: <stripes:text name=”person.firstName”/>

<br>

Age:<stripes:text name=”person.age”/><br>

<stripes:submit name=”hello” value=”Say Hello”/>

</stripes:form>

</body>

</html>

这个JSP相当简单—只负责读取和维护。其中表单及输入域中使用的Stripes标签非常类似于它们的HTML对应物。其中,stripes:form标签包含一个称为beanclass的属性—这是我们以前定义的控制器类的全称限定名。我们可以使用beanclass属性来代替该stripes:form标签中的action属性。然而,如果你需要重构一个Stripes行为的话,使用beanclass属性将会更为容易些。下面是如果你使用action属性的话,该stripes:form标签看起来的样子:

<stripes:form action="/example/HelloWorld.action">

其中,一个stripes:input标签用于指定Person.firstName的一个name属性—它将用于把输入域存储到控制器中的Person对象的firstName域的值中。最后,stripes:submit标签用于指定一个name属性—该属性将用于指示Stripes HelloWorldAction类使用hello事件。

现在,我们开始把姓名值提交到HelloWorldAction。为此,我们仅需要将它回显于一个单独的视图中。

<%@ taglib prefix=”stripes”

uri=”http://stripes.sourceforge.net/stripes.tld” %>

<html>

<body>

<stripes:errors/>

<h2>Hello ${actionBean.person.firstName} your age is

${actionBean.person.age} </h2>

<p/>

<stripes:link beanclass=”com.myco.web.stripes.action.

example.HelloWorldAction”>

Say Hello Again

</stripes:link>

</body>

</html>

#p#

这个JSP将显示用户的姓名域中的信息—通过访问一个对行为本身的引用来实现。为此,Stripes自动地包括了一个actionBean请求属性,这可以使用JSTL加以存取。最后,我们使用一个stripes:link标签来创建一个到HelloWorldAction类的往回的链接,以便我们可以输入另一个不同的名字。我们还能够像下面这样通过显式地引用index事件来创建该stripes:link标签:

<stripes:link

beanclass=”com.myco.web.stripes.action.

example.HelloWorldAction”

event=”index”>Say Hello Again</stripes:link>

既然我们已经使用@DefaultHandler注解了该index方法,所以,Stripes能够知道要执行哪个方法—即使不使用事件属性。

四、使用convention(约定)进行配置

现在,既然我们已经成功地创建了我们的Java组件,那么接下来,我们将把行为映射配置到一个URL,并且把它链接到我们以前创建的两个视图上。请等一下—我们是在使用Stripes框架进行开发,所以在此我们根本不需要任何外部配置!

尽管这听起来可能有点不太真实,但的确如此—这正是Stripes框架带给我们的最具生产效率的众多特征之一。在配置问题上,Stripes使用convention把Action映射到URL。我们也不需要使用一个外部配置文件来把一个符号名映射到实际的视图上。这意味着,开发者不必在配置文件之间来回切换以决定如何在众多的符号名(例如SUCCESS)中加以选择并最终把它们导航到视图的实际路径。根本不需要通过外部来配置Java和视图组件。显然,这提高了系统开发的可维护性,并进一步提高了软件生产效率。

那么,在没有以外部方式配置每一个行为或另一个注解的情况下,Stripes是如何提供到Java行为类的隐式的URL映射呢?这可以通过在web.xml文件中配置Stripes和该文件使用智能的缺省设置来创建URL映射方面得到解释。首先,我们需要讨论一个称为StripesFilter的Servlet过滤器。下面是在web.xml文件中的StripesFilter过滤器的缺省配置:

<filter>

<display-name>Stripes Filter</display-name>

<filter-name>StripesFilter</filter-name>

<filter-class>

net.sourceforge.stripes.controller.StripesFilter

</filter-class>

<init-param>

<param-name>ActionResolver.UrlFilters</param-name>

<param-value>/WEB-INF/classes</param-value>

</init-param>

</filter>

当启动Servlet容器时,StripesFilter执行其init-param元素的初始化。其中,最重要的一个init-param元素是ActionResolver.UrlFilters参数。这个参数告诉Stripes到哪里寻找Stripes相关的类。在本文示例中,Stripes将在缺省的/WEB-INF/classes路径下查找所有实现了ActionBean接口的类。所找到的每一个ActionBean类,连同该类缺省的绑定URL,都被添加到一个映射中。

现在,让我们通过一个示例来实际分析一下Stripes对我们的Hello世界示例类做了些什么。既然该HelloWorldAction类位于/WEB-INF/classes路径下并且实现了ActionBean,那么,它将被识别为一个Stripes servlet。在我们的示例中,该类的全称限定名为com.myco.web.stripes.action.example.HelloWorldAction。然后,这个全称限定的名字通过实现下面这些规则被翻译成一个URL绑定:

1.把每次出现的四个子串之一(www,web,stripesaction)连接成全称限定类名。在我们的示例中,我们在包名中使用了这四个匹配中的三个。没有使用此匹配的一个是“example.HelloWorldAction”。
2.从类名的最后删除字符串“Action”和“Bean”—如果它们存在的话。于是,这产生了“example.HelloWorld”,因为我们的类以“Action”结束。
3.现在,我们使用“/”符号代替“.”符号,结果是“example/HelloWorld””。
4.最后,我们在结尾添加上绑定后缀(默认情况下是“.action”),至此最终完成URL绑定。最终结果是“example/HelloWorld.action”。


现在,既然Stripes已经找到了ActionBean类并且为它创建了一个URL绑定,那么,接下来将对它们通过java.util.Map<string,class<?extendsactionbean>>(其中,键是URL绑定,而值为实现ActionBean的类)进行缓冲。下面是我们的示例在映射中看起来的样子:

URL绑定 ActionBean
/example/HelloWorld.action com.myco.web.stripes.action.example.HelloWorldAction

接下来,我们要讨论的第二个问题是,Stripes怎么把URL绑定翻译回你要使用的ActionBean类呢?这个任务由Stripes调度器(dispatcher)Servlet来完成—此调度器在web.xml文件中配置,如下所示:

<servlet>

<servlet-name>StripesDispatcher</servlet-name>

<servlet-class>

net.sourceforge.stripes.controller.DispatcherServlet

</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>StripesDispatcher</servlet-name>

<url-pattern>*.action</url-pattern>

</servlet-mapping>

#p#

StripesDispatcher的一个任务就是解析一个到一个Stripes ActionBean类的URL。当用户调用URL http://host/uri/example/HelloWorld.action时,该Stripes调度器servlet将认真检查此URL绑定映射以发现com.myco.web.stripes.action.example.HelloWorldAction类,并且创建它的一个实例。最后,调用index方法,因为它被定义为(注解为)缺省的处理器并且该URL中没有指定一个事件。

如果我们想直接执行HelloWorldAction类中的hello方法会怎么样呢?如果这样的话,URL中需要包括事件名—作为一个请求参数形式提供:http://host/uri/example/HelloWorld.action?hello=&firstName=Mark&age=13

【注意】在此,我们并没有为该hello请求参数指定一个值。在本文示例中,StripesDispatcher负责识别匹配hello请求参数名和一个方法名(该方法名位于HelloWorldAction类中,并且方法签名为“public Resolution hello()”)。为了提高性能,方法名在初始化时通过一个单独的Map进行缓冲。

现在,我们已经了解了Stripes的基本知识以及如何创建简单的Action。其实,还有许多关于此框架操作的细节。通过在web.xml中进行一些初始的配置,我们可以成功地避免再引入一个单独的XML配置文件来绑定我们的描述组件。这是很重要的,因为:第一,你可以查找一个URL并且立即知道要查找什么类—如果你需要进行任何修改的话;第二,当配置文件变得相当巨大和不可管理时,我们不需要使用一个单独的工具来帮助我们。通过消除了这个配置文件,我们不必再浪费大量的元数据来填充框架。最后,我们不需要继续维护一个单独的文件来描述我们的组件彼此之间的关系。

五、 基于Prototype JavaScript库集成Ajax技术

从现在开始,让我们来探讨Stripes是如何处理Ajax相关问题的。我们将使用一个更新适当内容的Ajax调用来修改上面的Hello世界示例。这个示例将展示如何使用Prototype以便为Stripes行为提供Ajax调用。这个示例的完整源码可以从本文提供的下载源码中获取。首先,让我们修改该Hello.jsp以便包括对Prototype JavaScript库的参考。我们还将添加一个相应于该Ajax调用的JavaScript函数,并且把原先的提交按钮改变为绑定了一个onclick事件的新按钮:

<stripes:button
<stripes:button

<%@ taglib prefix=”stripes”

uri=”http://stripes.sourceforge.net/stripes.tld” %>

<html>

<head>

<title>Stripes Hello World</title>

<script

src=”${pageContext.request.contextPath}/js/prototype.js”

type=”text/javascript”></script>

<script type=”text/javascript”>

function sayHelloAjax() {

var myAjax = new Ajax.Updater(‘hello’,

“<stripes:url

beanclass=”com.

myco.

web.

stripes.

action.

example.

HelloWorldAction”

event=”sayHelloAjax”/>”,

{

method: ‘get’,

parameters: Form.serialize(‘helloForm’)

});

}

</script>

</head>

<body>

<stripes:errors/>

<stripes:form

beanclass=”com.

myco.

web.

stripes.

action.

example.

HelloWorldAction”

id=”helloForm”>

Say hello to: <br>

First name: <stripes:text

name=”person.firstName”/><br>

Age:<stripes:text name=”person.age”/><br>

<stripes:button

name=”helloAjax”

value=”Say Hello”

onclick=”sayHelloAjax()”/>

<div id=”hello”></div>

</stripes:form>

</body>

</html>

在此,stripes:button拥有一个onclick事件,该事件将调用HelloWorldAction类中的sayHelloAjax方法并且把相应的结果返回到称为hello的div标签中。接下来,我们来看一下要在HelloWorldAction类中引入的另一个新的方法:

public Resolution sayHelloAjax(){
return new ForwardResolution(“SayHelloAjax.jsp”);
}

这个方法没有做多少工作,因为其涉及的前后绑定工作都由Stripes负责实现了。因此,这个方法的唯一责任就是把当前控制转发到一个称为SayHelloAjax.jsp的页面。下面是此SayHelloAjax.jsp的内容:

<h2>Hello ${actionBean.person.firstName} your age is ${actionBean.person.age}!</h2>

六、与Spring框架的集成

Stripes还提供了与Spring框架的内置集成。你可以把Spring beans或服务自动地注入到你的行为中。在Stripes框架中,实现这一点并不要求外部配置—除了你的Spring上下文配置之外。例如,如果我们在我们的Spring配置中存在一个如下所定义的bean:

<bean id=”personService” parent=”abstractTxDefinition”>

<property name=”target”>

<bean/>

</property>

</bean>

那么,为了实现把此personService注入到一个Stripes行为中,需要添加一个属性和匹配该Spring bean名字的setter。Stripes提供了@SpringBean注解来定位合适的Spring bean以注入到行为类中。下面是一个需要包括到Stripes行为中的示例:

private PersonService personService;
@SpringBean
public void setBlogService(BlogService blogService) {
this.blogService = blogService;
}

请注意:仅靠本文是无法涉及Stripes所有高级特征的;然而,Stripes参考文档中已经提供了比较综合性的相应资料。Stripes框架中还包括一个类似于Tiles的省略了外部配置的布局管理器。另外,Stripes框架中引入的拦截器(interceptor)还可以用于跨生命周期事件,文件上传,以及更多的内容上。

七、结论

从本文中我们可以看到,Stripes的确是一种强有力然而却十分简单的Java web框架。这个框架充分利用了Java 5中注解和泛型等特征,从而使Java开发者不必维护外部配置文件而提高了生产效率。