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 删除书本信息

Vim 开发 Rails 的那些坑

最近两个多月一直在用号称编辑器之神的 Vim 做着 Ruby on Rails 的开发。今天就来总结一下,讲讲我对文本编辑器、脚本语言的一些想法和遇到的坑。

  1. 麻烦的代码跳转,查看代码不方便。使用 IDE 的一大优势就是查看代码非常方便,Vim 这样的文本编辑器当然也可以使用 ctags 这样的插件支持代码跳转。但是只要你新加了一个方法,就要重新生成一次 tag 文件,你说麻烦不麻烦?

  2. 缺乏函数的outline(Vim 也许有这样的插件,只是我没有用到)。有的时候写了一个方法,过的时间久了你就忘了这个方法的存在。假设你开始写一个旧的项目,那么你每当要写一个新的函数的时候,你就要思索一下了,到底有没有写过类似的方法?总不见得每次都把相关源码重新看一遍吧!

  3. 鸡肋的代码补全。Vim 的代码补全基本靠猜,猜的策略大概就是根据你当前打开的页面中的关键词、过往的输入词频之类的,还是不能根据程序的上下文进行代码提示。我见过的比较智能的 IDE 比如 RubyMine,甚至能够对你的代码风格进行指导,在写的过程中就能提高你的代码质量。

  4. 快捷键的配置五花八门。Vim 的强大很多时候是依靠强大的插件功能和自定义配置文件。每个人都有自己的偏好和习惯,时间长了就固化了。不同人之间配置不同、操作习惯不同,那么无形中就给团队协作制造了一堵屏障。

  5. 静态语言的一大优势是在代码编写阶段就可以做类型检查,因此借助 IDE 就可以做到查看当前对象的可用方法以及使用说明。而写脚本语言则必须时常上网查看一下语言手册,冷不丁还可能遇到拼写错误。这些重复的行为和低级的错误一定程度上抵消了脚本语言带来的生产力优势。

说一些关于 IDE 和文本编辑器的话题。总有感觉很 Geek 的家伙会冒出来说使用 IDE 会让人变傻之类云云。但是我的观点恰恰相反,程序员的本职功能还是尽可能地解放出生产力,解决当前的业务问题。专注于业务本身而不是纠结这么些个细枝末节才是正确的态度。

一些观点认为:Vim 这样的文本编辑器的优势在于远程调试(例如 SSH 连接远程设备)的时候无法使用图形界面,而终端使用文本编辑器则不受影响。

但是我们有 Git

借助 Git 我们可以把远程的代码克隆到本地进行修改,修改完直接推送然后远程更新即可。使用 SSH 进行远程连接需要保证网络连接持续稳定,而使用 Git 只要求克隆和推送时两次连接的网络稳定即可!

历史证明编程工具总是向着以性能换取开发效率的方向演进。未来的程序员应该是 problems solver,而不是 coder。