`

STRUTS2.0影射REST风格地址

阅读更多

[摘要]: 介绍如何改写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.      }  
分享到:
评论

相关推荐

    struts2.0struts2.0struts2.0struts2.0struts2.0struts2.0

    struts2.0struts2.0struts2.0struts2.0struts2.0struts2.0struts2.0struts2.0struts2.0struts2.0

    struts2.0+rest

    struts2.0+rest 入门Demo

    struts2.0中文教程

    01 为Struts 2.0做好准备 02 常用的Struts 2.0的标志(Tag) 03 Struts 2.0的Action讲解 04 在Struts 2.0中国际化(i18n)您的应用程序 05 转换器(Converter)——Struts 2.0中的魔术师 06 在Struts 2.0中实现表单...

    Struts2.0视频教程+struts2.0中文教程

    Struts2.0视频教程,struts2.0中文教程,Struts2.0视频教程,struts2.0中文教程,

    sstruts2.0 struts2.0

    sstruts2.0 struts2.0sstruts2.0 struts2.0sstruts2.0 struts2.0sstruts2.0 struts2.0sstruts2.0 struts2.0sstruts2.0 struts2.0sstruts2.0 struts2.0sstruts2.0 struts2.0sstruts2.0 struts2.0

    struts2.0的数据校验框架struts2.0的数据校验框架

    struts2.0的数据校验框架struts2.0的数据校验框架struts2.0的数据校验框架struts2.0的数据校验框架

    Struts 2.0系列(MAX)

    Struts 2.0系列(MAX),pdf格式,全方位介绍struts2: 常用的Struts 2.0的标志(Tag)介绍 Struts 2.0的Action讲解 在Struts 2.0中国际化(i18n)您的应用程序 转换器(Converter)——Struts 2.0中的魔术师 在Struts ...

    JavaEE源代码 Struts2.0

    JavaEE源代码 Struts2.0JavaEE源代码 Struts2.0JavaEE源代码 Struts2.0JavaEE源代码 Struts2.0JavaEE源代码 Struts2.0JavaEE源代码 Struts2.0JavaEE源代码 Struts2.0JavaEE源代码 Struts2.0JavaEE源代码 Struts2.0...

    struts2.0jar包

    struts2.0jar包 struts2.0包 struts2.0源文件

    Struts2.0 Jar包

    此为Struts2.0最新Jar包,方便各位用于Struts2.0的开发.

    struts 2.0 详细配置

    struts 2.0 详细配置 struts 2.0 详细配置 struts 2.0 详细配置

    Struts 2.0

    清晰的介绍了Struts 2.0框架的工作流程,Action线程安全,程序入口,配置文件。

    Struts2.0学习Struts2.0文档

    为Struts 2.0做好准备 Struts作为MVC 2的Web框架,自推出以来不断受到开发者的追捧,得到用广泛的应用。作为最成功的Web框架,Struts自然拥有众多的优点: MVC 2模型的使用 功能齐全的标志库(Tag Library) 开放...

    struts2.0入门案例

    struts2.0 入门案例、简单的struts2.0入门案例 2.0配置包,基础

    struts2.0源代码

    struts2.0源代码(有关于struts2.0实现上传与下载和如何操作数据库的源代码),非常有用

    struts2.0的数据校验struts2.0的数据校验

    struts2.0的数据校验struts2.0的数据校验struts2.0的数据校验struts2.0的数据校验struts2.0的数据校验struts2.0的数据校验

    struts2.0的基本jar包

    struts2.0的基本jar包,用于开发struts2.0的项目

    struts2.0的特点

    struts2.0的特点

Global site tag (gtag.js) - Google Analytics