Struts 2 安装 REST 插件

阿超 发表于 2009-09-03 02:42 | 来源: | 阅读 701 次

安装 REST 插件非常简单,只需按如下步骤进行即可:

  1. 将 Struts 2 项目下 struts2-convention-plugin-2.1.6.jar、struts2-rest-plugin-2.1.6.jar 两个 JAR 包复制到 Web 应用的 WEB-INF\lib 路径下。
  2. 由于 Struts 2 的 REST 插件还需要将提供 XML、JSON 格式的数据,因此还需要将 xstream-1.2.2.jar、json-lib-2.1.jar、ezmorph-1.0.3.jar 以及 Jakarta-Common 相关 JAR 包复制到 Web 应用的 WEB-INF/lib 路径下。
  3. 通过 struts.xml、struts.properties 或 web.xml 改变 struts.convention.default.parent.package 常量的值,让支持 REST 风格的 Action 所在的包默认继承 rest-default,而不是继承默认的 convention-default 父包。

对于第三个步骤而言,开发者完全可以不设置该常量,如果开发者不设置该常量,则意味着开发者必须通过 Annotation 为每个 Action 类设置父包。

实现支持 REST 的 Action 类

在实现支持 REST 的 Action 之前,我们先为系统提供一个 Model 类:Book,该 Book 类非常简单,代码如下:

public class Book
{
    private Integer id;
    private String name;
    private double price;
    // 无参数的构造器
    public Book(){}
    //id 属性的 setter 和 getter 方法
    public void setId(Integer id)
    {
        this.id = id;
    }
    public Integer getId()
    {
        return this.id;
    }
    // 省略 name 和 price 的 setter 和 getter 方法
    ...
}

除了提供上面的 Book 类之外,我们还为该 Book 类提供一个业务逻辑组件:BookService。为了简单起见,BookService 类不再依赖 DAO 组件访问数据库,而是直接操作内存中的 Book 数组——简单地说,本系统中状态是瞬态的,没有持久化保存,应用运行过程中这些状态一直存在,但一旦重启该应用,则系统状态丢失。下面是 BookService 类的代码:

public class BookService
{
    private static Map<Integer , Book> books
        = new HashMap<Integer , Book>();
    // 保留下本图书的 ID
    private static int nextId = 5;
    // 以内存中的数据模拟数据库的持久存储
    static {
        books.put(1 , new Book(1
            , "疯狂 Java 讲义" , 99));
        books.put(2 , new Book(2
            , "轻量级 Java EE 企业应用实战" , 89));
        books.put(3 , new Book(3
            , "疯狂 Ajax 讲义", 78));
        books.put(4 , new Book(4
            , "Struts 2 权威指南" , 79));
    } 

    // 根据 ID 获取
    public Book get(int id)
    {
        return books.get(id);
    } 

    // 获取系统中全部图书
    public List<Book> getAll()
    {
        return new ArrayList<Book>(books.values());
    } 

    // 更新已有的图书或保存新图书
    public void saveOrUpdate(Book book)
    {
        // 如果试图保存的图书的 ID 为 null,表明是保存新的图书
        if (book.getId() == null)
        {
            // 为新的图书分配 ID。
            book.setId(nextId++);
        }
        // 将保存 book
        books.put(book.getId() , book);
    } 

    // 删除图书
    public void remove(int id)
    {
        books.remove(id);
    }
}

从上面粗体字代码可以看出,BookService 提供了 4 个方法,用于实现对 Book 对象的 CRUD 操作。

下面开始定义支持 REST 的 Action 类了,这个 Action 类与前面介绍 Struts 2 的普通 Action 存在一些差异——因为该 Action 不再用 execute() 方法来处理用户请求,而是使用前面介绍的 7 个标准方法来处理用户请求。除此之外,该 Action 总是需要处理 id 请求参数,因此必须提供 id 请求参数,并为之提供对应的 setter 和 getter 方法。

因为本系统已经提供了 Book Model 类,并且为了更好的模拟 Rails 中 ActiveController(Controller)直接访问 ActiveRecord(Model)的方式,本系统采用了 ModelDriven 的开发方式,下面是本系统中支持 REST 的 Action 类的代码。

