Board logo

标题: 基于 Struts2 标签的 BigPipe 技术实现(4) [打印本页]

作者: look_w    时间: 2018-9-23 14:11     标题: 基于 Struts2 标签的 BigPipe 技术实现(4)

基于 Struts2 的标签开发根据前面的分析,读者应该可以领略到 BigPipe 技术的优点了,为了让单线程和多线程版本更加实用,文章结合 Struts2 的标签技术,开发实现 BigPipe 技术,这样就可以让 Java 开发人员可以真正的将该技术用于实际。因此,这部分需要大致讲解一下 Struts2 自定义标签的开发方法。
实现基于 Struts2 的标签,需要重载两个类,org.apache.struts2.views.jsp.ComponentTagSupport 和 org.apache.struts2.components.Component,实现 ComponentTagSupport 类的 getBean 方法和 populateParams 方法,getBean 方法返回自定义 Component 的实例,populateParams 则是负责将页面传递的参数装配到 Component 里。在 Component 类里,需要重写 start 和 end 方法(也可以不重写),这两个方法分别代表标签的起始和标签的结束。最后再新建一个 tld 文件,来配置这个标签,由于篇幅限制,本文对 Struts2 的标签开发不多加解释,读者可以自行上网搜索相关资料。
单线程实现还记得单线程 BigPipe 的实现效果吗?它可以自定义页面模块的显示顺序。普通的 JSP 文档 , 它显示页面的顺序 , 是按照文档流的顺序 , 也就是从上到下 , 从左到右的生成。但是页面中的一些元素,我们希望它早点显示出来,但是往往它又在文档流的后半部分,前半部分耽误了很多时间,这可能直接导致用户因为看不到重要信息而不再等候,离开页面。有一些应用,用户只希望能看到希望(页面出现内容),而我们正文的内容需要访问数据库,可能稍微慢点,因此我们可以将文档的结构先显示给用户,文档内容再慢慢填充,这听起来像 Ajax,然而这不是,在 BigPipe 技术里,文档内容的填充只在一个请求内完成,而 Ajax 则可能发出多个请求,对服务器的压力较大,这在前面也已经多次提到,读者要谨记这个不同点。
单线程实现的原理是:网页的布局仍然是不重要的在上方,重要的在下方,但是对要显示的内容进行重新排序,重要的放在文档流上方,不重要的放在后方。当重要内容加载完之后,再使用 JavaScript 将内容移到原有的位置。因此,单线程需要两个标签,一个名为 bigPipeTo,是一个占位标签,在原有的位置。一个是 bigPipeFrom,它包含了需要显示的内容,它的原理图如图 10 所示。
图 10. 单线程原理图可以看到,如果按照普通的实现方式,网页是按照 PageLet1 To->PageLet2 To->PageLet3 To->PageLet4 To 的顺序加载内容。但是由于我们将内容放在了网页结构的下方,初始化为不可见,经过重新排序,顺序则变为了 2->3->1->4。bigPipeFrom 标签的内容,会经过 moveContent 的 JavaScript 方法移动到对应的 bigPipeTo 标签的位置。在本文的例子中,单线程的 JSP 使用代码如清单 2 所示。
清单 2. 单线程的 JSP
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
<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<%@ taglib prefix="b" uri="/WEB-INF/bigpipe.tld"%>  
<%long pstart = System.currentTimeMillis();%>
<table border="1" width="100%" height="500">
  <caption> 单线程例子 </caption>
  <tr>
    <td><b:bigPipeTo name="index1"> 编号:1
    <img src="images/loading.gif"/></b:bigPipeTo></td>
    <td><b:bigPipeTo name="index2"> 编号:2
    <img src="images/loading.gif"/></b:bigPipeTo></td>
    <td><b:bigPipeTo name="index3"> 编号:3
    <img src="images/loading.gif"/></b:bigPipeTo></td>
  </tr>
   <tr>
    <td><b:bigPipeTo name="index4"> 编号:4
    <img src="images/loading.gif"/></b:bigPipeTo></td>
    <td><b:bigPipeTo name="index5"> 编号:5
    <img src="images/loading.gif"/></b:bigPipeTo></td>
    <td><b:bigPipeTo name="index6"> 编号:6
    <img src="images/loading.gif"/></b:bigPipeTo></td>
  </tr>
