[摘要]: 介绍如何改写Struts2的Restful2ActionMapper来支持REST风格的URL映射。
Note: 不久前写了一篇文章《使用Restful2ActionMapper让Struts2支持REST风格的URL映射》,但后来发现有些不对,Struts2的Restful2ActionMapper并不按我想的那样运行。因为在我的实验项目中,我是改写了这个Restful2ActionMapper的。Struts2自己带的Restful2ActionMapper稍嫌复杂,而且我对它的有些地方的处理不甚满意,所以自己写了一个,没有使用Struts2自己的Restful2ActionMapper进行调试。特此向大家道歉,并修正该文档。另外,我对我改写的这个Restful2ActionMapper的代码进行了一些删减调整,将代码附于文后。
一、概述
REST是由 Roy Fielding 在他的论文《Architectural Styles and the Design of Network-based Software Architectures》中提出的一个术语。关于REST,请参考:http://www.redsaga.com/opendoc/REST_cn.pdf
在REST的定义中,一个Web应用总是使用固定的URI向外部世界呈现(或者说暴露)一个资源,并使用不同的HTTP请求方法来处理对资源的CRUD(创建、读取、更新和删除)操作。除了我们所熟知的GET和POST这两种HTTP请求方法,HTTP还有HEAD、PUT、DELETE等请求方法。我们在Web应用中处理来自客户端的请求时,通常只考虑GET和POST,并使用某种URL映射将URL映射到对资源的某种操作。而REST架构风格则要求使用HTTP的GET、POST、PUT、DELETE来分别表示资源的读取、创建、更新、删除,而URI保持不变。举例来说,/article/2007/8/a001这个URI表示一篇文章,表示形式为:/article/{year}/{month}/{id},对这个资源的CRUD操作如下(下面的表示形式中,我省去了http://host/context/namespace这样的前缀):
读取:GET /article/2007/8/a001
创建:POST /article/2007/8/a001
更新:PUT /article/2007/8/a001
删除:DELETE /article/2007/8/a001
如果我们用传统的struts或webwork的开发方法,我们可能会定义一个ArticleAction,定义好CRUD的method,并使用不同的URI映射来表示这几种操作。比如,我们可能会使用这样的URI来读取article:/getArticle.action?year=2007&month=8&id=a001,并使用这样的URI来删除article:/deleteArticle.action?year=2007&month=8&id=a001,或者,把这几种操作用相同形式的URI来表示:/article.action?method=get&year=2007&month=8&id=a001。显然,REST风格的URI表示更友好。
Struts2和Webwork2都带了一个RestfulActionMapper来支持REST风格的URI映射,但是它的功能太弱了,表现形式也很呆板。Struts2(我使用的是Struts 2.0.9)中还有一个Restful2ActionMapper,可以更好地支持REST风格。
从struts2的官方文档中可以找到关于Restful2ActionMapper的说明: Improved restful action mapper that adds several ReST-style improvements to action mapping, but supports fully-customized URL's via XML.
我查看了Restful2ActionMapper的源码,对它有些地方的处理有异议,所以改写了这个类。以下的配置中,请使用文后附上的Restful2ActionMapper代替Struts2原来的类。
二、配置和使用
现在,我们配置struts2使它使用Restful2ActionMapper。在Web项目中,修改struts.properties文件(它最终会发布到你的web应用的WEB-INF/classes目录中):
struts.mapper.class=org.apache.struts2.dispatcher.mapper.Restful2ActionMapper
struts.enable.SlashesInActionNames=true
当然,你也可以在struts.xml里进行配置,请参考struts2的相关文档。
(这里,请将struts.mapper.class这行修改为使用我修改后的Restful2ActionMapper)
这里有个小建议,许多人在WEB-INF/web.xml里对struts2的配置是让struts2处理所有扩展名为action的url,也就是设置url-pattern为*.action。<filter-mapping></filter-mapping> 我的建议是,不要使用扩展名来作为url-pattern,使用基于路径的匹配形式会更好,我一般是使用“/app/*”作为url-pattern。至于扩展名,我一般是在struts.properties文件中指定:
struts.action.extension=html,xml,json
或者,不要扩展名:
struts.action.extension=
不过,这些都是题外话。
现在,以上面讲到的article为例,我们定义ArticleAction。按照Restful2ActionMapper的规则,URL与method的对应关系如下:
GET /article => public String index(); 资源索引;
GET /article/2007/8/a001 => public String view(); 对应于读取操作;
POST /article/2007/8/a001 => public String create(); 创建资源;
PUT /article/2007/8/a001 => public String update(); 更新资源;
DELETE /article/2007/8/a001 => public String remove(); 删除资源;
GET /article/2007/8/a001!edit => public String edit(); 请求编辑资源,和REST的四种操作没有对应关系;
GET /article/!editNew => public String editNew(); 请求编辑新资源,和REST的四种操作没有对应关系。
后两种方式似乎和REST没什么关系,但为传统的Web应用开发提供了方便。比如edit(),服务器返回一个表单页面。但是,如果我们让应用服务器只返回xml或json,那么这个edit()是可以不要的,有读取操作就够了。(也许把view方法改为read更贴切点)。
按照这些规则,我们在ArticleAction中定义view()、create()、update()、remove()等method,并在这个action中定义year、month、id的getter/setter方法:
java 代码
- package app;
-
- public class ArticleAction {
- private String year, month, id;
- ...
- getter/setter methods for year,month,id
- ...
- public String view() { ... }
- public String index() { ... }
- public String create() { ... }
- public String update() { ... }
- public String remove() { ... }
- }
然后我们需要配置这个action,使它能与形如/article/{year}/{month}/{id}的URL对应起来。我们在相应的struts2的action配置文件中加入如下几行:
xml 代码
- <action name="article/*/*/*" className="app.ArticleAction">
- <param name="year">{1}</param>
- <param name="month">{2}</param>
- <param name="id">{3}</param>
- <result name="..." type="..."/>
- </action>
OK!现在已经可以使用这个action了。当然,这还需要浏览器客户端的支持。当你的客户端以GET来请求/article/2007/8/a001时,struts2就会调用ArticleAction的view方法,而PUT请求则会对应到update方法,DELETE请求会对应到remove方法...
但是,如果你的客户端只支持GET和POST怎么办?Restful2ActionMapper的文档中提到:To simulate the HTTP methods PUT and DELETE, since they aren't supported by HTML, the HTTP parameter "__http_method" will be used.对于只支持GET和POST的传统网页,我们可以增加一个"__http_method"参数来模拟PUT和DELETE,比如:POST /article/2007/8/a001&__http_method=DELETE。随着Javascript和Ajax框架的发展,我们已经可以使用PUT和DELETE等方法。Ajax使用XmlHttpRequest进行操作时,在发送请求之前,可以通过设置RequestType的方式来完成对请求方法的设定。
三、不足之处
Restful2ActionMapper对REST风格的支持是不完全的。在REST风格中,我们可以使用同一个URI来获取同一个资源的多种表现形式。在发送HTTP请求时,只要我们在请求头中指定一个Accept参数,那么服务器就可以通过判断该参数来决定返回什么类型的数据。如果Accept为text/xml,服务器会返回xml格式的数据,如果Accept为text/json,则会返回json格式的数据,但URI是固定的。而Restful2ActionMapper只是作了URI的映射,并没有考虑返回数据的格式问题。要让struts2支持完全的REST风格,我们不得不对它进行改造,或者,等待它的改进。
另外,Restful2ActionMapper所定义的URL映射规则也有一个小小的“陷阱”。比如,GET /user/1表示读取id为1的user,但按照Restful2ActionMapper的定义,/user/new会对应到action的editNew方法,如果这个"new"就是某个用户的id呢?为了避开这个陷阱,我宁愿使用/user/!editNew这种丑陋的形式。事实上,随着客户端技术的发展,我们完全可以不使用editNew方法而构造输入页面,然后向服务器发送POST来创建资源。同样,edit方法也不是必要的。
(注:我修改后的Restful2ActionMapper去除了/user/new这种形式的映射)
四、其它
有个struts2的插件,叫jsonplugin,可以让struts2很方便地支持json输出。而Adobe Spry Framework、YUI-ext、DOJO等都能很好地支持json,并能很好地支持HTTP的各种请求方法。我推荐struts2的用户使用jsonplugin和Adobe Spry Framework或YUI-ext(或其它UI Framework)。Struts2只输出json格式的结果(最好还能输出xml),而UI和数据装配交给Adobe Spry/YUI-ext等去做。这样的组合会让你更好更方便地使用REST风格。
五、修改后的Restful2ActionMapper
这里我附上修改后的Restful2ActionMapper,大家可以在此基础上进行扩充。比如,我前面提到Restful2ActionMapper不能根据Accept请求头来返回不同格式的数据,其实也是可以进行改进的。我看到已经有人在读过我这篇文章后提出一种方案,类似于这样的:
/user/a001/xml => 返回xml格式的result
/user/a001/json => 返回json格式的result
/user/a001/...
这是一种办法,另外,根据url的扩展名来做,也是一种办法。但是这都不是好方案!我前面已经提过,按照REST风格,一个Web应用总是使用固定的URI向外部世界呈现(或者说暴露)一个资源,而前面这两种方案只是使URL友好点而已,并不真正符合REST风格。当然,这样也不错了,也是不错的方案,其实ROR中也有类似的做法。
但我们还有更好的方案,我提个思路,然后大家自行对Restful2ActionMapper进行改进:
在Action中可以设置一个consumeMime属性,并写好对应的getter/setter方法。在Restful2ActionMapper返回mapping之前,提取request的Accept头信息,然后将该信息放到mapping.params之中。action的各个method最后只返回consumeMime,这样就可以在action的配置文件中按consumeMime来配置result了。
下面,附上修改后的Restful2ActionMapper代码:
java 代码
- import com.opensymphony.xwork2.config.ConfigurationManager;
- import javax.servlet.http.HttpServletRequest;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.apache.struts2.dispatcher.mapper.ActionMapping;
-
- public class Restful2ActionMapper extends DefaultActionMapper {
-
- protected static final Log LOG = LogFactory
- .getLog(Restful2ActionMapper.class);
- public static final String HTTP_METHOD_PARAM = "__http_method";
- private static final byte HTTP_METHOD_GET = 1;
- private static final byte HTTP_METHOD_POST = 2;
- private static final byte HTTP_METHOD_PUT = 3;
- private static final byte HTTP_METHOD_DELETE = 4;
-
- public Restful2ActionMapper() {
- setSlashesInActionNames("true");
- }
-
-
-
-
-
-
- public ActionMapping getMapping(HttpServletRequest request,
- ConfigurationManager configManager) {
-
- if (!isSlashesInActionNames()) {
- throw new IllegalStateException(
- "This action mapper requires the setting 'slashesInActionNames' to be set to 'true'");
- }
- ActionMapping mapping = super.getMapping(request, configManager);
-
- if (mapping == null)
- return null;
-
- String actionName = mapping.getName();
- if ((actionName == null) || (actionName.length() == 0))
- return mapping;
-
-
-
- if (mapping.getMethod() != null)
- return mapping;
-
- int lastSlashPos = actionName.lastIndexOf('/');
- if (lastSlashPos == -1)
- return mapping;
- String requestMethod = request.getMethod();
- String httpMethodParam = request.getParameter(HTTP_METHOD_PARAM);
- byte requestMethodCode = 0;
- if ("PUT".equalsIgnoreCase(requestMethod))
- requestMethodCode = HTTP_METHOD_PUT;
- else if ("DELETE".equalsIgnoreCase(requestMethod))
- requestMethodCode = HTTP_METHOD_DELETE;
- else if ("PUT".equalsIgnoreCase(httpMethodParam))
- requestMethodCode = HTTP_METHOD_PUT;
- else if ("DELETE".equalsIgnoreCase(httpMethodParam))
- requestMethodCode = HTTP_METHOD_DELETE;
- else if ("GET".equalsIgnoreCase(requestMethod))
- requestMethodCode = HTTP_METHOD_GET;
- else if ("POST".equalsIgnoreCase(requestMethod))
- requestMethodCode = HTTP_METHOD_POST;
-
- if (requestMethodCode == HTTP_METHOD_GET) {
- if (lastSlashPos == actionName.length() - 1)
- mapping.setMethod("index");
- else
- mapping.setMethod("view");
- } else if (requestMethodCode == HTTP_METHOD_POST)
- mapping.setMethod("create");
- else if (requestMethodCode == HTTP_METHOD_PUT)
- mapping.setMethod("update");
- else if (requestMethodCode == HTTP_METHOD_DELETE)
- mapping.setMethod("remove");
-
- return mapping;
- }
- }
分享到:
- 2007-08-10 15:51
- 浏览 5124
- 评论(6)
- 论坛回复 / 浏览 (3 / 15742)
- 查看更多
相关推荐
目录 1.Struts 2权威指南——第1章 Struts 2概述.doc 2.truts 2权威指南——第2章 Struts 2下的HelloWorld.doc 3.Struts 2权威指南——第3章 Struts...11.改写Restful2ActionMapper让Struts2支持REST风格的URL映射.doc
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
,,基于SMO的三相PMSM无速度传感器控制(基于反正切函数) ,核心关键词:SMO(滑模观测器); 三相PMSM(永磁同步电机); 无速度传感器控制; 反正切函数; 控制系统。,基于SMO算法的三相PMSM无速度传感器反正切函数控制
网络文化互动中的舆论引导与危机应对
人力资源+大数据+薪酬报告+涨薪调薪,在学习、工作生活中,越来越多的事务都会使用到报告,通常情况下,报告的内容含量大、篇幅较长。那么什么样的薪酬报告才是有效的呢?以下是小编精心整理的调薪申请报告,欢迎大家分享。相信老板看到这样的报告,一定会考虑涨薪的哦。
内容概要:本文全面探讨了大学生沉迷网络游戏的现状及成因,强调该问题已严重影响大学生的学业和个人发展。据统计显示,中国大学生网络游戏成瘾患病率超过15%,问题广泛且严重。分析指出沉迷原因涵盖个人因素(如自我管理能力缺失、逃避现实压力)、家庭因素(例如家庭教育缺失和家庭氛围不和谐)、学校因素(如大学管理松散和校园文化活动匮乏),以及社会因素(例如网游设计吸引人和监管部门不严)。基于以上成因,提出了多层次综合治理方案,包括但不限于强化家庭教育和沟通、改善大学管理模式、丰富校园文化、加强网络游戏审查力度和社会心理健康辅导等方面的对策。 适用人群:本研究适用于高校辅导员、心理学家、教育政策决策人员,以及关心青年成长的社会各界人士。 使用场景及目标:本文旨在引起社会对该问题的关注,并为教育界和其他相关群体提供了详细的参考资料用于制定相应的干预措施,以减少大学生游戏成瘾情况的发生。此外,也可供家长学习科学育子知识。 其他说明:除了直接提出具体治理办法外,还特别提到了营造健康的网络文化环境的重要性,提倡多方协作共促学生健康发展。同时呼吁进一步加强对网络游戏产业的研究与管理,确保产业的良性发展的同时也能
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
矢量边界,行政区域边界,精确到乡镇街道,可直接导入arcgis使用
TI维也纳整流器设计.rar
自驾游中的手机APP推荐
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
视讯镜头专利复现,基本复现
,,OMRON CP1H PLC脉冲控制三轴伺服, 码垛机,实际项目,程序结构清析,有完整的注释,重复功能做成FB功能块,在其它项目可以导出直接用,MCGS触摸屏程序,有电气CAD图纸。 ,关键词:OMRON CP1H PLC;脉冲控制;三轴伺服;码垛机;程序结构清晰;完整注释;FB功能块;MCGS触摸屏程序;电气CAD图纸。,OMRON PLC三轴伺服脉冲控制程序:结构清晰、注释完整,FB功能块可复用,配合MCGS触摸屏及CAD图纸的实际项目应用
是一款基于JAVA的串口调试工具,支持波特率9600-115200,仅供参考学习使用,
,,CO2激光切割机雕刻机打标机写字机喷涂机巡边机控制软件,包含上位机和控制板,也可源码 视频展示只体现工作流程和加工效果,如果激光功率足够大最快速度能跑到每秒两米 支持文件格式说明: 控制版和上位机通信接口为百兆以太网接口,数据载体为标准TCP协议 1.g代码 2.打印图片 3.plt格式文件 4.激光机在切割有效线条时匀速切割 5.有效线条切割速度和空程速度分别设置 6.空程运行具备加减速控制 7.图片打印时上位机界面实时显示打印进度 8.打开的图片和图形文件可鼠标缩放和拖动 9.图片格式转并保存转完成的指定格式图片 10.手动回原点控制 ,核心关键词: CO2激光切割机; 雕刻机; 打标机; 写字机; 喷涂机; 巡边机; 控制软件; 上位机; 控制板; 源码; 视频展示; 工作流程; 加工效果; 激光功率; 速度; 两秒; 文件格式; g代码; 打印图片; plt格式文件; 有效线条切割; 空程速度设置; 加减速控制; 上位机界面实时显示; 图片缩放和拖动; 图片格式转换; 手动回原点控制。 关键词用分号隔开: CO2激光切割机; 喷涂机; 控制软件; g代码; 图片格式转
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
城市周边自驾游短途路线