WebWork深入浅出

 

前言

本篇文章并没有太多WebWork 的实战代码细节。本人非常希望能充当一名导游的角色,带领读者逐步游览WebWork的功能特性和原理。在第一章,我们将提出基于三层架构的Web层需要解决的10个问题,这是本文的纵轴。围绕着纵轴,我们按照横轴的顺序逐步描述讲解:WebWork简介、WebWork入门、WebWork原理、WebWork实战和技巧、展望WebWork未来、最后是本文的总结。

基于三层架构的 Web 层需要解决的问题

我们这里讨论的Web层,是基于典型的三层架构:Web层,业务层,数据层。故,我们将不讨论任何涉及业务(业务逻辑)层和数据层功能的实现。

Web层需要解决的问题:

1、  数据的输入。如何获得基于无状态HTTP的请求数据?如何将请求的字符数据转换为对应的模型对象?

2、  输入数据的验证。如何验证输入数据的合法性并给出明确的错误消息提示

3、  数据的输出。如何展现复杂的对象结构?如何处理复杂的展现逻辑?

4、  数据的传递和共享。如何在不同的请求或页面之间传递和共享数据?

5、  页面的流程管理。如何管理Web应用中的页面流程?

6、  模块化的管理。如何将复杂的Web应用以模块化的方式管理?

7、  灵活可扩展的架构。如何支持各种不同的展现层技术?如何与业务层或数据层的各种框架整合?

8、  安全和访问控制的管理。如何提供基于Web的安全机制和资源访问控制的管理?

9、  代码实现的简洁和高效。如何让开发步骤和代码维护变得简单?如何尽量减少开发的中间环节?如何将公共的功能剥离出来,并可以灵活的组装应用?

10、                      其它问题。异步调用、国际化支持、文件上传、防止重复提交等等。

下面,让我们来一起看看WebWork是如何解决上面的这些问题。

 

WebWork 简介

 

WebWork是由OpenSymphony组织开发的,致力于组件化和代码重用的J2EE Web框架。WebWork目前最新版本是2.2.2,现在的WebWork2.x前身是Rickard Oberg开发的WebWork,但现在WebWork已经被拆分成了Xwork1WebWork2两个项目,如下示意图所示:

 


archit.gif


 work简洁、灵活功能强大,它是一个标准的Command模式框架实现,并且完全从web层脱离出来。Xwork提供了很多核心功能:前端拦截机(interceptor),运行时表单属性验证,类型转换,强大的表达式语言(OGNL – the Object Graph Notation Language),IoCInversion of Control依赖倒转控制)容器等。

WebWork2建立在Xwork之上,处理HTTP的请求和响应。所有的请求都会被它的前端控制器(ServletDispatcher,最新版本是FilterDispatcher)截获。前端控制器对请求的数据进行包装,初始化上下文数据,根据配置文件查找请求URL对应的Action类,执行Action,将执行结果转发到相应的展现页面。WebWork2支持多视图表示,视图部分可以使用JSP, Velocity, FreeMarker, JasperReportsXML等。

下面我们提到的WebWork将为WebWork2,使用的版本是WebWork2.2.2

 

WebWork 入门

WebWork 安装

如果只是搭建一个WebWork开发环境,那将非常简单:

1、  去网站http://www.opensymphony.com/webwork下载最新的WebWork2.2.2项目。

2、  搭建一个Web应用(这个不难吧);并拷贝WebWork框架运行需要的所有Jar文件到Web应用的WEB-INF/lib中。这些Jar文件,可以在WebWork项目中lib/default目录找到,即那个目录里面的所有文件。当然,别忘记今天的“主角”,在WebWork跟目录下的webwork-2.2.2.jar文件。

