采用DQM技术可以很容易做出动态页面,可是有些情况下,动态页面执行后的内容会长时间不变,例如一个公告系统,有可能几个月才发布一次新公告(写入数据库),但每天读公告的用户却很多(每次访问都要重新读取数据库)。因为公告更新频率很低,所以大部分时间用户所读取到的公告内容是相同的,这种情况下我们如果在每次发布新公告时生成静态页面,那么用户每次读取此静态页面就可以得知最新公告内容了,从而免去了执行动态文件和读取数据库的开销。当然我们完全可以手动生成这些静态页面,不过既麻烦又难以维护,而接下来所介绍的就是如何让系统自动的生成静态页面
动态页面如果要生成静态页面则首先要提供一个唯一的静态ID号,服务器会 根据这个ID号去检查静态页面,如果对应于此ID号的静态页面存在则直接读取,否则生成对应于此ID的静态页面。可以看出在静态页面功能中这个ID是很重要的,那么我们该如何指定ID呢?DQM容器为动态文件提供了一个类方法,只需要覆盖此方法就可以指定静态页面ID。
public String getStaticPageId(
DRequestItface request, DSessionItface session, DApplicationItface application)
根据当前访问的条件指定静态页面的ID。
参数:request当前访问状态下的内置对象,与第6章介绍的是一样的。
session当前访问状态下的内置对象,与第8章介绍的是一样的。
application当前访问状态下的内置对象,与第9章介绍的是一样的。
返回:根据当前访问的条件指定静态页面的ID ,如果返回null则表示本动态页面无需启用静态页面生成功能。
接下来我们来解决几个疑问。
首先request、session和application这些内置变量不是可以直接使用吗?为什么还要将这些变量传入getStaticPageId类方法中?因为在类方法中是无法访问到内置变量的,类方法是写在<%!和%>标记之间的,而在<%和%>标记间才能直接使用内置变量。
getStaticPageId是系统保留的类方法,所以请不要重定义此方法的返回类型,例如如果定义了下面的类方法编译时候就会出错。
public int getStaticPageId(
DRequestItface request, DSessionItface session, DApplicationItface application)
那么如果没有在当前动态页面中覆盖此方法会出错吗?当然是不会出错的,如果没有覆盖此方法则会使用默认提供的方法,默认的方法将会直接返回null,即不开启静态页面生成功能。
你或许也会很疑惑request、session和application这三个传入的内置变量有什么用?和生成静态文件ID有何关联?其实有莫大的关联,比如具有分页功能的动态文件,其第一页和第二页的内容是不相同的,于是要针对每一页的内容生成静态文件,而每个静态文件必须有自己唯一的ID号,所以我们必须根据每一页来指定其的ID号,而区分用户当前所访问的是哪一页就有可能要用要request、session和application这三个变量。
我们来举个例子,/test/mpage.dqm是可以分页显示的动态文件,根据访问时附带的page参数可以决定当前显示第几页内容,如果显示第一页则屏幕上显示“这是第1页的内容”, 如果显示第二页则屏幕上显示“这是第2页的内容”,以此类推。
以下是根据page属性的值确定显示哪一页的规则:
/test/mpagedqm?page=1 或 /test/viewnews.dqm 则显示第一页
/test/mpage.dqm?page=2 显示第二页
/test/mpage.dqm?page=hd 如果page参数的值无法转换成数字则同样显示第一页
于是我们要对viewnews.dqm生成静态文件就必须考虑到这些,从而指定合理的静态文件ID。源程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<% int pageid; String pageIdStr = request.getParameter("page"); if (pageIdStr != null) { try { //获取当前所要显示的页数 pageid = Integer.parseInt(pageIdStr); } catch(NumberFormatException e) { //如果无法转换成数字 pageid = 1; } } else { //如果没有附带参数则默认为第一页 pageid = 1; }
out.println("这是第"+ pageid + "页的内容"); %>
<%! public String getStaticPageId(DRequestItface request, DSessionItface session, DApplicationItface application) { String pageIdStr = request.getParameter("page"); if (pageIdStr != null) { try { return "/test_folder/mpage.dqm_page" + Integer.parseInt(pageIdStr); } catch(NumberFormatException e) { //如果无法转换成数字 return "/test_folder/mpage.dqm_page" + 1; } } else { //如果没有附带参数则默认为第一页 return "/test_folder/mpage.dqm_page" + 1; } } %> |
源程序4行用于判断当前请求是否附带page参数,如果有附带了则判断其值是否是数字(第7行),如果是数字则显示当前是此传入数字所在页(第17行),如果没有附带page参数(13到15行)或page参数值为不是数字(9到11行)则显示当前是第一页。
20行到35行是覆盖了getStaticPageId方法,用于生成静态页面,22行可以看到从传入的request内置变量中读取当前请求的page参数,如果当前请求附带了page参数(23行)则在判断附带的参数值是否为数字,如果是数字则根据当前请求的页数返回静态页面ID(25行),如果请求没有附带page参数或参数值不是数字则返回第一页的静态页面ID(28行和32行)。
接下来我们来看看最重要的静态页面ID的指定,源程序25、28和32行都用于返回静态页面ID,在了解如何制定静态ID前首先要搞清楚这个ID的用处,其实很简单每个静态文件ID就对应了一个静态页面文件。以当前的例子来说,当用户请求访问第一页时源程序28和32行返回"/test_folder/mpage.dqm_page1"这个ID,于是服务器回去寻找是否存在这个静态文件,如果存在则直接输出,如果不存在则先运行动态文件然后将运行后的内容写入对应的静态文件中,下次再访问就可以直接输出已生成的静态文件了。用户可以试着访问上面动态文件第一页,你会发现在web根目录下的WEB-INF目录下会多一个static_page目录,而此目录下会多三个文件,见下图:
图12-1-1
我们可以看到这三个文件的主文件名是相同的,都是e0006b1ab3bc729274f010eefd0e73bb,为什么会是那么奇怪一个名字,其实这是/test_folder/mpage.dqm_page1的MD5编码,因为MD5编码是一一对应的,所以你可以将e0006b1ab3bc729274f010eefd0e73bb看作是/test_folder/mpage.dqm_page1的另一种书写格式,而/test_folder/mpage.dqm_page1是由源程序的28或32行返回的。不过似乎又有一个问题,为什么会有三个文件?
在前面4.3节中介绍过系统支持二种压缩输出(gzip和deflate),所以系统会同时生成三个静态文件,二个是压缩文件(扩展名是gzip和deflate),一个是 未压缩文件(扩展名是no)。如果当前浏览器支持gzip压缩,则就会返回扩展名为gzip的静态文件内容,以此类推。下表列出了访问viewnews.dqm的第一页时所有可能的情况:
当前访问浏览器支持的压缩格式 |
生成的静态ID |
静态文件名(静态ID的MD5编码) |
gzip |
/test_folder/mpage.dqm_page1 |
e0006b1ab3bc729274f010eefd0e73bb.gzip |
deflate |
/test_folder/mpage.dqm_page1 |
e0006b1ab3bc729274f010eefd0e73bb.deflate |
不支持以上压缩 |
/test_folder/mpage.dqm_page1 |
e0006b1ab3bc729274f010eefd0e73bb.no |
表12-1-2
从上面的解释读者因该知道为何会一下子生成三个静态页面文件了,同时注意如果是.gzip结尾的(即上表中内容第1行),则生成的静态文件内容是经过gzip压缩的。如果是.deflate结尾的(即上表中内容第2行),则生成的静态文件内容是经过deflate压缩的。如果是.no结尾的(即上表中内容第3行),则生成的静态文件内容是未经压缩的。
每个静态ID都有可能会有三个静态文件,这看起来真是够复杂的,不过对于用户来说根本不需要去关心这个,因为系统会自动处理这些文件,对于用户来说全部都是透明的,而介绍这些的目的只是为了让用户更好的理解内部原理。所以我们接下来讨论的话题还是转回静态ID。前面已经说过其实每个静态文件ID就对应了一个静态页面文件(其实有可能对应三个,但是对用户来说是透明的,所以用户可以认为就是对应一个静态页面文件)。从viewnews.dqm的源程序中可以看到此动态文件的每一页内容都不一样,所以必须根据每一页来生成静态页面。那如果有100页是不是就要生成100个静态页面呢?的确是,因为这100页的内容都不相同,而为了生成这100个静态页面则必须提供100个互不相同既唯一的静态ID,因为系统会根据提供的静态ID来作为生成的静态页面的文件。如果提供的静态ID有重复会怎样呢?举个例子,假设viewnews.dqm的第一页和第二页所提供的静态ID是一样的,那么如果第一页的静态页面文件先生成,则访问第二页时就会把第一页的静态内容作为结果返回,而如果第二页的静态页面文件先生成,则访问第一页时就会把第二页的静态内容作为结果返回。
从上面的介绍我们可以看到生成静态页面文件的核心就是生成静态ID,更具体点就是根据动态文件执行后的不同结果逐个生成唯一的静态ID。从viewnews.dqm源程序25、28和32行中可以看到我们是采用动态页面的完整路径加上page数来生成唯一的静态ID,我们推荐使用该方法,为了更灵活用户可以通过前面6.6节介绍的方法来自动获取当前静态页面的完整路径,修改后的源程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
22 23 24 25
26 27 28 29 30 31 32 33 34 35 |
<% int pageid; String pageIdStr = request.getParameter("page"); if (pageIdStr != null) { try { //获取当前所要显示的页数 pageid = Integer.parseInt(pageIdStr); } catch(NumberFormatException e) { //如果无法转换成数字 pageid = 1; } } else { //如果没有附带参数则默认为第一页 pageid = 1; }
out.println("这是第"+ pageid + "页的内容"); %>
<%! public String getStaticPageId(DRequestItface request, DSessionItface session, DApplicationItface application) { String pageIdStr = request.getParameter("page"); if (pageIdStr != null) { try { return request.getNowPath() + "_page" + Integer.parseInt(pageIdStr); } catch(NumberFormatException e) { //如果无法转换成数字 return request.getNowPath() + "_page" + 1; } } else { //如果没有附带参数则默认为第一页 return request.getNowPath() + "_page" + 1; } } %> |
当然用户也可以根据自己的实际情况来生成静态ID,只要符合上面所提到的规范。
viewnews.dqm在生成静态文件时只使用到了传入的request内置对象,这是因为单单使用request就已经可以生成静态ID了,但是如果判断当前应该显示第几页的page属性是在session中则就要用到session这个内置对象了。
上节介绍了如何指定静态ID以便开启静态页面功能,那么用户能不能知道当前生成了多少静态页面?静态ID所对应的静态页面文件名?回答是当然可以,因为静态ID及其页面信息都会在系统中注册,而用户可以随时从系统中读取这些注册信息。
如何获取静态页面的注册信息呢?还记得第10章介绍的command内置对象吗?command内置对象也含有相关静态页面的方法,我们将在这节介绍。
1. public int getStaticPageCount()
返回当前主机或虚拟主机中所有注册的静态页面数。
返回:当前主机或虚拟主机中所有注册的静态页面数
2. public boolean clearStaticPage(String staticPageId)
清除当前主机或虚拟主机中指定的已注册静态页面。
参数:staticPageId指定要删除的已注册静态ID。
返回:如果删除成功返回true,否则返回false。
3. public void clearAllStaticPage()
清除当前主机或虚拟主机中所有注册的静态页面。
返回:如果删除成功返回true,否则返回false。
第一个方法是比较好理解的,用于查看当前已经生成了的静态页面数,那么2和3方法的用途是什么呢?因为系统不会检查动态文件是否已经过期,所以必须使用这2个方法手动删除静态页面的注册,这样当再次访问次动态文件就会重新生成静态页面文件。
我们来举个例子,/test_folder/readfile.dqm会读取与其同一个目录下的content.txt文件内容,并将其输出。源程序如下:
1 2 3 4 5 6 7
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
27 28 29 |
<pre> <% File readFile = new File(request.getNowFolder() + File.separatorChar + "content.txt"); if (readFile.exists()) { BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(new FileInputStream(readFile), "GBK")); String line; out.println("以下是从content.txt中读取的内容:"); while ((line = br.readLine()) != null) { out.println(line, "GBK"); } } finally { if (br != null) { br.close(); } } } else { out.print("文件没有找到!"); } %> </pre> <%! public String getStaticPageId(DRequestItface request, DSessionItface session, DApplicationItface application) { return "/test_folder/readfile.dqm"; } %> |
上面源程序第3行定义了在同目录下需要读取的content.txt文件,第4行检查content.txt文件是否存在,如果不存在则输出文件没有找到(第21行),如果文件存在则输出此文件的内容(4到19行)。
25到29行可以看到覆盖了getStaticPageId方法,说明开启了静态页面功能。
接下来我们看一下content.txt中的内容:
小明 小王 小张 |
于是当我们执行readfile.dqm的结果就是:
以下是从content.txt中读取的内容: 小明 小王 小张 |
从上面结果来看程序正确的读取了content.txt中的内容了。接下来我们就要开始进入正题了,在content.txt原先内容后再加上一行,修改后的content.txt内容如下:
小明 小王 小张 小李 |
之后我们再次执行readfile.dqm,可是结果和没修改前一样,这是为什么呢?因为readfile.dqm是开启了静态页面功能的,而系统是不会检测先前生成的静态页面是否已经过期了,所以当再次访问时系统只是返回了先前生成的静态页面内容。那如何才能解决这个问题呢?这就要用到上面刚提到的2和3的方法,我们可以用这二个方法手动删除静态页面的注册,这样当再次执行readfile.dqm后会重新生成静态页面的。
clearStaticPage.dqm这个程序是用于操作“以注册的静态页面”的程序,其源代码如下:
1 2 3 4 5 6 7 8 9 10
11
12
13
14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
<%@page command="true"%> <% String password = "123456"; if (request.getParameter("B1") == null) { %>
<form method="POST" action=""> <p align="center"><b><font size="5">静态页面注册清除工具</font></b></p> <p align="center">操作密码:<input type="password" name="psw" size="20"></p> <p align="center"><input type="radio" name="system" value="getSize" checked>返回当前主机中含有的注册静态页面数</p> <p align="center"><input type="radio" name="system" value="clearAll">清除当前主机中所有注册静态页面</p> <p align="center"><input type="radio" name="system" value="clear">清除当前主机中指定的注册静态页面 静态ID:<input type="text" name="staticID" size="20"></p> <p align="center"><input type="submit" value="Submit" name="B1"><input type="reset" value="Reset" name="B2"></p> </form>
<%} else {%>
<p align="center"><a href="<%=request.getNowUrlFolder() + request.getNowFile().getName()%>">返 回</a></p>
<% if (!password.equals(request.getParameter("psw"))) { out.println("操作密码错误!!"); return; } // String system = request.getParameter("system");
if ("getSize".equals(system)) { out.println("当前主机中含有的注册静态页面数:" + command.getStaticPageCount()); return; }
if ("clearAll".equals(system)) { command.clearAllStaticPage(); out.println("已清除当前主机中所有注册静态页面"); return; }
if ("clear".equals(system)) { if (command.clearStaticPage(request.getParameter("staticID"))) { out.println("清除当前主机中指定的注册静态页面 成功"); } else { out.println("清除当前主机中指定的注册静态页面 失败"); } return; }
%>
<%}%> |
程序运行后的界面如下:
图12-2-1
这个程序具有三个功能,用户可以通过三个单选按钮来选择需要完成的功能,不过只有输入了正确的操作密码才行,在这里密码是“123456”,是由源程序第3行中的password变量定义的,如果想换成其它密码请修改这个变量的值。
我们来看第一个功能(源程序29到32行),返回当前主机中含有的注册静态页面数,使用了getStaticPageCount()这个方法,执行后会显示当前主机或虚拟主机中已注册的静态也面数。如果重新启动服务器即确保一开始系统中没有注册过任何静态页面,然后访问本节介绍的readfile.dqm,之后再执行本程序的本功能就会看到当前主机中含有的注册静态页面数为1了,说明readfile.dqm生成且注册了静态页面,下次再次访问readfile.dqm系统就会直接返回已生成的静态内容了。用户同时也可以试着访问上一节介绍的mpage.dqm动态文件,看看是不是每访问一页当前主机中含有的注册静态页面数就会增加一个。
第二个功能(源程序34到38行),清除当前主机中所有已注册的静态页面,使用了clearAllStaticPage()这个方法。用户可以试着访问readfile.dqm和mpage.dqm后用第一个功能查看前主机中含有的注册静态页面数,然后执行此功能清除所有已注册的静态页面,之后再用第一个功能查看注册静态页面数,你会发现实例数变成了0,这就说明系统中所有的静态页面注册信息都被删除了。
在介绍完第二个功能的同时,我们就可以解决本节前面所遇到的content.txt内容修改,但是readfile.dqm却因为静态页面的原因无法显示出content.txt修改后的内容。现在在改变了content.txt内容后我们手动删除所有注册静态页面后再次访问readfile.dqm,就会看到更新后的内容了。
删除注册静态页面似乎没有必要,因为大部分时间只是由于某个静态页面发生变化了,所以我们是不是可以只清除某个或某些指定的注册静态页面呢?当然是可以的,只要用clearStaticPage(String staticPageId)这个方法就可以了,第三个功能就是清除当前主机中指定的注册静态页面(源程序40到48行)。我们来做个试验,清除所有注册静态页面后访问readfile.dqm和mpage.dqm,通过第一个功能查看当前主机中含有的注册静态页面数,之后再用第三个方法删除静态ID为/test_folder/readfile.dqm的静态页面,最后再用第一个功能查看注册静态页面数,你会发现少了一个,因为我们刚刚删除了。删除指定的注册静态页面会返回一个boolean值,删除成功是true否则为false。
从上面的介绍中我们已经知道了如何更新一个已经过期的静态页面,只不过是手工的而非自动的,为此为了保证静态页面的内容有效性,在对于会改变原静态页面内容的操作中务必加上上面介绍的删除注册静态页面的方法。例如a.dqm开启了静态页面功能且是从A数据库中读取内容的,b.dqm是对A数据库进行修改的,那么因该在b.dqm程序中增加相应的删除a.dqm静态页面的语句,使其在更新了A数据库后a.dqm能够重新生成最新的静态页面。
在删除指定的注册静态页面时需要提供对应的静态ID,可是如果静态页面太多有可能会记不清静态ID,为此我们接下来要介绍可以读取当前主机或虚拟主机中所有注册的静态ID的方法。
4. public String[] getAllStaticID()
返回当前主机或虚拟主机中所有注册静态页面的静态ID。
返回:当前主机或虚拟主机中所有注册静态页面的静态ID
5. public String getStaticPageFileName(String staticPageId)
根据指定的静态ID返回当前主机或虚拟主机中对应的静态页面的文件全名。
参数:staticPageId指定的已注册静态ID。
返回:如果指定的静态ID已注册则返回对应静态页面的文件全名,否则返回null。
我们来举个例子,allStaticID.dqm会显示出当前主机或虚拟主机中所有注册静态页面的静态ID以及其对应的静态文件名。源程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<%@page command="true"%> <html>
<head> <meta http-equiv="Content-Language" content="zh-cn"> <meta http-equiv="Content-Type" content="text/html; charset=gb2312"> <title>静态ID</title> </head>
<body>
<table border="0" width="100%" id="table1"> <tr> <td bgcolor="#C0C0C0" width="275" align="center"><b>静态ID</b></td> <td bgcolor="#C0C0C0" align="center"><b>生成的静态文件名</b></td> </tr> <% String[] staticIds = command.getAllStaticID(); for (int i = 0; i < staticIds .length; i++) { %> <tr> <td width="275"><%=staticIds[i]%></td> <td><%=command.getStaticPageFileName(staticIds[i])%></td> </tr> <%}%>
</table>
</body>
</html> |
从上面19到25行可以看到使用了getAllStaticID方法先读取所有的静态ID,在一一输出静态ID的同时使用getStaticPageFileName方法输出静态页面文件名。以下是我运行此程序的结果:
图12-2-2
从上图中可以看出有三个静态ID已注册,同时显示出了相对应的静态文件名。不过请注意显示的静态文件名是不含有扩展名的,例如上图中显示的第一个静态文件名为:
C:\kgserver\webapp\WEB-INF\static_page\c74eee8f52fffe5ecb2456e543117739
那么实际存在的静态页面文件应该有三个(参见上一节相关内容),分别是:
C:\kgserver\webapp\WEB-INF\static_page\c74eee8f52fffe5ecb2456e543117739.gzip
C:\kgserver\webapp\WEB-INF\static_page\c74eee8f52fffe5ecb2456e543117739. deflate
C:\kgserver\webapp\WEB-INF\static_page\c74eee8f52fffe5ecb2456e543117739.no
用户可以在上面的路径中找到这些文件。不过注意因为服务器安装目录不同,所以用户在自己环境中运行的结果会有所不同。
或许有些用户会问知道静态页面文件名有何用,显示出当前所有已注册的静态ID还算有些用途,毕竟本节介绍的第三个clearStaticPage(String staticPageId)方法需要用静态ID做参数。其实知道静态页面文件名也是有用的,就是可以直接删除对应静态ID的三个静态页面文件,效果和用clearStaticPage(String staticPageId)删除是一样的。
本节我们将介绍静态页面执行的流程,以便用户更好理解静态页面。前面介绍了静态ID和注册,其中提到了可以覆盖getStaticPageId方法用以返回静态ID,如果返回的静态ID为null则不开启静态页面功能。其实这已经概括了静态页面功能的流程,我们在此基础上再更加详细的来介绍一下此流程。
当用户请求一个动态文件时,系统首先回去执行
public String getStaticPageId(
DRequestItface request, DSessionItface session, DApplicationItface application)
这个方法,如果此方法执行结果是返回null的,则系统认为是关闭了静态页面功能,于是接下来就执行动态页面程序并返回结果。
但是如果执行上面方法返回的不是null,则认为用户开启了静态页面功能,于是接下来相应的流程也改变了。系统会先检查获取的静态ID是否已经注册,如果已注册则还要检查其对应的静态页面文件是否存在,只有这2个条件都满足才会输出静态页面,这就是上一节所介绍的删除静态页面文件和使用clearStaticPage(String staticPageId)删除静态页面注册,其效果是一样的原因。
如果上面注册和静态页面存在的2个条件有一个不满足,则相应的流程又是不同的。此时将继续执行当前访问的动态页面,在将执行结果返回的同时会将刚才返回的结果保存成三个静态页面和注册静态ID。这样当下次再获取此静态ID时就可以按上面一提到过的流程直接输出静态页面内容了。
另外当系统每次重新启动后所有静态页面注册信息都会丢失,所以重新启动后所有的静态文件都会重新生成和注册。
本节我们将介绍静态页面一些需要注意的事项,以便用户更好的理解和使用静态页面这个功能。
前面5.1节和5.7节分别介绍过重定向客户端和有条件的文件输出,然而如果含有这二个功能那么请不要使用静态页面功能,否则有可能会得到不正确的结果。
前面5.4节介绍过encodeURL方法,如果使用了这个方法那么请不要使用静态页面功能,因为有可能以后不论谁访问此文件都会附带第一次生成静态文件时的sessionID。
如果开启了静态页面功能(即返回了非null的静态ID),那么不管你先前设置如何系统会强制开启缓存和关闭压缩功能。所以你会发现即使你关闭了缓存功能还是能够使用那些开启缓存才能够用的方法,同样即使你开启了压缩,在第一次生成静态页面时的结果还是未经压缩的。
某些情况下每次执行动态文件比执行生成的静态文件更快,因为每次执行还要读取已生成的静态文件内容,所以用户必须自己权衡。