JAX-RS 从傻逼到牛叉 3:路径匹配

JAX-RS 的核心功能是处理向 URI 发送的请求,所以它提供了一些匹配模式以便简化对 URI 的解析。楼主在本系列的上一篇文章中已经使用了最简单的路径参数,本文将介绍一些稍微高级点的咚咚。

模板参数

前面已经见过用 @Path("{id}") 和 @PathParam("id") 来匹配路径参数 id。这种匹配方式可以被嵌入到 @Path 注解中的任何地方,从而匹配多个参数,例如下面的代码用来查找 ID 在某一范围内的电影:

@GET
@Path("{min}~{max}")
@Produces(MediaType.APPLICATION_JSON)
public List<Movie> findMovies(@PathParam("min") int min, @PathParam("max") int max) {

于是,GET /ms/rest/movie/5~16 就将返回 ID 为 5 到 16 的电影。此处的 min 和 max 已被自动转换为 int 类型。JAX-RS 支持多种类型的自动转换,详见 @PathParam的文档。

根据 HTTP 规范,参数可能会编码。默认情况下,JAX-RS 会自动解码。如果希望得到未解码的参数,只需在参数上再加个 @Encoded 注解。该注解适用于大多数 JAX-RS 注入类型,但并不常用。

模板参数虽然灵活,也可能会带来歧义。例如想用 {firstName}-{lastName} 匹配一个人的姓名,但恰好某人的名(lastName)含有“-”字符,像 O-live K 这种,匹配后就会变成姓 live-K,名 O。这种场景很难避免,一种简单的解决方法就是对参数值进行两次编码,然后在服务端代码解码一次,因为 JAX-RS 默认会进行一次解码,或者加上 @Encoded 注解,自己进行两次解码。

另外,在一个复杂系统中,多个 @Path 可能会造成路径混淆,例如 {a}-{b} 和 {a}-z 都能匹配路径 a-z。虽然 JAX-RS 定义了一些规则来指定匹配的优先级,但这些规则本身就比较复杂,并且也不能完全消除混淆。楼主认为,设计一个 REST 系统的核心就是对 URI 的设计,应当小心处理 URI 的结构,合理分类,尽量保证匹配的唯一性,而不要过度使用晦涩的优先级规则。楼主将在下一篇文章介绍优先级规则。

正则表达式

模板参数可以用一个正则表达式进行验证,写法是在模板参数的标识符后面加一个冒号,然后跟上正则表达式字符串。例如在根据 ID 查询电影信息的代码中,模板参数{id} 只能是整数,于是代码可以改进为:

@GET
@Path("{id : \\d+}")
@Produces(MediaType.APPLICATION_JSON)
public List<Movie> findMovies(@PathParam("min") int min, @PathParam("max") int max) {

冒号左右的空格将被忽略。用正则表达式验证数据很有局限性,可惜 JAX-RS 目前并不能直接集成 Bean 验证框架,因此复杂的验证只能靠自己写代码。

查询参数

查询参数很常见,就是在 URI 的末尾跟上一个问号和一系列由“&”分隔的键值对,例如查询 ID 为 5 到 16 的电影也可以设计为 /ms/rest/movie?min=5&max=16。JAX-RS 提供了 QueryParam 来注入查询参数:

@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Movie> findMovies(@DefaultValue("0") @QueryParam("min") int min,
@DefaultValue("0") @QueryParam("max") int max) {

查询参数是可选的。如果 URI 没有设定某个查询参数,JAX-RS 就会根据情况为其生成 0、空字符串之类的默认值。如果要手动设定默认值,需要像上面的代码一样用@DefaultValue 注解来指定。另外还可以加上 Encoded 注解来得到编码的原始参数。

有的查询参数是一对多的键值对,例如 /xyz?a=def&a=pqr,这种情况只需将注入的参数类型改为 List 即可。

矩阵参数

矩阵参数应该属于 URI 规范中的非主流类型,但它实际上比查询参数更灵活,因为它可以嵌入到 URI 路径中的任何一段末尾(用分号隔开),用来标识该段的某些属性。例如 GET /ms/rest/movie;year=2011/title;initial=A 表示在 2011 年出品的电影中查找首字母为 A 的标题。year 是电影的属性,而 initial 是标题的属性,这比把它们都作为查询参数放在末尾更直观可读。匹配 URI 的时候,矩阵参数将被忽略,因此前面的 URI 匹配为 /ms/rest/movie/title。矩阵参数可以用 @MatrixParam 来注入:

@GET
@Path("title")
@Produces(MediaType.APPLICATION_JSON)
public List<String> findTitles(@MatrixParam("year") int year, @MatrixParam("initial") String initial) {

如果 URI 的多个段中含有相同名称的矩阵参数,例如 /abc;name=XXX/xyz;name=OOO,这种直接注入就失效了,只能用下面要讲的编程式访问来取得。

编程式访问

如果简单的注入不能达到目的,就需要通过注入 PathSegment 或 UriInfo 对象来直接编程访问 URI 的信息。

一个 PathSegment 对象代表 URI 中的一个路径段,可以从它得到矩阵参数。它可以通过 @PathParam 来注入,这要求该路径段必须整个被定义为一个模板参数。例如下面的代码也可以用来处理 GET /ms/rest/movie/{id}:

@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Movie findMovie(@PathParam("id") PathSegment ps) {

@PathParam 也可以注入多个段,如果想把 /a/b/c/d 匹配到 /a/{segments}/d,直接注入一个字符串显然不行,因为 b/c 是两个路径段。唯一的选择是把注入的类型改为 List<PathSegment>。楼主严重不推荐用一个模板参数匹配多个路径段,因为这很容易干扰其他匹配的设计,最后搞成一团乱麻。URI 路径段应当尽量设计得简单明晰,再辅以矩阵参数或查询参数就能应付大多数场景。不论对服务端还是客户端开发人员来说,简洁的 URI 既便于管理,又便于使用。网上有不少关于 URI 设计指南的文章,此处不再赘述。

如果想完全手动解析路径,则可以用 @Context 注入一个 UriInfo 对象,通过此对象可以得到 URI 的全部信息,详见 API 文档。例如:

@GET
@Path("{id}/{segments}")
@Produces(MediaType.PLAIN_TEXT)
public String getInfo(@PathParam("id") int id, @Context UriInfo uriInfo) {

UriInfo 主要用在某些特殊场合下起辅助作用,设计良好的 URI 用普通的注入就能完成大部分匹配。

工欲善其事必先利其器,为此 JAX-RS 提供了这些利器来解析 URI。至于如何用这些器来做出一个好系统,则还是依赖于 URI 本身的设计。

转载于:https://my.oschina.net/lhplj/blog/170946

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值