J2EE相关: Struts的几个精细之处
发表于 Friday, April 23 @ 19:32:27 CST by joezxh

Joe.zhang 投递 "摘自赛迪网 作者:王和全 最近在网上看到一篇N. Alex Rupp写的“Beyond MVC: A New Look at the Servlet Infrastructure”文章,意思大致是说MVC被Struts等框架错误地应用到了Servlet架构中。我想只有对Struts有足够的了解再加上在MVC方面有足够深的功力,才敢发此言论,不是经常听人说:最熟悉自己的人是你的敌人。本人功力尚浅,没有引领风潮的能力,而且生活还得继续,只能先来熟悉熟悉Struts。

申明: 强烈建议在阅读本文之前先阅读一下N. Alex Rupp老兄的文章,如果你赞同他的看法,可能你会觉得研究Struts就没什么意义了。

说明:本文所讲的Struts知识基于Struts 1.1版本,除非特别说明,本文中的Struts都特指Struts 1.1这个版本。

目录:

精细之处一:“利用Token解决重复提交”背后的前提
精细之处二:页面流转控制中的职责分配

精细之处一:“利用Token解决重复提交”背后的前提

我们知道,可以利用同步令牌(Token)机制来解决Web应用中重复提交的问题,Struts也给出了一个参考实现。服务器端在处理到达的请求之前,会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较,看是否匹配。在处理完该请求后,且在答复发送给客户端之前,将会产生一个新的令牌,该令牌除传给客户端以外,也会将用户会话中保存的旧的令牌进行替换。这样如果用户回退到刚才的提交页面并再次提交的话,客户端传过来的令牌就和服务器端的令牌不一致,从而有效地防止了重复提交的发生。对应于这段描述,你可能会在你的Action子类中有这么一段代码:
if (isTokenValid(request, true)) {
// your code here
return mapping.findForward("success");
} else {
saveToken(request);
return mapping.findForward("submitagain");
}

其中isTokenValid()和saveToken()都是org.apache.struts.action.Action类中的方法,而具体的Token处理逻辑都在org.apache.struts.util.TokenProcessor类中。Struts中是根据用户会话ID和当前系统时间来生成一个唯一(对于每个会话)令牌的,具体实现可以参考TokenProcessor类中的generateToken()方法。

不知道大家有没有注意到这样一个问题,因为Struts是将Token保存在Session的一个属性中,也就是说对于每个会话服务器端只保存而且只能保存一个最新Token值。对于这一点,我的同事就提出了疑问:那如果我在同一个会话中打开两个页面,那么后提交的那个页面肯定不能提交成功了。他还给出了一个实际的例子:比如现在需要把两个客户A和B的地址都改为某个值,那用户就可能同时打开两个页面,修改A,修改B,提交A,提交B,按照Struts中的处理逻辑,B的修改提交就肯定不能成功,但是这个提交操作对于用户来说并不存在操作不正确的地方。

在这里,可能有人要问:怎么可能在同一个会话中打开两个页面呢?重新打开一个IE浏览器不是重新开始了一个会话吗?不错,这种情况下是两个会话,不存在任何问题。但是,你还可以通过菜单“文件”-“新建”-“窗口”(或者快捷键Ctrl+N)来复制当前窗口,这个时候你会发现该页面与原有页面同处在一个会话当中。其实,能够发现这个问题得归功于我的那位同事对IE习惯性的操作方法。

这下我的那位同事不满意啦,他于是开始动手修改Struts中的实现方式,让每个页面(至少某类页面)在服务器端都保存有一个唯一的Token值。这样,前面所讲的客户A,B同时修改的限制就不存在了。但是不久,我的那位同事就开始意识到他正在走向一条危险的道路。首先,如果每个页面都在服务器端保存一个Token值,则服务器端保存的数据量将越来越大。而且,如果考虑这种同一个会话中打开多个页面的情况的话,就好像打开了潘多拉魔盒,将会给自己带来无穷无尽的麻烦。比如,首先打开页面P1,然后利用Ctrl+N得到页面P2,P1提交,P2提交,目前为止一切正常。但是如果此时,在P1,P2中点击“后退”按钮,然后再提交P1, P2呢,情况会是怎样?如果在P2中提交完后执行其它操作,而在P1中回退后提交,情况又是怎么样呢?如果有P1,P2,P3,那情况又是如何呢?太复杂啦!我想你也会和我们有同感,你需要考虑许多种可能的组合,而且有的时候结果并不是你想象中的那样简单。