// 定义返回 success 时重定向到 book Action
 @Results(@Result(name="success"
    , type="redirectAction"
    , params = {"actionName" , "book"}))
 public class BookController extends ActionSupport
    implements ModelDriven<Object>
 {
    // 封装 id 请求参数的属性
    private int id;
    private Book model = new Book();
    private List<Book> list;
    // 定义业务逻辑组件
    private BookService bookService = new BookService();
    // 获取 id 请求参数的方法
    public void setId(int id)
    {
        this.id = id;
        // 取得方法时顺带初始化 model 对象
        if (id > 0)
        {
            this.model = bookService.get(id);
        }
    }
    public int getId()
    {
        return this.id;
    }
    // 处理不带 id 参数的 GET 请求
    // 进入首页
    public HttpHeaders index()
    {
        list = bookService.getAll();
        return new DefaultHttpHeaders("index")
            .disableCaching();
    }
    // 处理不带 id 参数的 GET 请求
    // 进入添加新图书。
    public String editNew()
    {
        // 创建一个新图书
        model = new Book();
        return "editNew";
    }
    // 处理不带 id 参数的 POST 请求
    // 保存新图书
    public HttpHeaders create()
    {
        // 保存图书
        bookService.saveOrUpdate(model);
        addActionMessage("添加图书成功");
        return new DefaultHttpHeaders("success")
            .setLocationId(model.getId());
    }
    // 处理带 id 参数的 GET 请求
    // 显示指定图书
    public HttpHeaders show()
    {
        return new DefaultHttpHeaders("show");
    }
    // 处理带 id 参数、且指定操作 edit 资源的 GET 请求
    // 进入编辑页面 (book-edit.jsp)
    public String edit()
    {
        return "edit";
    }
    // 处理带 id 参数的 PUT 请求
    // 修改图书
    public String update()
    {
        bookService.saveOrUpdate(model);
        addActionMessage("图书编辑成功!");
        return "success";
    }
    // 处理带 id 参数,且指定操作 deleteConfirm 资源的方法
    // 进入删除页面 (book-deleteConfirm.jsp)
    public String deleteConfirm()
    {
        return "deleteConfirm";
    }
    // 处理带 id 参数的 DELETE 请求
    // 删除图书
    public String destroy()
    {
        bookService.remove(id);
        addActionMessage("成功删除 ID 为" + id + "的图书!");
        return "success";
    }
    // 实现 ModelDriven 接口必须实现的 getModel 方法
    public Object getModel()
    {
        return (list != null ? list : model);
    }
 }

上面 Action 代码中粗体字代码定义了 7 个方法,这 7 个方法正是前面提到的标准方法。除此之外,该 Action 里还包含一个额外的 deleteConfirm() 方法,这个方法用于处理带 id 参数、且指定操作 deleteConfirm 资源的 GET 请求。也就是说,当用户请求 /book/1/deleteConfirm 时,该请求将由该方法负责处理。实际上,RestActionMapper 不仅可以将对 /book/1/edit 的请求映射到 Book 控制器的 edit() 方法,而 1 将作为 id 请求参数。实际上,它可以将任意 /book/1/xxx 的请求映射到 Book 控制器的 xxx() 方法,而 1 是请求参数。上面 Action 类使用了 @Results 进行修饰,这表明当 Action 的任何方法返回“success”逻辑视图时,系统将重定向到 book.action。

可能有读者会对 index()、create()、show() 三个方法的返回值感到疑惑:它们不再直接返回普通字符串作为逻辑视图名字,而是返回一个以字符串为参数的 DefaultHttpHeaders 对象?其实读者不必对 DefaultHttpHeaders 感到疑惑,其实 DefaultHttpHeaders 只是普通字符串的加强形式,用于 REST 对处理结果进行更多额外的控制。当 Action 类的处理方法返回字符串作为逻辑视图时,Struts 2 只能将其当成一个简单的视图名,仅能根据该视图名映射到实际视图资源,仅此而已。如果使用 DefaultHttpHeaders 作为逻辑视图,DefaultHttpHeaders 除了可以包含普通字符串作为逻辑视图名之外,还可以额外增加更多的控制数据,从而可以增强对 Response 的控制。关于 HttpHeaders 和 DefaultHttpHeaders 的介绍请参考 REST 插件的 API。

还有一点需要指出,上面的 BookController 控制器实现类的类名并不以 Action 结尾,而是以 Controller 结尾,因此我们可以在 struts.xml 文件中配置如下常量:

<!--  指定控制器类的后缀为 Controller -->
 <constant name="struts.convention.action.suffix"
    value="Controller"/>
本应用里的 struts.xml 文件如下:
程序清单:codes\12\12.6\BookShow\WEB-INF\src\struts.xml
 <?xml version="1.0" encoding="GBK" ?>
 <!-- 指定 Struts 2 配置文件的 DTD 信息 -->
 <!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN"
    "http://struts.apache.org/dtds/struts-2.1.dtd">
 <!-- 指定 Struts 2 配置文件的根元素 -->
 <struts>
    <constant name="struts.i18n.encoding" value="GBK"/>
    <!--  指定控制器类的后缀为 Controller -->
    <constant name="struts.convention.action.suffix"
        value="Controller"/>
    <constant name="struts.convention.action.mapAllMatches"
        value="true"/>
    <!-- 指定 Action 所在包继承的父包 -->
    <constant name="struts.convention.default.parent.package"
        value="rest-default"/>
 </struts>