</table>

<b:bigPipeFrom name="index6" bigPipeJSPath="js/bigpipe.js">
<%
long start = System.currentTimeMillis();
Thread.sleep(6000);
long seconds = System.currentTimeMillis() - start;
%>
6 秒的内容 <br>
加载耗时:<%=seconds%> 毫秒 ;
</b:bigPipeFrom>

// 中间的 4 个由于篇幅限制,省略…

<b:bigPipeFrom name="index1" bigPipeJSPath="js/bigpipe.js">
<%
long start = System.currentTimeMillis();
Thread.sleep(1000);
long seconds = System.currentTimeMillis() - start;
%>
1 秒的内容 <br>
加载耗时:<%=seconds%> 毫秒 ;
</b:bigPipeFrom> …




从清单 2 可以看出,bigPipeFrom 标签的 name 属性和 bigPipeTo 标签的 name 是一一对应的,这样在 bigPipeFrom 标签里的内容加载完以后,会准确的将内容移到对应 bigPipeTo 标签的位置。bigPipeFrom 标签对应类 BigPipeFrom 的关键代码如清单 3 所示。
清单 3. BigPipeFrom 关键代码
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
@Override
public boolean start(final Writer writer) {

boolean result = super.start(writer);
try {
writer.flush();// 刷新显示网页结构
// 用 DIV 包围内容
if (visiable)
{
writer.write("<div style='display:none' id='" + name + "_from'>");
} else {
writer.write("<div id='" + name + "_from'>");
}
} catch (IOException e) {
e.printStackTrace();
}
return result;
}

@Override
public boolean end(Writer writer, String body) {

boolean end = super.end(writer, body);
try {
//DIV 的结束,也就是该标签里的内容加载完毕
writer.write("</div>");
// 引入移动内容的 JavaScript 文件,就是 <script src=”…”></script>
BigPipeWriter.instance().writeJavaScript(bigPipeJSPath, writer);
// 调用 moveContent 方法的脚本代码
BigPipeWriter.instance().writeFromToTo(name, writer, copy);
} catch (Exception e) {
e.printStackTrace();
}
return end;
}




在清单 3 的 start 方法里,执行 flush,将已经加载的内容先写回浏览器,使用一个 div 包含主体内容。在 end 方法里,不仅要写回 div 的后半部分,还要将移动内容的 JavaScript 代码写回去。实际上就是 <script src=” js/bigpipe.js”></script><script>moveContent(fromDiv, toDiv);</script>。其中 moveContent 方法就是在 bigpipe.js 里定义的。为了让 bigPipeFrom 的内容知道要移动到什么位置,所以在 bigPipeTo 标签对应的 BigPipeTo 类里,需要用一个 div 包围,BigPipeTo 的代码如清单 4 所示。
清单 4. BigPipeTo 关键代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public boolean start(final Writer writer) {

boolean result = super.start(writer);
try {
writer.write("<div id='"+name+"'>");
} catch (IOException e) {
e.printStackTrace();
}
return result;
}

@Override
public boolean end(Writer writer, String body) {
boolean end = super.end(writer, body);
try {
writer.write("</div>");
} catch (IOException e) {
e.printStackTrace();
}
return end;
}




最后,只要将 bigPipeFrom 标签里的 div 内容,移动到 bigPipeTo 的 div 里,就完成了。移动 div 内容的 JavaScript 代码非常简单,代码如清单 5 所示。
清单 5. JavaScript 移动内容的代码
1
2
3
4
5
function moveContent(fromId, toId)
{
document.getElementById(toId).innerHTML = document.getElementById(fromId).innerHTML;
document.getElementById(fromId).innerHTML = "";
}




基于以上的代码实现,bigPipeFrom 使用的顺序,就是网页模块 PageLet 加载并显示的顺序,调整代码就等同于调整了加载顺序。达到了重要在前,次要在后的效果。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0