RESTful开发
从上面粗体字代码可以看出,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使用Struts2开发的REST服务
Struts 2 的 REST 插件支持一种资源具有多少表示形式,当浏览者向 book.xml 发送请求将可以看到如图 2 所示页面。
图2REST服务的XML形式
从图 2 可以看出,该页面正是 Action 所包含的全部数据,当使用 XML 显示时 REST 插件将会负责把这些数据转换成 XML 文档。
除此之外,REST 插件还提供了 JSON 格式的显示方式,当开发者向 book.json 发送请求将看到如图 3 所示页面。
图3REST服务的JSON形式
Struts2的REST插件默认支持XHTML、XML和JSON三种形式的数据。