3、  Web.xml文件中配置WebWork的前端控制器FilterDispatcher,也就是一个普通的Servlet Filter(过滤器)而已。代码如下:

    < filter >

        < filter-name > webwork </ filter-name >

        < filter-class > com.opensymphony.webwork.dispatcher.FilterDispatcher </ filter-class >

    </ filter >

 

    < filter-mapping >

        < filter-name > webwork </ filter-name >

        < url-pattern > /* </ url-pattern >

  </ filter-mapping >

 

不过如果是在实际项目中使用,安装过程中还是有一些问题需要注意的:

1、  关于前端控制器。在以前WebWork2版本里面,前端控制器是ServeltDispatcher,它是一个JavaServlet。而现在是一个Filter(过滤器),会导致无法在页面中使用Jspinclude来包含一个WebWorkAction请求的URL。如果真的需要这样做,可以使用WebWorkaction标签库。

2、  关于Action请求URL的后缀。我们知道,在一般的Web框架中,前端控制器会将特定后缀的请求URL映射到对应的Action请求中。而我们这里的前端控制器会接受任意的请求,但它默认是将.action结尾的URL映射为WebWorkAction请求。我们可以在webwork.properties文件中,设置我们自己的后缀名,多个后缀名可以用逗号隔开。例如:webwork.action.extension=action,do

3、  Jsp 页面中, WebWork 的标签库不需要在 web.xml 中定义,在页面中通过如下的代码直接引用: <%@ taglib prefix = "ww" uri = "/webwork" %>

4、  Jsp页面中,默认“altSyntax”是开启的。它是用来解决标签库中的字符串和表达式语言混淆的问题。所以,作为变量的表达式语言应该放在%{}中,否则WebWork会把它当作字符串处理。

5、  如果展现层技术使用FreemarkerWebWork官方的推荐,也是我个人的推荐),如果在页面中需要使用标签库,必须在web.xml中配置JspSupportServlet,代码如下:

      < servlet >

      < servlet-name > JspSupportServlet </ servlet-name >

      < servlet-class > com.opensymphony.webwork.views.JspSupportServlet </ servlet-class >

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

   </ servlet >

6、  还有一些其它的定制,比如:编码,标签的模板文件等等,都可以在webwork.properties文件中配置。如果在ClassPath中没有这个文件,WebWork会自动读取WebWorkJar包里面的default.properties文件。

WebWork 核心概念

WebWork的三个关键部分

1、  Actions。一般一个Action代表一次请求或调用。在WebWork中,一般Action类需要实现Action接口,或者直接继承基础类ActionSupport。这是,它要实现默认的execute方法,并返回一个在配置文件中定义的Result(也就是一个自定义的字符串而已)。当然,Action也可以只是一个POJO(普通Java对象),不用继承任何类也不用实现任何接口。Action是一次请求的控制器,同时也充当数据模型的角色,我们强烈建议不要将业务逻辑放在Action中。

2、  Results。它是一个结果页面的定义。它用来指示Action执行之后,如何显示执行的结果。Result Type表示如何以及用哪种视图技术展现结果。通过Result TypeWebWork可以方便的支持多种视图技术;而且这些视图技术可以互相切换,Action部分不需做任何改动。

3、  InterceptorsWebWork的拦截器,WebWork截获Action请求,在Action执行之前或之后调用拦截器方法。这样,可以用插拔的方式将功能注入到Action中。WebWork框架的很多功能都是以拦截器的形式提供出来。例如:参数组装,验证,国际化,文件上传等等。

第一个例子: Welcome

 

入门,我们从简单的Welcome开始吧!需求如下:一个页面,有一个用户名输入框;输入自己的名字,例如Moxie,提交按钮返回一个欢迎的页面,并显示前面输入的用户名和一句欢迎信息,例如:Moxie, Welcome!页面操作流程图如下:


login.gif

WelcomeAcion.java

welcomeResult.jsp

welcome.jsp

开发步骤如下:

1、  输入页面。Welcome.jsp是一个非常简单输入页面,代码如下:

 

< form name = "register" action = "welcome.action" method = "POST" >

      Name: < input name = "username" > Please input your name.

</ form >

2、  WebWorkAction类。它实现了Action接口,获得welcome.jsp页面传入的数据即输入的用户名,根据这个用户名生成了一条欢迎信息,并在结果页面中打印出来。WelcomeAction.java代码如下:

 

public class WelcomeAction implements Action{

      private String username;

      private String welcomeMessage;

 

      public String execute() throws Exception {

            welcomeMessage = username +",Welcome!";

            return SUCCESS;

      }

     

      public void setUsername(String username) {

            this.username = username;

      }

     

      public String getWelcomeMessage() {

            return welcomeMessage;

      }

}

看了上面的代码,也许你都不敢相信这就是 Web 编程。没有调用任何一个 JavaServlet API ,它是如何获得请求的数据并将执行结果暴露给输入页面的呢?它是通过值堆栈和表达式语言来实现(后面会有详细介绍)。我们先看看 Action 是如何获得请求的数据。输入框的名字“ username ”,即请求的参数的名字,它就是一个表达式语言。 WebWork 遇到这个表达式语言,就会执行相应的 setUsername 方法,而 username 参数的值就是执行这个方法的参数。这些是在 Action 执行 execute 方法之前完成,这样 Action 就可以获得请求的数据对象。类似的原理, WebWork 也是通过表达式语言将 getWelcomeMessage 方法暴露给结果页面。

3、  结果页面。welcomeResult.jsp页面,将通过表达式语言,取得Action执行之后暴露出来的数据对象。代码如下:

<%@ taglib prefix = "ww" uri = "/webwork" %>

< ww:property value = "%{welcomeMessage}" />

我们在结果页面中使用了 WebWork 标签库。我们用到的是 property 标签,它通过表达式语言,打印 Action 暴露出的数据对象。

4、  定义配置文件xwork.xml。代码如下:

< xwork >

    < include file = "webwork-default.xml" />

    < package name = "default" extends = "webwork-default" >

  < action name = "welcome" class = "com.noname.web.action.WelcomeAction" >

            < result name = "success" type = "dispatcher" > /welcomeResult.jsp </ result >

              < interceptor-ref name = "params" />

      </ action >

</package>

</xwork>

name ”参数用于请求的 URL 。例如: http://localhost/welcome.action ,这样通过请求的 url ,我们就可以在配置文件中找到对应的 action 。“ class ”即实现 Action 的类。一个 Action 中可以定义多个 Result Result 的“ name ”对应 Action 方法返回的字符串。在我们的 WelcomeAction 方法中,执行成功之后返回字符串“ success ”。即我们在这个 Action 里定义的那个 result 。“ interceptor-ref ”定义这个 action 所使用到的拦截器。我们这里使用了 WebWork 提供的 params 拦截器,它能自动的帮我们将请求的参数组装成 Action 中需要的数据对象。通过这个拦截器,它会调用 Action setUsername 方法,取得 username 参数的值,这样 Action 就可以获得用户输入的 username 数据。也许你会奇怪, params 这个拦截器是从哪里变出来的?这个 xwork.xml 文件包含了 WebWork 提供的默认配置文件 webwork-default.xml,可以在webwork-2.2.2.jar文件中找到。我们定义的这个package继承了它里面的packagewebwork-default”,这样就可以共享“webwork-defaultpackage里面定义的所有拦截器。

 

 

WebWork 原理

通过上面的例子,我们已经了解WebWork开发、运行的基本流程(一定要亲自安装,并开发这个Welcome的例子哦)。如果要在实际项目中使用WebWork,我们必须要了解下面的概念和WebWork的原理。

 

ValueStack( 值堆栈 ) EL( 表达式语言 )

关于ValueStack的描述:

1、  ValueStack其实就是一个放置Java对象的堆栈而已,唯一特别的是可以使用EL来获得值堆栈中对象属性的数据,并可以为值堆栈的对象属性赋值。

2、  EL,全称Express Language,即表达式语言。不要被语言吓倒,它是简单的对象导航语言。有字符串(例如:方法名)和特殊字符组成(例如用.表示调用对应的属性方法)。通过EL,我们可以存、取对象数据,而且还可以直接访问类的静态数据,调用静态方法。

3、  WebWorkValueStack底层有第三方开源项目OGNL实现。所以EL也都遵循OGNL的规范。我们在开发中,几乎不需要知道OGNL的细节。

4、  WebWork为每一次请求构建一个ValueStack,并将所有相关的数据对象(例如:Action对象、Model对象等)放到ValueStack中。再将ValueStack暴露给视图页面,这样页面就可以直接访问后台处理生成的数据。

下面我们用一个雇员类为例,使用Junit框架(单元测试框架)来展示ValueStack的功能。

我们有一个Employee类,它有两个属性:姓名,地址。姓名是一个字符串,地址是一个对象,地址类有国家、城市、街道三个属性。代码如下:

Employee.java代码如下:

public class Employee {

      private String name;

     

      private Address address;

     

      public Employee() {

      }

     

      public String getName() {

            return name;

      }

 

      public void setName(String name) {

            this.name = name;

      }

 

      public Address getAddress() {

            if(address == null)

                  address = new Address();

            return address;

      }

     

      public void setAddress(Address address) {

            this.address = address;

      }

}

 

Address.java代码如下:

public class Address {

            private String country;

            private String city;

            private String street;

           

            ……

            //默认的GetSet方法,省略

           

}

下面出场的是OgnlValueStackTest,它有两个测试方法。分别测试使用ELValueStack中取值和存值。代码如下:

import com.opensymphony.xwork.util.OgnlValueStack;

import junit.framework.TestCase;

 

public class OgnlValueStackTest extends TestCase {

            private OgnlValueStack valueStack;

            private Employee employee;

           

            @Override

            protected void setUp() throws Exception {

                        valueStack = new OgnlValueStack();

                        employee = new Employee();

                        valueStack.push(employee);

            }

 

            public void testCouldGetDataFromObjectInOgnlValueStackByEL() throws Exception{

                        employee.setName("Moxie");

                        Address address = new Address();

                        address.setCountry("China");

                        employee.setAddress(address);

                       

                        assertEquals("Moxie",valueStack.findValue("name"));

                        assertEquals("China",valueStack.findValue("address.country"));

            }

           

            public void testCouldSetDataForObjectInOgnlValueStackByEL() throws Exception{

                        valueStack.setValue("name","Moxie");

                        valueStack.setValue("address.country","China");

                       

                        assertEquals("Moxie",employee.getName());

                        assertEquals("China",employee.getAddress().getCountry());

            }

}

这是一个Junit Test,关于Junit的使用不是本篇文章的范畴。作为一个Java开发者,熟悉这样的测试框架是最基本的要求。setUp方法在每个测试方法调用之前都会执行,它用来初始化每个测试方法都需要的对象和数据。我们的setUp方法首先创建两个对象:valueStack对象和employee对象,然后将employee对象入栈。这样,emloyee对象就在值堆栈的最上端。

第一个测试方法testCouldGetDataFromObjectInOgnlValueStackByEL测试可以用表达式语言取得值堆栈里的对象数据。我们首先为值堆栈里的employee对象设置数据,设置了用户名和用户地址所在的国家。

第一个验证断言             assertEquals("Moxie",valueStack.findValue("name"))解释为:我们期望使用表达式语言“name”去ValueStack中查找得到的对象是”Moxie”。即期望valueStack.findValue("name")语句执行返回的数据是”Moxie”对象。再深入下去,这条语句会调用ValueStack里对象的getName方法(即employee对象的getName方法),并返回这个方法返回的数据。

第二个断言assertEquals("China",valueStack.findValue("address.country"))。它期望表达式语言“address.country”取得的数据是对象的address属性对象的country属性,即取得雇员对象的地址所在的国家。深入下去,它也就是调用对象employeegetAddress().getCountry()方法。  第二个测试方法testCouldSetDataForObjectInOgnlValueStackByEL测试通过表达式语言为ValueStack中的对象设置数据。

第一个设值语句valueStack.setValue("name","Moxie"),它通过表达式语言“name”将“”Moxie””赋值给ValueStack里的对象,即调用值堆栈中的对象的setName方法,并把后面的值作为方法的参数。同理,第二个设置语句会调用值堆栈中的对象的getAddress().setCountry()方法,把后面的数据作为setCountry方法的参数。

 

看了这个例子,我们就会知道如何通过ValueStack进行数据的存取。在我们使用ValueStack时需要注意:

1、  所有存取操作的目标对象都是已放入ValueStack中的对象。所以在使用之前,必须要先将对象入栈。例如我们在setup方法中的语句:valueStack.push(employee)

2、  每一次WebWork请求,在创建Action对象之前都会先生成一个ValueStack对象,再将Action对象入栈。这样我们就可以通过表达式语言来直接存取action对象的数据,所以在WebWork中,action具有数据模型的功能。

3、  在对ValueStack进行存取操作时,我们的操作指令(表达式语言)并不知道它是对哪个对象进行操作。例如,我们在获取员工姓名时,我们给的操作指令是”name”,这时,并不知道ValueStack里面的对象一定就是employeeValueStack会从上而下,遍历栈里面的对象,并试图调用当前遍历对象的getName方法,当它找到了这个方法,并执行之后,就会将执行得到的数据返回。这就是那神秘的ValueStack

4、  关于值堆栈的context map,它是一个放置值堆栈上下文数据的对象。通过符号“#“再加上对象的名称,可以访问这些数据(只可以访问)。一些JavaServlet相关的数据都放在这个容器中。这个对webwork的标签库特别有用,这样我们可以直接通过表达式语言去访问requestattributesessionapplication里的数据。例如:用property标签库打印出所有请求参数的数据,代码如下:<ww:property value="%{#request}"/>

5、  其它。“top”是ValueStack里面的关键字,通过它可以找到ValueStack中最上面的那个对象。可以试着打印一下valueStack.findValue("top"))看看。表达式语言除了可以调用基于JavaBean规范的getset方法之外,还可以调用一般的Java方法(这是需要使用方法的全名,并传入需要的参数),也可以直接访问Java类的静态字段和静态方法。具体的使用,可以查看WebWork的官方文档。

 

Interceptor( 拦截器 )

拦截器在前面的“WebWork核心概念章节做了简单介绍,这里我们将对它进行更进一步的探讨。关于拦截器的描述:

1、  一个拦截器就是在xwork.xml文件中定义的一个无状态Java类,它至少要实现XWorkcom.opensymphony.xwork.interceptor.Interceptor接口。代码如下:

public interface Interceptor extends Serializable {

    void destroy();

 

    void init();

 

    String intercept(ActionInvocation invocation) throws Exception;

}

2、  实现Interceptor接口的拦截器,代码部分在intercept方法中实现。在intercept方法中,可以直接返回一个Result字符串,这样整个执行直接“短路”,这时Actionexecute方法也不会执行(一般很少会这么用)。所以,一般都会在这个方法里调用参数对象invocationinvoke方法,并返回这个方法执行的结果。这样会持续执行后面的拦截器方法以及Actionexecute方法等。

3、  大部分的时候,拦截器直接继承WebWork的抽象类com.opensymphony.xwork.interceptor.AroundInterceptor就可以了。这时,需要实现它的beforeafter方法。Before方法会在Action执行之前调用,after方法在Action执行之后调用。

4、  拦截器的执行顺序。我们可将多个拦截器放一起组装成一个拦截器栈。这样拦截器会按照栈的顺序由上而下执行before方法,所有before方法执行结束,再执行Action的方法,执行Result的方法,再返回执行结果,最后再从下而上执行拦截器的after方法。

5、  拦截器的过滤功能。我们通常会在应用中使用一个通用的定义多个拦截器的拦截器栈。但有些Action方法在调用的时候,不需要要其中的部分拦截器。这时,我们就可以使用拦截器过滤功能。如果拦截器要拥有过滤功能,必须实现抽象类com.opensymphony.xwork.interceptor.MethodFilterInterceptor。这样,拦截器在定义的时候或者在Action引用拦截器栈的时候,我们就可以指定哪些Action方法是需要过滤的,哪些Action是不需要过滤的。

 

WebWork提供的拦截器介绍

1、  自动为Action设置Http请求数据的拦截器(Parameters Interceptor)。这个拦截器非常方便实用,但完全自动组装对象数据,很可能会带来安全问题。如果Action不需要设置数据,那么这个Action只要实现com.opensymphony.xwork.interceptor.NoParameters接口即可。如果是Action中部分数据需要自动设置,部分数据不允许设置,这样可以实现接口com.opensymphony.xwork.interceptor.ParameterNameAware,可以在这个接口的acceptableParameterName(String parameterName)方法中,定义我们可以接受哪些方法,如果允许只要让这个方法返回True就可以了。

2、  过虑参数功能的拦截器(Parameter Filter Interceptor)。它可以全局阻止非法或不允许Action访问的参数。可以很好的和上面的组装参数的拦截器一起使用。

3、  Action设置静态数据的拦截器(Static Parameters Interceptor)。它可以将Action定义的静态<param/>参数,设置到Action中。

4、  数据验证拦截器(Validation Interceptor)。定义之后,会调用验证文件或实现验证接口com.opensymphony.xwork.Validateable的所有验证。

5、  验证流程处理拦截器(Workflow Interceptor)。它和上面的拦截器一起使用,处理验证的流程。如果验证通过则继续前进,如果发现有验证错误消息,直接转到Action中定义的输入结果(input)页面。

6、  类型转换错误处理拦截器()。它首先去取得类型转换的错误消息(主要是由设置Http请求参数的拦截器产生),如果取到错误消息,它会将错误消息传递给实现接口com.opensymphony.xwork.ValidationAwareAction,这样我们可以将这些错误消息暴露到页面中。

7、  Action链拦截器(Chaining Interceptor)。它是用来拷贝前一个Action的属性数据到当前Action中。它要求前一个Action必须是chain Result<result type="chain">),这样才能进行Action的数据拷贝。

8、  防止页面重复提交(或页面重复刷新)拦截器。Token InterceptorToken Session Interceptor都是防止重复提交的拦截器。不同点是后者在Session存贮了最近一次请求的结果数据。

9、  文件上传的拦截器(File Upload Interceptor)。实现文件上传的功能。如果有人曾经手工写过文件上传程序,那一定会惊叹于这个拦截器。我们可以在这个拦截器中设定上传文件的大小和类型限制。记得需要第三方的文件上传库的支持,只要在webwork.properties中配置过,并拷贝相应的jar包就可以了。

10、                      进度条等待拦截器(Execute and Wait Interceptor)。当Action的执行需要很长实际的时候,我们可以使用这个进度条等待的拦截器。它会将Action放到后台执行,而在前端显示进度条或等待消息提示的页面。

11、                      还有一些其它不常用的拦截器,我们可以在WebWork文档中找到,这里就不再做介绍。

IoC 容器

关于IoC容器的描述:

1、  它是一个容器。用来处理不同对象之间的依赖,组装不同的程序元素。

2、  IoC全名是Inversion of Control,即依赖反转控制。它是一种设计模式,IoC容器就是基于这个模式的实现。

3、  有一个比IoC更适合用来命名这一原理的名词,它叫Dependency Injection,即依赖注入。

4、  有了依赖注入,我们可以将IoC容器简单理解为:它是一个用来为对象组装其依赖的对象的容器;也就是说对象本身不用关心它依赖的对象(可以是组件或者服务,反正就是提供业务方法的对象)的创建,初始化,生命周期等,而是由容器来控制。

5、  关于IoC的更多信息可以查看Martin Fowler的经典文章《IoC 容器和Dependency Injection 模式》

WebWork框架本身就提供了一个基于接口的IoC容器。同时WebWork框架和提供了和第三方IoC容器的集成。在WebWork的项目中,我们可以直接使用Spring框架和Pico框架作为自己的IoC容器。而且SpringWebWork官方推荐的IoC容器。

下面我们将介绍如何使用Spring作为WebWorkIoC容器。在讨论如何集成Spring之前,我们要讨论一个非常有意义的架构问题:

WebWorkAction类是否需要由Spring管理?

1、  否。这样Action类还是象以前那样在xwork文件中定义,我们可以通过标签<external-ref/>定义Action所依赖的Bean组件,或者根据Bean的名字或类型进行自动注入。

2、  是。Action类本身也是有Spring来管理。在xwork定义文件中的Action class就是对Bean的引用,具体的类在SpringBean配置文件中定义。这样可以更好的使用Sping为我们提供的更多功能,例如:Spring的复杂AOP功能,基于Acegi的权限控制,等等。

两种方法,根据需要选择一种,我个人更倾向后者。下面我们介绍一下Spring容器的安装步骤:

1、  拷贝Spring依赖的所有jar文件

2、  web.xml文件定义和开启Spring的监听器,代码如下:

<listener>

      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

      </listener>

3、  webwork.properties中设置WebWorkSpring的支持:webwork.objectFactory = spring

4、  可以在webwork.properties中设置Bean组件的默认组装方式,默认是按照Bean的名称,可以选择按照类型、构造函数、自动选择等方式。

 

WebWork 原理


webwork archit.png

WebWork的网站上提供了一个完整的WebWork架构图。它描述了从客户端的一次请求到最后服务器端响应的的整个执行过程。架构图如下:

 

此架构图一个分为五个部分,其中五个部分分别有五中不同颜色表示。

1、  浅灰色方框。分别代表了客户端的一次Http请求,和服务器端运算结束之后的一次响应。

2、  浅红色方框。表示一次Action请求所要经过的Servlet filtersServlet 过滤器)。我们可以看到最后一个filter就是我们前面介绍的WebWork的前端控制器。

3、  蓝色方框。这是WebWork框架的核心部分。

1)  一次请求到了WebWork的前端控制器,它首先会根据请求的URL解析出对应的action 名称,然后去咨询ActionMapper这个action是否需要被执行。

2)  如果ActionMapper决定这个action需要被执行,前端控制器就把工作委派给ActionProxy。接着她们会咨询WebWork的配置管理器,并读取在web.xml文件中定义的配置信息。接下来ActionProxy会创建ActionInvocation对象。

3)  ActionInvocationXwork原理的(Command模式)实现部分。它会调用这个Action已定义的拦截器(before方法)Action方法,Result方法。

4)  最后,看上面流程的图的方向,它会再执行拦截器(after方法),再回到Servlet Filter部分,最后结束并传给用户一个结果响应。

4、  靛色方框。这是拦截器部分,在上面的拦截器章节我们已经有了详细的介绍。

5、  黄色方框。这是我们在开发Web应用时,需要自己开发的程序。其中包括:Action类,页面模板,配置文件xwork.xml

     

WebWork 实战和技巧

限于篇幅,我们无法在本章节给出很多详尽的具体实例。其实,在WebWork的代码包中,有一个非常好的演示项目——showcase,它用例子演示了WebWork的几乎所有特性。值得初学的朋友反复研究。我们在本章节中会截取其中的部分代码脚本。

 

1、  多视图支持。WebWork框架天生支持多种视图技术,包括:JspFreeMarkerVelocityJasper ReportsXSLT,还有其它的视图技术。这将在敏捷项目中特别有用。在我咨询的项目中,就有一个因为技术的原因,视图技术由最先的Jsp改为Velocity,后来又改造为FreeMarker。其中,Action类以及后台的程序没有做任何的改动。如果您需要一个视图展现层技术,我在这会好不犹豫的向您推荐FreeMarker。这也是官方的推荐。

关于视图技术的使用,首先是搭建视图技术运行的环境。然后就是编写页面脚本,最后就是在xwork.xml文件中配置。Xwork配置文件中,Resulttype参数,就是用来标示所使用的视图技术。在showcase项目中,使用到的视图技术有:JspFreeMarkerVelocityJasper Reports

 

2、  Action的数据验证功能。在WebWork中,可以在三处实现数据验证功能。一、验证文件(例如:ActionClass-validation.xml文件)中定义数据验证规则。二、在Action中实现com.opensymphony.xwork.Validateable接口的validate方法。三、在Action的执行方法中,硬编码实现验证功能。当然,在实现验证时,我们尽可能的用前面两中方法。

关于验证的说明:

1)、第一种验证需要“validation”拦截器的支持。并可以从任意层次绑定验证文件,可以为一个Action类绑定一个验证文件,也可以为一个具体在xwork.xml文件中的Action定义绑定一个验证文件,可以为Action的一个属性对象绑定一个验证文件,甚至可以为Action的父类绑定验证文件。

2)、WebWork为验证文件提供了一些标准的验证实现:例如:字段必须填写,整型、E-mail地址等等。我们也可以使用表达式语言实现更复杂的数据验证。

 

3、  类型转换。前面外面一直提到过,WebWork会自动从请求的字符串参数中组装Action需要的数据对象。这样,就会存在一个类型转换的问题。如果Action的字段是基本类型或是一个数据对象,WebWork会自动帮我们处理。如果Action的字段是一个集合,或者我们需要特定的类型转换,这时,我们可以在类型转换的定义文件(ClassName-conversion.properties)中定义转换规则。

4、  一个Action的多个执行方法。WebWorkAction是基于Command模式的实现,在WebWork中,除了实现Action接口的execute()方法之外,Action还可以定义多个执行方法。这些方法必须要是无方法参数,并且返回返回字符串的方法。这样我们在Url中可以用类似下面的格式访问:actionName!methodName.action,例如user!doAdd.action,调用user Action类的doAdd方法。在最新的WebWork中,Action类甚至可以不实现Action接口。

5、  Action链(Action Chaining)。在WebWork中,一次用户请求,可由多个Action共同完成。每个Action可以只实现自己本身的功能单元,这样我们可以根据业务需要为用户的一次请求选择一个或多个Action功能单元来实现。在这样的多个Action之间可以通过chain拦截器共享数据。如果请求由Action x 链到Action y,如果这时y需要获得x的数据,我们就需要为Action y添加chain拦截器。

例如:在showcase中就由Action Chaining的应用。它将ActionactionChain2”链到另外一个ActionactionChain3”,配置文件代码如下:

      < action name = "actionChain2" class = "com.opensymphony.webwork.showcase.actionchaining.ActionChain2" >

                  < result type = "chain" > actionChain3 </ result >

            </ action >

            < action name = "actionChain3" class = "com.opensymphony.webwork.showcase.actionchaining.ActionChain3" >

                  < result > /actionchaining/actionChainingResult.jsp </ result >

         </ action >

注意:如果要加 chain 拦截器,是需要加到 actionChain3 中,而不是 actionChain2

 

6、  多模块支持解决方案。 WebWork 提供了很灵活的多模块解决方案,这样我们可以很好的组织复杂的 Web 应用项目。

1 )可以在 xwork.xml 文件中,用 include 标签包含另外的一个 xwork 配置文件。例如: <include file="webwork-default.xml"/>

2 xwork.xml 配置文件支持 package 。我们可以将一个业务模块的定义方到一个 package package 支持继承功能,子 package 可以享有父 package 的所有定义。

3 )可以为 package 定义一个命名空间。不同的命名空间可以定义相同的 action 名字。命名空间会用于访问 action URL ,基于这个命名空间,我们可以实现资源权限的访问控制。

 

7、  doInput 方法。这是我们常用的小技巧,有时候请求的就是一个页面模板,总不能为这个单独写一个 Action 类吧,这时我们就可以用 ActionSupport doInput 方法,直接返回在 Actoion 中定义为“ input ”的 result

8、  prepare方法。如果在Action执行之前,必须要初始化一些数据。我们可以将这些初始化的代码方到prepare方法中。这时,Action类要实现接口com.opensymphony.xwork.Preparable,同时这个Action的定义还需要PrepareInterceptor拦截器的支持。

9、  ActionModel Driven。我们大部分的时候用得都是ActionField Driven,即直接将Action的字段作为数据模型。Model Driven是专门为这个Action指定一个模型对象,这样有什么好处呢?好处是在表达式语言中少了一个对象名的前缀。例如:前面值堆栈部分关于Employee的例子,要设置雇员名称,我们必须要表达式语言例如:“employee.name”,如果这个数据是有页面输入得到,那么你就必须有一个Input输入框的name为“employee.name”,因为它有关键字符“.”,如果Javascript脚本使用这个输入框的名字,就会有错误。使用Model Driven之后,这个表达式就可以变为“name”,省去了前面的“employee.”。

10、                      Quick Start。这是WebWork2.2.2中非常激动人心的特性。它可以象perlPHP可以快速看到程序运行结果。这样在Web开发时,可以不用编译Java源代码,也不用去做打包和部署,就可以快速看到最新程序的运行结果,提高开发效率。我们可以在WebWork源码解压包的根目录,输入命令:java -jar webwork-2.2.2.jar quickstart:showcase(需要jdk1.5的支持),以Quick Start模式,运行showcase项目。

 

 

展望 WebWork 未来

这是很多人非常关心的一个问题。特别是WebWork2.2版本发布之后,官方宣称WebWork框架将要和Struts合并。这让一些WebWork的用户产生了担忧,合并之后,是不是就意味着自己在WebWork这方面技术和经验的积累都已浪费?已使用或即将使用WebWork的项目是不是就意味着更多的风险?

答案是:完全不用担心这些。WebWorkStruts的合并,是各取所长,然后诞生出一个更加高效的Web框架。而这个框架用得就是WebWork的优秀技术和Struts的强大社区。

合并的情况如下:

1、  产生一个新的项目Struts Action 2.0 = WebWork2.2 + 一些Struts的功能和特性。

2、  WebWork框架将会中止新功能的增加,如果有新的版本发布都会是Bug的修改。

3、  代码、框架的开发者、社区都将移到Struts

4、  合并的目标是致力于生产率的提高。

5、  Struts不再是一个框架,它是一个社区。

6、  Struts社区中主要有两个Web框架。一个是基于Action模型的Struts Action;另一个是基于组件模型的Struts Shale

总结

 

WebWork是本人工具箱中最爱的一个J2EE Web框架。本人开发过单纯使用JspJavaServlet的项目;也曾经自己开发过基于MVCWeb框架;在2002年开始使用Struts开发;后来也在项目中分别使用过TapestrySpring MVC Web框架;也在当今的AJAX潮流中随波逐流。上面的一些技术也都非常优秀,擅用他们任何一个都会给您带来很多生产效率的提高。但我仍然是偏爱WebWorkWebWork的与众不同,得力于它基于OGNL的强大的数据存、取方式,得力于它那解耦的拦截器功能,得力于它那无侵入的架构设计。正是由于它,才让Web编程变得更加的自然、简单、灵活、高效。

 

版本修改记录: V2.2.0.2修改: 修改了HttpPost相对路径的一些问题。 V2.2.0.0增加: [id(0x00010041), helpstring("Get Rev Index")] HRESULT GetRevCount( [out,retval] long * pbool); [id(0x00010042), helpstring("Get Rev Index Info")] HRESULT GetRevInfo([in] long lIndex, [in] long lType, [out,retval] BSTR* pbool); [id(0x00010043), helpstring("Set Doc Prop")] HRESULT SetValue([in] BSTR strValue, [in] BSTR strName, [out,retval] long* pbool); [id(0x00010044), helpstring("Set Doc Variable")] HRESULT SetDocVariable([in] BSTR strVarName, [in] BSTR strValue,[in] long lOpt, [out,retval] long* pbool); [id(0x00010045), helpstring("Save page To Doc")] HRESULT SetPageAs([in] BSTR strLocalFile, [in] long lPageNum, [in] long lType,[out,retval] long* pbool); ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- LoadDso.js var s = "" s += "" s += "" document.write(s) ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- 接口文档: /* 1.新建 */ //新建Word document.all.FramerControl1.CreateNew("Word.Document"); //新建Excel document.all.FramerControl1.CreateNew("Excel.Sheet"); /* 2.打开文件 */ //打开制定的本地文件 document.all.FramerControl1.Open("C:\\TestBook.xls"); //制定用Word来打开c:\plain.txt文件 document.all.FramerControl1.Open("C:\\Plain.txt",false, "Word.Document"); //打开服务器的文件 document.all.FramerControl1.Open "https://secureserver/test/mytest.asp?id=123",true, "Excel.Sheet", "MyUserAccount", "MyPassword"); //打开服务器的文件 document.all.FramerControl1.Open("http://localhost/1.doc", true); /* 3.保存文件 */ //到本地 document.all.FramerControl1.Save("c:\\1.doc",true); //服务器 /*增加Http协议Post上传接口,可以Post一个动态页面(jsp,asp,php...),由动态页面负责解析数据 bool HttpInit(); bool HttpAddPostString(BSTR strName, BSTR strValue); bool HttpAddPostCurrFile(BSTR strFileID, BSTR strFileName); BSTR HttpPost(BSTR bstr); */ //初始化Http引擎 document.all.FramerControl1.HttpInit(); //增加Post变量 document.all.FramerControl1.HttpAddPostString("RecordID","20060102200"); document.all.FramerControl1.HttpAddPostString("UserID","李局长"); //上传打开的文件 document.all.FramerControl1.HttpAddPostCurrFile("FileData", "文档名.doc"); //执行上传动作 document.all.FramerControl1.HttpPost("http://xxxx.com/uploadfile.asp"); /* 4.修订留痕 */ //进入留痕状态 document.all.FramerControl1.SetTrackRevisions(1); //进入非留痕状态 document.all.FramerControl1.SetTrackRevisions(0); //接受当前修订 document.all.FramerControl1.SetTrackRevisions(4); /* 5.设置当前用户 */ document.all.FramerControl1.SetCurrUserName("张三"); /* 6.设置当前时间(笔迹留痕会显示("Like 2006:02:07 11:11:11") */ document.all.FramerControl1.SetCurrTime("2006:02:07 11:11:11"); /* 7.设置和创建书签,此功能比较强大,设置书签数据、添加书签和添加红头文件就靠他了 SetFieldValue(BSTR strFieldName, BSTR strValue, BSTR strCmdOrSheetName) strFieldName:书签名 strValue:要设置的值 strCmdOrSheetName: 命令 ::ADDMARK:: 添加BookMark ::DELMARK:: 删除这个BookMark ::GETMARK:: 定位到这个BookMark ::FILE:: 插入的是文件 ::JPG:: 插入的是图片 一般来说:WORD中书签是做好的,可以通过此接口把外界数据设置进书签中去。 */ //在当前WORD位置插入标签,标签名为"book1",数值为"test" document.all.FramerControl1.SetFieldValue("book1","test","::ADDMARK::"); //设置书签"Time",数值为"2006-03-16 22:22:22" document.all.FramerControl1.SetFieldValue("Time","2006-03-16 22:22:22",""); //在书签位置"hongtou",插入红头文件"http://222.222.222.222/hongtou1.doc" 这样,红头就自动插进去了 document.all.FramerControl1.SetFieldValue("hongtou","http://222.222.222.222/hongtou1.doc","::FILE::"); /* 8.设置菜单显示情况 BOOL SetMenuDisplay(long lMenuFlag) lMenuFlag为以下数值的组合 #define MNU_NEW 0x01 #define MNU_OPEN 0x02 #define MNU_CLOSE 0x04 #define MNU_SAVE 0x08 #define MNU_SAVEAS 0x16 #define MNU_PGSETUP 0x64 #define MNU_PRINT 0x256 #define MNU_PROPS 0x32 #define MNU_PRINTPV 0x126 */ //只有“新建”菜单可用 document.all.FramerControl1..SetMenuDisplay(1); //只有“打开”菜单可用 document.all.FramerControl1.SetMenuDisplay(2); //只有“打开”和“新建”菜单可用 document.all.FramerControl1.SetMenuDisplay(3); /* 9.保护文档和解保护文档 lProOrUn:1:保护文档;0:解除保护 lProType: wdNoProtection = -1, wdAllowOnlyRevisions = 0, wdAllowOnlyComments = 1, wdAllowOnlyFormFields = 2 strProPWD:密码 */ //完全保护文档,密码为"pwd" document.all.FramerControl1.ProtectDoc(1,1,"pwd"); //解除文档保护 document.all.FramerControl1.ProtectDoc(0,1,"pwd"); /* 10.显示或隐藏修订内容 ShowRevisions(long nNewValue) nNewValue = 0 则隐藏修订 = 1 则显示修订 */ //显示修订留痕 document.all.FramerControl1.ShowRevisions(1); //隐藏修订留痕 document.all.FramerControl1.ShowRevisions(0); /* 11.插入合并文件, strFieldPath 文件路径,可以是http,ftp的路径 pPos = 0 //当前鼠标位置 1;文件开头 2;文件末尾 pPos的第4位为1的时候,代表插入的是图片 InSertFile(BSTR strFieldPath, long lPos) */ //文件头部插入文件 document.all.FramerControl1.InSertFile("http://XX.com/XX.doc",1); //文件尾部插入文件 document.all.FramerControl1.InSertFile("http://XX.com/XX.doc",2); //当前光标位置插入文件 document.all.FramerControl1.InSertFile("http://XX.com/XX.doc",0); //文件头部插入图片 document.all.FramerControl1.InSertFile("http://XX.com/XX.jpg",9); //文件尾部插入图片 document.all.FramerControl1.InSertFile("http://XX.com/XX.jpg",10); //当前光标位置插入图片 document.all.FramerControl1.InSertFile("http://XX.com/XX.jpg",8); /* 0x31. 文档另存为 HRESULT SaveAs([in] VARIANT strFileName, [in] VARIANT dwFileFormat, [out,retval] long* pbool); 参数: strFileName:文件本地路径,如c:\\11.doc dwFileFormat: 文件格式 dwFileFormat的数值为: Excel: Type enum XlFileFormat { xlAddIn = 18, xlCSV = 6, xlCSVMac = 22, xlCSVMSDOS = 24, xlCSVWindows = 23, xlDBF2 = 7, xlDBF3 = 8, xlDBF4 = 11, xlDIF = 9, xlExcel2 = 16, xlExcel2FarEast = 27, xlExcel3 = 29, xlExcel4 = 33, xlExcel5 = 39, xlExcel7 = 39, xlExcel9795 = 43, xlExcel4Workbook = 35, xlIntlAddIn = 26, xlIntlMacro = 25, xlWorkbookNormal = -4143, xlSYLK = 2, xlTemplate = 17, xlCurrentPlatformText = -4158, xlTextMac = 19, xlTextMSDOS = 21, xlTextPrinter = 36, xlTextWindows = 20, xlWJ2WD1 = 14, xlWK1 = 5, xlWK1ALL = 31, xlWK1FMT = 30, xlWK3 = 15, xlWK4 = 38, xlWK3FM3 = 32, xlWKS = 4, xlWorks2FarEast = 28, xlWQ1 = 34, xlWJ3 = 40, xlWJ3FJ3 = 41, xlUnicodeText = 42, xlHtml = 44 }; Word: Type enum WdSaveFormat { wdFormatDocument = 0, wdFormatTemplate = 1, wdFormatText = 2, wdFormatTextLineBreaks = 3, wdFormatDOSText = 4, wdFormatDOSTextLineBreaks = 5, wdFormatRTF = 6, wdFormatUnicodeText = 7, wdFormatEncodedText = 7, wdFormatHTML = 8 }; PPT: enum PpSaveAsFileType { ppSaveAsPresentation = 1, ppSaveAsPowerPoint7 = 2, ppSaveAsPowerPoint4 = 3, ppSaveAsPowerPoint3 = 4, ppSaveAsTemplate = 5, ppSaveAsRTF = 6, ppSaveAsShow = 7, ppSaveAsAddIn = 8, ppSaveAsPowerPoint4FarEast = 10, ppSaveAsDefault = 11, ppSaveAsHTML = 12, ppSaveAsHTMLv3 = 13, ppSaveAsHTMLDual = 14, ppSaveAsMetaFile = 15, ppSaveAsGIF = 16, ppSaveAsJPG = 17, ppSaveAsPNG = 18, ppSaveAsBMP = 19 }; */ /* 0x32. 删除本地文件 HRESULT DeleteLocalFile([in] BSTR strFilePath); 参数: strFileName:文件本地路径,如c:\\11.doc */ /* 0x33.创建临时文件 HRESULT GetTempFilePath([out,retval] BSTR* strValue); 返回: 临时文件的路径地址。使用完后,用DeleteLocalFile 删除 */ /* 0x34.设置文档显示模式 HRESULT ShowView([in] long dwViewType, [out,retval] long * pbool); dwViewType的可取值为: enum WdViewType { wdNormalView = 1, wdOutlineView = 2, wdPrintView = 3, wdPrintPreview = 4, wdMasterView = 5, //这个是大纲 wdWebView = 6 }; */ //大纲模式 document.all.FramerControl1.ShowView(5); /* 0x39:下载远程文件 HRESULT DownloadFile( [in] BSTR strRemoteFile, [in] BSTR strLocalFile, [out,retval] BSTR* strValue); 参数: strRemoteFile:远程路径地址,http or Ftp strLocalFile: 本地保存地址,if strLocalFile == NULL then Create Temp File and return TempFile's Path */ /* 0x40:增加Http上传时候的,附加其他文件 HRESULT HttpAddPostFile([in] BSTR strFileID, [in] BSTR strFileName, [out,retval] long* pbool); 参数: strFileID:文件的ID,供服务器端页面解析 strFileName: 本地文件地址 */ /* 0x41,0x42.获取详细的修订信息。 GetRevCount( [out,retval] long * pbool); GetRevInfo([in] long lIndex, [in] long lType, [out,retval] BSTR* pbool); 例子如下 */ var vCount; vCount = document.all.FramerControl1.GetRevCount(); alert(vCount); var vOpt = 0; var vDate; for(var i=1; i<= vCount; i++){ vOpt = document.all.FramerControl1.GetRevInfo(i,2); if("1" == vOpt){ vOpt = "插入"; }else if("2" == vOpt){ vOpt = "删除"; }else{ vOpt = "未知操作"; } vDate = new String(document.all.FramerControl1.GetRevInfo(i,1)); vDate = parseFloat(vDate); alert(vDate); dateObj = new Date(vDate); alert(dateObj.getYear() + "年" + dateObj.getMonth() + 1 + "月" + dateObj.getDate() +"日" + dateObj.getHours() +"时" + dateObj.getMinutes() +"分" + dateObj.getSeconds() +"秒" ); alert("用户:"+document.all.FramerControl1.GetRevInfo(i,0) + "\r\n操作:" + vOpt + "\r\n内容:" + document.all.FramerControl1.GetRevInfo(i,3)); } /* 0x43.设置基本信息: HRESULT SetValue([in] BSTR strValue, [in] BSTR strName, [out,retval] long* pbool); 1.设置文件只读密码 SetValue("password","::DOCPROP:PassWord"); 2.设置文件修改密码 SetValue("password","::DOCPROP:WritePW"); 返回值: 0 正确 -1:不支持此命令,请确定您的第二个参数没有传错 -127:异常 */ //设置文件只读密码 document.all.FramerControl1.SetValue("password","::DOCPROP:PassWord"); //设置文件修改密码 document.all.FramerControl1.SetValue("password","::DOCPROP:WritePW"); /* 0x44.设置文档变量,这个很少能用到 HRESULT SetDocVariable([in] BSTR strVarName, [in] BSTR strValue,[in] long lOpt, [out,retval] long* pbool); strVarName: 变量名 strVlaue:变量值 lOpt: 操作类型, 按位 第一位为1: 表示update域关联的 第二位为1: 表示如果没有这个变量则添加 第三位为1: 未来支持 return: 0:OK -127:异常 */ /* 0x45: 分页保存 HRESULT SetPageAs([in] BSTR strLocalFile, [in] long lPageNum, [in] long lType,[out,retval] long* pbool); strLocalFile:本地路径 lPageNum:页数 */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值