实现视图层

定义了上面 Action 之后,接下来应该为这些 Action 提供视图页面了,根据 Convention 插件的约定,所有视图页面都应该放在 WEB-INF\content 目录下,例如当用户向 /book.action 发送请求时,该请求将由 BookController 的 index() 方法进行处理,该方法处理结束后返回“index”字符串,也就是将会使用 WEIN-INF\content\book-index.jsp 页面作为视图资源。下面是 book-index.jsp 页面的代码:

<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
 <%@taglib prefix="s" uri="/struts-tags" %>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
    <title> 图书展示系统 </title>
    <link href="<%=request.getContextPath() %>/css/demo.css"
        rel="stylesheet" type="text/css" />
 </head>
 <body>
 <s:actionmessage />
 <table>
    <tr>
        <th> 图书 ID</th>
        <th> 书名 </th>
        <th> 价格 </th>
        <th> 操作 </th>
    </tr>
    <s:iterator value="model">
    <tr>
        <td><s:property value="id"/></td>
        <td>${name}</td>
        <td>${price}</td>
        <td><a href="book/${id}"> 查看 </a> |
        <a href="book/${id}/edit"> 编辑 </a> |
        <a href="book/${id}/deleteConfirm"> 删除 </a></td>
    </tr>
    </s:iterator>
 </table>
 <a href="<%=request.getContextPath() %>/book/new"> 创建新图书 </a>
 </body>
 </html>

上面 JSP 页面非常简单,它负责迭代输出 Action 里包含的集合数据,向该应用 book.action 发送请求将看到如图 1 所示页面
图 1 使用 Struts 2 开发的 REST 服务

figure001

Struts 2 的 REST 插件支持一种资源具有多少表示形式,当浏览者向 book.xml 发送请求将可以看到如图 2 所示页面。

图 2 REST 服务的 XML 形式

figure002

从图 2 可以看出,该页面正是 Action 所包含的全部数据,当使用 XML 显示时 REST 插件将会负责把这些数据转换成 XML 文档。

除此之外,REST 插件还提供了 JSON 格式的显示方式,当开发者向 book.json 发送请求将看到如图 3 所示页面。

图 3 REST 服务的 JSON 形式

figure003

Struts 2 的 REST 插件默认支持 XHTML、XML 和 JSON 三种形式的数据。

当浏览者单击页面右边的“编辑”链接,将会向 book/idVal/edit 发送请求,这是一个包含 ID 请求参数、且指定操作 edit 资源的请求,因此将由 BookController 的 edit() 方法负责处理,处理结束后进入 book-edit.jsp 页面。浏览器里将看到如图 4 所示页面。

图 4 编辑指定图书

figure004

该页面单击“修改”按钮时需要修改图书信息,也就是需要使用 PUT 操作,但由于 HTML 不支持 PUT 操作,因此需要为该表单页增加一个额外的请求参数:_method,该请求参数的值为 put。该表单页的代码如下:

<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
 <%@taglib prefix="s" uri="/struts-tags" %>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
    <title> 编辑 ID 为 <s:property value="id"/> 的图书 </title>
    <link href="<%=request.getContextPath() %>/css/demo.css"
        rel="stylesheet" type="text/css" />
 </head>
 <body>
 <s:form method="post"
    action="%{#request.contextPath}/book/%{id}">
 <!-- 增加 _method 请求参数,参数值为 put 用于模拟 PUT 操作 -->
 <s:hidden name="_method" value="put" />
 <table>
    <s:textfield name="id" label="图书 ID" disabled="true"/>
    <s:textfield name="name" label="书名"/>
    <s:textfield name="price" label="价格" />
    <tr>
        <td colspan="2">
            <s:submit value="修改"/>
        </td>
 </table>
 </s:form>
 <a href="<%=request.getContextPath() %>/book"> 返回首页 </a>
 </body>
 </html>

该表单将提交给 BookController 的 update() 方法处理,update() 方法将负责修改系统里指定 ID 对应的图书信息。

与之类似的是,当请求需要执行 DELETE 操作时,一样需要增加名为 _method 的请求参数,并将该请求参数值设置为 delete。

喜欢Java豆技术站点的文章,那就通过 RSS Feed 功能订阅阅读吧!

已经有2 个评论

  1. 金象大药房 说:

    不错。

  2. ㈩⑦歲ㄖㄅ旅 说:

    说得很详细! 不错!
    oooO ↘┏━┓ ↙ Oooo
    ( 踩)→┃你┃ ←(死 )
    \ ( →┃√┃ ← ) /
      \_)↗┗━┛ ↖(_/

我要评论

*

* 绝不会泄露



返回首页 | 关于我们 | 联系我们 | 广告合作 | 网站地图 | 友情链接 | 版权声明 | 模板设计