Spring MVC 中的 url 问题

这两天在学习 Spring MVC 的时候与到了一个关于 url 的问题让我很困惑,查找资料和同学讨论没找出一个结论。现在先这里 Mark 一下,等找出原因以后再补完这篇文章。

1. 问题描述

首先上一个表单:

<form:form commandName="book" action="book_update" method="post">
<fieldset>
  <legend>Add a book</legend>
  <form:hidden path="id"/>
    <p>
      <label for="category">Category</label>
      <form:select id="category" path="category.id" items="${categories}"
                   itemLabel="name" itemValue="id"></form:select>
    </p>
    <p>
      <label for="title">Title</label>
      <form:input id="title" path="title" />
    </p>
    <p>
      <label for="author">Author</label>
      <form:input id="author" path="author" />
    </p>
    <p>
      <label for="isbn">ISBN</label>
      <form:input id="isbn" path="isbn" />
    </p>
    <p id="buttons">
      <input id="reset" type="reset" tabindex="4" />
      <input id="submit" type="submit" tabindex="5" 
             value="Update Book" />
    </p>
</fieldset>
</form:form>

这个表单在我访问 http://127.0.0.1:8080/book-demo/book_edit/{id} 时会展示。简单来说,这个 url 的格式可以表示为:

http://hostname[:端口号]/context/action/{id}

在这里 hostname[:端口号] 即为 127.0.0.1:8080context 为 webapp 的名字 book-demo,而后面的 action 则使用了路径变量。

从上面的表单代码可以看到表单提交对应的 actionbook_update。这个表单提交的时候,我预期的 url 是:

http://127.0.0.1:8080/book-demo/book_update

但实际表单提交后访问的 action 为:

http://127.0.0.1:8080/book_demo/book_edit/book_update

也就是说程序把 /book_demo/book_edit 作为了 context,把路径变量作为了 action

2. 尝试的解决方案

显然,错误出在访问 action 的时候产生了错误的 url,所以针对这个错误原因我尝试了两种解决方案:

1) action=”/book_update”

我把表单的 action 改了一下,结果产生的 url 是:

http://127.0.0.1:8080/book_update

显然连 url 的 context 都丢了,action 中带了 / 之后有了一点根路径的意思。

2) action=”/book-demo/book_update”

结果正确,获得了我想要的 url 。

3. 思考

a. 关于 url 产生机制

这个问题不禁引人思考 url 路径产生的机制。

之前做过 ruby on rails 的开发,作为对比:tomcat 运行应用的时候都会带上这个应用的名称,也就构成了第一级的上下文。对比 ruby on rails 在运行应用的时候 url 不会带上应用名称。因此,这个表单在 ruby on rails 中使用不会有问题,但是在这里就会有!

从绝对路径与相对路径的角度来看:首先 action=”book_update” 的写法相当于使用相对路径,把 /book_demo/book_edit 当做了上一级路径。而 action=”/book_update” 以及 action=”/book-demo/update” 则属于绝对路径的范畴。

b. 关于 RESTful 的 url

上面的表单其实并不符合 RESTful 的风格(我觉得 rails 提倡的 RESTful 风格的 url 很是优雅,之前做 rails 的时候一直想写写来着)。比如更新一本书的信息,对应的 action 可以叫 update,路径可以映射为 /books/{id}/update。上文的表单是把书本的 id 信息作为一个隐藏域来传递,虽然可行但绝对不是一个良好的风格。这里附一下 RESTful 风格的路由:
Rails:

url HTTP 方法 对应的 action 操作说明
/books GET list 获取所有书籍的信息
/books/new GET new 返回新增书籍的页面
/books POST create 新增一本书籍
/books/{id} GET show 显示某特定id书本的详细信息
/books/{id}/edit GET edit 返回编辑书本信息的页面
/books/{id} PUT/PATCH update 更新书本信息
/books/{id} DELETE destroy 删除书本信息

Rails 的 HTTP 方法使用了一些小技巧来实现 PUT/PATCH 和 DELETE 方法。通过在表单内增加一个隐藏域来实现:

<input type="hidden" name="_method" value="put">

对应地,我们可以借鉴并稍作修改在 Spring MVC 中应用:

url HTTP 方法 对应的 action 操作说明
/books GET list 获取所有书籍的信息
/books/new GET new 返回新增书籍的页面
/books/create POST create 新增一本书籍
/books/{id} GET show 显示某特定id书本的详细信息
/books/{id}/edit GET edit 返回编辑书本信息的页面
/books/{id}/update POST update 更新书本信息
/books/{id}/destroy POST destroy 删除书本信息