如何使用oauth2实现jersey调用rest接口 接口安全认证

转自.cn/showcontent_75305.htm
InfoQ:安全是恒久的话题,对于基于WSDL和SOAP的Web Service,我们有WS-Security这样的安全规范来指导实现认证、授权、身份管理等安全需求。那么,RESTful API有无成熟可用规范或实现框架呢?如何保证RESTful API的安全性呢? 李锟:保证RESTful&API的安全性,主要包括三大方面:
a)&对客户端做身份认证 b)&对敏感的数据做加密,并且防止篡改 c)&身份认证之后的授权 对客户端做身份认证,有几种常见的做法: 在请求中加签名参数 1.为每个接入方分配一个密钥,并且规定一种签名的计算方法。要求接入方的请求中必须加上签名参数。这个做法是最简单的,但是需要确保接入方密钥的安全保存,另外还要注意防范replay攻击。其优点是容易理解与实现,缺点是需要承担安全保存密钥和定期更新密钥的负担,而且不够灵活,更新密钥和升级签名算法很困难。 使用标准的HTTP身份认证机制 HTTP&Basic身份认证安全性较低,必须与HTTPS配合使用。HTTP&Digest身份认证可以单独使用,具备中等程度的安全性。
HTTP&Digest身份认证机制还支持插入用户自定义的加密算法,这样可以进一步提高API的安全性。不过插入自定义加密算法在面向互联网的API中用的不是很多。 这个做法需要确保接入方“安全域-用户名-密码”三元组信息的安全保存,另外还要注意防范replay攻击。 优点:基于标准,得到了广泛的支持(大量HTTP服务器端、客户端库)。在服务器端做HTTP身份认证的职责可以由Web&Server(例如Nginx)、App&Server(例如Tomcat)、安全框架(例如Spring&Security)来承担,对应用开发者来说是透明的。HTTP身份认证机制(RFC&2617)非常好地体现了“分离关注点”的设计原则,而且保持了操作语义的可见性。
2.缺点:这类基于简单用户名+密码机制的安全性不可能高于基于非对称密钥的机制(例如数字证书)。 使用OAuth协议做身份认证 OAuth协议适用于为外部应用授权访问本站资源的情况。其中的加密机制与HTTP&Digest身份认证相比,安全性更高。需要注意,OAuth身份认证与HTTP&Digest身份认证之间并不是相互取代的关系,它们的适用场景是不同的。OAuth协议更适合于为面向最终用户维度的API提供授权,例如获取隶属于用户的微博信息等等。如果API并不是面向最终用户维度的,例如像七牛云存储这样的存储服务,这并非是OAuth协议的典型适用场景。
3.对敏感的数据做加密,并且防止篡改,常见的做法有: 部署SSL基础设施(即HTTPS),敏感数据的传输全部基于SSL。& 仅对部分敏感数据做加密(例如预付费卡的卡号+密码),并加入某种随机数作为加密盐,以防范数据被篡改。 & 身份认证之后的授权,主要是由应用来控制。通常应该实现某种基于角色+用户组的授权机制,这方面的框架有不少(例如Spring&Security),不过大多数开发团队还是喜欢自己来实现相关功能。 李建业:我不认为安全是RESTful&API需要考虑的问题,事实上我觉得这是两个正交的问题。当然,如果使用RESTful&API来提供认证、授权和身份管理,那也算是双方有关系,但是这和其它风格的API设计所要考虑的问题似乎没什么区别,不值得特别注意。
但是在具体设计层面,这两者的“正交点”上似乎确实有些问题,因为REST是一个推崇状态无关原则的架构风格,而认证和授权通常基于第三方解决方案,所以往往会出现违背有状态约束的问题,这个地方我也没有特别的想法,当然这个困难和原问题关系不大。 至于WS-族的协议,我不太了解,不太能参与讨论。 丁雪丰:对于RESTful&API,常见的安全措施都是可以继续使用的。例如,为了防篡改,可以对全部参数进行签名;为了防范重放攻击可以在请求中增加一次性的Token,或者短时间内有效的Token;对内容加密可以实现数据防泄露……;对于DDoS攻击,各种HTTP流量清洗策略,都可以继续发挥作用,因为这就是基本的HTTP请求。
在授权和认证方面,OAuth&2.0已经基本成熟了,并且得到了广泛地应用。如果可以,接入第三方账户体系是个不错的选择,比如Google和Facebook的,国内的当然也有几个候选。 马钧:个人认为RESTful的安全性分为几个层次,在安全要求较高的场合,可以通过HTTPs这样的加密协议来保证网络层的安全,应用层的安全可以通过OAuth实现认证,而对于资源的访问授权,则只能依靠应用程序来实现了。 InfoQ:如何对RESTful API进行版本控制,请分享您认为实用的做法? 李锟:一个比较简单实用的做法是直接在URI中插入版本号,这样做允许多个版本的API并行运行。
另一个做法是在HTTP请求中加入自定义头信息,标明使用的版本号。不过这个做法其实对浏览器不够友好,简单地使用浏览器+HTML无法测试。 李建业:目前比较好的方式还是在uri设计中添加版本信息,其它方法都不如这个实用。 丁雪丰:个人认为最好的版本化,就是没有明显的版本。在对已发布的服务进行变更时,要尽量做到兼容,其中包括URI、链接和各种不同的表述的兼容,最关键的就是在扩展时不能破坏现有的客户端。例如,要变更一个参数,可以选择同时兼容新旧两种输入,或者保持老参数不动,提供一个新的参数,在文档中必须做出说明,不推荐新用户再继续使用之前的参数。
如果必须要进行不兼容的变更,那么可以选择标记不同的版本号,这时可以选择在路径或参数中增加版本信息。也有做法是增加HTTP标头,只是在调用时会稍有不便,推荐前两种方法。 马钧:RESTfulAPI的版本升级,尽量兼容之前的版本,保证原有的API都能正常工作,可以通过HTTP&301转跳到新的资源。另外一种实用的做法就是在url中保留版本号,同时提供多个版本供客户端使用,如&&或者&/v1/&这样。 InfoQ:HTTP1.1规范中给出的动词对于设计RESTful API够用吗?您在实际项目中会扩展自己的动词吗?在什么情况下需要扩展?
李锟:这个问题取决于设计者如何看待和设计资源。如果资源抽象做的很好,对于某个资源的任何操作,通常都能够映射到CRUD四个类别中。CRUD四个类别对于操作资源来说,绝大多数情况下是完备的。HTTP的GET/POST/PUT/DELETE四个方法,对于CRUD四个类别的操作来说是足够的,映射关系是Create-POST/Retrieve-GET/Update-PUT/Delete-DELETE。 我们通常不会选择创建自己的动词,这样做对于客户端开发者来说,需要更多的学习成本。如果在资源上定义的操作过多,我们会选择拆分出更多的资源。
李建业:一般是够用的,有时一些“不够用”的场景是由于我们没有设计出合理的资源,比如批量操作。但是,正如之前所说的那样,对于某些内部的、传统的(因此模型稳定且已知)系统,API提供者和调用者会有自已的固定动词表,此时没必要拘泥。另外,我不建议扩展动词,一旦扩展了动词,其实已经破坏了我之前说的*“尽可能少的先验信息”*,那么,扩展动词和重新设计动词的成本差别不大。基于这个考虑,我建议尽可能保持动词不变,除非你想重新设计动词表。 丁雪丰:一般情况下,常用的HTTP动词是够用的,并没有出现一定要自己扩展动词的情况。其实,最常用的也就是GET、POST、DELETE和PUT,而HEAD、OPTIONS、TRACE则基本用不太到。如果出现一时找不到合适的动词,安全幂等的操作用GET,其他都可以用POST,在设计资源时稍加考虑即可。
马钧:在我的实际项目中,只用到了POST,PUT,DELETE,GET这四个动词。 InfoQ:今年5月份发布的JAX-RS 2.0规范对于RSTfulAPI的设计最有价值的特性是哪个(些)? 它(们)用于解决什么问题? 李锟:REST开发框架RESTEasy项目负责人Bill&Burke,去年写了一篇文章介绍JAX-RS&2.0。 我同意Bill在文章中的观点,在JAX-RS&2.0增加的内容中,最重要的三部分为: a)&Client&API——用来规范化JAX-RS客户端的开发方式。 b)&Server-side&Asynchronous&HTTP——用来实现服务器端推送功能,而不需要依靠低效的轮询方式。
c)&Filters&and&Interceptors——用来分离关注点,将鉴权、日志等逻辑与业务逻辑分离开,更好地实现代码重用。 这三部分的内容对于开发者来说都很有用。遵循JAX-RS规范做开发,可以确保服务器端以及客户端代码的可移植性。 李建业:我个人关注异步API这部分,主要是因为流式服务将会越来越多,那将大量需要这类支持。 InfoQ:能否为InfoQ的读者推荐一款实用的RESTful API开发框架,并说明您的推介理由。 李锟:这个问题我就不详细回答了。不同的编程语言有不同的REST开发框架,对于REST的支持程度也不同。开发RESTful&API的需求范围很广,可选择的开发框架的范围也很广。保持多样性是繁荣生态环境的基础。像Java就有支持JAX-RS规范的Jersey、RESTEasy、Restlet、Apache&CXF,和不支持JAX-RS规范的Spring&MVC等等很多框架。这些框架目前都做的不错。我对框架的选择没有倾向性。RESTful&API设计的最佳实践应该是通用的,而不是必须依赖某种特定的开发框架。
李建业:不好意思,这个我不太重视,没法推荐,不过我可以解释一下为什么对RESTful&API框架不感冒的原因。 REST作为一个架构风格,对我们的系统开发有很大影响,但是这些影响一般是针对架构(例如状态无关)或者设计(例如资源识别)上的,所以一旦涉及到具体实现,主要工作就基本结束了,此时开发框架能做的事也就只有简化编程了(相较而言,有的框架还能起到引导设计的作用),而由于RESTful会抽象动词,所以实现层面中和API规范相关的工作本来就不多,那么框架的价值就更小了。 当然,我们也不可能直接基于servlet/rakc/wsgi来开发,不过一般的编程语言都会提供一些简单的url&route/match策略,我们使用这些就足够了。另外,有些框架能帮我们生成全部的动词支持,但这也未必是好事,我一般倾向于按需实现——用到了再支持,这就更不需要太关注开发框架对RESTful的支持了。
丁雪丰:由于本人是Spring的拥护者,工作中也一直在使用Spring,所以在选择框架时会更多地倾向Spring&MVC(并不是说别的框架不好,这里有些个人主观的成份)。如果一定要选择其他框架,也要选择能够方便与Spring集成的框架。如果在项目中已经使用了Spring,那么没有什么理由不选择Spring&MVC,鉴于目前Spring在各种项目中的高出镜率,相信一般情况下都会选择Spring&MVC。 REST的成熟度模型中,第三层就是HATEOAS,Spring目前还提供了Spring&Hateoas子项目,对链接、资源等方面的支持都做了一定的增强。
马钧:我目前在实际项目中使用的是Spray,这是一个开源的&REST/HTTP&工具包和底层网络&IO&包,基于&Scala&和&Akka&构建。轻量级、异步、非堵塞、基于&actor&模式、模块化和可测试是Spray的特点。 InfoQ:HTTP2.0规范正在制定当中,您对它的期待是什么? 李锟:我的期待包括两个方面:应该做的和不应该做的。 HTTP/2.0规范应该做的: 1.与HTTP/1.1协议保持兼容。兼容的含义是说两者可以并存,客户端应用可以根据服务器端的能力,自由地选择使用HTTP/2.0还是HTTP/1.1,而且选择过程对应用来说是透明的。&
2.改进HTTP协议(作为资源的统一接口)之中操作语义表达方式的语法,提高网络传输效率。& 3.更好地模块化,这样HTTP/2.0协议的实现能够更好地模块化。应用程序可根据需要选择适当的模块,而不是要么全有、要么全无。& 4.废弃掉HTTP/1.1协议中一些很少有人用到的部分,例如采用管道(pipelining)方式发送请求。& 5.增加更多的动词,以适应除CRUD之外的其他场景。 & HTTP/2.0规范不应该做的: HTTP/2.0协议不应该把底层的数据加密机制(即SSL)作为必选项。 & HTTP/2.0协议不应该背离REST架构风格的约束,尤其是要确保操作语义对于中间组件的可见性。&
在上面这两个方面,Roy&Fileidng曾经与SPDY协议设计者Mike&Belshe发生过激烈争论,详情请看:Roy&Fielding谈Google&SPDY协议 李建业:对此规范关注不多,不知道会不会有对于流的支持,目前我所知道的只有chunk方式进行简单的支持,但是真正的流需要区分数据通道和控制通道——哪怕是逻辑上的区分,这样就直接对REST风格产生了很大冲击,考虑到流式服务在未来的发展潜力,我特别期待业界在这方面有所进展。 丁雪丰:HTTP&2.0很大程度上是借鉴了Google的SPDY,就我而言,首先,希望这个规范能做到与HTTP&1.1的兼容,使用者如果只认识1.1,那么2.0能优雅“降级”;其次,希望2.0能带来更好的性能,SPDY在这方面还是有所改进的,希望HTTP&2.0能再接再厉;最后,希望这个规范能在最终定稿时附带一个最佳实践,正确引导人们合理地使用HTTP&2.0。
马钧:没研究过,估计即使出来,1.1还有很长的生命周期,不会很快被取代&
TechTarget中国原创内容,原文链接:&
(C) TechTarget中国:.cn
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:58019次
积分:2210
积分:2210
排名:第17002名
原创:150篇
转载:78篇
评论:19条
(10)(5)(2)(2)(1)(2)(4)(7)(11)(61)(21)(3)(24)(24)(27)(5)(6)(1)(6)(7)5137人阅读
OAuth2.0是从2006年开始设计OAuth协议的下一个版本,OAuth2.0同时提供Web,桌面和移动应用程序的支持,并较1.0相比整个授权验证流程更简单更安全。也是新浪微博开放平台未来最主要的用户身份验证和授权方式。
1. 引导需要授权的用户到如下地址:
/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI
2. 如果用户同意授权,页面跳转至 YOUR_REGISTERED_REDIRECT_URI/?code=CODE
3. 换取Access Token
/oauth2/access_token?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=authorization_code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI&code=CODE
(其中client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET可以使用basic方式加入header中)
返回值
{ &access_token&:&SlAV32hkKG&, &expires_in&:3600}
4. 使用获得的OAuth2.0 Access Token调用API
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:107693次
积分:1310
积分:1310
排名:千里之外
原创:29篇
转载:13篇
评论:14条
(1)(2)(3)(4)(5)(2)(5)(10)(3)(5)(3)(2)REST API 只给自己的APP提供接口,需要oauth认证吗? - 知乎11被浏览2718分享邀请回答01 条评论分享收藏感谢收起Java RESTful Web Service实战(第2版)
Java核心技术系列
Java RESTful Web Service实战
图书在版编目(CIP)数据
Java RESTful Web Service实战 / 韩陆著.
Java核心技术系列
Java RESTful Web Service实战
图书在版编目(CIP)数据
Java RESTful Web Service实战 / 韩陆著. —2版. —北京:机械工业出版社,2016.7
(Java核心技术系列)
ISBN 978-7-111-54213-1
Ⅲ. JAVA语言-程序设计
中国版本图书馆CIP数据核字(2016)第156331号
Java RESTful Web Service实战(第2版)
出版发行:机械工业出版社(北京市西城区百万庄大街22号 邮政编码:100037)
责任编辑:李 艺 责任校对:董纪丽
印  刷: 版  次:2016年8月第2版第1次印刷
开  本:186mm×240mm 1/16 印  张:18.75
书  号:ISBN 978-7-111-54213-1 定  价:59.00元
凡购本书,如有缺页、倒页、脱页,由本社发行部调换
客服热线:(010)361066 投稿热线:(010)
购书热线:(010)995259 读者信箱:
版权所有·侵权必究
封底无防伪标均为盗版
本书法律顾问:北京大成律师事务所 韩光/邹晓东
Foreword 第2版序一
韩陆是我在阿里巴巴的同事,业余时间大家经常一起聊新的Java技术。REST对当前软件开发非常重要,除了我们一直了解的Service API、Open API、移动端对后端的Gatway API调用(这些基本都是REST模式设计的),现在很多的DevOps操作也是通过REST API完成的,如我们了解的Docker和SpringBoot Actuator API都是REST风格的,另外HTTP/2的逐步采用,也为REST带来更多的功能和性能的提升。对Java程序员来说,本书非常难得,你可以深入了解JAX-RS标准和Jersey框架;为了方便落地开发,书中更着重介绍了Spring Boot和Spring Cloud,这些知识目前涉及的中文图书并不多;最后结合Docker容器技术,给出了完整基于SpringBoot REST服务应用容器部署的思路。本书的每一个技术点都可以单独成书,用以详细阐述,但是能够浓缩到一本图书中,挑战和难度确实比较大,希望这些新的技术和思想能够帮助到真正前进的程序员。
阿里巴巴资深技术专家、速卖通中间件掌门人 陈立兵(花名:雷卷)
第2版序二 Foreword
认识韩陆已有近10 年的时间,那时他在北京航空航天大学软件学院做硕士毕业论文。他的论文写的是面向对象数据库引擎的设计与实现,完全自主实现了包括文件读写、缓存、索引和事务、数据访问接口等完整的面向对象数据库引擎。从那时就了解到他是一位技术达人,喜欢钻研和实践各种最新的技术。所以当听说他出版本书时一点都不觉得意外,他就是这样一个热衷于新技术的人。
早在本书第1版的时候,他就找到我希望为本书写一篇序,那时我婉拒了,因为我本人对RESTful相关技术并没有太多的接触,不敢贸然推荐。作为一种轻量级Web服务实现架构,两年多来RESTful架构得到了普遍认可和使用;越来越多的学生也开始学习相关的技术,而本书就是这方面非常有意义的参考资料。书中首先系统地解读了JAX-RS2标准,之后基于JAX-RS标准的参考实现:Jersey开发框架,系统地讲解了如何基于该框架开展RESTful Web服务的实践。本书实践性很强,体系较为完整,涵盖了RESTful Web服务开发各个层面的问题;书中不仅提供了一些典型场景的代码示例,还有完整的项目案例的讲解,这些实践代码能够有助于读者开展具体的项目实践。与第1版相比,第2版还新增了有关微服务和容器化等目前热门应用技术实践的内容,有助于读者了解最新的技术发展方向。
北京航空航天大学 谭火彬
Foreword 第1版序一
——REST开发的理想与现实
REST是一种分布式应用的架构风格,也是一种大流量分布式应用的设计方法论。REST是由(构成了Web基础架构的)HTTP、URI等规范的主要设计者Roy Fileding博士在其2000年的博士论文(中文版名为《架构风格与基于网络应用软件的架构设计》)中提出的。到目前为止,关于REST最系统、最全面的论述,仍然是Fielding的博士论文。
REST就是Web(World Wide Web,简称Web或者WWW)本身的架构风格,是设计、开发Web相关规范、Web应用、Web服务的指导原则。不符合REST风格要求的架构和技术,很难在Web这个生态系统中得到繁荣发展。在我看来,Roy Fielding博士就是15年以来对于分布式应用架构设计理论贡献最大的人。Fielding在HTTP规范的设计过程中,并没有采用当时大行其道的DO(Distributed Object,分布式对象)风格,而是自出机杼、另辟蹊径,提出了一整套新的设计方法论。Fielding的开创性工作,极大地推动了分布式应用设计理论的发展。
有趣的是,其实基于SOAP/WSDL的“大Web Service”(以下简称Web Service),几乎是与REST同时发展起来的。虽然在Web Service中也使用了对象,但是Web Service其实是RPC风格的,而不是DO风格的。Web Service在最初几年发展很快,很大原因是它解决了DO风格难以解决的异构系统(不同的硬件系统、不同操作系统、不同的编程语言,等等)之间互操作性的问题。
然而遗憾的是,设计Web Service协议栈的核心人员,几乎都是来自于企业应用阵营的,尤其是来自于IBM和微软两家公司的人。这些企业应用的专家们没有充分认识到Web基础架构的巨大优点,甚至可以说并没有理解HTTP协议究竟是用来做什么的、为何要如此设计。在Web Service协议栈的设计之中,仍然有深深的企业应用痕迹。Web Service虽然宣称能够很好地支持互操作,然而因为协议栈的复杂性很高,在实战中互操作性并不好(例如升级过程困难而且复杂)。此外,Web Service仅仅将HTTP协议当做一种传输协议来使用,还依赖XML这种冗余度很高的文本格式,这导致Web Service应用性能低下。很多开发团队宁可使用Hessian等轻量级的RPC协议,也不愿意使用Web Service。在面向互联网的大流量Web应用(包括Web服务在内)这种运行环境中,Web Service在复杂性、互操作性、性能、可伸缩性等方面的短板更加突出。因此,设计今日面向互联网的API,已经很少有人会考虑Web Service。这使得Web Service的使用被局限在企业应用运行环境之中,其名称中的“Web”更像是一个笑话(除了都使用HTTP协议,基本上与Web没什么关系)。假如在2000年,设计Web Service规范的专家们,能够认真读一下Fielding的博士论文,或者找HTTP、URI等Web基础架构规范的核心设计人员深入交流一下,Web Service很可能就不是现在这个样子了。不过,历史是无法假设的。
在Java世界中,与大Web Service相对应的规范是JAX-WS。在大Web Service已经成为明日黄花之后,Java世界急需一套新的规范来取代JAX-WS。这套新的规范就是JAX-RS:Java世界开发RESTful Web Service(与RESTful API含义相同,可混用)的规范。虽然起步很晚,毕竟走上了正确的道路。
从Java EE 6开始,JAX-RS在Java EE版图中,作为最重要的组成部分之一,逐步取代了JAX-WS的地位。在所有Java EE相关规范中,JAX-RS是优点很突出的一个。例如,完全基于POJO、很容易做单元测试、将HTTP作为一种应用协议而不是可替代的传输协议(因此提高了性能)、优秀的IDE集成,等等。可以说,在大多数场合,JAX-RS完全可以取代JAX-WS,作为Java Web Service开发的主要技术。JAX-RS同样也可以完全取代Hessian等基于HTTP协议的RPC风格远程调用协议。毕竟HTTP本身就是一种REST风格的应用协议,以REST风格来使用HTTP,才是最高效的使用方式。
Jersey、CXF等支持JAX-RS规范的REST开发框架还支持输出WADL。WADL支持客户端代码自动生成,还可以将WADL导入到SoapUI等测试工具中,然后做自动化集成测试。从开发Java企业应用、取代JAX-WS的角度来看,JAX-RS已经做得非常棒了。
尽管如此,不可不提的是,JAX-RS这套规范,仍然存在着很多遗憾。需要特别指出的是,JAX-RS规范并不等于REST架构风格本身,REST的内涵要比JAX-RS广泛得多。学会了使用JAX-RS了,并不等于就完全理解了REST,开发者仍然需要下工夫认真学习一下本源的REST究竟是什么。
例如,JAX-RS规范对于应该如何定义一个资源,以及应该如何使用HTTP作为一个统一接口来操作资源,显然缺乏必要的指导。有很多开发者只是简单地将以前JAX-WS中的一个endpoint接口转换成一个资源接口。接口的方法太多,导致映射到的HTTP方法不够用,这也难不倒他们,在URI路径中加一些字符串就能够解决(例如,三个接口方法都映射到POST,但是其PATH不同)。这样的开发方式非常常见,虽然开发者使用了JAX-RS规范,但是开发方式完全是RPC风格的,可以说与REST风格没有任何关系。
此外,JAX-RS规范目前尚不支持HATEOAS(将超文本作为应用状态的引擎,REST风格的核心特征之一),从著名的Richardson成熟度模型(由《RESTful Web APIs》的作者Richardson提出)来衡量,基于JAX-RS规范实现的RESTful API仅仅能够达到成熟度模型的第二级,即支持资源抽象、统一接口的“CRUD式Web服务”。
可以这样说,JAX-RS规范与真正的REST风格,覆盖的范围其实是不同的。JAX-RS覆盖的是简单基于HTTP协议(没有使用SOAP/WSDL)的各种远程调用需求,很多需求对于可伸缩性、松耦合的要求并不高,仅仅是希望使用HTTP本身来取代大Web Service作为一种轻量级、容易测试的远程调用协议。REST架构风格的严格要求,在这些场合并不是非常重要。慵懒是人类的天性,大多数开发者写代码只是为了解决手头的问题,JAX-WS并不好用,JAX-RS解救了他们。
如果按照Roy Fielding博士的严格要求(REST APIs must be hyper-text driven),那么包括JAX-RS规范在内都不能算是真正的RESTful。然而,从实战角度,我认为革命不分先后,只要能够达到Richardson成熟度模型第一级,即有清晰的资源抽象,就可以认为是RESTful API了。如果连第一级都达不到,所设计的架构根本就不是面向资源的,那八成还是RPC风格的,就没有必要非要往RESTful API阵营里面挤了。从来没有人说过RPC就是万恶的,RPC在企业应用的大多数场合其实都非常有效,只是不适合面向互联网的大流量Web应用而已。
因此,能够完美支持HATEOAS,攀登到成熟度模型第三级,是一种理想情况(当然也是值得追求的)。而通过部分拥抱REST风格的要求,来更好地解决手头的问题,是更多开发者所面对的现实情况。JAX-RS反映的正是这种现实情况,从实战的角度,它是一套非常有用也很好用的规范。
韩陆兄的新著《Java RESTful Web Service实战》是JAX-RS规范方面的专著,也是国内第一本REST开发的原创著作。这本书的实战性非常强,全面介绍了JAX-RS 2.0的方方面面,可以作为一线Java分布式应用开发者的案头必备书。如同我在前面所指出的,JAX-RS规范并不等于REST架构风格本身,它们有着不同的覆盖范围。在本书中,作者也介绍了很多设计RESTful API的最佳实践,这些内容假如读者不理解REST,甚至在亲自阅读了JAX-RS规范之后也未必能够总结出来。读者在阅读本书的过程中,不应该仅仅满足于掌握了JAX-RS开发的基本方法、解决了手头的问题、用其完全取代JAX-WS,更重要的是,读者还应该就REST架构风格本身做更多的学习。幸运的是,除了本书之外,目前REST设计和开发方面的图书资料已经非常多了。
本书的内容非常严谨,有非常好的系统性,对于设计开发大流量Web服务会面临的各种问题都有涉及。特别是在自动化测试方面着墨颇多,在我看来是本书的一大亮点。RESTful API的自动化测试非常重要,需要在设计之初就充分考虑到。本书是一本难得的原创佳作,值得所有Java分布式应用的开发者购买。
理想富丽丰满,现实贫瘠骨感,追求理想和注重解决现实问题其实并不矛盾。JAX-RS规范的发展,反映出了Java社区在更好地开发RESTful Web Service方面的求索。尽管存在争议,在我看来,规范化是推动RESTful Web Service取得更大发展的必由之路。目前对于优秀的RESTful API有哪些判断标准,Web开发者社区已经达成了高度共识,也积累了大量非常有价值的成果。JAX-RS规范的发展,离不开Web开发者社区的这些成果。在未来的JAX-RS 3.0规范中,我们将会看到更多令人兴奋的成果被规范化。JAX-RS 2.0已经做得不错了,但是在RESTful Web Service规范化的道路上,其实才刚刚起步,任重而道远。
李锟 于上海
Foreword 第1版序二
半年前初识韩陆的时候,我们就聊到他正在写的这本书,当得知我从2006年就参与了Apache CXF开发,他立即邀请我为他的新书写序,我也就欣然答应了。
Apache CXF作为JAXWS以及JAX-RS规范的实现框架,已经成为很多Web服务开发者必选的开发框架。作为这一框架的开发维护者之一,我的日常工作经常需要熟悉这些JSR规范,并实现JSR所定义的API,解决最终用户的使用问题。
熟悉Java的人大多都听说过JSR(Java Specification Requests)、JCP(Java Community Process),通过JSR可以就Java某一方面的应用定义一组标准的API或者服务。对于最终用户来说,他们的代码只需要调用JSR定义的标准API,不做任何修改就可以调用不同的JSR实现。这里常见的例子就是Java Servlet应用,用户开发的Web应用可以不做任何修改就部署到Tomcat、JBoss等不同的Web容器中。
JAXRS是JCP为Java RESTful Web Service定义的一套API。由于Web服务的描述模型与Java类和接口有一定的差距,JAX-RS定义了很多annotation,通过这些annotation我们可以很方便地将Java类描述成为相关的REST服务。由于RESTful Web Service通常需要部署到Web容器中,JAX-RS也定义了相关服务来发现部署到容器中的JAX-RS应用。
读过JSR规范的朋友或多或少都会有这样的体会,JSR作为规范文档,其目标是将API定义以及实现功能描述清楚、完备,其目标读者是相关API的实现人员,或者是相关API的高级使用人员。如果读者对相关的背景知识还不熟悉的话,JSR文档读起来会比较晦涩而且难以理解。加之绝大部分JSR文档都没有相关的中文翻译,对于绝大多数初学者来说,通过阅读JSR文档来学习相关的API的知识是一个艰难的过程。
如果我们想要对JAX-RS规范有一个比较快速并且全面的了解应该怎么办呢?一般来我们可以通过JSR的相关参考实现入手,我们不但可以通过运行相关的参考实现的例子快速入门,还可以通过跟踪相关的代码对实现细节有一个全面的了解。韩陆的这本新作以JAX-RS的参考实现Jersey为蓝本,由浅入深地向大家介绍了JAX-RS的由来,以及与RESTful Web服务开发的相关API,并结合实例分享了作者的实战经验。
好了,现在打开你熟悉的IDE工具,加载Jersey代码库,沿着本书的指引去探索Java RESTful Web Services开发世界吧。
RedHat姜宁
Preface 前  言
本书第1版发行后,Jersey版本从2.9更新到了2.22.2,此间REST服务得到了更广泛的认可和使用。与此同时,Java 8、Spring Boot和Docker的爆发式发展,使得Java领域的RESTful开发有了新的发展。
迫不及待,这是我想为读者更新REST服务新发展的心情,遂有此第2版。首先,我们要拥抱Java 8。lambda表达式在大数据处理,尤其在Spark中是默认的语法表达;Java 8带给我们的不只是“语法糖”,而是开发和执行效率的提升。我从实践中得到了其中的好处,也希望读者能跟上时代的步伐。其次是Spring Boot,这是Java领域实现微服务的事实标准框架。我已经无法回去适应部署war到Tomcat的时代,请保守的读者原谅我的情不自禁。再次是Docker,我希望读者具备使用Docker完成开发自测阶段的一切,也希望读者能运用Docker实现微服务的部署和可伸缩实践。
从第1版第1次印刷至今,我始终关注着读者的反馈。邮件都做了认真的回复。根据读者的反馈,我在第2版中重新梳理了章节的结构,删除了第1版中反馈不好的第9章和第11章,调整后的章节与第1版的对应关系如下。
第1章合并了第1版的第1章和第2章。
第2章对应第1版第3章。
第3章对应第1版第4章。
第4章包含了第1版的第8章。
第5章在第1版的基础上做了更新。
第6章包含了第1版的第7章,并升级了第1版2.5节的示例。
第7章和第8章是新增章节。
第9章对应第1版第10章。
第10章包含了第1版的第6章。
与许多技术作者一样,写书的时间是挤出来的。如果精力尚可,每晚7点到9点、11点到凌晨2点是我动笔的时间,偶尔,早上6点到8点我也会赶赶。写书成为我梳理、总结和思考的最佳方式。
于此过程,我总结了3句话与读者共享。搞技术的人,是停不下来的。时而要开疆拓土,学习和研究新的知识点,弥补自己的技术债;时而要运筹帷幄,将知识点梳理成线,编织成网;时而要深耕细作,面对当下要攻坚的业务所对应的知识点,深入研究、反复实践、勤于思考、勇于交流。只有这样,我们才可以坦然地用手推一下眼镜,谦虚地告诉别人,“其实我是个程序员”。
本书提供源代码下载,地址是/feuyeux/jax-rs2-guide-II。
勘误和交流
本书的勘误会在/feuyeux/jax-rs2-guide-II/wiki发布,欢迎读者批评指正。
我的邮箱:
我的新浪微博:六爷1_1
感谢我的妻子Caroline和女儿Doris一直以来的关心和陪伴。
感谢华章公司的杨福川对我的专业指导。感谢华章公司编辑高婧雅、李艺专业和耐心的审阅和指正。
感谢阿里巴巴速卖通中间件团队在微服务、容器化上对我的影响。感谢雷卷、许晓斌在DDD、Spring Boot和Docker上对我的帮助。感谢Technicolor的敏捷团队、阿里巴巴国际站测试架构团队,前者带我悟得Jersey,后者给我深入实践的机会。
最后我要感谢阿里巴巴阿里云事业群大安全的各位兄弟对我的支持。我正在这里,与大家一天天、一步步将微服务和容器化落地生花。
Contents 目  录
第1章 JAX-RS2入门 1
1.1 解读REST 1
1.1.1 一种架构风格 2
1.1.2 基本实现形式 2
1.2 解读REST服务 3
1.2.1 REST式的Web服务 3
1.2.2 对比RPC风格 3
1.2.3 对比MVC风格 4
1.3 解读JAX-RS标准 5
1.3.1 JAX-RS2标准 5
1.3.2 JAX-RS2的目标 5
1.3.3 非JAX-RS2的目标 6
1.3.4 解读JAX-RS元素 7
1.4 Jersey项目概要 7
1.4.1 获得Jersey 8
1.4.2 Jersey问答 8
1.4.3 Jersey项目管理 8
1.4.4 Jersey许可 9
1.4.5 Jersey的模块 10
1.4.6 GlashFish项目 10
1.5 快速实现Java REST服务 12
1.5.1 第一个REST服务 13
1.5.2 第一个Servlet容器服务 17
1.6 快速了解Java REST服务 19
1.6.1 REST工程类型 19
1.6.2 REST应用描述 24
1.7 Java领域的其他REST实现 27
1.7.1 JAX-RS的其他实现 27
1.7.2 其他的REST实现 31
1.8 REST调试工具 33
1.8.1 命令行调试工具 33
1.8.2 基于浏览器的图形化调试插件 34
1.9 本章小结 37
第2章 REST API设计 38
2.1 统一接口 38
2.1.1 GET方法 39
2.1.2 PUT方法 41
2.1.3 DELETE方法 43
2.1.4 POST方法 44
2.1.5 WebDAV扩展方法 45
2.2 资源定位 47
2.2.1 资源地址设计 48
2.2.2 @QueryParam注解 50
2.2.3 @PathParam注解 52
2.2.4 @FormParam注解 55
2.2.5 @BeanParam注解 57
2.2.6 @CookieParam注解 58
2.2.7 @Context注解 58
2.3 传输格式 59
2.3.1 基本类型 59
2.3.2 文件类型 60
2.3.3 InputStream类型 61
2.3.4 Reader类型 62
2.3.5 XML类型 62
2.3.6 JSON类型 66
2.4 连通性 82
2.4.1 过渡型链接 82
2.4.2 结构型链接 83
2.5 处理响应 84
2.5.1 返回类型 85
2.5.2 处理异常 86
2.6 内容协商 89
2.6.1 @Produces注解 89
2.6.2 @Consumes注解 91
2.7 本章小结 92
第3章 REST请求处理 93
3.1 Jersey的AOP机制 93
3.2 Providers详解 94
3.2.1 实体Providers 94
3.2.2 上下文Providers 100
3.3 REST请求流程 100
3.4 REST过滤器 102
3.4.1 ClientRequestFilter 102
3.4.2 ContainerRequestFilter 103
3.4.3 ContainerResponseFilter 104
3.4.4 ClientResponseFilter 105
3.4.5 访问日志 107
3.5 REST拦截器 109
3.6 绑定机制 111
3.6.1 名称绑定 111
3.6.2 动态绑定 113
3.7 优先级 115
3.8 本章小结 116
第4章 REST服务与异步 117
4.1 为什么使用异步机制 117
4.1.1 服务器异步机制 117
4.1.2 客户端异步机制 118
4.2 JAX-RS2的异步机制 119
4.2.1 服务端实现 119
4.2.2 客户端实现和测试 122
4.3 基于HTTP1.1的异步通信 124
4.3.1 Polling技术 124
4.3.2 Comet技术 126
4.3.3 Web Hook异步通信 127
4.3.4 SSE技术 128
4.4 基于HTML5的异步通信 129
4.4.1 SSE的原理 129
4.4.2 发布—订阅模式的实现 131
4.4.3 广播模式的实现 135
4.4.4 WebSocket技术 137
4.5 本章小节 138
第5章 REST客户端 139
5.1 客户端接口 140
5.1.1 Client接口 140
5.1.2 WebTarget接口 141
5.1.3 Invocation接口 142
5.2 连接池 142
5.2.1 资源释放 142
5.2.2 连接器 144
5.2.3 HTTP连接池 146
5.3 封装Client 147
5.4 请求Spring Boot微服务 148
5.4.1 不同的JSON解析方式 148
5.4.2 完整示例 150
5.5 JavaScript客户端 150
5.5.1 jQuery客户端 151
5.5.2 AngularJs客户端 152
5.6 本章小结 152
第6章 REST测试 153
6.1 Jersey测试框架 153
6.2 单元测试 156
6.2.1 集成Spring的单元测试 156
6.2.2 异步测试 158
6.3 集成测试 158
6.4 日志增强 159
6.5 本章小结 160
第7章 微服务 161
7.1 微服务技术栈 162
7.1.1 服务发现 163
7.1.2 可伸缩性 163
7.1.3 回到起点 164
7.2 REST服务与Spring Boot 165
7.2.1 Bootiful 165
7.2.2 RESTful 167
7.2.3 Actuator 168
7.3 REST服务与Spring Cloud 172
7.3.1 Spring Cloud Zookeeper 172
7.3.2 Spring Cloud Consul 182
7.3.3 Spring Cloud Etcd 187
7.4 本章小结 193
第8章 容器化 195
8.1 容器技术 195
8.1.1 容器 195
8.1.2 Docker技术栈 197
8.1.3 容器文化 199
8.2 REST服务与容器 201
8.2.1 开始容器化之路 201
8.2.2 开发自测容器化 204
8.3 容器化微服务 206
8.3.1 Zookeeper 207
8.3.2 Kafka 212
8.3.3 微服务 214
8.3.4 Nginx 217
8.4 本章小结 220
第9章 JAX-RS调优 223
9.1 使用缓存优化负载 223
9.1.1 缓存协商 223
9.1.2 条件GET 225
9.1.3 REST缓存实践 227
9.1.4 ab测试 229
9.2 使用版本号优化服务 229
9.2.1 何时使用版本号 230
9.2.2 如何使用版本号 230
9.3 使用参数配置优化服务 232
9.3.1 通用配置 232
9.3.2 服务器端和客户端配置类 233
9.4 Java虚拟机调优 234
9.4.1 虚拟机概述 234
9.4.2 内存溢出与内存泄漏 236
9.5 本章小结 238
第10章 REST安全 239
10.1 身份认证 240
10.1.1 基本认证 241
10.1.2 摘要认证 241
10.1.3 表单认证 242
10.1.4 证书认证 242
10.2 资源授权 244
10.2.1 容器管理权限 244
10.2.2 应用管理权限 246
10.3 认证与授权实现 247
10.3.1 基本认证与JDBCRealm 247
10.3.2 摘要认证与UserDatabase-Realm 255
10.3.3 表单认证与DataSource-Realm 258
10.3.4 Form认证和JAASRealm 263
10.3.5 证书认证与UserDatabase-Realm 266
10.4 JAX-RS2实现 270
10.4.1 Application类 270
10.4.2 资源类 271
10.4.3 资源测试类 271
10.5 REST服务与OAuth2 273
10.5.1 OAuth2概述 274
10.5.2 OAuth2流程 275
10.5.3 OAuth2实现 276
10.6 本章小结 280
参考资料 282
JAX-RS2入门
本章将详细讲述REST服务(RESTful Web Service)的概念、生态环境,并通过简单的示例,使读者快速掌握REST服务开发的基本能力。
前四节将逐一解读REST的概念、REST服务、JAX-RS标准和Jersey项目。这四者之间的联系是:REST是一种跨平台、跨语言的架构风格,REST式的Web服务是对REST在Web领域的实现;JAX-RS标准是Java领域对REST式的Web服务制定的实现标准,Jersey是JAX-RS标准的参考实现,是Java EE参考实现项目GlassFish的成员项目。接下来的三节介绍基于Jersey的REST服务开发,以及Java领域中其他的REST服务框架。最后,介绍REST服务的调试工具。
1.1 解读REST
REST(Representational State Transfer)翻译为表述性状态转移,源自Roy Thomas Fielding博士在2000年就读加州大学欧文分校期间发表的一篇学术论文《Architectural Styles and the Design of Network-based Software Architectures》。REST之父在该论文中提出了REST的6个特点,分别是:客户端–服务器的、无状态的、可缓存的、统一接口、分层系统和按需编码。
REST具有跨平台、跨语言的优势。从其诞生开始,就得到了诸多语言的快速支持,最著名的是ROR(Ruby on Rails)框架。新兴的语言(比如NodeJs、Golang)、工具平台(Docker、Spark)和公有云,更是将REST默认为服务的开放形式。
1.1.1 一种架构风格
REST是一种架构风格。在这种架构风格中,对象被视为一种资源(resource),通常使用概念清晰的名词命名。
表述性状态是指资源数据在某个瞬时的状态快照。资源可以有多种表述(representation),表述状态具有描述性,包括资源数据的内容、表述格式(比如XML、JSON、Atom)等信息。
REST的资源是可寻址的,通过HTTP1.1协议(RFC 2616)定义的通用动词方法(比如GET、PUT、DELETE、POST),使用URI协议(RFC3305)来唯一标识某个资源公布出来的接口。
请求一个资源的过程可以理解为访问一个具有指定性和描述性的URI,通过HTTP协议,将资源的表述从服务器“转移”到客户端或者相反方向。
REST不是一种技术(technology),也不是一个标准(standard)/协议(protocol),而是一种使用既有标准:HTTP+URI+XML(XML似乎成为了数据格式的借指,不仅代表XML本身)来实现其要求的架构风格。因此,与之对应的不是SOAP协议,而是像RPC这样的架构风格。
1.1.2 基本实现形式
HTTP+URI+XML是REST的基本实现形式,但不是唯一的实现形式。REST一开始便使用已有的HTTP协议(RFC 2616)、URI协议(RFC3305)来描述其特征,而对如何使用一种编程语言来实现,并没有进行任何描述和规定,甚至应该包含哪些传输类型或者数据格式也没有描述,但通常的实现至少包含XML格式。
具体而言,HTTP协议和URI用于统一接口和定位资源,文本、二进制流、XML和JSON等格式用来作为资源的表述。正如采用已有技术XMLHttpRequest+JavaScript+XML(XML后来几乎被JSON替代)实现Ajax一样,使用HTTP+URI+XML实现REST的好处是让开发者持有这些已知的技术来开发REST的入门门槛较低,关注点更容易放到REST的核心概念和业务逻辑上。
以HTTP+URI+XML实现的应用并不一定是REST服务,但对于Ajax,这个逆命题是成立的。因为Ajax是一种技术,而REST是一种架构风格。学习和使用REST的关键是掌握这种思想,而不是具体的实现形式。
1.2 解读REST服务
RESTful对应的中文是REST式的,RESTful Web Service的准确翻译应该是REST式的Web服务,我们通常简称为REST服务。RESTful的应用或者Web服务是最常见的两种REST式的项目部署、存在的方式。本节将介绍REST服务并对比其与传统Web Services的不同。
1.2.1 REST式的Web服务
RESTful Web Service是一种遵守REST式风格的Web服务。REST服务是一种ROA(Resource-Oriented Architecture,面向资源的架构)应用。其主要特点是方法信息存在于HTTP协议的方法中(比如GET、PUT),作用域存在于URI中。例如,在一个获取设备资源列表的GET请求中,方法信息是GET,作用域信息是URI中包含的对设备资源的过滤、分页和排序等条件。
1.2.2 对比RPC风格
相比Web服务领域广为流行的RPC(Remote Procedure Call Protocol,远程过程调用协议)风格,REST风格更轻量和快速。从方法信息角度看,REST采用标准的HTTP方法,而RPC请求都是HTTP协议的POST方法,其方法信息包含于SOAP协议包或HTTP协议包中,方法名称不具有通用性。从作用域角度看,REST采用URI显式定义作用域,而RPC的这一信息同样包含于协议包中,不能直观呈现。
RPC风格的开发关注于服务器–客户端之间的方法调用,而不关注基于哪个网络层的哪种协议。也就是说,RPC是面向方法调用过程的,相比而言,REST是面向资源状态的。RPC风格的两个代表是XML-RPC和大Web服务。
1. XML-RPC
XML-RPC是一种使用XML格式封装方法调用,并使用HTTP协议作为传送机制的RPC风格的实现。XML-RPC的请求方法都是HTTP协议的POST方法,请求和响应的数据格式均为XML。
XML-RPC的数据格式和使用XML作为资源的表述的REST外观上很相似,但数据的内容则大相径庭。REST式的XML信息的主体是对一个资源状态的表述,无须包含方法信息,因为其请求的HTTP方法就已经决定了这一点。XML-RPC的请求数据结构额外包含方法调用信息和参数信息。
对于响应信息的内容两者也截然不同,REST式通常会包含响应实体信息,以及HTTP状态码和可选的异常信息,而XML-RPC的返回信息仅仅是对方法调用的响应信息。
XML-RPC是一种遗留技术,已经被SOAP取代。在Java领域,JAX-RPC标准已经并入JAX-WS2标准。XML-RPC的应用依然存在,著名的测试用例管理系统TestLink的对外接口就是使用PHP开发的XML-RPC。
2. 大Web服务
大Web服务(Big Web Service)是Leonard Richardson和Sam Ruby在其所著的《RESTful Web Services》一书中,对基于SOAP+ WSDL+UDDI+WS-标准栈等技术实现RPC风格的大型Web服务的统称。事实上,“大Web服务”这一说法也被Java EE 7的布道者们在多次演讲中使用。在Java领域,对应的标准主要是JAX-WS 2.0/2.1/2.2(JSR 224)。相较REST式的Web服务,大Web服务功能更强大,设计更复杂。大Web服务同样是跨平台、跨语言的,对复杂的数据类型的支持也非常好。大Web服务是基于RPC风格的重量设计,因此方法和作用域无法通过直观断定,需要定义在消息中,而且方法名不是统一和通用的。同时,大Web服务走HTTP协议时,请求都是基于POST方法的。
对比RPC风格的Web服务,REST式的Web服务形式更简单、设计更轻量、实现更快捷。REST无须引入SOAP消息传输层,无须注册服务,也没有客户端stub的概念等。但是,REST式的Web服务并没有像大Web服务那样提供诸如安全策略等全面的标准规范。
大Web服务和REST式的Web服务各有优势,并不是一种替换关系。在实际开发中,两者共存于一个项目中也是一种解决方案。
1.2.3 对比MVC风格
MVC风格的出现将模型、视图、控制解耦,其亮点是从前到后的一致性,其结构整洁、逻辑清晰,易于扩展和增强。MVC在Java领域的普遍实现方式是在Web前端使用标签库来对应服务端的模型类实例和控制类实例,标签库和服务端依赖库可以是松散的耦合,比如Spring生态系统,也可以是全栈式的统一体系,比如JSF体系。但无论如何实现,在Web前端的开发过程中,必须时刻考虑页面标签和服务端的映射关系,包括模型类的匹配和转换、数据结构、控制类的输入和输出的参数类型和数量等。
因此,MVC风格偏重于解决服务器端的逻辑分层问题,以及客户端是逻辑分层的延伸问题。MVC的标签库虽然其形态已经和HTML页面融合,但本质上还是Java编写的装饰模式的类实例,对应的是服务器端使用Java编写的模型类或者控制器类,因此MVC很难实现跨语言解耦。而REST风格偏重于统一接口,因此具体实现就可以跨平台和跨语言。REST推动了Web开发的新时代,使用平庸的纯HTML作为客户端,没有服务器端和客户端的耦合。显而易见,使用纯HTML开发的REST客户端和使用Java开发的REST服务器端并不存在语言上的耦合。
MVC和REST式并不是互斥的,如Spring的MVC模块已经开始支持REST式的开发。Jersey作为JAX-RS标准的实现,也实现了MVC的功能,请参考相关模块:jersey-mvc、jersey-mvc-freemarker和jersey-mvc-jsp。需要说明的是,本书致力于讲述JAX-RS,对于Jersey实现中的JAX-RS之外的功能,只做必要的讲述。由于MVC和REST之间有更多的并行存在性,本书余文没有将MVC放入讲述之列。
1.3 解读JAX-RS标准
JAX-RS是Java领域的REST式的Web服务的标准规范,是使用Java完成REST服务的基本约定。
1.3.1 JAX-RS2标准
Java领域中的Web Service是指实现SOAP协议的JAX-WS。直到Java EE 6(发布于2008年9月)通过JCP(Java Community Process)组织定义的JSR311(http://www.jcp.org/en/jsr/detail?id=311),才将REST在Java领域标准化。
JSR311名为The Java API for RESTful Web Service,即JAX-RS,其参考实现是Glassfish项目中的Jersey1.0。此后,JSR311进行了一次升级(2009年9月),即JAX-RS1.1。JAX-RS诞生后,时隔5年(2013年5月)发布的Java EE7包含了JSR339,将JAX-RS升级到JAX-RS2(http://www.jcp.org/en/jsr/detail?id=339)。JAX-RS2.0在前面版本的基础上增加了很多实用性的功能,比如对REST客户端API的定义,异步REST等,对REST的支持更加完善和强大。
JAX-RS的版本对应的参考实现Jersey项目版本信息参见表1-1。
表1-1 JAX-RS标准和Jersey版本信息
JSR标准 JSR名称 标准发布时间 JSR实现
jsr311 JAX-RS 1.0 日 Jersey1.x
jsr311 JAX-RS 1.1 日 Jersey1.x
jsr339 JAX-RS 2.0 日 Jersey2.x
1.3.2 JAX-RS2的目标
JAX-RS2标准(即JSR339)中定义了目标、非目标和元素等内容。JSR339标准中的这部分内容通常被以实现业务功能为目的的开发人员所忽视,在此和读者分享的一个开发经验是:要掌握一项技术,先要掌握它背后标准的定义。首先我们来看看JAX-RS2的目标。
1)基于POJO:JAX-RS2的API提供一组注解(annotation)和相关的接口、类,并定义了POJO(Plain Ordinary Java Object)对象的生命周期和作用域。规定使用POJO来公布Web资源。
2)以HTTP为中心:JAX-RS2采用HTTP协议,并提供清晰的HTTP和统一资源定位(URI)元素来映射相关的API类和注解。JAX-RS2的API不但支持通用的HTTP使用模式,还对WebDAV和Atom等扩展协议提供灵活的支持。
3)格式独立性:JAX-RS2对传输数据(HTTP Entity)的类型/格式的支持非常宽泛,允许在标准风格之上使用额外的数据类型。
4)容器独立性:JAX-RS2的应用可以部署在各种Servlet容器中,比如Tomcat/Jetty,也可以部署在支持JAX-WS的容器中,比如GlassFish。
5)内置于Java EE:JAX-RS2是Java EE规范的一部分,它定义了在一个Java EE容器内的Web资源类的内部,如何使用Java EE的功能和组件。
WebDAV(Web-based Distributed Authoring and Versioning,基于Web的分布式创作和版本控制)是IETF组织的RFC2518协议。WebDAV基于并扩展了HTTP 1.1,在HTTP标准方法以外添加了以下内容。
Mkcol:创建集合。
PropFind/PropPatch:针对资源和集合检索和设置属性。
Copy/Move:管理命名空间上下文中的集合和资源。
Lock/Unlock:改写保护,支持文件的版本控制。
针对在REST风格的Web服务中是否应该使用WebDAV,业内的声音并不一致,持反对意见的主要观点是WebDAV带来了非统一的接口,这违背了REST的初衷。本书的示例将不采用WebDAV,但文字部分将讲述如何支持WebDAV。Atom类型传输格式将在2.3节讲述。
1.3.3 非JAX-RS2的目标
那么哪些不是JAX-RS2的目标呢?
1)对J2SE 6.0之前版本的支持:JAX-RS2中大量使用了注解(annotation),需要J2SE 6.0以及更新的版本,因此不提供对J2SE 6.0以下版本的支持。
2)对服务的描述、注册和探测:JAX-RS2没有定义也无须支持任何服务的描述(description)、服务的注册(registration)和服务的探测(discovery)。
3)HTTP协议栈:JAX-RS2没有定义新的HTTP协议栈。承载JAX-RS2应用的容器提供对HTTP协议的支持。
4)数据类型/格式类:JAX-RS2没有定义处理实体内容的类,它将这一类型的类交由使用JAX-RS2的应用中的类去实现。
1.3.4 解读JAX-RS元素
最后,我们来看看JAX-RS2中定义了哪些元素。
1)资源类:使用JAX-RS注解来实现相关Web资源的Java类。如果用MVC的三层结构来解读,那么资源类位于最前端,用于接收请求和返回响应。通常,但不是约定,我们使用resource作为包名,三层的包定义形如:resource-service-dao。
2)根资源类:使用@Path注解,提供资源类树的根资源及其子资源的访问。资源类分为根资源类和子资源类,由于Jersey默认提供WADL(参见1.6节),每个应用公布的全部资源接口可以通过WADL页面查阅。
3)请求方法标识符:使用运行期注解@HttpMethod,用来标识处理资源的HTTP请求方法。该方法将使用资源类的相应方法处理,标准的方法包括DELETE、GET、HEAD、OPTIONS、POST、PUT,详见2.1节。
4)资源方法:资源类中定义的方法使用了请求方法标识符,用来处理相关资源的请求。就是上面提到的资源类的相应方法。
5)子资源标识符:资源类中定义的方法,用来定位相关资源的子资源。
6)子资源方法:资源类中定义的方法,用来处理相关资源的子资源的请求。
7)Provider:一种JAX-RS扩展接口的实现类,扩展了JAX-RS运行期的能力。第4章详述了各种Provider及其实现。
8)Filter:一种用于过滤请求和响应的Provider,详见3.3节。
9)Entity Interceptor:一种用于处理拦截消息读写的Provider,详见3.5节。
10)Invocation:一种用于配置发布HTTP请求的客户端API对象,详见5.1.3节。
11)WebTarget:一种使用URI标识的Invocation容器对象,详见5.1.2节。
12)Link:一种携带元数据的URI,包括媒体类型、关系和标题等,详见2.4节。
1.4 Jersey项目概要
Jersey是JAX-RS标准的参考实现,是Java领域中最纯正的REST服务开发框架。本节将带读者走近Jersey的世界。
Jersey项目是GlashFish项目的一个子项目,专门用来实现JAX-RS(JSR 311 & JSR 339)标准,并提供了扩展特性。
1.4.1 获得Jersey
Jersey项目的地址是https://jersey.java.net。该网站同时提供了JAX-RS和JAX-RS2两个并行版本,分别是JAX-RS1.1(截至本书发稿,最新版本是Jersey1.19)和JAX-RS2(截至本书发稿,最新版本是Jersey2.22.1)。读者可以通过单击latest Jersey User Guide获取和阅读最新版本的用户手册,这是官方发布的第一手参考资料。
Jersey项目的下载地址http://jersey.java.net/download.html。该页面自上而下的内容分别如下。
JAX-RS标准列表链接(JAX-RS 2.0 API)。
Jersey最新参考实现的jar包下载(Jersey JAX-RS 2.0 RI bundle)。
Jersey最新参考实现的示例代码下载(Jersey 2.22.1 Examples bundle)。
通过Maven模板(archetype),使用Jersey最新版本创建REST服务的命令。
Jersey最新参考实现的模块和依赖(Jersey 2 modules and dependencies)。
JAX-RS1.1的参考实现包下载。
Jersey源代码的托管地址是/jersey/jersey,我们可以通过git命令,将Jersey主干代码迁出到本地。示例如下。
git clone /jersey/jersey.git
1.4.2 Jersey问答
StackOverflow是专业的程序员问答系统,Jersey的问题列表地址是:/questions/tagged/jersey。该链接在Jersey官网首页底部被列出,可见Jersey对问答系统的重视。另外,邮件列表也是一种知识共享的途径,读者可以自行订阅,地址是:https://jersey.java.net/mailing.html。
1.4.3 Jersey项目管理
Jersey使用JIRA作为项目管理平台,相应的地址是:https://java.net/jira/browse/JERSEY。JIRA和StackOverflow不同的是,JIRA平台是Jersey团队日常开发的管理平台,即Jersey官方的缺陷管理平台,用于上报缺陷和改进意见,而不是社区性质的交流平台。通过这个平台我们可以从中了解到Jersey项目的进展情况。Jersey是一个非常活跃的项目,不仅可以从github源代码的提交活动中看到该项目频繁的更新,在JIRA中也可以看到该项目推进的速度。
这里为喜欢开源社区活动的读者举个例子。在撰写本书第1版的开始,Jersey2.0并不支持与Spring的集成,因为Jersey的IoC容器由GlashFish的另一个子项目HK2来支持。随后,我在JIRA上发现一个Jersey2.x支持与Spring集成的任务被创建了(https://java.net/jira/browse/JERSEY-1957),此后我经常观察其进展状态,最终看到了这个功能在Jersey2.2中以扩展包的形式发布了。
因此,在使用Jersey的过程中,如果读者遇到Jersey本身的问题,可以跟踪Jersey的JIRA平台检索、查看Bug的修复状态,包括将在哪个版本修复,有什么样的临时解决办法(workaround)。同时,跟踪JIRA也可以了解新版本的发布情况,包括新增哪些功能,升级对哪一部分带来性能、安全的提升等。换句话说,JIRA展示了Jersey项目的缺陷修复和新功能发版的计划(roadmap)。
1.4.4 Jersey许可
开发者使用开源软件的前提是了解它的许可证版本,否则可能会带来侵权问题。相信在正规的公司,大家都有被开发管理部门的人“恐吓”的经历。开发者需感谢这样的团队所做的工作,他们为公司规避了商业侵权的风险,因为引用的源代码如果出自“传染性”许可,该项目是不能用于闭源的商业用途的。
Jersey的许可证说明地址是:https://jersey.java.net/license.html。从中我们可以了解到Jersey使用的是双许可证:CDDL(Common Development and Distribution License,开源通用开发和分发许可证)1.1和GPLv2(类路径例外)许可证。双重许可是依照两套(或更多套)不同的条款和条件分发相同软件的作法。在为软件授予双重许可时,接收人可以选择他们希望依照哪种条款获得软件。使用双重许可的两个常见动机是遵循商业模式和保持许可证兼容性。GPLv2.0许可证为无法依照CDDL许可证使用Jersey的供应商提供了一个额外选项。Jersey许可证使整套产品和包保持一致(GlassFish项目同样依照CDDL和GPLv2(类路径例外)授予双重许可)。
类路径例外是由自由软件基金会的GNU/类路径项目制订的。它允许将依照任何许可证提供的应用程序链接到依照GPLv2许可的软件中包含的库,而该应用程序不受GPL要求公开其本身的限制。
为什么需要使用类路径例外?因为作为“基于GPL程序的作品”的一部分提供的所有代码还应获得GPL许可。因此,需要指定GPL许可证例外的情况,以便明确将链接到GPL实现的任何应用程序从该许可要求中排除。类路径例外就实现了这一目的。
1.4.5 Jersey的模块
Jersey框架是由核心模块、容器模块、连接器模块、Media模块、扩展模块、测试框架模块、安全模块以及Glassfish Bundle模块等8个大的模块组成。详情请读者浏览官方文档:https://jersey.java.net/documentation/latest/modules-and-dependencies.html。
Jersey核心模块包括3个子模块,分别是通用包、服务器端实现包和客户端实现包。Jersey提供了3种HTTP容器,分别是Grizzly2、JDK-HTTP和SIMPLE-HTTP,Grizzly2同时提供了Servlet容器。Jersey客户端底层依赖于连接器来实现网络通信,如果标准的客户端模块功能不能满足业务需求,读者可以考虑引入Grizzly连接器包或者Apache连接器包。
Jersey在2.6版本做了一次包重构,清除了对guava和ASM的自然依赖。如果你的项目需要做Jersey版本迁移,则需要注意这一点。新的包名为:.mon和 jersey.repackaged.objectweb.asm。
1.4.6 GlashFish项目
GlashFish项目地址为https://glassfish.java.net。GlashFish著名于世的是Java EE服务器项目Oracle GlassFish Server,该项目还同时包含Java EE中的一系列标准规范的参考实现,这些参考实现集成于GlashFish Server,为其Java EE容器提供支持。其中对应JAX-RS2的实现项目是Jersey。
为什么要在JAX-RS2的介绍中提及和罗列GlashFish项目集呢?因为Jersey处于GlashFish生态环境中,GlashFish又是Java EE生态环境的参考实现。通过了解GlashFish项目,我们可以更好地设计和实现REST服务。
这里所列的项目是除Jersey以外,其他的GlashFish项目,排列顺序并不严谨,大体上以其与Jersey的紧密关系降序排列。
HK2项目:JSR-330参考实现,项目地址为http://hk2.java.net。HK2是轻量级DI架构,实现IoC和DI的内核。是Jersey实现容器内管理Bean的基础。
Grizzly项目:中文直译为灰熊。JSR-356参考实现,项目地址为https://grizzly.java.net。Grizzly是一个异步IO的、高效而健壮的服务器,可以被用作HTTP服务器、Servlet容器,支持Ajp、Comet、WebSocket以及相对于RESTful的另一种Web Service实现(JAX-WS)。
EclipseLink项目:该项目实现了多个JSR标准,包括JSR-338/JPA2.1、JSR-222/JAXB2.2、JSR-235/SDO2.1.1、JSR-353/Java API for Processing JSON。项目地址为http://www.eclipse.org/eclipselink。EclipseLink是JPA2.1的一个实现,同时它还实现了其他的JSR作为扩展。JPA2.1是Java EE 7的成员,是对JSR317(JPA2.0)的升级。JPA2.1的实现中,最常用的是JBoss的Hibernate,该项目从4.3开始实现JPA2.1。也就是说Hibernate4.2是JPA2.0的最后一个版本。读者在开发的时候要注意依赖项目版本对标准的支持。JPA标准还有其他的实现,请参考http://en.wikipedia.org/wiki/Java_Persistence_API。
Metro项目:该项目是JSR中多个标准的官方实现集,目的是实现全栈式的Web Service。包括JSR-224/JAX-WS 2.2、JSR-222/JAXB2.2、JSR-206/JAXP 1.4.6、JSR-067/SAAJ1.3。项目地址为https://metro.java.net。Metro项目中的多个标准作用各有不同。
JAX-WS标准结合了XML-RPC,使用SOAP协议来实现Web Service。在JAX-WS的实现中,不可不提的另外两个实现分别是Apache的CXF和Axis。
WSIT的前身是Tango,是一种JAX-WS和.NET互操作的技术,实现了WS*标准。
SAAJ规范的作用是基于SOAP协议XML格式传递带附件的SOAP消息。
JAXP标准涵盖了Java对XML过程式处理的诸多技术,包括DOM、SAX和StAX,同时该标准定义了解读XML样式的XSLT。
JAXB标准是Java处理XML和POJO映射的技术,是Jersey中处理传输数据的重要依赖。
Open MQ项目:地址https://mq.java.net。Open MQ是JMS 2.0的参考实现。JSR-343是Java EE 7的成员,旨在简化JMS的API。关于消息队列的实现数量,恐怕是其他任何一个标准都望尘莫及的。几乎每一个有能力开发服务器软件、中间件的公司都有自己的MQ,请参考http://en.wikipedia.org/wiki/Message_queue。
Mojarra项目:JSR-344/JSF2参考实现,项目地址为https://javaserverfaces.java.net。JSF是一种全栈式的、事件驱动的B/S开发模式框架,它包括浏览器端的丰富组件,服务器端覆盖Java EE的各种特性。JSF相对于Spring,借鉴了其核心思想IoC和AOP,同时给出了标准规范。这有点类似JPA借鉴了hibernate的O/R Mapping思想并标准化。JSF的另一个实现是Apache的myfaces,当前版本为2.0.18。另外,JBoss的RichFaces是基于JSF的扩展中最为完善和常用的。更多有关JSF的内容和原理,请参考笔者的拙作《JSF2和RichFaces4使用指南》。
OpenJDK项目:项目地址为http://openjdk.java.net。OpenJDK是开源的JDK,从版本1.7开始成为官方JDK的先行版本,因此是Java开发者窥探Java发展的第一线的最好资源,同时也是活跃的Linux发行版本Ubuntu和Fedora等默认安装的JDK版本。
当前使用的JDK版本号的升级规则是从JDK5.0发布开始的,Java升级发布一直采用两种方式发布更新。
有限升级(Limited Update)包含新功能和非安全修正。
重要补丁升级(Critical Patch Update,CPU)只包含安全修正。
有限升级发行序号为20的倍数,即一个偶数;重要补丁升级顺延上一个CPU的版本号加5的倍数并取奇数(必要时加1)。
举例来说,下一个有限升级的版本号为7u40,那么接下来的3个CPU版本号依次为40+5=7u45,45+5+1=7u51和51+5=7u55。再下一个有限升级的版本号为7u60,随后的CPU版本号依次为7u65、7u71和7u75。
这种命名规则会为重要补丁升级保留几个版本序号,以便新的CPU版本号可以取区间值之和而不是在最新版本号上顺延。
1.5 快速实现Java REST服务
本节包含两个Jersey实战示例,目的是让读者具备快速创建REST服务的能力。
在开始实战之前,首先需要读者确认你的环境是否已经安装了Java和Maven。这里使用Maven命令,示例如下。
Apache Maven 3.3.3 (4ec3e0dfe41d4a06; T19:57:37+08:00)
Maven home: /usr/local/Cellar/maven/3.3.3/libexec
Java version: 1.8.0_40, vendor: Oracle Corporation
Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre
Default locale: zh_CN, platform encoding: UTF-8
OS name: "mac os x", version: "10.11.1", arch: "x86_64", family: "mac"
从Maven版本显示命令的结果中,自上而下可以看到Maven的版本信息和HOME路径信息、Java的版本信息和HOME路径信息、本地语言、平台字符集以及操作系统信息。
1.5.1 第一个REST服务
Jersey提供了Maven原型(archetype)来快速创建REST服务项目。
1. 创建项目
我们首先使用archetypeGroupId为org.glassfish.jersey.archetypes的原型、archetypeArtifactId为jersey-quickstart-grizzly2的原型,创建REST服务项目。示例如下。
mvn archetype:generate \
-DarchetypeArtifactId=jersey-quickstart-grizzly2 \
-DarchetypeGroupId=org.glassfish.jersey.archetypes \
-DinteractiveMode=false \
-DgroupId=my.restful \
-DartifactId=my-first-service \
-Dpackage=my.restful \
-DarchetypeVersion=2.22.1
上述命令将创建一个标准的Maven工程。其中,interactiveMode=false代表无需交互,archetypeVersion指定原型的版本,这个版本与Jersey的版本一致。groupId、artifactId和package分别定义了我们这个项目的组ID为my.restful,工件ID为my-first-service,包名为my.restful。我们通过观察项目根目录下的pom.xml,可以对应出上述命令参数与Maven坐标的关系。相关部分的示例如下。
&groupId&my.restful&/groupId&
&artifactId&my-first-service&/artifactId&
&packaging&jar&/packaging&
&version&1.0-SNAPSHOT&/version&
&name&my-first-service&/name&
2. 运行服务
Maven工程建立好后,我们首先启动REST服务体验一下该项目的功能。进入项目的根目录,并执行如下命令构建和启动服务。
cd my-first-service
mvn package
mvn exec:java
Jersey app started with WADL available at http://localhost:8080/myapp/application.wadl
Hit enter to stop it...
该命令启动了REST服务,端口是8080,我们可以随时通过回车键停止这个服务。同时,该服务还提供了WADL(详见1.6节)。通过访问application.wadl,可以获取当前REST服务公布的接口。本例WADL的关键部分,示例如下。
&ns0:resources base="http://localhost:8080/myapp/"&
&ns0:resource path="myresource"&
&ns0:method id="getIt" name="GET"&
&ns0:response&
&ns0:representation mediaType="text/plain" /&
&/ns0:response&
&/ns0:method&
&/ns0:resource&
&/ns0:resources&
这里定义了一个资源路径myresource,在该路径下,定义了一个GET方法getIt,表述类型为text/plain。
3. 访问服务
我们使用cURL(详见1.8节)来访问REST服务公布的myresource资源方法getIt,示例如下。
curl http://localhost:8080/myapp/myresource
HTTPie(读作H-T-T-Pie)是和cURL类似的CLI工具,但交互上更人性化。我们使用HTTPie请求相同的资源地址,请求和响应信息如下。
http http://localhost:8080/myapp/myresource
HTTP/1.1 200 OK
Content-Length: 7
Content-Type: text/plain
Date: Sat, 14 Nov :54 GMT
响应信息的第一行包含了HTTP协议版本和状态码,接下来是部分HTTP HEAD信息,最后是HTTP BODY信息。cURL携带-i或者--include参数可以得到相同的结果,示例如下。
curl -i http://localhost:8080/myapp/myresource
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Sat, 14 Nov :54 GMT
Content-Length: 7
要想获得更多的cURL请求响应信息,可以使用-v参数,示例如下。
curl -v http://localhost:8080/myapp/myresource
* Hostname was NOT found in DNS cache
Trying ::1...
* connect to ::1 port 8080 failed: Connection refused
Trying fe80::1...
* connect to fe80::1 port 8080 failed: Connection refused
Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
& GET /myapp/myresource HTTP/1.1
& User-Agent: curl/7.38.0
& Host: localhost:8080
& Accept: */*
& HTTP/1.1 200 OK
& Content-Type: text/plain
& Date: Sat, 14 Nov :56 GMT
& Content-Length: 7
* Connection #0 to host localhost left intact
4. 分析项目
完成了最初的体验后,我们来分析下面这个示例工程。首先,从启动服务的命令mvn exec:java入手。该命令实际调用了exec-maven-plugin插件中定义的一个值为java的goal,用以触发mainClass中的main函数。本例的mainClass定义为my.restful.Main。在pom.xml中,exec插件完整定义如下。
&groupId&org.codehaus.mojo&/groupId&
&artifactId&exec-maven-plugin&/artifactId&
&version&1.2.1&/version&
&executions&
&execution&
&goal&java&/goal&
&/execution&
&/executions&
&configuration&
&mainClass&my.restful.Main&/mainClass&
&/configuration&
除了pom.xml和Main类,示例还包含哪些内容呢?我们可以使用如下命令查看。
├── pom.xml
└── src
├── main
└── java
└── restful
├── Main.java
└── MyResource.java
└── test
└── java
└── restful
└── MyResourceTest.java
源代码中,还包括了资源类MyResource和它的单元测试类MyResourceTest。
在资源类MyResource中,@Path中定义了资源路径,@GET中定义了GET方法getIt(),@Produces中定义了响应的类型为普通的字符串,示例如下。
@Path("myresource")
public class MyResource {
@Produces(MediaType.TEXT_PLAIN)
public String getIt() {
return "Got it!";
相应地,资源测试类MyResourceTest中实现了对getIt()方法的测试,示例如下。
public void testGetIt() {
String responseMsg = target.path("myresource").request().get(String.class);
assertEquals("Got it!", responseMsg);
在上述代码中,target()、path()、request()和get()方法都是Jersey Client中定义的方法,这些方法组合在一起,形成了流式风格的API。我们期待响应值为“Got it!”的字符串。
5. 单元测试
最后,我们使用如下命令执行单元测试。使用IDE可以直接通过图形界面单击对该方法的测试。
-------------------------------------------------------
-------------------------------------------------------
Running my.restful.MyResourceTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
jersey-quickstart-grizzly2原型提供的模板代码,使用了main函数,并在其中启动了Grizzly的HttpServer。这是典型的Java SE形式的REST应用。更多情况下,我们希望得到的是一个可以以war包形式部署到Servlet容器的轻量级Java EE项目。接下来的示例就是这样的Web形式的项目。
1.5.2 第一个Servlet容器服务
jersey-quickstart-webapp原型会为我们生成Servlet容器服务。
1. 创建项目
使用如下命令创建名为my-first-webapp的Web项目。
mvn archetype:generate \
-DarchetypeArtifactId=jersey-quickstart-webapp \
-DarchetypeGroupId=org.glassfish.jersey.archetypes \
-DinteractiveMode=false \
-DgroupId=my.restful \
-DartifactId=my-first-webapp \
-Dpackage=my.restful \
-DarchetypeVersion=2.22.1
2. 运行服务
由于这是一个Web项目,没有main函数,我们必须将其部署到Servlet容器(比如Tomcat、Jetty)中,才能将其运行。在开发阶段,我们无需真正将其部署,而是使用Maven插件这种更轻量级的方式启动服务。在pom.xml中,增加如下定义来添加插件。
&groupId&org.eclipse.jetty&/groupId&
&artifactId&jetty-maven-plugin&/artifactId&
&version&9.3.5.v&/version&
有了插件,我们可以使用如下命令编译和启动服务,使用Ctrl+C停止服务。
mvn jetty:run
如果我们要对示例项目进行断点调试,应在服务启动前设置监听端口等信息。这里以IntelliJ IDEA所使用的5050端口为例,示例如下。
export MAVEN_OPTS="-Xdebug -Xnoagent -piler=NONE -Xrunjdwp: transport=dt_socket,address=5050,server=y,suspend=y"
mvn jetty:run
以这样的方式启动服务,需要IDE与之交互。过程是首先启动端口,然后IDE向该端口请求监听,服务启动并接收请求,在代码的某个断点处,服务会向该端口推送事件,IDE在代码的断点处停留并高亮显示该行。
3. 访问服务
服务启动后,我们使用HTTPie请求资源地址,示例如下。
http http://localhost:8080/webapi/myresource
HTTP/1.1 200 OK
Content-Length: 7
Content-Type: text/plain
Date: Sat, 14 Nov :03 GMT
Server: Jetty(9.3.5.v)
4. 分析项目
本例是一个标准的Maven Web工程。Web的根目录默认名称为webapp,默认的Servlet版本为2.5,需要使用WEB-INF/web.xml文件来配置REST服务。我们通过tree命令得到完整的工程结构如下。
├── my-first-webapp.iml
├── pom.xml
└── src
└── main
├── java
└── restful
└── MyResource.java
├── resources
└── webapp
├── WEB-INF
└── web.xml
└── index.jsp
5. 扩展项目
本例与前例提供的资源类和资源方法相同,我们在此基础上增加两个资源方法,分别用来新增和查询资源,示例如下。
private static ConcurrentHashMap&String, MyDomain& map=new ConcurrentHashMap&&();
@Path("{key}")
@Produces(MediaType.APPLICATION_XML)
public MyDomain getMy(@PathParam("key") final String key) {
final MyDomain myDomain = map.get(key);
if (myDomain == null) {
return new MyDomain();
return myD
@Consumes(MediaType.APPLICATION_XML)
public void addMy(final MyDomain myDomain) {
map.put(myDomain.getName(), myDomain);
如上所示,POST方法addMy用于接收并存储新增的表述,GET方法getMy用于查询表述。MyDomain类是基于JAXB的POJO类,用于表示XML格式的表述。
首先,我们通过如下命令,新增一条记录。
curl -X POST http://localhost:8080/webapi/myresource -d '&myDomain name="eric" value=""/&' -H "Content-type:application/xml"
然后通过如下命令查询和验证新增记录的存在。
curl http://localhost:8080/webapi/myresource/eric
&?xml version="1.0" encoding="UTF-8" standalone="yes"?&&myDomain name="eric" value=""/&
1.6 快速了解Java REST服务
1.6.1 REST工程类型
在REST服务中,资源类是接收REST请求并完成响应的核心类,而资源类是由REST服务的“提供者”来调度的。这一概念类似其他框架中自定义的Servlet类,该类会将请求分派给指定的Controller/Action类来处理。本节将讲述REST中的这个提供者,即JAX-RS2中定义的Application以及Servlet。
Application类在JAX-RS2(JSR339,详见参考资料)标准中定义为javax.ws.rs.core.Application,相当于JAX-RS2服务的入口。如果REST服务没有自定义Application的子类,容器将默认生成一个javax.ws.rs.core.Application类。
本节根据JAX-RS2规范第2章中对REST服务场景的定义,将REST服务分为四种类型,如图1-1所示。
图1-1将JAX-RS2标准中对REST服务的类型图形化,依据不同的条件分为了四种类型。
类型一:当服务中没有Application子类时,容器会查找Servlet的子类来做入口,如果Servlet的子类也不存在,则REST服务类型为类型一,对应图1-1中的例1。
类型二:当服务中没有Application子类时,存在Servlet的子类,则REST服务类型为类型二,对应图1-1中的例2。
类型三:服务中定义了Application的子类,而且这个Application的子类使用了@ApplicationPath注解,则REST服务类型为类型三,对应图1-1中的例3。
类型四:如果服务中定义了Application的子类,但是这个Application的子类没有使用@ApplicationPath注解,则REST服务类型为类型四,对应图1-1中的例4。
图1-1 REST工程类型示意图
上面提到的四个示例在下面的“阅读指南”中给出了源代码目录和Github下载地址,需要读者仔细体会示例之间的差异,以更好地理解和使用不同类型的REST服务。
1. REST服务类型一
类型一对应的是图1-1中的例1,相应的逻辑是服务中同时不存在Application的子类和Servlet子类。在JAX-RS2(JSR339)中定义这种情况下应作如下处理:为REST服务动态生成一个名称为javax.ws.rs.core.Application的Servlet实例,并自动探测匹配资源。与此同时,需要根据Servlet的不同版本,在web.xml定义REST请求处理的Servlet为这个动态生成的Servlet,并定义该Servlet对资源路径的匹配。在没有Application的子类存在的情况下,在web.xml中定义Servlet是必不可少的配置。
REST服务类型一所对应的示例,即例1的源代码地址如下。
/feuyeux/jax-rs2-guide-II/tree/master/1.6.1.myrest-servlet2-webxml。
/feuyeux/jax-rs2-guide-II/tree/master/1.6.2.myrest-servlet3-webxml。
请使用mvn jetty:run启动服务,使用curl http://localhost:8080/webapi/myresource测试服务。
REST服务类型一的示例包含两个小项目,分别对应Servlet2和Servlet3两种容器依赖场景。我们只须关注Maven配置文件(pom.xml)和Web服务配置文件(web.xml)的区别即可理解无Application子类情况下,如何实现基于Servlet2和Servlet3容器内的服务。
Servlet3的最简配置示例代码如下。
&?xml version="1.0" encoding="UTF-8"?&
&web-app version="3.0" xmlns="/xml/ns/Java EE" xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xsi:schemaLocation="/xml/ns/Java EE /xml/ns/Java EE/web-app_3_0.xsd"&
&servlet-name&javax.ws.rs.core.Application&/servlet-name&
&/servlet&
&servlet-mapping&
&servlet-name&javax.ws.rs.core.Application&/servlet-name&
&url-pattern&/webapi/*&/url-pattern&
&/servlet-mapping&
&/web-app&
相对于Servlet2而言,在Servlet3中,servlet的定义可以只包含servlet-name。再次强调,Jersey的Servlet3的容器支持包是jersey-container-servlet。 Servlet2的最简配置示例代码如下。
&?xml version="1.0" encoding="UTF-8"?&
&web-app version="2.5" xmlns="/xml/ns/Java EE" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="/xml/ns/Java EE /xml/ns/Java EE/web-app_2_5.xsd"&
&servlet-name&Jersey Web Application&/servlet-name&
&servlet-class&org.glassfish.jersey.servlet.ServletContainer&/servlet-class&
&init-param&
&param-name&jersey.config.server.provider.packages&/param-name&
&param-value&com.example&/param-value&
&/init-param&
&load-on-startup&1&/load-on-startup&
&/servlet&
&servlet-mapping&
&servlet-name&Jersey Web Application&/servlet-name&
&url-pattern&/webapi/*&/url-pattern&
&/servlet-mapping&
&/web-app&
servlet的定义包含servlet-name和servlet-class,其初始化参数需要显示给出要加载的资源类所在的包名,可以看出Servlet2的支持包jersey-container-servlet-core不具备自动扫描资源类的功能。
2. REST服务类型二
类型二对应的是图1-1中的例2,相应的逻辑是不存在Application的子类但存在Servlet的子类。
REST服务类型二所对应的示例,即例2的源代码地址如下。
/feuyeux/jax-rs2-guide-II/tree/master/1.6.3.myrest-subservlet。
本例定义了Servlet子类AirServlet,该类继承自org.glassfish.jersey.servlet.ServletContainer类,这是Jersey2中Servlet的基类,继承自HttpServlet。AirServlet类的代码示例如下。
@WebServlet(
initParams = @WebInitParam(
name = "jersey.config.server.provider.packages", value = "com.example"),
urlPatterns = "/webapi/*",
loadOnStartup = 1)
public class AirServlet extends ServletContainer {
AirServlet使用了WebServlet注解来配置Servlet参数。包括初始化参数initParams中定义扫描的资源类所在的包名:com.example,Servlet匹配的资源路径:urlPatterns="/webapi/*"和启动时的加载标识:loadOnStartup=1。
例2是基于Servlet3容器的REST服务,使用了WebServlet注解和无web.xml等Servlet3引入而Servlet2没有的功能。在自定义Servlet3.x子类的场景下,web.xml可以省略,但需要修改Maven的maven-war-plugin插件的配置,添加failOnMissingWebXml为false,这样编译时才不会报错。Maven配置文件中相关信息如下所示。
&groupId&org.apache.maven.plugins&/groupId&
&artifactId&maven-war-plugin&/artifactId&
&version&2.3&/version&
&configuration&
&failOnMissingWebXml&false&/failOnMissingWebXml&
&/configuration&
&dependency&
&groupId&javax.servlet&/groupId&
&artifactId&javax.servlet-api&/artifactId&
&version&3.1.0&/version&
&scope&provided&/scope&
&/dependency&
3. REST服务类型三
类型三对应的是图1-1中的例3,相应的逻辑是存在Application的子类并且定义了@ApplicationPath注解。
REST服务类型三所对应的示例,即例3的源代码地址如下。
/feuyeux/jax-rs2-guide-II/tree/master/1.6.4.myrest-servlet3-application。
/feuyeux/jax-rs2-guide-II/tree/master/1.6.5.myrest-servlet2-rc。
REST服务类型三的示例包含两个小项目。其中,servlet2-rc项目基于Servlet2,AirResourceConfig类继承自Application的子类ResourceConfig类;servlet3-application项目基于Servlet3,AirApplication类继承自Application类。基于Servlet2的REST服务需要定义web.xml(但内容可以是“空的”,即只有web-app的基本定义),基于Servlet3的REST服务可以省略此文件。AirApplication类代码示例如下。
@ApplicationPath("/webapi/*")
public class AirApplication extends Application {
public Set&Class&?&& getClasses() {
final Set&Class&?&& classes = new HashSet&Class&?&&();
classes.add(MyResource.class);
AirApplication类覆盖了getClasses()方法,注册了资源类MyResource,这样在服务启动后,MyResource类提供的资源路径将被映射到内存,以便请求处理时匹配相关的资源类和方法。AirResourceConfig类代码示例如下。
@ApplicationPath("/webapi/\*")
public class AirResourceConfig extends ResourceConfig {
public AirResourceConfig() {
packages("com.example");
AirResourceConfig类在构造子中提供了扫描包的全名,这样在服务启动后,com.example包内资源类所提供的资源路径将被映射到内存。
4. REST服务类型四
类型四对应的是图1-1中的例4,相应的逻辑是一有二无:一有是存在Application的子类;二无是不存在Servlet子类、不存在或者不允许使用注解@ApplicationPath。
REST服务类型四所对应的示例,即例4的源代码地址如下。
/feuyeux/jax-rs2-guide-II/tree/master/1.6.6.myrest-servlet2-application。
/feuyeux/jax-rs2-guide-II/tree/master/1.6.7.myrest-servlet3-application。
REST服务类型四的示例包含两个小项目,演示了基于Servlet2和Servlet3两个版本的REST服务,其差异仅此而已,关于差异性配置前面的例子已经讲过,不再冗述。如下以servlet3-application为例说明。AirApplication类是Application的子类,代码示例如下。
public class AirApplication extends Application {
public Set&Class&?&& getClasses() {
final Set&Class&?&& classes = new HashSet&Class&?&&();
classes.add(MyResource.class);
代码和类型三的示例相仿,但是该类没有定义@ApplicationPath注解,因此我们需要在web.xml中配置Servlet和映射资源路径,代码示例如下。
&?xml version="1.0" encoding="UTF-8"?&
&web-app xmlns="/xml/ns/Java EE" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="/xml/ns/Java EE /xml/ns/Java EE/web-app_3_0.xsd"version="3.0"&
&servlet-name&com.example.AirApplication&/servlet-name&
&/servlet&
&servlet-mapping&
&servlet-name&com.example.AirApplication&/servlet-name&
&url-pattern&/webapi/\*&/url-pattern&
&/servlet-mapping&
&/web-app&
在servlet-name中使用自定义的Application子类com.example.AirApplication的全名作为Servlet名称,并在url-pattern中映射资源路径。
1.6.2 REST应用描述
在明白如何创建和部署各种类型的REST服务后,我们来了解一下部署好的REST服务中一个特殊的成员,REST应用的描述:以XML格式展示当前REST环境中所提供的REST服务接口。这种XML格式的

我要回帖

更多关于 api开放接口安全认证 的文章

 

随机推荐