前言
在 Java 的 Web 世界中,构建Web应用最为常见的 dispatcher 框架,老一点的 Struts 系列,现在最为流行的 SpringMVC。它们起初的目标都是构建基于模板的 web 应用程序,也就是作为一名当时的程序员,前后端都得会。
而随着分布式系统 和 互联网的发展,前后端分离、后端服务化的趋势越来越明朗,SpringMVC 加入了诸如 @RestController 这样概念性的 annotation 以 restful 风格的 webservice call 来语义上支持前后分离 和 后端的服务化。前端程序员不强调必须会后端,后端程序员也不强调必须要会前端,JAX-RS 不论用来做 Web 与前端程序员的分离, 或者后端一系列的服务都是一个非常好的选择。
从 51cto 来的图来表述前后端分离:
从百度查来的后端分布式服务图:
1、对应后端概念
相信资历比较老一些的研发,或多或少得接触过 SOA(Service Oriented Architecture)、ESB(Enterprise Service Bus)、RPC(Remote Procedure Call), 还有最近的微服务 这些概念即是为构建复杂的分布式系统而生,为后端服务化提供一套套解决方案的思路。
ESB 企业资源总线例子图(百度来的与作者理解一致的图,作者很懒):
2、概念的 Implementations
这些思路的出现,也有着对应的 java 框架进化着 ——— 从遥远过去的程序员制定规则到 SOAP 标准的出现到 Sun 建立的 Restful webservice 标准 — JAX-RS, 再到后来基于 Netty 的一系列 RPC 框架, 诞生了非常非常多优秀的 Java 框架,如下面举一些我的程序职业中见过得一些例子。
WebService的SOAP标准系列: Apache Axis2、Codehaus XFire、Apache CXF
Rest 的 WebService 的 JAX-RS 系列: RestEasy、Jersey、CXF
基于 Netty 的 RPC 系列: Finagle、JProtobuf(我们点融内部服务项目在使用这个RPC框架)、Thrift、apache avro
它们让我们从使用菜刀的冷兵器战场到使用AK47的热兵器时代。
3、派别模式的优点和区别
在我的 java 世界中有两个派别:第一派即 j2ee 系列,走的是标准化道路,先有标准的定义,再由具体厂商去实现,再慢慢地进化标准; 另一派走得比较敏捷一些(Spring 系列), 反设计模式的特性,使得这一派如鱼得水;它们共同构成了 java web 的世界,模式不同但又相互交融。
j2ee、spring 类似的对比图:
讲了这么多,我们进入我将要介绍的正题——JAX-RS, 它属于 Restful 系列的标准、是 j2ee 系列中的产物之一。
那么何为标准呢?标准就像你我写程序时定义的 interface,我拿着接口使用其中一个实现,即可实现系统的交互; 标准,也如同攻城射击狮画攻城图纸使用了神器-UML (统一建模语言), 画出来的东西,程序员就能够直接懂里面发生了什么, 大大减少非标准路子造成的程序员之间的沟通问题; 是标准 也就提供了通过编程序使程序理解程序、自动生成代码的可能性。
JAX-RS 是一种标准也即拥有了这些优点,看了我的后面的代码,你会发现后端程序员可以这样构建 Restful 的 Webservice 服务 和 客户端。
下面的代码中我将介绍一些 JAX-RS 标准能为我们带来的好处,如:
自动生成可与服务器交互的客户端
在客户端、服务端埋点:
—— Track可能的性能问题
—— 传递一些作为上游服务器拥有的session类参数给无状态的服务端
—— 其他, 如 配合配置服务器做 服务注册与发现; 由调用方传入 transactionId保证事务最终一致; 使用 token(如 jwt) 来获得 api 的访问权限 etc。
4、Talk is cheap, show me the code.
为了避免篇幅过于冗长,代码中仅提供 “四”中明确提出的 3个好处的实现。
1)我们既然是 In action, 我不解释太多标准细节的东西,读者可以自行通过附件1提供的链接,去查询标准相关的细节,下面是两张 JAX-RS 的 Lifecycle(具体请参见附件 spec 链接)的图片,读者可以先尝试理解一下图片 以帮助理解程序。
Server-side :
Client-side:
2)将附录2中的源代码下载下来,用 intellij 或 eclipse 以 gradle 项目形式导入. 该项目的 JAX-RS 标准由 cxf 实现,与 Springboot 进行集成,在将项目导入后找到 类 Application, 右键 debug run,即可将该 springboot 的服务器端启动。
3)服务端启动后,http 服务即被放在了 http://localhost:8080, 可访问 http://localhost:8080/ws 测试 cxf servlet dispatcher 是否启动成功。此时找到 类 ClientMainDemo,这个类既是我们生成的客户端 它是业务逻辑、性能监控、传递一些 session 信息给 stateless 服务的使用 main 方法的测试。我们在 “2” 中的服务启动好之后,直接 debug 执行 ClientMainDemo 中的 main 方法:
具体代码详解我们下一步再讲(其实我不想讲解代码,代码即是文档,还烦请各位有兴趣的同学下载代码来看,代码并不复杂)我们可以看到如下的日志:
case a.
第一个 request 在服务端可以看到日志:
在客户端看到日志:
我们可以看到,服务器发现了一个 request,并且记录了它被 call 了一次(我在代码中加的日志),与此同时 在客户端我们看到该 request 发起和结束的日志, 可以统计到 request 的状态、耗时等信息, 在客户端的track埋点功能 done。
case b.
是 DEMO 中的第三个 request, 我们将一个 ThreadLocal 中的变量设置好, 并使用 jaxrs 的 provider 进行拦截并将该值读取出来放进 http request 的 header。
我们可以在客户端看到日志:
服务端看到日志:
4)执行步骤“3”之后,我们发现我们需要的三个功能都已经实现了, 我们再来具体看一看代码是怎么做的(具体的框架搭建请参考源码):
1、自动生成可与服务器交互的客户端(该客户端需要 publish 成 jar 包到 repo 中,给集成方使用) CXF 框架提供了一个叫做 JAXRSClientFactory 的类, 可以根据 endpoint + 接口 + jaxrs providers 的方式来生成客户端接口, 详细代码参见JaxrsExampleFacade
2、埋点 track 客户端执行性能(服务端我没做), 读者可以自己做我自定义了一个类 叫做 UUIDHeaderClientRequestFilter,该类实现了 jaxrs 标准中 client 端的两个 filter 接口, 并且我将其放进了 client 的生命周期,即可用于生成每个 request 的 uuid 并且将其性能 track 起来,代码如下:
3、传递一些作为上游服务器拥有的 session 类参数给无状态的服务端与 UUIDHeaderClientRequestFilter 一样, 实现一个叫做 OperatorHeaderClientRequestFilter 的 jaxrs provider (多个 provider 之后请读者注意控制它们的 Priority),该 Filter 会从 ThreadLocal 中读取变量值,放进 requestheader,具体代码如下:
与此同时在服务器端也实现一个叫做 OperatorFilter 的类, 作为 jaxrs 的 provider 实现 ContainerRequestFilter 和 ContainerResponseFilter, 将 http header 中的值读出来并放到 ThreadLocal 中, 在 request 结束时清空 threadlocal,详细代码如下:
希望这篇文章在您的服务化之路上有一定思路上的帮助。
附录:
jax-rs spec下载地址:https://jcp.org/aboutJava/communityprocess/final/jsr339/index.html
本文源码地址:https://github.com/Agileaq/jax-rs-example.git
Uniauth的读服务提供给了点融网各个集成系统,其服务提供的war包 使用了这一套解决方案并且代码已经开源,源码: https://github.com/dianrong/UniAuth
cxf jaxrs文档: http://cxf.apache.org/docs/jax-rs.html
有任何疑问, 都欢迎直接与我联系并交流经验: [email protected] , 微信号 Arc_Qian
本文作者:钱晟龙 Arc_Qian(点融黑帮),现任点融网架构组产品研发工程师,主要任务是思考并尝试解决各类点融网迈出第一公里之后遇到的现实问题。