在阅读之前, 先参考
当看完这些之后, 大概应该就能理解, RESTful 的规范, 以及 JFinal 的路由实现方式.
首先, 先看下 JFinal 目前的 RESTful 风格路由实现.
翻开 com.jfinal.core.ActionMapping#getAction
方法
/** * Support four types of url * 1: http://abc.com/controllerKey ---> 00 * 2: http://abc.com/controllerKey/para ---> 01 * 3: http://abc.com/controllerKey/method ---> 10 * 4: http://abc.com/controllerKey/method/para ---> 11 * The controllerKey can also contains "/" * Example: http://abc.com/uvw/xyz/method/para */ Action getAction(String url, String[] urlPara) { Action action = mapping.get(url); if (action != null) { return action; } // -------- int i = url.lastIndexOf(SLASH); if (i != -1) { action = mapping.get(url.substring(0, i)); urlPara[0] = url.substring(i + 1); } return action; }
这里有说明, 目前 JFinal 支持的路由格式, 在此之外, JFinal 还提供了一个 Restful
的拦截器, 来达到严格的 RESTful url 风格实现.
/** * Invocation 中添加 Method method * The standard definition is as follows: index - GET - A view of all (or a selection of) the records show - GET - A view of a single record add - GET - A form to post to create save - POST - Create a new record edit - GET - A form to edit a single record update - PUT - Update a record delete - DELETE - Delete a record * * GET /user ---> index * GET /user/id ---> show * GET /user/add ---> add * POST /user ---> save * GET /user/edit/id ---> edit * PUT /user/id ---> update * DELETE /user/id ---> delete */ public class Restful implements Interceptor { // ... }
当看完这些之后, 进入到正题.
首先, JFinal 基本上已经可以很好的实现 RESTful 风格的 url 了, 但是仍然有不足, 无论是默认的路由实现, 或者是 Restful 拦截器的实现, 仍然无法做到全可控的 RESTful 实现.
Restful 拦截器的实现实际上是匹配链接以及 Request method, 然后进行 forward 到相应的 action 中.
例如, 参考链接 RESTful API 设计指南 文章中的
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
就没办法通过 Restful 拦截器来实现了.
那么, 所有问题都抛出来了.
因此, 这里要说的就是, 把 JFinal 的路由机制给改了下, 达到完美兼容 RESTful 链接, 并且保留 JFinal 的默认实现.
具体实现代码已提交至 iaceob/jfinal
先不讨论怎么实现, 其实原理也很简单, 看看代码很容易理解. 这里主要说说使用方式, 以及一些问题.
使用的时候非常简单, 首先在你的 AppConfig.configConstant 中, 设置开启 RESTful 模式.
@Override public void configConstant(Constants constants) { constants.setRestful(true); constants.setXxx(); }
当设置了 constants.setRestful(true);
之后, 路由解析就是 RESTful 风格实现的, 默认是 false, 也就是目前 JFinal 自身的路由实现.
接下来就是定义你的 RESTful 链接格式就好, 下面是一个测试案例.
package com.jfinal.test.restful.test.ctl; import com.jfinal.core.ActionKey; import com.jfinal.core.Controller; import com.jfinal.plugin.activerecord.Record; import com.jfinal.restful.Method; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; /** * Created by iaceob on 2017/3/17. */ public class ZooCtl extends Controller { private List<Record> getAttrs() { List<Record> rets = new ArrayList<Record>(); Enumeration<String> ens = super.getAttrNames(); while (ens.hasMoreElements()) { String key = ens.nextElement(); rets.add(new Record().set(key, super.getAttr(key))); } return rets; } private Record buildRet(String intro) { Record ret = new Record(); ret.set("intro", intro) .set("attrs", this.getAttrs()) .set("paras", super.getParaMap()); return ret; } @ActionKey("/zoo") public void list() { super.renderJson(this.buildRet("zoo list")); } @ActionKey(value = "/zoo", method = Method.POST) public void addZoo() { super.renderJson(this.buildRet("add zoo")); } @ActionKey("/zoo/:id") public void getZoo() { super.renderJson(this.buildRet("get zoo by id")); } @ActionKey(value = "/zoo/:id", method = Method.PUT) public void putZoo() { super.renderJson(this.buildRet("put zoo by id")); } @ActionKey(value = "/zoo/:id", method = Method.PATCH) public void patchZoo() { super.renderJson(this.buildRet("patch zoo by id")); } @ActionKey(value = "/zoo/:id", method = Method.DELETE) public void deleteZoo() { super.renderJson(this.buildRet("delete zoo by id")); } @ActionKey("/zoo/:id/animals") public void animals() { super.renderJson(this.buildRet("show zoo animals")); } @ActionKey("/zoo/:zooId/animal/:animalId") public void getAnimal() { super.renderJson(this.buildRet("show zoo animal by id")); } @ActionKey("/zoo/:zid/animal2/:aid") public void getAnimal2() { super.renderJson(this.buildRet("show zoo animal by id, 2")); } }
使用时也是使用 @ActionKey
注解, url 中的值通过 :name
来标识, ActionKey 注解中的另外一个参数就是绑定相应的请求 method.
获取参数也未作改变, 表单传递的参数仍然通过 getPara()
来获取, 使用方式详细见 JFinal 文档.
但是需要注意的是:
开启 RESTful 模式后,
getPara()
方法按索引取值的方法将失去效果
例如,/zoo/:id/1-3
若这样的请求则会直接返回 404 错误, 因为 RESTful 模式采用的匹配规则较严格(可查看代码实现, 或对该处进行修改令其支持), 另外若是采用 RESTful 风格链接, 应该也不会使用此方式传参.存放在 url 中的参数标识无法通过
getPara()
取值.
若要获取 url 中指定的参数值, 需要通过getAttr()
获取, 例如/zoo/:id
获取这个 id 时, 使用getAttr("id")
.RESTful 模式与非 RESTful 的 ActionKey 是不可共用的.
这个描述可能比较模糊, 但是要表达的意思是, 如果 ActionKey 中的 url 是采用 RESTful 风格写的, 但是却没有开启 RESTful 模式, 就会发生问题, 非 RESTful 模式是不去分析 url 中参数的.无需再使用 POST GET 拦截器
JFinal 提供了 GET POST 拦截器, 用以指定某个路由只能某中请求, 但是开启 RESTful 模式后, 在路由匹配是就已经限定了指定的 method 访问, 因此无需再使用 `@Before(POST.class)` 这样的代码.
使用方式大抵如此, 剩下还有一些可能会令人不爽的小问题.
使用
getAttr()
获取 url 中参数时, 获取到的值都是 String 类型.
因HttpServletRequest
自身的设计, 在解析完 url 后无法将值写入到parameter
中, 因此无法使用 JFinal 提供的getPara()
取值. 后续也考虑过做一个类型自动识别, 但是最终抛弃了, 在不同的系统中, 参数的类型都不同, 自动识别是不靠谱的, 因此建议使用中, 在获取这个参数时都写一个validator
对这个值进行验证,controller
中就可以对该值进行强转类型.性能问题
也不要想得太过与悲观, 这里说的性能只是相对于 JFinal 自身的路由实现. JFinal 的路由实现非常简单, 甚至可以就简单的看成是字符串匹配, 但是这里的 RESTful 模式, 匹配时使用了正则, 因此效能肯定会比 JFinal 自身实现要来的低一些. 后续会做相关方面性能测试.非插拔式
意思是, 并非提供扩展, 而是通过修改内核实现, 其原因其实看看 JFinal 提供Restful
拦截器就知道了 = =, 以及 JFinal 的极简的设计缘故. 首先, JFinal 的路由实现, 路由最初加载进来时, 就通过 ActionMapping 隐射到具体的 Action 中, 随后只要有请求就通过 ActionHandler 找到具体的 Action, 因此想要在此基础上实现 RESTful 几乎是不可能, Restful 拦截器的实现也只能通过 forward 来实现, 且无法在 url 中指定参数. 也就是说, 无论你是加自己的过滤器, 甚至创建一个新的注解, 在请求进来时最先经过的 ActionHandler 就以及拒绝了请求, 无法进入到新定义的路由处理中. 因此要实现就只能从 ActionMapping 中入手, 修改路由实现方式.
基本就这样.
@jfinal 官方库收不收这功能, 收我推给你 XDD
------
百度的 ueditor 好难用 = =, 换 markdown 呗, 格式全没了, http://blog.3u3.me/post/jfinal-restful/ 这里有格式.
现在 jfinal 用户基数非常大,所以对于这类改变要非常小心谨慎,如果确实要改进,也需要确保原有的使用方式可以被兼容,楼主的分享中也谈过了一些不足,所以是无法收了这功能的 ^_^
其实,jfinal 一直没有彻底的去支持 Roy Fielding 博士在他论文中提出的 RESTFUL 风格的 URL 是有强烈的原因的
首先,RESTFUL 是一种软件架构风格,该架构风格的内容很丰富,其核心在于资源的抽象,并不在于 URL 的风格,URL 风格仅仅只是 RESTFUL 对 URL 提出的一个建议,并非行业标准,更非事实标准。很多人将 restful 风格的 URL 当成了 restful 架构本身,这是个误解
其次, Roy Fielding 博士在论文中建议的 restful url 风格并不接地气。有些浏览器并不支持 DELETE、PUT 之类的请求类型,而是通过提供类似于 _method 这样额外的参进行模拟。不接地气还体现在:对资源的操作类型很可能会超出 POST、GET、PUT、DELETE 等等类型
最后,jfinal 自身在更重要的资源的抽象上建议 RESTFUL 风格,但对 url 风格保持了自己的优势,那就是将对资源的的操作从 http 的协议中转移到 url 之中,仅此而已,例如 DELETE 这个动作,在 jfinal 中是 abc.com/resource/delete/id
restful 本身是一个软件架构风格,而 url 仅为其中的一个建议,并且这个建议并不是那么接地气,所以现在腾迅、阿里、百度这些巨头的开放平台提供的 url 都是 jfinal 的风格,这里举出一下微信公众平台的 API 的 url 风格:
1:创建菜单:https://api.weixin.qq.com/cgi-bin/menu/create?access_token=
2:新增素材:https://api.weixin.qq.com/cgi-bin/media/upload?access_token=
3:微信支付:https://api.mch.weixin.qq.com/pay/unifiedorder
4:二维码生成:https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=
以上的 url 全部都只使用了 GET、POST 两种请求类型,并且对资源的操作类似于 upload、create 都放在了 url 之中,所以无论是在逻辑上,还是在实践中Roy Fielding 博士的 restful url 仅为一个学术化上的建议,并不适合用于实践
综上,jfinal 在资源抽象这个核心问题上认同 restful架构风格,但对其建议的 url 风格持高度怀疑态度