WebWork 原理
通过上面的例子,我们已经了解WebWork开发、运行的基本流程(一定要亲自安装,并开发这个Welcome的例子哦)。如果要在实际项目中使用WebWork,我们必须要了解下面的概念和WebWork的原理。
ValueStack( 值堆栈 ) 和 EL( 表达式语言 )
关于ValueStack的描述:
1、 ValueStack其实就是一个放置Java对象的堆栈而已,唯一特别的是可以使用EL来获得值堆栈中对象属性的数据,并可以为值堆栈的对象属性赋值。
2、 EL,全称Express Language,即表达式语言。不要被语言吓倒,它是简单的对象导航语言。有字符串(例如:方法名)和特殊字符组成(例如用.表示调用对应的属性方法)。通过EL,我们可以存、取对象数据,而且还可以直接访问类的静态数据,调用静态方法。
3、 WebWork的ValueStack底层有第三方开源项目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;
……
//默认的Get和Set方法,省略
}
下面出场的是OgnlValueStackTest,它有两个测试方法。分别测试使用EL从ValueStack中取值和存值。代码如下:
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属性,即取得雇员对象的地址所在的国家。深入下去,它也就是调用对象employee的getAddress().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里面的对象一定就是employee。ValueStack会从上而下,遍历栈里面的对象,并试图调用当前遍历对象的getName方法,当它找到了这个方法,并执行之后,就会将执行得到的数据返回。这就是那神秘的ValueStack。
4、 关于值堆栈的context map,它是一个放置值堆栈上下文数据的对象。通过符号“#“再加上对象的名称,可以访问这些数据(只可以访问)。一些JavaServlet相关的数据都放在这个容器中。这个对webwork的标签库特别有用,这样我们可以直接通过表达式语言去访问request、attribute、session、application里的数据。例如:用property标签库打印出所有请求参数的数据,代码如下:<ww:property value="%{#request}"/>。
5、 其它。“top”是ValueStack里面的关键字,通过它可以找到ValueStack中最上面的那个对象。可以试着打印一下valueStack.findValue("top"))看看。表达式语言除了可以调用基于JavaBean规范的get和set方法之外,还可以调用一般的Java方法(这是需要使用方法的全名,并传入需要的参数),也可以直接访问Java类的静态字段和静态方法。具体的使用,可以查看WebWork的官方文档。
Interceptor( 拦截器 )
拦截器在前面的“WebWork核心概念”章节做了简单介绍,这里我们将对它进行更进一步的探讨。关于拦截器的描述:
1、 一个拦截器就是在xwork.xml文件中定义的一个无状态Java类,它至少要实现XWork的com.opensymphony.xwork.interceptor.Interceptor接口。代码如下:
public interface Interceptor extends Serializable {
void destroy();
void init();
String intercept(ActionInvocation invocation) throws Exception;
}
2、 实现Interceptor接口的拦截器,代码部分在intercept方法中实现。在intercept方法中,可以直接返回一个Result字符串,这样整个执行直接“短路”,这时Action的execute方法也不会执行(一般很少会这么用)。所以,一般都会在这个方法里调用参数对象invocation的invoke方法,并返回这个方法执行的结果。这样会持续执行后面的拦截器方法以及Action的execute方法等。
3、 大部分的时候,拦截器直接继承WebWork的抽象类com.opensymphony.xwork.interceptor.AroundInterceptor就可以了。这时,需要实现它的before和after方法。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.ValidationAware的Action,这样我们可以将这些错误消息暴露到页面中。
7、 Action链拦截器(Chaining Interceptor)。它是用来拷贝前一个Action的属性数据到当前Action中。它要求前一个Action必须是chain Result(<result type="chain">),这样才能进行Action的数据拷贝。
8、 防止页面重复提交(或页面重复刷新)拦截器。Token Interceptor和Token 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容器。而且Spring是WebWork官方推荐的IoC容器。
下面我们将介绍如何使用Spring作为WebWork的IoC容器。在讨论如何集成Spring之前,我们要讨论一个非常有意义的架构问题:
WebWork的Action类是否需要由Spring管理?
1、 否。这样Action类还是象以前那样在xwork文件中定义,我们可以通过标签<external-ref/>定义Action所依赖的Bean组件,或者根据Bean的名字或类型进行自动注入。
2、 是。Action类本身也是有Spring来管理。在xwork定义文件中的Action class就是对Bean的引用,具体的类在Spring的Bean配置文件中定义。这样可以更好的使用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中设置WebWork对Spring的支持:webwork.objectFactory = spring
4、 可以在webwork.properties中设置Bean组件的默认组装方式,默认是按照Bean的名称,可以选择按照类型、构造函数、自动选择等方式。
WebWork 原理

WebWork的网站上提供了一个完整的WebWork架构图。它描述了从客户端的一次请求到最后服务器端响应的的整个执行过程。架构图如下:
此架构图一个分为五个部分,其中五个部分分别有五中不同颜色表示。
1、 浅灰色方框。分别代表了客户端的一次Http请求,和服务器端运算结束之后的一次响应。
2、 浅红色方框。表示一次Action请求所要经过的Servlet filters(Servlet 过滤器)。我们可以看到最后一个filter就是我们前面介绍的WebWork的前端控制器。
3、 蓝色方框。这是WebWork框架的核心部分。
1) 一次请求到了WebWork的前端控制器,它首先会根据请求的URL解析出对应的action 名称,然后去咨询ActionMapper这个action是否需要被执行。
2) 如果ActionMapper决定这个action需要被执行,前端控制器就把工作委派给ActionProxy。接着她们会咨询WebWork的配置管理器,并读取在web.xml文件中定义的配置信息。接下来ActionProxy会创建ActionInvocation对象。
3) ActionInvocation是Xwork原理的(Command模式)实现部分。它会调用这个Action已定义的拦截器(before方法),Action方法,Result方法。
4) 最后,看上面流程的图的方向,它会再执行拦截器(after方法),再回到Servlet Filter部分,最后结束并传给用户一个结果响应。
4、 靛色方框。这是拦截器部分,在上面的拦截器章节我们已经有了详细的介绍。
5、 黄色方框。这是我们在开发Web应用时,需要自己开发的程序。其中包括:Action类,页面模板,配置文件xwork.xml。
评论