此路不通,还得回来看看Struts。其实经过以上一番折腾,我们可以发现在Struts中的Token机制背后隐藏着这样一个前提:不允许你(客户端)在同一会话中打开多个页面。注意是同一会话,如果打开两个IE浏览器,那已经是两个会话啦,不受该限制。其实,这个看似不合理的规定却自有其道理:一是它极大地简化了Token的实现,二个这种限定也符合大部分人的使用习惯。

精细之处二:页面流转控制中的职责分配

我们知道,Struts的执行过程大致如下:首先,控制器接收到客户端请求,将这些请求映射至相应的Action,并调用Action的execute方法,这中间可能还涉及到ActionForm的创建和填充。Action的execute方法执行完以后,返回一个ActionForward对象,控制器根据该ActionForward对象将请求转发至下一个Action或JSP。最后,产生视图响应客户。在大的层面上,Struts是采用了MVC这种架构,没什么特别之处。但从一些小的地方,我们还是可以看出Craig R. McClanahan老兄的一些考虑。我们看到Action与控制器之间传递的是ActionForward对象,由于Action的execute方法要求返回一个ActionForward对象,所以你会经常在Action子类中看到如下语句:

return (mapping.findForward("success"));

其实返回的就是一个ActionForward对象。在Action中我们根据程序执行的不同情况,决定接下来的页面走向(比如返回到输入页面或者转到下一个页面),并将这些信息保存在ActionForward对象中。而接下来控制器就可以直接利用该ActionForward对象来进行页面的流转。下面是org.apache.struts.action.RequestProcessor类的processForwardConfig()方法的摘录,该方法调用发生在Action实例调用后。

protected void processForwardConfig(HttpServletRequest
request,
HttpServletResponse
response,
ForwardConfig forward)
throws IOException, ServletException {

String forwardPath = forward.getPath();
String uri = null;

// paths not starting with /
should be passed through without any processing // (ie. they're absolute)
if (forwardPath.startsWith("/")) {
uri = RequestUtils.forwardURL(request, forward);
// get module relative uri
} else {
uri = forwardPath;
}
if (forward.getRedirect()) {
// only prepend context path for relative uri
if (uri.startsWith("/")) {
uri = request.getContextPath() + uri;
}
response.sendRedirect(response.encodeRedirectURL(uri));
}
else {
doForward(uri, request, response);
}
}


注意:
ForwardConfig是ActionForward的父类

该方法首先调用ForwardConfig的getPath()方法获得下一步流转的路径,在某些条件下还需要进行一些拼装得到正确的URI,最后根据该URI进行页面跳转。可见在processForwardConfig()方法中只是对ActionForward进行了一些“技术上”的处理,没有任何和业务相关的内容,这样就将控制器(ActionServlet)和Action完全分开来,两者互不影响,达到了功能模块之间松散耦合的目的。

模块间(系统间)松散耦合一直是OO设计所追求的,但是具体如何去实现这样一种松散耦合却不是那么容易做到的。Struts中的设计给了我们一些启示:模块间相互关联影响因素的传递可以用对象的形式来包装起来。其实,个人觉得Struts中的做法还可以稍微有一点点改进,就是在ActionForward中提供一个getURI()方法来给出最终的URI岂不是更好?

参考:

1、Beyond MVC: A New Look at the Servlet Infrastructure

2、Allen Holub的Build user interfaces for object-oriented systems系列文章,可以从这篇文章中学到很多面向对象设计方面的知识,虽然作者并不认为MVC是一种面向对象的方法,但是我们这些MVC的实践者仍然可以从中学到面向对象的知识。

3、Struts 1.1的介绍性文章:深入Struts 1.1

4、Apache Struts Website

5、关于重复提交问题的讨论及其解决方案,可以参考《Core J2EE Patterns》一书(中文版《J2EE核心模式》)。

Deepak Alur,John Crupi,Dan Malks: Core J2EE Patterns-Best Practices and Design Strategies

