1.相关概念
(1)webx中的MVC
在webx中control就是action,view和layout、control、screen、template等概念相关。layout指页面布局;control指页头、页脚、左右边栏等呗多个页面共享的页面区域;screen指个性化的页面区域
layout、control、screen具体的就用.vm文件来表示,这就是template,查看webapp的app1子目录,可以看到其中包含了templates子目录,之下有layout、control、screen三个子目录,里面包含了一系列的vm文件。
(2)form表单
各个子应用目录下有一个form.xml文件,定义了表单的相关信息。以WEB-INF\app1\form.xml为例,里面定义了app1子应用的相关表单
上图是登录表单,name="login",可以看到其中定义了各表单字段field,包括一些校验器、校验模式和错误提示信息。所有这些都会被相关vm引用,例如webapp\app1\templates\screen\login.vm中通过以下语句引用了login表单:
#set ($group = $form.login.defaultInstance)
之后就可以通过$group.field_name来引用form.xml中定义的各个字段值了
(3)action
用来处理用户提交的表单。
在com\alibaba\webx\tutorial1\app1\module\action目录下有LoginAction.java,其中定义处理login相关请求的方法。在login.vm中有以下代码:
<input type="hidden" name="action" value="LoginAction"/>
这里定义了这个form会提交由LoginAction处理
LoginAction包含多个方法,可以处理多个请求,具体调用哪个请求,可以根据按钮的name属性来确定,如login.vm中
<input type="submit" name="event_submit_do_check"/>
说明LoginAction的doCheck方法会被调用
2.具体流程
(1)发出请求
首先思考发出请求必须有完整的路径,包括服务器地址、哪个component、target、哪个action响应、action中方法,下面依次对应1)服务器地址、哪个component、target
这几个都是通过form中的action来定义的
<form action="$app1Link.setTarget("register.vm")" method="post">
表示请求路径为$app1Link/register.vm,其中$app1Link定义在uris.xml中
注意:这里如果和地址栏中完全相同的话,可以将action设为空
2)哪个action响应
通过表单中的隐藏字段来定义
<input type="hidden" name="action" value="RegisterAction"/>
也就是说通过RegisterAction来响应
3)action中的哪个方法来响应
通过submit按钮的name属性的值来确定,name的格式为:event_submit_+响应函数名称(如果有多个单词则用下划线连接)
<input type="submit" name="event_submit_do_register" value="立即注册!"/>
表示通过doRegister方法来响应
(2)分析请求如何找到处理类
1)当用户发出请求,会被webx拦截到:WebxFrameworkFilter,然后调用WebxRootController
WebRootController对象存在于root context中,是所有子应用共享的。它会创建RequestContext实例,从而增强Request、Response、session功能。
2)然后WebxController会被调用
WebxController对象是由每个子应用独享的,比如petstore中的子应用admin和user,可以有不同的WebxController实现。
3)然后WebxController调用子应用自己的Pipeline
Pipeline也是各个子应用自己配置的。Pipeline中配置了多个valve,依次执行,下面说明几个比较重要的
1.<analyzeURL>
用来分析url获得target
比如url为:http://localhost:8081/user/register.htm,
/user:component path
/register.htm被称为servlet path。
根据默认的映射规则,/register.htm被转换成/register.vm,这个就是target
这个转换被定义在webx-component-and-root.xml中
2.<loop>
1-当<when>中的条件被满足时,就执行这个<when>的分支,如果所有的<when>都不满足,那么就执行<otherwise>分支
<when>
<pl-conditions:target-extension-condition extension="null, vm, jsp" />
......
</when>
如果target的后缀是空(null),或者vm,或者jsp时,执行这个分支
<when>
<pl-conditions:target-extension-condition extension="do" />
....
</when>
如果target后缀是do,就执行下面的分支
2-<performActionValve>
就是用来执行action的,实际上是这样写的
<valve class="com.alibaba.turbine.pipeline.PerformActionValve" actionParam="action"/>
可以看出是由提交请求的action参数的值来确定要执行的Action的名称
例如URL为:http://localhost:8081/user/register.htm?action=RegisterAction,那么就会执行RegisterAction
3-<performTemplateScreen>
用来执行screen和模板,实际写法为:
<valve class="com.alibaba.turbine.pipeline.PerformScreenTemplateValve"/>
假如target为:/xxx/yyy/register.vm,那么valve会:
A)依次在本模块screen下查找对应的类,查找顺序为:
screen.xxx.yyy.register
screen.xxx.yyy.Default
screen.xxx.Default
screen.Default
如果找到了就执行screen,screen的工嗯呢该,通常是读取数据库,然后把模板需要的对象放入context中;
如果找不到也没关系,因为有些页面本来就是静态页面
B)执行完screen中对应的类之后,在/templates/screen目录下,找到并绘制/xxx/yyy/register.vm模板,但是还是需要<renderTemplate>来渲染vm
所以一般<performTemplateScreen>后面都会有<renderTemplate>
4-<renderTemplate>
这里需要将vm渲染并将vm放入layout中,所以就需要两个映射关系:target到screen template;target到layout template
因为这里是将vm渲染,所以vm必须存在,如果不存在就会报404错误。查找方法同上
layout的查找规则为:
/templates/layout/xxx/yyy/zzz
/templates/layout/xxx/yyy/default
/templates/layout/xxx/default
/templates/layout/default
如果layout模板找到了,就把渲染后的screen模板嵌入到layout模板中;如果没有找到,那么就只渲染screen即可5-<performScreen>
执行Screen下面的java类,不去渲染模板
注意:<performTemplateScreen>与<performScreen>的区别:前者除了要执行screen中对应的.java还会执行screen中的.vm;后者只会执行screen中对应的.java
(3)如何传递参数
1)在Action中
方法一:通过类来封装参数(适用于表单参数比较多的情况)
1- 通过定义一个类,和表单中的数据来对应
public class LoginObject {
private String name;
private String passwd;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
2-在Action的方法中使用注解
public void doCheck(@FormGroup("login") LoginObject param, Navigator nav)
这样的话,直接使用对象的getter就可以获得表单中的值
方法二:通过@Param获得单个参数(适用于表单参数比较少的情况)
1- 前台
定义好参数的name
<input type="text" name="userId" value="$!group.userId.value"/>
2- 后台
使用@Param注解获得
2)在Screen中
还是使用注解public void execute(@Param("name") String name, Context context)
(4)请求转发与重定向
1)重定向
使用传入的参数Navigator nav
nav.forwardTo("index").withParameter("name", name);
由于是内部的重定向,所以forwardTo中的参数直接设置为target就可以了
2)请求转发
还是使用传入的参数Navigator nav
nav.redirectTo("app1Link").withTarget("hello").withParameter("name", name);
因为是请求转发,所以需要定义完整的地址。
app1Link在uris中已经定义了:
<uri id="server" requestAware="true" />
<turbine-uri id="app1Link" exposed="true" extends="server">
<componentPath>/</componentPath>
</turbine-uri>
所以完整的地址就是:域名:端口号/hello
(5)如何将查询到的数据放到页面上
在Screen中使用context.put,将数据放入context中
1)单一数据在页面中显示
直接使用 : $数据名称
public class Register {
public void execute(Context context) {
context.put("name", "xxxx");
}
}
页面中
$name
2)List数据显示
使用velocity的foreach标签即可
public class Register {
public void execute(Context context) {
List<String> list = new ArrayList<String>(5);
list.add("abc");
list.add("bbc");
list.add("cbc");
list.add("dbc");
list.add("ebc");
context.put("myList", list);
}
}
页面中:
#foreach( $elem in $myList)
$elem</br>
#end
(6)在Screen中引用control
$control.setTemplate("header.vm")