网站内容管理系统
作者:板桥banq
上页
4 表现层的实现
表现层主要使用Struts框架完成,Struts分两个部分,一部分是视图View的实现,基本是JSP页面。另外一部分是控制器部分,具体是Action类的继承者,通过Action类的实现将前台界面用户输入的表单数据打包成基本对象,递送到后台处理。
在图4-11中,pageAction负责两种页面的输出。一个是创建页面;另一个是编辑页面。由于这两个页面数据结构类似,因此采取统一的Action控制输出。当用户填写好创建页面或编辑页面的表单数据后,单击确认按钮后提交,系统将由SavePageAction负责表单数据的处理和保存。
在SavePageAction中,主要是从用户输入的表单数据PageForm中获取数据,检查合法性,然后将这些数据封装成基本业务对象(Menu或Bodu等),委托给PageHandler实现真正的逻辑处理。而从图4-10中可以看到PageHandler以后的处理过程。

图4-11 表现层的流程图
关于Struts的开发工具,Jbuilder 8以后版本支持Struts开发,也可以使用其他工具甚至文本编辑器进行相关JSP 和Javabean的编辑。Struts的使用涉及面比较多,而且有不少类似“暗沟”的机制,只要耐心地去理解,相信会熟练掌握。
1 Struts相关设置
1.创建一个自己的项目目录。
要在自己硬盘上建立如下图结构的目录系统:
CMS
|
|--- WEB-INF
| |
| |--- classes
| |
| |--- lib
|
|--- navlink (菜单JSP所在目录)
|--- template (布局模板目录)
|--- admin (管理目录)
|--- body (页面内容所在目录)
2.加入所需要的Strutss1.1文件。
将Struts中的*.tld文件加入WEB-INF目录下:
Strutss-bean.tld
Strutss-html.tld
Strutss-logic.tld
Strutss-nested.tld
Strutss-template.tld
Strutss-tiles.tld
Struts的*.jar的库文件加入lib目录下。
3.建立web.xml文件如下。
<?xml version=1.0 encoding=UTF-8?>
<!DOCTYPE web-app PUBLIC -//Sun Microsystems, Inc.//DTD Web Application 2.3//EN http://java.sun.com/dtd/web-app_2_3.dtd>
<web-app>
<description>内容管理系统</description>
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.Strutss.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/Strutss-config.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<taglib>
<taglib-uri>/WEB-INF/Strutss-bean.tld</taglib-uri>
<taglib-location>/WEB-INF/Strutss-bean.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/Strutss-html.tld</taglib-uri>
<taglib-location>/WEB-INF/Strutss-html.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/Strutss-logic.tld</taglib-uri>
<taglib-location>/WEB-INF/Strutss-logic.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/Strutss-template.tld</taglib-uri>
<taglib-location>/WEB-INF/Strutss-template.tld</taglib-location>
</taglib>
</web-app>
关于编码问题。由于Java是跨平台的,Java在进行需要读取文件或显示等操作时,往往是按照运行平台的默认编码方式。在中文Winodws平台是GBK, 在Linux平台是ISO8859-1。由于在项目中用到很多组件框架,比如Strutss等,这些软件的默认编码方式都可能是ISO8859-1。英语环境下的人们还没习惯用UTF-8,那么就很容易造成在整个项目系统中编码不统一,从而导致乱码。
统一编码就成了编写支持中文系统的重要而且棘手的任务,本项目中将使用UTF-8为统一编码。这包括几个方面:JSP输入输出、Javabeans的编译、数据库或文件系统的访问、配置文件的设置。
2 创建PageForm
Struts中需要一个ActionForm,它实际是一个Javabean,字段都是JSP中form的一些字段,如果有过JSP/Javabeans开发经验的人会使用下列代码:
<jsp:useBean id=userInfo class=com.ora.jsp.beans.userinfo.UserInfoBean>
<jsp:setProperty name=userInfo property=* />
</jsp:useBean>
这里UserInfoBean的字段对应HTML中Form表单的字段,从而在Form提交时,表单字段自动映射到UserInfoBean的字段。如果不清楚,可以参阅有关JSP/Javabeans的章节。
将要建立的PageForm其实就是类似UserInfoBean的Javabean:
//pageForm是继承Struts中的ActionForm的
public class PageForm extends ActionForm {
// Page的Id
private Integer id;
//Page的名字,就是菜单的名字
private String name;
//Page的Html内容
private String htmlText;
//Page的标题
private String title;
public void setId(Integer id) { this.id = id; }
public Integer getId() { return id; }
public void setName(String name) { this.name = name; }
public String getName() { return name; }
public void setHtmlText(String htmlText) { this.htmlText = htmlText; }
public String getHtmlText() { return htmlText; }
public void setTitle(String title) { this.title = title; }
public String getTitle() { return title; }
/**
* 下面是设置校验,进行表单的错误检查
* 如果本方法返回是null,Struts将不会进行错误输出
* 如果不为空,将会输出错误,错误内容是error.name.required的值
*/
public ActionErrors validate(ActionMapping actionMapping,
HttpServletRequest httpServletRequest) {
ActionErrors errors = new ActionErrors();
//如果没有输入页面的名字,需要报错
if ( (name == null) || (name.length() < 1)){
Debug.logVerbose( error: name is required , module);
errors.add(name, new ActionError(error.name.required));
}
//如果没有输入页面的标题,需要报错
if ( (title == null) || (title.length() < 1)){
errors.add(title, new ActionError(error.title.required));
}
return errors;
}
//复位,将数据清空
public void reset(ActionMapping actionMapping,
HttpServletRequest httpServletRequest) {
this.id = null;
this.name = null;
this.title = null;
this.htmlText = null;
}
}
在PageForm中validate方法要启用,需要几个步骤:
首先在validate方法中加入字段检验,如果返回不为空,Struts将启动错误输出。
然后,为Strutss错误输出做准备工作,比如上面的name为空,则Struts会到一个ApplicationResources.properties 文件中查询error.name.required对应的值。
在ApplicationResources.properties输入error.name.required的值:
error.name.required=<li>need name</li>
error.title.required=<li>need title</li>
error.id.required=<li>need id</li>
error.action.required=<li>need action</li>
# 下面是错误输出时的标题和尾部,可以自己定制
errors.footer=<hr>
errors.header=<h3><font color=red>Validation Error</font></h3>You must correct the following error
这样,Struts会取得error.name.required对应的值<li>need name</li>输出。那么输出到哪里?当然是JSP页面,只要在需要错误输出的JSP页面中增加如下语句:
<html:errors/>
至于具体位置加在JSP页面的哪个部位,则取决于页面设计了。
注意,最后还有最重要的一步,必须告诉Struts这个ApplicationResources.properties在什么位置。一般ApplicationResources.properties是放在项目的classes目录下和具体的class绑定在一起。
在WEB-INF下建立Strutss-config.xml文件,这是Struts最重要的核心配置文件,需要在其中加入:
<Strutss-config>
<form-beans>
<form-bean name=pageForm type=com.jdon.cms.events.PageForm />
</form-beans>
<message-resources parameter=com.jdon.cms.ApplicationResources />
</Strutss-config>
在Struts-config.xml中,定义了一个FormBean是PageForm。它实际是PageForm类。同时,还需要告诉Struts,ApplicationResources.properties是在类classess目录的com/jdon/cms/events目录下,和com.jdon.cms.events的其他类在一起。
3 创建PageAction
Action是和控制器Servlet在一起工作的,Action是整个系统中关键的、起调度控制作用的类。
在Action类中,将要处理前面页面输入本类的参数,进行相关业务逻辑运算。然后根据结果,从Strutss-config.xml中根据用户定制的Forward页面,将Forward页面推到用户的浏览器中去。
PageAction是控制页面输出的,使用pageAction.do?action=create 或pageAction.do? action=edit控制create或edit页面输出,前者会输出createPage.jsp,后者则会输出editPage.jsp。
PageAction中主要实现两种功能:
如果要求输出的是编辑页面,那么就需要将编辑的数据首先显示出来。
查询Strutss-config.xml,决定输出新增页面还是编辑页面。
PageAction主要代码如下:
public class PageAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) {
String action = request.getParameter(action);
//清除之前的Attribute中的数据
FormBeanUtil.remove(mapping, request);
//获得pageForm对象并在attribute中保存起来,实现第一个功能
extractPageForm(action, mapping, form, request);
//根据action的值,从mapping中查询出决定跳转的页面,然后直接跳转
if (action.equals(PageEvent.DELETE))
return (mapping.findForward(deletePage));
else if (action.equals(PageEvent.EDIT))
return (mapping.findForward(editPage));
else
return (mapping.findForward(createPage));
}
…
}
其中extractPageForm方法主要是实现PageAction的第一个功能,主要内容如下:
private void extractPageForm(action, ActionMapping mapping, ActionForm form,
HttpServletRequest request){
//获得id
Integer id = new Integer(request.getParameter(id));
//通过PageHandler来获得该id的数据
PageHandler pageHandler = new PageHandler();
Page page = pageHandler.getPage(id);
//使用Javabean复制功能,将page对象中和pageForm一样的字段
//复制到pageForm中,这是一个自动方便的转换工具
PropertyUtils.copyProperties(pageForm, page);
}
PageAction中跳转页面的功能是通过mapping.findForward实现的,这个方法是从ActionMapping中获取跳转页面,而ActionMapping的数据是在Strutss-config.xml中设置的:
<action-mappings>
<action attribute=pageForm
type=com.jdon.cms.events.PageAction
validate=false
scope=request
path=/pageAction>
<forward name=createPage path=/admin/createPage.jsp />
<forward name=editPage path=/admin/editPage.jsp />
<forward name=deletePage path=/admin/deletePage.jsp />
</action>
</action-mappings>
在action-mappings中指定了“/admin/createPage.jsp”等几个跳转的JSP文件。
PageAction的第一个功能实现是事先创建好一个ActionForm实例,根据id查询数据源,获得相应的数据并将其赋值到这个实例中,名称为pageForm。
这样,就得到了一个装有数据的pageForm实例,PageAction在自己结束后,如何让editPage.jsp自动获取这个有数据的pageForm,这里有一个Hook钩子,Struts是通过使用Servlet的request或session的attribute来作为中转储存空间的。
如果当前的Scope是request,那么使用
request.setAttribute(actionMapping.getAttribute(), pageForm);
如果当前Scope是session,那么使用
session.setAttribute (actionMapping.getAttribute(), pageForm);
那么,actionMapping.getAttribute()的值是什么?这是在Strutss-config.xml里的action-mapping中设置的,设置attribute=pageForm。
注意这里没有设置通常的name=pageForm,这两者是有区别的。
由于没有设置name=pageForm,那么需要同时设定validate=false。因为这个Action没有与ActionForm相联系,所以不要实现校验功能。
关于使用Action的注意点: Action其实是Servlet的一部分,而Servlet本质就是线程,在默认情况下,Action只有一个实例对象,以多线程方式为所有请求服务,因此编写Action类必须考虑线程安全性:
使用局部变量,不要用类变量。
要考虑资源争夺的问题。
对于Exception的捕获,捕获后输出日志系统,而不要使用throw抛出。
4 创建page.jsp页面
在JSP中消除Java代码分两个步骤:
能够在Action中实现的功能移动到Action中实现。
使用Struts标签语句替代Java代码实现的功能。在JSP中使用Struts特殊标签语句,参考http://jakarta.apache.org/Strutss/userGuide/dev_html.html。
page.jsp是将createPage.jsp和editPage.jsp的功能合并在一起,因为这两个页面的数据布局和结构非常相似。page.jsp代码如下:
<!-- Strutss需要的标识库支持 -->
<%@ page contentType=text/html; charset=UTF-8 %>
<%@ taglib uri=/WEB-INF/Strutss-logic.tld prefix=logic %>
<%@ taglib uri=/WEB-INF/Strutss-template.tld prefix=template %>
<%@ taglib uri=/WEB-INF/Strutss-bean.tld prefix=bean %>
<%@ taglib uri=/WEB-INF/Strutss-html.tld prefix=html %>
<%@ taglib uri=/WEB-INF/app.tld prefix=app %>
<html:html>
<head>
<meta http-equiv=Content-Type content=text/html; charset=UTF-8>
<!-- 根据action判断是否创建? 如果是,标题显示新增页面 -->
<logic:equal name=pageForm property=action
scope=request value=create>
<title>新增页面</title>
</logic:equal>
<!-- 根据action判断是否编辑? 如果是,标题显示编辑页面 -->
<logic:equal name=pageForm property=action
scope=request value=edit>
<title>编辑页面</title>
</logic:equal>
<!-- 根据action判断是否删除? 如果是,标题显示删除页面 -->
<logic:equal name=pageForm property=action
scope=request value=delete>
<title>删除页面</title>
</logic:equal>
<html:base/>
</head>
<body>
<center>
<logic:equal name=pageForm property=action
scope=request value=create>
<h3>新增页面</h3>
</logic:equal>
<logic:equal name=pageForm property=action
scope=request value=edit>
<h3>编辑页面/
<!-- 这里使用了自己定义的标签库,下面章节会解释 -->
<app:linkHtml page=/pageAction.do?action=delete name=pageForm >
删除本页
</app:linkHtml></h3>
</logic:equal>
<p>
<!-- 开始Form表单 是向savePageAction提交-->
<html:form action=/savePageAction.do method=POST>
<!-- 除了创建功能 其他需要id的值 -->
<logic:notEqual name=pageForm property=action
scope=request value=create>
<html:hidden property=id />
</logic:notEqual>
<!-- 下列语句将从pageForm对象中获取相应的值 -->
<html:hidden property=action />
<table><tr><td>
页面名称:</td><td><html:text property=name/>
</td></tr><tr><td>
页面标题:</td><td><html:text property=title/>
</td></tr><tr><td>
页面内容:</td><td>
<html:textarea property=htmlText cols=50 rows=20></html:textarea>
</td></tr></table>
<html:submit property=submit value=确定/><html:reset value =复位/>
</html:form>
</center>
</body></html:html>
从上面JSP页面看出来,这个页面中已经全部没有了Java代码,代替以特有的标签语句,其中典型html:text语法如下:
<html:text property=name/>
相当于以前的代码:
<% if (pageForm.getName() ! = null) %>
<input type=text name=”name value=”<% pageForm.getName() %>”>
<% else %>
<input type=text name=”name >
由此可见,标签库使用是简单方便的。一开始有很多人并不喜欢标签库,从而在JSP中嵌入Java,这种倾向导致的危险是非常大的。可以毫不夸张地说,这样做最终会将Java体系的优越性丧失殆尽。
5 自定义标签库
在page.jsp中,还自定义了一个自己特定的标签:
<app:linkHtml page=/pageAction.do?action=delete name=pageForm >
删除本页
</app:linkHtml>
这相当于:
<a href=<%request.getContextPath()%>
/pageAction.do?action=delete&id=<%id%> >
删除本页
</a>
可见,为了完全取消JSP中的Java代码,必要时,要亲自动手定制标签库。下面看看如何实现。
在page.jsp的顶部引入了app.tld:
<%@ taglib uri=/WEB-INF/app.tld prefix=app %>
这需要在WEB-INF目录下建立app.tld:
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>Application Tag Library</shortname>
<uri>http://jakarta.apache.org/taglibs/</uri>
<info></info>
<!-- 以下是自己定义的标签 -->
<tag>
<name>linkHtml</name>
<!-- 标签类是 com.jdon.cms.tags.LinkHtmlTag -->
<tagclass>com.jdon.cms.tags.LinkHtmlTag</tagclass>
<info> </info>
<!-- 以下是标签类的两个输入参数name 和page -->
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>page</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
在这个app.tld中定义了linkHtml标签,这样在page.jsp中就可以使用
<app:linkHtml page=XXX name=XXX>。
其中page和name是两个属性参数,需要输入到类com.jdon.cms.tags.LinkHtmlTag进行处理的。实际上app.tld是在JSP页面和Javabean之间作了一个连接。编写LinkHtmlTag的代码:
public class LinkHtmlTag extends TagSupport {
protected String name = null;
protected String page = null;
public String getName() { return (this.name); }
public void setName(String name) { this.name = name; }
public String getPage() { return (this.page); }
public void setPage(String page) { this.page = page; }
/**
* 生成标签开始 也就是在JSP中写<app:linkHtml的地方
*
*/
public int doStartTag() throws JspException {
//相当于在JSP中写入<%request.getContextPath()%>
HttpServletRequest request =
(HttpServletRequest) pageContext.getRequest();
StringBuffer url = new StringBuffer(request.getContextPath());
url.append(page);
//根据name属性值,获取pageForm对象
PageForm pageForm = null;
try {
pageForm = (PageForm) pageContext.findAttribute(name);
} catch (ClassCastException e) {
pageForm = null;
}
if (page.indexOf(?) < 0)
url.append(?);
else
url.append(&);
url.append(id=);
url.append(pageForm.getId());
// 产生连接内容,相当于在JSP中写入:
//<a href=”<%request.getContextPath()%>
HttpServletResponse response =
(HttpServletResponse) pageContext.getResponse();
StringBuffer results = new StringBuffer(<a href=\);
results.append(response.encodeURL(url.toString()));
results.append(\>);
// 将results字符串输出到JSP页面 JspWriter writer = pageContext.getOut();
try {
writer.print(results.toString());
} catch (IOException e) {
throw new JspException(LinkHtmlTag error);
}
return (EVAL_BODY_INCLUDE);
}
/**
* 生成替代JSP页面中</app:linkHtml>代码
* 这里是以</a>替代
*/
public int doEndTag() throws JspException {
JspWriter writer = pageContext.getOut();
try {
writer.print(</a>);
} catch (IOException e) {
throw new JspException (LinkHtmlTag error);
}
return (EVAL_PAGE);
}
public void release() {
super.release();
this.page = null;
}
}
标签库表面上好像比较繁琐,没有在JSP直接写Java代码来得直接方便。但是,J2EE的整个体系本身的特点就是细化、细分,这也符合面向对象分派和封装的原则,因此在J2EE中到处可以看到一点一点的“碎片”,XML配置文件和Java代码在J2EE中往往是自然地组合在一起。当然,这样带来的缺点是增加了复杂性。
6 创建SavePageAction
上面的page.jsp是向savePageAction.do提交表单Form的,SavePageAction也是一个Action,主要负责将表单提交的数据实现保存。
SavePageAction和PageAction的用途不一样。后者是主要控制创建或编辑功能页面的输出。这两种Action基本包括了Action的用途,在其他项目中可以参照这两个Action做法,其实这也算一个模式了吧?这个模式主要是对数据创建或编辑功能适用。
为了激活savePageAction.do,需要在Strutss-config.xml中的action-mappings加入:
<action name=pageForm
type=com.jdon.cms.events.SavePageAction
validate=true
input=/admin/pageError.jsp
scope=request
path=/savePageAction />
应该注意到这里使用了name=pageForm,不同于PageAction中使用的attribute=pageForm,两者目的各有不同,因为PageForm和SavePageAction联系了在一起,当page.jsp向SavePageAction提交表单数据时,Strutss将数据保存在PageForm对应的字段中,如果validate又设置为true,那么就会执行PageForm中的validate方法。SavePageAction可以从PageForm中获取page.jsp中的表单数据。
这里还有一层含义,因为page.jsp中提交Action是savePageAction.do,而上面关于savePageAction.do的设置是使用com.jdon.cms.events.SavePageAction类,同时name是pageForm,那么就是说pageForm指向的实例是page.jsp 的FormAction了,或者说是Form Bean,这也说明了为什么在page.jsp显示之前,PageAction将包含数据的PageForm实例保存在request或session的attribute中,而page.jsp在显示时,会自动读取PageForm实例中的数据。
在上面的action-mappings配置中,激活了校验功能。如果Struts校验发现了错误,Struts将会输出错误,那么错误输出哪个JSP页面?就是input中输入的值/admin/pageError.jsp,这里专门做了一个出错页面pageError.jsp。当然不能忘记在pageError.jsp中加入<html:errors/>。
SavePageAction主要是委托业务逻辑层的PageHandler再通过数据层实现数据保存和持久化:
public class SavePageAction extends Action {
public ActionForward execute(ActionMapping actionMapping,
ActionForm actionForm,
HttpServletRequest request,
HttpServletResponse response) {
…
//生成PageEvent
PageEvent pageEvent = new PageEvent();
Page page = new Page();
try {
//将pageForm中的数据复制到Page对象中
PropertyUtils.copyProperties(page, pageForm);
//使PageEvent装载Page数据对象
pageEvent.setPage(page);
//设置操作类型是创建还是编辑
pageEvent.setAction(pageForm.getAction());
}
catch (Exception e) {
Debug.logError(copyProperties Error: + e, module);
}
//委托pageHandler实现具体数据操作
PageHandler pageHandler = new PageHandler();
if (pageHandler.perform(pageEvent)){
Debug.logVerbose(update page success , module);
return actionMapping.findForward(pageOk);
}else
return actionMapping.findForward(pageError);
}
}
当然页面提交后台实现操作后,如果成功,SavePageAction就向前台用户界面输出pageOk.jsp页面,表示操作成功。
至此,本项目中关于Page的内容操作流程基本结束,下面将讨论模板的实现。
7 Tile模板
在前面设计中,一个页面被分为几个区域:标题栏、页尾栏、菜单栏以及内容栏部分。如何使用Tile将这几个部分组合在一起?
Tile的页面组装配置可以在JSP,也可以在tiles-defas.xml中。
关于在JSP中定义已经在前面介绍中看到,只要输入下列语句:
<tiles:insert page=/layout.jsp flush=true>
<tiles:put name=header value=/header.jsp/>
<tiles:put name=body value=/body.jsp/>
<tiles:put name=footer value=/footer.jsp/>
</tiles:insert>
tiles:insert就类似JSP的include,在这个JSP运行时,Strutss-Tiles将分别将header.jsp body.jsp和footer.jsp一起输出。
但是,如果每次将其他不变部分都写入JSP,也将是比较琐碎的事情,在配置文件tiles-defs.xml中定义将更加灵活,还可以有继承的概念。
在WEB-INF下建立tiles-defs.xml文件,当然建立好tiles-defs.xml后,还要记得告诉Strutss-Tiles,那么就要在Strutss-config.xml中加入下列语句:
<plug-in className=org.apache.Strutss.tiles.TilesPlugin>
<set-property value=/WEB-INF/tiles-defs.xml property=definitions-config />
</plug-in>
这表示将要使用插件TilesPlugin,插件的配置文件是/WEB-INF/tiles-defs.xml。
下面建立/WEB-INF/tiles-defs.xml:
<tiles-definitions>
<!-- 定义名叫site.mainLayout 实际是classicLayout.jsp -->
<definition name=site.mainLayout path=/template/no1/layouts/classicLayout.jsp>
<put name=title value= />
<put name=header value=/template/no1/header.jsp />
<put name=footer value=/template/no1/footer.jsp />
<put name=menu value=site.menu.bar />
<put name=body value= />
</definition>
<!-- 定义名叫site.index.page 继承的是上面site.mainLayout -->
<definition name=site.index.page extends=site.mainLayout >
<put name=title value=Home Page />
<put name=body value=/body/body.jsp />
</definition>
<definition name=site.menu.bar path=/navlink/cmsMenu.jsp />
</tiles-definitions>
在tiles-defs.xml中,定义了一个叫site.mainLayout的页面,实际页面是来自相对目录/template/no1/layout/下的classicLayout.jsp。
那么应该在classicLayout.jsp中定义整个页面的大体布局,如下:
<%@ page contentType=text/html; charset=UTF-8 %>
<%@ taglib uri=/WEB-INF/Strutss-tiles.tld prefix=tiles %>
<html><head>
<title> </title>
<meta http-equiv=Content-Type content=text/html; charset=utf-8>
</head>
<body topmargin=5 marginheight=5 leftmargin=5 marginwidth=5>
<table border=0 width=100% cellspacing=0 cellpadding=0><tr> <td >
<!-- 标题栏放置这里 -->
<tiles:insert attribute=header />
</td></tr><tr> <td colspan=2>
<table width=100% border=0 cellspacing=0 cellpadding=0>
<tr><td bgcolor=#666666><font color=#666666> </font></td> </tr>
</table></td></tr><tr><td colspan=2>
<table width=100%> <td width=140 valign=top>
<!-- 菜单栏放置这里 -->
<tiles:insert attribute='menu'/>
</td> <td valign=top align=left>
<!-- 内容栏放置这里 -->
<tiles:insert attribute='body' />
</td></table></td></tr><tr> <td colspan=2>
<table width=100% border=0 cellspacing=0 cellpadding=0>
<tr> <td bgcolor=#666666><font color=#666666> </font></td> </tr>
</table></td></tr><tr> <td colspan=2>
<!-- 页尾栏放置这里 -->
<tiles:insert attribute=footer />
</td></tr></table>
</body></html>
在上面布局中,分别使用tiles:insert 来代替这里的区域,这个页面布局模板是可以自己调换,例如可以将菜单栏搬迁到右边或上下边,这些都可以根据美工师设计要求进行设计,一旦确定4个部分后,只要使用tiles:insert来放置在这些HTML语法中,将来JSP显示时,Strutss-Tiles会用实际的页面自动替代这些tiles:insert。例如:
<put name=header value=/template/no1/header.jsp />
<put name=footer value=/template/no1/footer.jsp />
表示分别用相对目录/tempalte/no1/下header.jsp和footer.jsp替代<tiles:insert attribute=header />和<tiles:insert attribute=footer />部分。
那么,菜单部分使用什么JSP来替代?
8 创建cmsMenu.jsp
菜单不像header.jsp那样,可能不是动态变化的。由于管理员随时可能会增加或删除页面,那么肯定引起指向页面的菜单按钮的变化,所以菜单的显示是需要和本项目中的逻辑处理层联系在一起的。
在上面的tiles-defs.xml中有一句定义菜单的配置:
<definition name=site.menu.bar path=/navlink/cmsMenu.jsp />
这表示菜单是从目录navlink下的cmsMenu.jsp中读取的,因此需要创建cmsMenu.jsp:
<%
//获得PageFactory指向
PageFactory pageFactory = PageFactory.getInstance();
//从pageFactory中获得Navlink实例
Navlink navlink = pageFactory.getNavlink();
request.setAttribute(navlink, navlink);
%>
<%-- 遍历 Navlink对象中menus集合,取出com.jdon.cms.Menu 对象 --%>
<logic:iterate id=menu name=navlink
property=menus type=com.jdon.cms.Menu >
<%
String link = menu.getLink();
%>
<tr> <td >
<a href=<%=link%>><%=menu.getName()%> </a>
</td></tr>
</logic:iterate>
cmsMenu.jsp是将页面Page的Navlink中menus集合逐个读出来,然后从上向下显示。当然具体显示方式(如并排显示)可以自己定制。这个JSP含有Java代码,因此需要重整一下。类似前面章节Page操作一样,使用ActionForm或标识库实现优化。
9 创建index.jsp
为了验证上述配置和设计,可以在根目录下创建index.jsp作为模拟显示,测试观察效果。在index.jsp中写入:
<%@ page contentType=text/html; charset=UTF-8 %>
<%@ taglib uri=/WEB-INF/Strutss-tiles.tld prefix=tiles %>
<tiles:insert definition=site.index.page flush=true />
这表示,在这里插入tiles-defs中的site.index.page页面,而site.index.page是从tiles-defa.xml中得知,正好继承的是site.mainLayout,而site.mainLayout指向classicLayout.jsp,
使用Tiles实现本项目中的模板功能是成功的。当管理员创建一个新的页面时,本系统将依次创建菜单、HTML内容以及模板JSP,在模板JSP中只要写入index.jsp中类似内容。例如,管理员创建了一个叫“产品和服务”的新页面,得到的页面id是2,那么在2.jsp中程序会自动写入:
<tiles:insert definition=site.mainLayout flush=true>
<tiles:put name=title value=产品与服务 />
<tiles:put name=body value=/data/1.jsp />
</tiles:insert>
这样,图4-12屏幕中的“This is body”部分将被管理员输入的有关“产品和服务”的实际内容所代替。
10 小技巧
在J2EE应用中经常需要知道某个文件的绝对路径,如何能从Web容器中自动获得某文件的绝对路径?
例如上节中,1.jsp中body指向的是“/data/1.jsp”,在相对目录data下的1.jsp是系统程序自动生成的。也就是说使用了File有关API操作,而File操作必须提供绝对路径。例如,在项目系统建立c:\cms下,目录结构见前面章节,那么“/data/1.jsp”的绝对路径应该是c:\cms\data\1.jsp。注意,如果有本地文件操作,那么系统就不能打包成*.war,必须提供完整的目录路径。
那么如何获取根目录c:\cms?通过配置文件设定也可以,但是不灵活,每次移动项目都要修改配置文件。
Servlet中有servlet.getServletContext().getRealPath(pathname);,可以得到pathname的绝对路径,因为每个Web应用中,肯定有WEB-INF/web.xml文件,而通过上句就可以获得web.xml的绝对路径。经过简单处理就可以获得系统的绝对路径。
但是在哪里执行语句:servlet.getServletContext().getRealPath(“web.xml”)?
专门写个初始化的Servlet不是很合适;如果在JSP中写入显然不很规范。 Strutss 1.1提供了Plug-in功能,一般是在系统启动先执行Plug-in,前面定义的tiles-defa.xml就是通过Plug-in启动使用的。
只要创建一个类FileEnvPlugIn来继承org.apache.Strutss.action.PlugIn,在init方法中将系统的绝对根目录获得就可以了。有兴趣者可以进一步参考PlugIn的API。
5 项目重整Refactoring
本项目主要开发过程和相关设计代码基本结束,如果需要完整的源代码,可以见本书的光盘,或者到作者网站https://www.jdon.com下载。
整个项目的开发首先是从业务逻辑开始的,也可以称之为Domain logic。其中基本业务对象的建模是一个重要的开始,一旦从一个新的项目中准确本质地提炼出业务对象,那么可以说是成功了一半。但是往往实际情况则是要经过几次反复,从反复设计和实现中会真正发现事物的本质,这也是程序员的快乐源泉之一。
当然,划分成多层后会增加系统的复杂性,Struts的MVC模式分层也增加了使用的难度。当然,随着Struts的图形化开发工具日益成熟,相信很快就可以按动鼠标拖放实现表现层的设计。
本项目很多部分是经过重整(Refactoring)而来,而且还有很多需要重整的地方,重整就是在不改变代码功能,提高代码质量而实行的修改行为,提高代码质量的目的无非是为了降低耦合性、提高重用性和增强灵活和伸缩性。
重整是J2EE项目中不可或缺的一部分,原有的代码经过好的重整,比如重整到一个设计模式等,那么新的功能增加往往只是举手之劳,相反,如果不实行重整,整个代码只能越加越乱,在平常生活中可以找到类似点,假如有一个储物室,一开始只放一点点东西,只要随手一放就可以,下次再进来就很快发现,但是当东西很多时,就不得不整理,以使得他们放置有规律有规则,很显然重整也是这样,将原有代码层次分清楚,归好类,是为了更方便的加入新的代码。
6 调试、发布和运行
6.1 配置和运行
本项目由于只使用了J2EE中的Web框架,因此可以使用专门的Web容器服务器。Tomcat (http://jakarta.apache.org/tomcat/)是免费开源的Web服务器,是Jakarta项目中的最著名的子项目,它还是Sun公司推荐的servlet和jsp容器(http://java.sun. com/products/jsp/tomcat/)。
很多人一直对免费或开放源代码的产品实现商业应用存有疑惑,但是Tomcat作为类似Apache一样的Java服务器,经过几年来大量使用证明,其稳定性和成熟性是有目共睹的。经过最新的研究表明:系统在并发用户100以下时,Tomcat的响应时间和商业服务器相差不多,可见Tomcat是能够大量被应用到中小型企业系统中。
有关Tomcat的详细配置可以参见之前章节。下面是将本项目发布到Tomcat下调试运行:
编辑Tomcat目录下conf目录的Server.xml中找到下列行数:
<!-- Tomcat Root Context -->
<!-- <Context path= docBase=ROOT debug=0/> -->
其后加入本项目的配置:
<!-- 本项目应用 -->
<Context path=/jdoncms docBase=D:/javasource/JdonCMS/web debug=1
reloadable=true crossContext=true />
其中/jdoncms是路径,也就是可以通过http://主机名/jdoncms来访问本项目,而docBase则是本项目的绝对路径。
启动Tomcat,如果想检查启动过程中的错误,可以到Tomcat/logs目录观看相应的日志。在浏览器窗口输入http://localhost:8080/jdoncms。
6.2 Log调试信息的配置
项目第一次调试总会存在诸多问题,因此需要打印出系统中每个步骤的运行结果,这样可以跟踪调试。以前是使用System.out.println来设置输出调试信息。但是这有一个缺点,就是当项目切入正式运行后,不希望这些日志输出到容器的控制台上。因为这样还会降低系统性能,但是如果手工逐个到程序中删除,又将为以后扩展维护带来不便。
以后章节会谈到如何使用Log4J来跟踪调试,但是在Tomcat中设置Log4J不是一件方便的事情。需要做一个专门的Proxy类,通过这个Proxy类,可以动态地设置是使用System.out.println还是Log4J,或者关闭一些调试信息。
定义Log的级别如下:
ALWAYS = 0;
VERBOSE = 1;
TIMING = 2;
INFO = 3;
IMPORTANT = 4;
WARNING = 5;
ERROR = 6;
FATAL = 7;
错误程度分别是从上到下逐步严重,一般实行调试信息输出可以使用VERBOSE,但是在一个大型系统中,如果都实行VERBOSE级别,那么,势必在调试一些重要组件时,其他信息会发生严重干扰。这时可以在这些重要组件中实行其他级别输出,如IMPORTANT,在配置文件中只要指定输出级别就可以。
在本项目配置文件中定义:
<log>
<level>1</level>
<log4j>false</log4j>
</log>
这说明将输出VERBOSE以上信息输出开启,同时表示将不使用Log4J。
在本项目中,Debug.java实现了这样日志Proxy功能。在Debug中,先读取配置文件,以确定当前开启级别是在哪个级别,当外界以如下语句调用时:
Debug.logVerbose( this result is + result , module);
Debug会和配置文件的设置进行比较。如果大于设置的级别,将向控制台输出。至于是使用System.out还是Log4J,将取决于配置文件的第二项配置。
当运行本项目时,Tomcat控制台会输出如下信息:
[Debug:com.jdon.cms.events.PageAction:Verbose] ----> extractPageForm Action
[Debug:com.jdon.cms.events.FormBeanUtil:Verbose] enter to save the arribute
[Debug:com.jdon.cms.xml.NavlinkManager:Verbose] ... try to get the exsited file
D:\javasource\JdonCMS\web\WEB-INF/conf\navlink.xml
这样,如果发生错误,就能跟踪到错误发生位置,从而纠正问题。
7 小结
网站内容管理系统从表面上看似乎是非常简单的一个网站系统。实际上,要开发出一个真正可动态伸缩、适应不同规模网站内容管理系统是有一定的难度的。
本章主要是通过一个简单内容系统的建立,介绍了J2EE的Web层技术,与前面两章同样是介绍Web技术所不同的是:本章的主要特点是从模式和框架的角度去讨论建设一个面向对象的可重用的Web系统。
前两章虽然也是讨论在Web层技术的实现,但是主要侧重在Web层中实现数据库操作管理,这种界面显示和数据操作混合式的设计架构其实是延伸传统的设计概念,最大的缺点也是传统系统的最大问题,即可拓展性和可维护性差。由于设计开发比较简单,在一些网站应用系统或专用系统中应用广泛。
但是,一旦系统的并发访问量提高,对系统性能要求随之提高,那么就必须在这种混合式系统中加入一些提高性能的技术支持层,如对象池、缓存等,开源代码工作流OFBiz和Jive论坛是这方面的典型代表。
在真正的J2EE多层结构中,Web层只被设计成负责用户界面的显示,从Web容器的线程池实现原理也可以了解这点,线程只适合那些处理过程简单短小的功能实现。复杂的业务功能计算将通过EJB等框架实现。
通常在B/S结构下,用户界面的可重用性较低,以对象化概念来设计开发有一定的困难,但是MVC模式的提出和Struts的实现,使得开发一个可重用的面向对象的Web系统成为可能。
当然,Struts的使用对于习惯在JSP中直接写入Java代码的程序员来说是复杂了一点,一般经过多个步骤才能完成一个界面功能的开发,例如要实现ActionForm、Action和配置Strutss-config.xml、在JSP中使用taglib等。
复杂了就容易产生问题,经常可能由于某个环节不小心导致调试不通。特别是数据的增、删、改操作中,如果数据对象有几百个,可能要创建双倍的Action子类,然后再配置Strutss-config.xml,其实每次数据的增、删、改操作过程都是差不多的。
那么,能不能将这种复杂的过程简单化,同时又不丧失使用Strutss的优点呢?针对数据的增、删、改、查通用操作过程,在Struts框架基础上再架构设计一层应用框架,目的是使开发简单、快速而稳定,关于这个Struts应用框架可以见后面章节“网上商店系统”的介绍。
总之,Java是复杂的,但是,Java可以由复杂变成简单。关键在于,需要针对程序员自己的开发领域设计新的应用框架,把那些复杂的东西隐藏到框架后面,这也是“磨刀不误砍柴工”的道理吧。
单从网站内容管理系统上看,使用Java或J2EE实现这个系统并不比其他语言PHP或ASP有更多的优势和好处,但是,在目前Java世界中,网站内容管理系统已经被整合进入门户Portal系统中,内容管理成为Protal系统中一个主要的Portlet。
首页