Struts的几个精细之处相关推荐

  1. Struts 2基础入门

    学习内容 Struts 2的优势 Strust 2体系结构 Struts 2运行流程 能力目标 熟悉Struts 2的体系结构和运行流程 熟练使用MyEclipse进行Struts 2开发 本章简介 ...

  2. 存储架构|Bitcask 引擎的设计,秒!

    坚持思考,就会很酷 Bitcask 是什么? Bitcask 是一种很有趣的存储模型的设计,这是一种底层格式为日志模样的 kv 存储.Bitcask 起源于 Riak 分布式数据库,Bitcask 论 ...

  3. YUV图像实时去雾算法的优化与改进

    YUV图像实时去雾算法的优化与改进 2016-03-02 21:04 摘要:针对已有的基于DSP的实时去雾算法出现的问题进行优化和改进.在降采样倍数过大时,去雾效果会出现一些副作用,包括纹理细节丢失. ...

  4. shell的logo含义_Shell(壳牌石油)标志历史

    荷兰皇家壳牌集团(Royal Dutch Shell,又译蚬壳)是世界第二大石油公司,总部位于荷兰海牙. 荷兰皇家壳牌集团由荷兰皇家石油与英国的壳牌两家公司合并组成.荷兰皇家石油于1890年创立,并获 ...

  5. unit英语读音_7款超好玩的英语启蒙APP,假期在家教娃So Easy

    马上就到中秋小长假啦,此时的你们,也许在准备出行.等车等飞机,自驾途中--如何帮孩子打发时光? 不如给他们下载几个超级有趣的APP,玩的同时,还能进行英文启蒙,一举两得. 今天小E分享几个英语启蒙AP ...

  6. c++ opencv mat_图像拼接Opencv源码重构

    请看赵春江https://me.csdn.net/zhaocj的主页,他已经对Opencv图像拼接流程中的代码做了很详细的解释.前人栽树,后人乘凉. 一.本文所做的事 1.重构了Opencv图像拼接的 ...

  7. 优秀课件笔记之视听巧记汉英成语2

    *i 恭敬 成语 五体投地 英译 admire somebody from the bottom of one's heart 解释 比喻崇敬钦佩到极点. 例句 他的文章写得真好,同学们个个佩服得五体 ...

  8. 主动降噪耳机哪个牌子性价比最高?千元内主动降噪耳机推荐

    作为一名狂热的蓝牙发烧友,使用了很多牌子的蓝牙耳机.首先,我要纠正一下大家的误解,认为贵就是好,这是一种很大的误解,有些牌子的降噪效果很好,但价钱很具性价比.因此,我们在选择耳机时,不能过于关注品牌的 ...

  9. HTML5的离线储存怎么使用,它的工作原理是什么?

    离线缓存的目的是当浏览器处于离线状态或网络连接较慢时,利用应用程序缓存机制让web程序能在离线状态下正常访问.使用离线缓存技术一般是为了让用户在: 离线状态也能正常访问 提高访问速度 减轻服务器响应压 ...

最新文章

  1. SAP Forecasting and Replenishment for Retail – A short Overview【中英文双语版】
  2. ★ 省时省力又省钱--快来看win7家庭版升级旗舰版 ★
  3. 用计算机源码计算加法,MFC实现简单计算器(支持加减乘除和括号运算)
  4. Atom JS 代码智能提示补全
  5. python基础语法手册_说一说python中的几个基础语法
  6. 使用Hibernate在CQRS读取模型中进行快速开发
  7. VisualStudio2019配置OpenCV
  8. 劳心者、劳力者或CEO、CTO各得其所,足矣
  9. elasticsearch-head 谷歌插件以及安装和使用说明
  10. java点_java常见基础点
  11. c++11 随机数random
  12. 科创板鸣锣开市 一图带你了解首批25家公司
  13. [转]架构师的职责及工作描述
  14. SQL语句的执行计划
  15. springboot springcloud 启动优化、性能优化
  16. 密码学的安全性浅析3
  17. python请输入一个人的名字_print('曾经有一份真挚的爱情放在我面前,那个人的名字是' + goddess),Python旅程开始的地方!...
  18. 什么输入法对计算机英语,电脑怎么把英文输入法设置为默认输入法
  19. android实现页面黑白色
  20. vue 脚手架 elementUi element-ui 兼容 ie 360 急速/兼容模式 完美处理

热门文章

  1. 《精通J2EE网络编程》中讲的JNDI 6.2 使用JNDI
  2. java主机上切换用户,linux 主机上更换默认的jdk
  3. 开发用户导航栏和权限信息接口
  4. 分布式架构的NoSQL
  5. SpringMVC的数据响应-页面跳转-返回字符串形式(应用)
  6. 后台服务系统之dubbo架构
  7. 数据库case when语句
  8. 创建provider服务
  9. 设置新生代与老年代比例关系
  10. linux电视改安卓,mstar安卓智能电视方案源代码常用修改