这两天在学习 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:8080,context
为 webapp 的名字 book-demo,而后面的 action
则使用了路径变量。
从上面的表单代码可以看到表单提交对应的 action
为 book_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 | 删除书本信息 |