(English version see README-English.md)
jWebBox
这是一个服务端(支持JSP和FreeMaker)页面布局工具,特点是简单,无XML,仅用500行源码实现了与Apache Tiles类似的页面布局功能。
目前一些服务端JSP页面布局工具的缺点:
Apache Tiles: 功能强大但过于臃肿,源码复杂,第三方库引用多,XML配置不方便,动态配置功能差。
Sitemesh: 采用装饰器模式,功能不如Apache Tiles灵活。
JSP Layout或Stripes等JSP布局工具:功能不够强,在布局的继承或参数传递上有问题。
JWebBox特点:
简单, 整个项目仅500行源码,易于学习和维护。
与jBeanBox和jSqlBox项目类似,用纯JAVA类代替XML配置(实际上前两个项目是受此项目启发),支持动态配置,配置可以在运行期动态生成和修改。
无侵入性,支持JSP和FreeMaker两种模板混用。可用于整个网站的服务端布局,也可用于编写页面局部零件。
支持静态方法、实例方法、URL引用三种数据准备方式。
可利用它搭建小巧的MVC架构,向复杂的Spring-MVC告别,详见jBooox项目。
使用方法:
在项目的pom.xml中添加如下内容:
com.github.drinkjava2
jwebbox
2.1.2
javax.servlet
javax.servlet-api
3.0.1
provided
javax.servlet.jsp
javax.servlet.jsp-api
2.3.1
provided
jWebBox运行于Java6或以上,依赖于javax.servlet-api和javax.servlet.jsp-api这两个运行期库(通常由Servlet容器提供)。
详细介绍
以下通过对示例的解释来详细说明jWebBox的使用,示例项目源码位于项目的jwebbox-demo目录下,在项目的根目录,也有一个打包好的jwebbox-demo.war文件,可直接扔到Tomcat或WebLogic里运行。
示例1 - 一个带菜单和底脚的左右布局
服务端代码如下:
public static class demo1 extends WebBox {
{ this.setPage("/WEB-INF/pages/homepage.jsp");
this.setAttribute("menu",
new WebBox("/WEB-INF/pages/menu.jsp").setAttribute("msg", "Demo1 - A basic layout"));
this.setAttribute("body", new LeftRightLayout());
this.setAttribute("footer", "/WEB-INF/pages/footer.jsp");
}
}
public static class LeftRightLayout extends WebBox {
{ this.setPage("/WEB-INF/pages/left_right_layout.jsp");
ArrayList boxlist = new ArrayList();
boxlist.add("/WEB-INF/pages/page1.jsp");
boxlist.add("/WEB-INF/pages/page2.jsp");
this.setAttribute("boxlist", boxlist);
}
}
其中homepage.jsp是主模板文件,主要内容如下:
left_right_layout.jsp是一个布局模板,内容如下(其它的JSP文件类似,此处略,详见示例):
解释:
setPage方法用于设定当前WebBox实例的目标页面(可选),WebBox构造器允许带一个页面参数。
setAttribute方法在WebBox的一个内部HashMap中暂存一个键值,值可以为任意Java对象类型,相应地取值用getAttribute方法,在JSP中可以用EL表达式${jwebbox.attributeMap.keyname}获取。
在JSP页面中调用标签来显示对应键值的页面,值只能是String、WebBox实例或它们的List。
show标签的另一个用法是, target只能是String、WebBox或List。如下5种写法在JSP中是等同的:
//仅当menu属性为WebBox对象时
后三种写法不推荐,但有助于理解WebBox的运作机制。每个被WebBox调用的页面,都在request中存在一个WebBox实例,可以用request.getAttribute("jwebbox")或EL表达式${jwebbox}获取。
show标签使用时必须在JSP页面加入TagLib库的引用:
每个WebBox实例,可以设定一个可选的name属性,每个页面用只能获取属于自已的一个WebBox实例,但是可以用getFatherWebBox方法获取当前WebBox实例的调用者所在页面的WebBox实例(有点绕口)。
在JSP和Servlet中,jWebBox支持在页面中动态生成WebBox实例并调用show方法显示,例如:
本示例项目中运用了一个小技巧,利用一个Servlet将所有".htm"后缀的访问转化对WebBox的创建和显示,在web.xml中配置如下
htm2box
/htm2box.jsp
htm2box
*.htm
其中htm2box.jsp当作Servlet来使用,作用类似于Spring MVC中的DispatcherServlet:
String uri=StringUtils.substringBefore(request.getRequestURI(),".");
uri = StringUtils.substringAfterLast(uri, "/");
if (uri == null || uri.length() == 0)
uri = "demo1";
WebBox box = (WebBox) Class.forName("com.github.drinkjava2.jwebboxdemo.DemoBoxConfig$" + uri).newInstance();
box.show(pageContext);
%>
示例1的输出:
示例2 - 布局的继承
服务端代码:
public static class demo2 extends demo1 {
{ ((WebBox) this.getAttribute("menu")).setAttribute("msg", "Demo2 - Change body layout");
this.setAttribute("body", new TopDownLayout());
}
}
public static class TopDownLayout extends LeftRightLayout {
{ this.setPage("/WEB-INF/pages/top_down_layout.jsp");
}
}
demo2继承于demo1类,将"body"属性改成了一个上下布局top_down_layout.jsp模板(源码见示例)。
示例2的输出:
示例3 - 数据准备
服务端代码:
public static class demo3 extends demo1 {
{ setPrepareStaticMethod(DemoBoxConfig.class.getName() + ".changeMenu");
setAttribute("body", new WebBox().setText("
.setPrepareURL("/WEB-INF/pages/prepare.jsp").setPrepareBean(new Printer()));
setAttribute("footer", new WebBox("/WEB-INF/pages/footer.jsp").setPrepareBean(new Printer())
.setPrepareBeanMethod("print"));
}
}
public static void changeMenu(PageContext pageContext, WebBox callerBox) throws IOException {
((WebBox) callerBox.getAttribute("menu")).setAttribute("msg",
"Demo3 - Prepare methods
This is modified by \"changeMenu\" static method");
}
public static class Printer {
public void prepare(PageContext pageContext, WebBox callerBox) throws IOException {
pageContext.getOut().write("This is printed by Printer's default \"prepare\" method
");
}
public void print(PageContext pageContext, WebBox callerBox) throws IOException {
pageContext.getOut().write("This is printed by Printer's \"print\" method
");
pageContext.getOut().write((String) pageContext.getRequest().getAttribute("urlPrepare"));
}
}
相比与普通的Include指令,Apache Tiles和jWebBox这类布局工具的优势之一在于可以在各个子页面加载之前进行数据准备工作,从而达到模块式开发的目的。jWebBox有三种数据准备方式:
setPrepareStaticMethod方法指定一个静态方法用于数据准备。
setPrepareBean方法指定一个对象实例用于数据准备,用setPrepareBeanMethod来指定对象的方法名,如果不指定方法名,将缺省使用"prepare"作为方法名。
setPrepareURL方法将调用一个URL来作为数据谁备,这是一个服务端的URL引用,可以访问/WEB-INF目录下的内容。
setText方法可以额外设置一小段文本,将直接作为HTML代码片段插入到子页面前面。
各个准备方法及页面输出的顺序如下:
prepareStaticMethod -> prepareBeanMethod -> PrepareURL -> text output -> page
示例3输出:
示例4 - 列表
服务端代码:
public static class demo4 extends demo1 {
{
((WebBox) this.getAttribute("menu")).setAttribute("msg", "Demo4 - List");
ArrayList child = new ArrayList();
for (int i = 1; i <= 3; i++)
child.add(new WebBox("/WEB-INF/pages/page" + i + ".jsp").setText(" "));
ArrayList mainList = new ArrayList();
for (int i = 1; i <= 3; i++) {
mainList.add("/WEB-INF/pages/page" + i + ".jsp");
if (i == 2)
mainList.add(child);
}
this.setAttribute("body", mainList);
}
}
如果属性是一个列表,当JSP页面中调用方法时,如果值是一个List,将假定List中属性为页面或WebBox实例并依次显示。
示例4输出:
示例5 - FreeMaker模板支持
从2.1版起,jWebBox开始支持FreeMaker,且可以与JSP混用,例如如下配置:
public static class demo5 extends WebBox {
{ this.setPage("/WEB-INF/pages/homepage.ftl");
this.setAttribute("menu",
new WebBox("/WEB-INF/pages/menu.jsp").setAttribute("msg", "Demo5 - Freemaker demo"));
this.setAttribute("body", new FreemakerLeftRightLayout());
this.setAttribute("footer", new WebBox("/WEB-INF/pages/footer.jsp"));
}
}
FreeMaker不支持直接在页面嵌入Java代码,语法也与JSP不同,引入标签要写成, show标签要写成
使用FreeMaker,需要在web.xml中添加如下配置:
freemarker
freemarker.ext.servlet.FreemarkerServlet
TemplatePath
/
freemarker
*.ftl
并在pom.xml中添加对FreeMaker库的依赖:
org.freemarker
freemarker
2.3.23
示例5输出:
示例6 - 表格和分页演示
这个例子展示了利用WebBox配置的继承功能来创建表格和分页条组件,输出两个表格和分页条,并处理表单提交数据。因篇幅较长,此处只摘录布局部分代码:
public static class demo6 extends demo1 {
{
setAttribute("menu",
((WebBox) this.getAttribute("menu")).setAttribute("msg", "Demo6 - Table & Pagination"));
List bodyList = new ArrayList();
bodyList.add(new TableBox());
bodyList.add(new TablePaginBarBox());
bodyList.add(new WebBox().setText(
"
-----------------------------------------------------------------------------------"));
bodyList.add(new CommentBox());
bodyList.add(new CommentPaginBarBox());
bodyList.add(new WebBox("/WEB-INF/pages/commentform.jsp"));
this.setPrepareStaticMethod(DemoBoxConfig.class.getName() + ".receiveCommentPost");
this.setAttribute("body", bodyList);
}
class TableBox extends WebBox {
{
this.setPrepareBean(new PrepareForDemo6()).setPrepareBeanMethod("prepareTable");
setPage("/WEB-INF/pages/page_table.jsp");
setAttribute("pageId", "table");
setAttribute("targetList", tableDummyData);
setAttribute("row", 3).setAttribute("col", 4);
setAttribute("render", new WebBox("/WEB-INF/pages/render_table.jsp"));
}
}
class TablePaginBarBox extends TableBox {
{
this.setPrepareBean(new PrepareForDemo6()).setPrepareBeanMethod("preparePaginBar");
setPage("/WEB-INF/pages/pagin_bar.jsp");
}
}
class CommentBox extends TableBox {
{
setAttribute("pageId", "comment");
setAttribute("targetList", commentDummyData);
setAttribute("row", 3).setAttribute("col", 1);
setAttribute("render", new WebBox("/WEB-INF/pages/render_comment.jsp"));
}
}
class CommentPaginBarBox extends CommentBox {
{
this.setPrepareBean(new PrepareForDemo6()).setPrepareBeanMethod("preparePaginBar");
setPage("/WEB-INF/pages/pagin_bar.jsp");
}
}
}
示例6截图:
以上即为jWebBox的全部说明文档,如有不清楚处,可以查看项目源码或示例项目的源码。
附录-版本更新记录:
jWebBox2.1 添加FreeMaker模板支持;增加一个JSP标签;添加了表格、分页、表单处理的演示;更正WebLogic不能运行的bug。
jWebBox2.1.1 添加了beforeShow、beforeexecute、execute、afterExecute、afterShow、afterPrepared几个空方法作为回调函数给子类用。示例详见jBooox项目。
jWebBox2.1.2 show()方法原来为void类型,现改为WebBox实例,便方便使用。