首先请确定您拥有jdk6或更高版本的java开发包, 并且拥有一个支持servlet 2.4或更高版本的app容器,如tomcat 6, 然后在下载页下载webwheel-1.0-rc.jar,随意使用您喜欢的开发工具,推荐IntelliJ IDEA:)
创建一个空白的web应用的目录,我们取目录名为FirstApp,如下图所示:
web.xml文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<filter>
<filter-name>WebWheel</filter-name>
<filter-class>cn.webwheel.WebWheelFilter</filter-class>
<init-param>
<param-name>main</param-name>
<param-value>firstapp.WebMain</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>WebWheel</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
在classes目录下创建app入口类firstapp.WebMain
WebMain.java文件内容如下:
package firstapp;
import cn.webwheel.*;
import cn.webwheel.utils.*;
public class WebMain extends DefaultMain {
protected void init() {
}
}
如果正常的话,现在就可以编译和部署了,在classes目录下执行编译命令:
然后将整个FirstApp目录拷贝到web服务器的应用列表目录,如tomcat下的webapps目录。(以下章节中,我将省略掉编译和部署步骤,希望您能首先学习javaEE的基本常识)
javac -cp ../lib/webwheel-1.0-rc.jar firstapp/*.java
下面我们来创建一个动态页面,首先在应用根目录建立网站的首页文件index.html,然后在firstapp.web包下面新建一个类,名字为PageIndex。
index.html内容如下:(注意文件编码格式为utf-8)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>FirstApp首页</title>
</head>
<body>
Hello ${message}!
</body>
</html>
PageIndex内容如下:
package firstapp.web;
import cn.webwheel.*;
import cn.webwheel.utils.*;
public class PageIndex extends DefaultPage {
public String message = "WebWheel";
}
PageIndex与index.html所对应,二者的对应关系为:java类名去掉Page开头后首字母小写并在末尾加上后缀.html即html文件的文件名。PageIndex类需要继承自DafaultPage类才能构成映射关系,index.html文件需要满足xml规范,否则格式将出错。WebWheel模板引擎使用${}符号作为表达式解释操作符,如这里的${message}将使用PageIndex的公开属性message进行替换和动态渲染到浏览器。
最后,我们需要更改一下WebMain类的代码,告诉框架自动配置firstapp.web包下面的类文件。
(粗体显示的为更改部分)
package firstapp;
import cn.webwheel.*;
import cn.webwheel.utils.*;
public class WebMain extends DefaultMain {
protected void init() throws Exception {
autoBind("firstapp.web");
}
}
autoBind方法的参数为一个包路径,调用autoBind方法后,框架将扫描此包下面的所有符合条件的页面、组件、动作三种java类,并根据约定进行配置(后面的章节将会详细叙述)。例如这里的PageIndex类,将被映射到/index.html这个uri并且和index.html页面模板装配到一起来执行http响应。
在动态网站中,http参数的传递几乎是必然会用到的,在WebWheel框架中,http参数通过DI方式进行传递,例如上面的PageIndex类中的message属性,如果我们不希望它是一成不变的,而是通过http参数传递而得的,那么我们可以如此来修改其代码:
使用WebParam注释标识需要注入http参数的地方,可以是公开的属性,也可以是公开的方法或构造方法以及其参数。http参数的类型支持:Map<String, String[]>、String、String[]、boolean、byte、byte[]、short、short[]、int、int[]、long、long[]、float、float[]、double、double[]。WebParam的唯一参数即http参数的名称,如这里的msg即对应下述index.html页面上的链接index.html?msg=Beijing中的msg参数名。
package firstapp.web;
import cn.webwheel.*;
import cn.webwheel.utils.*;
public class PageIndex extends DefaultPage {
public String message = "WebWheel";
@WebParam("msg")
public void setMessage(String msg) {
if(msg != null) message = msg;
}
}
为了方便测试,我们将index.html进行修改,如下(仅显示body部分):
<body>
Hello ${message}!
<br/>
index.html <a href="index.html">无参数链接</a>
<br/>
index.html?msg=Beijing <a href="index.html?msg=Beijing">有参数链接</a>
</body>
现在您可以将程序编译部署并看到效果了。
有很多情况下,客户端的请求并不是一个具体页面,而是提交一个动作,例如注册用户、添加日志等等,这时服务器不但要完成动作执行的任务,也要完成页面跳转的工作,也就是MVC中的C的角色。WebWheel提供了方便的手段用以复用动作或者页面。(WebWheel最佳实践中将会提出,服务器端并不适合做C的角色)
我们将在index.html页面上加入一个form表单,将一个message参数传递给动作类,然后动作类做相应处理后跳转到首页。index.html内容增加如下:
index.html?msg=Beijing <a href="index.html?msg=Beijing">有参数链接</a>
<form action="action.do" method="post">
信息:<input name="sendmsg.do"/><input type="submit" value="提交表单"/>
</form>
</body>
然后在firstapp.web包下面新建一个类,名为ActSendmsg,内容如下:
首先,在触发函数execute里,如果参数message不为空,则替换其中的<符号为<,然后使用get方法从DI容器中获得一个页面类实例,设置其message属性,并用其进行渲染页面(调用execute)。
package firstapp.web;
import cn.webwheel.*;
import cn.webwheel.utils.*;
public class ActSendmsg extends DefaultAction {
@WebParam("message")
public String message;
public Object execute() throws Exception {
if(message != null) message = message.replace("<", "<");
PageIndex page = get(PageIndex.class);
page.setMessage(message);
return page.execute();
}
}
现在,请重新编译部署应用,并在index.html的表单输入框中输入包含<符号的文字验证结果,
WebWheel最大的特色之一即在于其独到的模板引擎技术,利用三类特殊的标签属性,做到了对html最大化的友好,使页面开发人员和java程序员可以最大程度的进行并行开发。
首先,修改index.html内容如下,然后我再来一一解释:
</form>
<h4>1.表达式输出</h4><br/>
${svalue}
${svalue.hashCode}
${svalue.class.name}
${revert(svalue)}
${revert(svalue).length}
${revert(svalue).toString().toCharArray()[0]}
<div title="${svalue}">${svalue}</div>
被silence处理的错误表达式:${abcdefg}
<br/>
<h4>2.标签输出</h4><br/>
<div w:tag="tag" w:style="background-color:yellow;${style}" w:content="${content}">I will disappear</div>
<br/>
<h4>3.逻辑控制</h4><br/>
<div q:tag="tagout">我是内容,会出现</div>
<div style="color:red" q:style="false" q:content="false">我是内容,会消失,我的样式,也会消失</div>
<div q:="rand>0.5">我,完全会消失,也许不会</div>
<h4>4.循环和组件输出</h4><br/>
<div w:="svalue">
<span>${revert(this)}</span>
<span w:content="hashCode">hahaha</span>
<div w:="revert(this)" title="${this}">
${this} ${hashCode}
</div>
</div>
<div w:="array" title="第${_idx+1}个${this}">
第${_idx+1}个上下文字符串是"${this}" hashCode:${hashCode}
</div>
这行是保留的缝隙,注意上下文不是array的元素,而是上一级${this}
<div w:="array" title="第2个hahha">
因为array是数组,所以我会消失
</div>
<h4>5.变量定义</h4>
<div v:ss="svalue">
${revert(ss)} - ${ss.hashCode}
</div>
<h4>6.外部组件</h4>
<div w:="*outer"/>
</body>
</body>
WebWheel模板引擎拥有自己的表达式语言(以下简称WEL),其语法和java语言表达式基本相同,不同点在于:如果方法名称以get或者is开头则可以省略掉get或is并将其后的字符小写,如果方法的参数个数为0则可省略掉小括号,例如
参见上述index.html页面中的第一部分。java: a.foo()
WEL: a.foo() 或 a.foo
java: a.getLuck()
WEL: a.getLuck() 或 a.luck() 或 a.getLuck 或 a.luck
java: a.isFunny()
WEL: a.isFunny() 或 a.funny() 或 a.isFunny 或 a.funny
java: a.dowith(b.getHead()[0])
WEL: a.dowith(b.head[0]) 或 ...
如果页面上WEL解析失败,则WebWheel模板引擎做忽略处理,视其为普通的文本(非表达式)做处理。
WEL可支持的数值运算符和逻辑运算符包括:+ - * / % < <= > >= == != && || ?:,运算符的优先级与java语言表达式相同。支持数组取值操作。
WEL可支持的常量包括:字符串,数值常量,true,false,null。
WEL不支持类常量,如String.class,不支持字符常量,不支持位操作。
WEL的执行始终是在一个树状结构的对象上下文之下进行的,顶级对象即当前的模板组件对象(模板所对应的java类的实例),下级为当前表达式所在位置所处的dom组件对象。当WEL求值时,将从内层组件对象逐级向上查找匹配方法,直至模板组件对象(下面会有详细解释)。
WEL可以使用${}符号进行标识,如index.html中的${svalue.hashCode},将会执行表达式svalue.hashCode,并将其结果输出在页面中的相应位置。
WebWheel页面模板利用三类标签属性进行动态内容控制,分别以w: q: v:开头,代表:输出、是否输出、变量定义三种功能。
${exp0}
<div w:="obj1" attr="${exp1}">
${exp1}
<div w:="obj2" attr="${exp2}">
${exp2}${this}
</div>
<div w:="obj3" attr="${exp3}">
${exp3}
<div w:="obj4" attr="${exp4}">
${exp4}
</div>
</div>
</div>
此模板中每个表达式exp对应的上下文都是相同编号的obj(exp0的上下文为模板所对应的java类对象),例如exp3对应obj3。每个表达式首先对于当前上下文进行求值,如果求值失败,再使用父级上下文,直至模板java类对象。
<div>
我是${msg}
</div>
它也必须满足WebWheel模板的要求(松散的xml)。
上述index.html模板与outer.html模板分别对应的组件类是PageIndex与CompOuter,源码如下:
PageIndex.java
CompOuter.java
package firstapp.web;
import cn.webwheel.*;
import cn.webwheel.utils.*;
public class PageIndex extends DefaultPage {
public String message = "WebWheel";
@WebParam("msg")
public void setMessage(String msg) {
if(msg != null) message = msg;
}
public String svalue = "svalue";
public StringBuilder revert(String s) {
if(s==null) return null;
StringBuilder sb = new StringBuilder();
for(int i=s.length()-1; i>=0;i--) sb.append(s.charAt(i));
return sb;
}
public String getTag() {
return "span";
}
public String getStyle() {
return "color:blue";
}
public String getContent() {
return "I am content";
}
public boolean tagout() {
return false;
}
public double getRand() {
return Math.random();
}
public String[] getArray() {
return new String[]{"Chiang", "Kai", "shek"};
}
public Object getOuter() {
return new CompOuter();
}
}
package firstapp.web;
public class CompOuter {
public String msg = "外部组件";
}
WebWheel框架使用DefaultMain.autoBind方法执行http请求和页面模板的约定映射(同时可进行更加自由的手工映射)。
autoBind方法需要一个字符串类型的参数,代表需要映射的包路径,它将扫描此包下符合若干规则的类并对其进行映射。
可映射的类分为三种:页面类,模板组件类和动作类,分别要求以Page、Comp和Act作为类名的前缀。
对于动作类,要求此类必须直接或间接实现cn.webwhee.Action接口。autoBind截取扫描包以下的动作类的包路径并加上除去前缀的类名和.do后缀作为http请求的路径,并将其绑定到此动作类之上。例如有一个动作类:
当在DefaultMain.init方法中执行autoBind("com.abc")后,即会调用DefaultMain.bindAction("/xyz/dosome.do", ActDosome.class)方法进行绑定。当http请求为/xyz/dosome.do时此动作类将会被触发。
package com.abc.xyz;
public class ActDosome implements Action {
...
}
对于模板组件类,要求与此类相对于扫描包的同样类路径位置,在web应用下有同名的模板文件。例如有一个模板类:
并且在web应用目录的子目录xyz下具有一个文件名为temp.html:
package com.abc.xyz;
public class CompTemp {}
当在DefaultMain.init方法中执行autoBind("com.abc")后,即会调用DefaultMain.bindComponent(CompTemp.class, "/xyz/temp.html")方法进行模板绑定。这时,当页面中引入外部模板,w:表达式的返回值类型为CompTemp或其子类时,外部模板将会正常渲染。
<div>
...
</div>
对于页面类,必须要同时满足动作类和模板组件类的要求,并以Page为类名前缀。例如有一个页面类:
并且在web应用目录的子目录xyz下具有一个文件名为index.html:
package com.abc.xyz;
public class PageIndex extends DefaultPage {
...
}
当在DefaultMain.init方法中执行autoBind("com.abc")后,即会调用DefaultMain.bindPage("/xyz/index.html", PageIndex.class)方法进行模板绑定。这时,当/xyz/index.html的http请求到达框架时,此类将被触发,并且通过DefaultPage的默认execute方法返回模板渲染结果。<div>
...
</div>
注:DefaultPage.execute方法只有一行:return new TemplateResult(this)。当this对象为模板组件类时才可生效,因为bindPage方法中绑定了此类为模板组件类,所以这里这样使用没有问题。
Action接口唯一的方法execute的返回值在逻辑上是动作执行的结果,如何解释这些结果是通过结果类解释器完成的。在WebWheel中有一个结果类解释器的注册表,每一个结果类类型被映射到一个结果类解释器实例,当框架得到一个Action返回的结果时,便在此注册表中找到最适合的解释器,并把解释权利交给它。查找解释器的过程为先使用结果对象的类型找对应的解释器,再按照结果对象的接口找,如果仍未找到则取得父级类型按此规则递归查找。
注册结果类解释器通过Main.registerResultType方法进行,此方法的签名为:
第一个参数为结果对象的类型,第二个参数为结果解释器实例。
public final <T> void registerResultType(Class<T> resultType, ResultInterpreter<? extends T> interpreter)
WebWheel内建有两个结果解释器,一个是模板结果TemplateResult的解释器TemplateResultInterpreter,一个是简单结果SimpleResult的解释权SimpleResultInterpreter。
模板结果TemplateResult主要有两个参数,一个是模板文件的位置,一个是组件类或组件实例,当使用autoBind或通过bindPage/bindComponent进行绑定之后,则TemplateResult里面的模板文件位置便可忽略。