`
cheng163134
  • 浏览: 32851 次
  • 性别: Icon_minigender_1
  • 来自: 湖北
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

改写Restful2ActionMapper让Struts2支持REST风格的URL映射

阅读更多

网站:JavaEye 作者:andlu 发表时间: 2007-08-10 15:51 此文章来自于 http://www.iteye.com
声明:本文系JavaEye网站原创文章,未经JavaEye网站或者作者本人书面许可,任何其他网站严禁擅自发表本文,否则必将追究法律责任!
原文链接: http://www.iteye.com/topic/110934

[摘要]: 介绍如何改写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。 我的建议是,不要使用扩展名来作为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 代码


 


  1. package app;  

  2.   

  3. public class ArticleAction {  

  4.     private String year, month, id;  

  5.     ...  

  6.     getter/setter methods for year,month,id  

  7.     ...  

  8.     public String view() { ... }  

  9.     public String index() { ... }  

  10.     public String create() { ... }  

  11.     public String update() { ... }  

  12.     public String remove() { ... }  

  13. }  





然后我们需要配置这个action,使它能与形如/article/{year}/{month}/{id}的URL对应起来。我们在相应的struts2的action配置文件中加入如下几行:

xml 代码


 


  1. <action name="article/*/*/*" className="app.ArticleAction">  

  2.     <param name="year">{1}</param>  

  3.     <param name="month">{2}</param>  

  4.     <param name="id">{3}</param>  

  5.     <result name="..." type="..."/>  

  6. </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 代码


 


  1. import com.opensymphony.xwork2.config.ConfigurationManager;  

  2. import javax.servlet.http.HttpServletRequest;  

  3. import org.apache.commons.logging.Log;  

  4. import org.apache.commons.logging.LogFactory;  

  5. import org.apache.struts2.dispatcher.mapper.ActionMapping;  

  6.   

  7. public class Restful2ActionMapper extends DefaultActionMapper {  

  8.   

  9.     protected static final Log LOG = LogFactory  

  10.             .getLog(Restful2ActionMapper.class);  

  11.     public static final String HTTP_METHOD_PARAM = "__http_method";  

  12.     private static final byte HTTP_METHOD_GET = 1;  

  13.     private static final byte HTTP_METHOD_POST = 2;  

  14.     private static final byte HTTP_METHOD_PUT = 3;  

  15.     private static final byte HTTP_METHOD_DELETE = 4;  

  16.   

  17.     public Restful2ActionMapper() {  

  18.         setSlashesInActionNames("true");  

  19.     }  

  20.   

  21.     /* 

  22.      * (non-Javadoc) 

  23.      *  

  24.      * @see org.apache.struts2.dispatcher.mapper.ActionMapper#getMapping(javax.servlet.http.HttpServletRequest) 

  25.      */  

  26.     public ActionMapping getMapping(HttpServletRequest request,  

  27.             ConfigurationManager configManager) {  

  28.   

  29.         if (!isSlashesInActionNames()) {  

  30.             throw new IllegalStateException(  

  31.                     "This action mapper requires the setting 'slashesInActionNames' to be set to 'true'");  

  32.         }  

  33.         ActionMapping mapping = super.getMapping(request, configManager);  

  34.   

  35.         if (mapping == null)  

  36.             return null;  

  37.   

  38.         String actionName = mapping.getName();  

  39.         if ((actionName == null) || (actionName.length() == 0))  

  40.             return mapping;  

  41.   

  42.         // If a method hasn't been explicitly named, try to guess using  

  43.         // ReST-style patterns  

  44.         if (mapping.getMethod() != null)  

  45.             return mapping;  

  46.   

  47.         int lastSlashPos = actionName.lastIndexOf('/');  

  48.         if (lastSlashPos == -1)  

  49.             return mapping;  

  50.         String requestMethod = request.getMethod();  

  51.         String httpMethodParam = request.getParameter(HTTP_METHOD_PARAM);  

  52.         byte requestMethodCode = 0;  

  53.         if ("PUT".equalsIgnoreCase(requestMethod))  

  54.             requestMethodCode = HTTP_METHOD_PUT;  

  55.         else if ("DELETE".equalsIgnoreCase(requestMethod))  

  56.             requestMethodCode = HTTP_METHOD_DELETE;  

  57.         else if ("PUT".equalsIgnoreCase(httpMethodParam))  

  58.             requestMethodCode = HTTP_METHOD_PUT;  

  59.         else if ("DELETE".equalsIgnoreCase(httpMethodParam))  

  60.             requestMethodCode = HTTP_METHOD_DELETE;  

  61.         else if ("GET".equalsIgnoreCase(requestMethod))  

  62.             requestMethodCode = HTTP_METHOD_GET;  

  63.         else if ("POST".equalsIgnoreCase(requestMethod))  

  64.             requestMethodCode = HTTP_METHOD_POST;  

  65.   

  66.         if (requestMethodCode == HTTP_METHOD_GET) {  

  67.             if (lastSlashPos == actionName.length() - 1)  

  68.                 mapping.setMethod("index");  

  69.             else  

  70.                 mapping.setMethod("view");  

  71.         } else if (requestMethodCode == HTTP_METHOD_POST)  

  72.             mapping.setMethod("create");  

  73.         else if (requestMethodCode == HTTP_METHOD_PUT)  

  74.             mapping.setMethod("update");  

  75.         else if (requestMethodCode == HTTP_METHOD_DELETE)  

  76.             mapping.setMethod("remove");  

  77.   

  78.         return mapping;  

  79.     }  

  80. }  





《 改写Restful2ActionMapper让Struts2支持REST风格的URL映射 》 的评论也很精彩,欢迎您也添加评论。查看详细 >>

推荐相关文章:
  基于lucene的对数据库&文件夹检索(ss2,appfuse,jdon,nutch杂烩)
  jboss seam 2.0beta1的常见bug列表及解决(持续更新)




JavaEye推荐
上海乐福狗信息技术有限公司:诚聘技术经理和开发工程师
免费下载IBM社区版软件--它基于开放的标准,支持广泛的开发类型,让您的开发高效自主!
京沪穗蓉四地免费注册,SOA技术高手汇聚交锋.
上海:优秀公司德比:高薪诚聘 资深Java工程师
广州:优易公司:诚聘Java工程师,开发经理
上海:尤恩斯国际集团:诚聘开发工程师
北京:优秀公司NHNChina招聘:WEB开发,系统管理,JAVA开发, DBA


分享到:
评论

相关推荐

    Struts2 chm文档

    目录 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

    使用Struts2开发RESTful服务

    另外,Struts2.1还提供了REST插件,使Struts2可以支持Rails风格的URL,以对外提供REST风格的资源服务。本文作者通过代码示例演示了这些特性。REST简介REST是英文RepresentationalStateTransfer的缩写

    RestFul整合struts所需包

    Struts 2 依然是一个 MVC 框架,...由于 Struts 2 提供了良好的可扩展性,因此允许通过 REST 插件将其扩展成支持 REST 的框架。REST 插件的核心是 RestActionMapper,它负责将 Rails 风格的 URL 转换为传统请求的 URL。

    Struts2请求转restful所需jar包

    Struts2请求转restful所需jar包 ezmorph-1.0.6.jar json-lib-2.3-jdk15.jar struts2-convention-plugin-2.3.14.jar struts2-rest-plugin-2.3.14.jar xstream-1.4.3.jar

    Struts2+rest简单实例

    自己写的小例子,部署以后就能运行,包涵七种默认action方法,适合新人入门,高手不必下载了

    struts2 restful Demo

    因為剛入職 公司要求會使用struts2 restful風格,剛開始都沒聽說過restful,然後在網上找了N久的講解或demo,都沒找到,花了一週 在老大的指導下搞出來了,希望可以幫助到想學restful風格的同學們

    Struts_2_开发_RESTful

    使用_Struts_2_开发_RESTful_服务

    解决struts2支持restful访问url长短不一的问题.txt

    由于老项目用的struts2,需要扩展支持restful访问接口,前提是不要破坏原系统struts的访问方式。网上查了好多方法,都很麻烦。最后使用了最简单也是最有效的方法,解决了大问题。在此提供给大家参考,希望对大家有...

    Struts2 Spring3 JPA RESTful

    不包含jar文件,Struts2需要用2.2.3以上版本,JPA使用的是Eclipselink的实现。

    RESTful-Struts2-2.3.15_JARs

    struts2整合RESTful所需全部JAR包,其中struts2版本为2.3.15

    strust2 restful demo

    STRUTS2 RESTFUL PLUGIN

    一个Delphi实现的HttpServer,支持RESTful风格路由

    一个Delphi实现的HttpServer,支持RESTful风格路由

    使用 Struts 2 开发 RESTful 服务

    使用 Struts 2 开发 RESTful案例

    struts2官方开发文档

    Try the Convention Plugin or its RESTful cousin, the REST Plugin. Interested in books, articles, and presentations about Struts? Check out some Other Resources. For other community-supported Struts ...

    Angular-RESTfulService, Angular 调用rest式服务的示例.zip

    Angular-RESTfulService, Angular 调用rest式服务的示例 Angular rest式服务示例这个项目简单地介绍了如何使用 Angular 来调用rest式服务。 只需克隆项目或者下载并提取. zip 即可开始。 Angular 概念使用app类和...

    SpringMVC-REST风格和SSM整合(注解版)彩色PPT版本.pptx

    REST风格简介 2、RESTful入门案例 入门案例 请求方法设定 请求路径参数 3、REST快速开发 RESTful快速开发(标准开发) @RestController 标准请求动作映射(4种) 4、案例:基于RESTful页面数据交互 5、SSM整合 ...

    spring加载restful(文档+程序源码)

     注释,如@RequestMapping 和 @PathVariable,支持资源标识和URL映射  ContentNegotiatingViewResolver支持为不同的MIME/内容类型使用不同的表示方式  使用相似的编程模型无缝地整合到原始的 MVC 层  创建一个...

    基于java的企业级应用开发:RESTful支持.ppt

    模糊查询的实现非常简单,只需在映射文件中通过元素编写相应的SQL语句 * * * RESTful支持 LOREM IPSUM DOLOR 14.2.1 什么是RESTful 什么是RESTful? RESTful也称之为REST,是英文“Representational State Transfer...

    Restful风格的介绍

    Rest风格的发展历史简介,Rest的实例,实例之间的性能比较,选取最优。

Global site tag (gtag.js) - Google Analytics