Jersey with SpringBoot
前言
恰巧有机会用到Jersey这个RESTful Webservice框架,项目看起来比较老旧,有点脏乱差,遂整合到SpringBoot上,并且同时配上Swagger作为API文档解决方案。从完全不了解Jersey到整合完成耗时并不长,总得来说还是非常方便的。
Jersey入门
说起来Java总是一堆规范,这回也不例外,Jersey同样是JAX-RS规范的标准开源实现。JAX-RS即Java API for RESTful Web Services,属于Java EE6的一部分,现如今已经发展到2.1版本(JSR311、JSR370)。本篇不讨论Jersey本身的实现,因此大部分的应用情景使用的就是JAX-RS的接口和注解。
如今web中的概念颇多,MVC、WebServie、SOA、微服务等等,尤为理不清的就是这WebService,在UDDI、WSDL(WADL)的概念下迷失。但是刨开有的没的,说到底就是Request和Response,在如今前后端分离的情境下MVC的V基本被砍光,剩下的MC和除去自描述的WebService在应用接口的开发上个人认为已经极其相似,使用的时候也可以发现两者形式也基本一样。那么用惯了SpringMVC后,学习使用Jersey并不会有什么障碍,纵观发展可以发现SpringMVC在完善支持返回json结构体,而Jersey现如今也支持MVC模板,殊途同归。
Jersey目前也是两个大版本1.x和2.x,2.x也不是什么新鲜事物了,因此就以2.x为准。关于Jersey的入门推荐参看https://www.jianshu.com/nb/21274943这里面的几篇文章,讲的算比较不错了。本篇就相当于浓缩再浓缩,详细了解还是参考官方文档最为稳妥。
关键概念
- Jersey是一个REST框架,是JAX-RS规范的标准开源实现
- 基于Jersey的应用可以不依赖Servlet环境,可以在Servlet容器中使用,也可以在Http容器中使用
- 不仅仅是Server端,JAX-RS也提供了客户端API,客户端也可以不准确的描述为请求发送器。Jersey通过扩展机制交由其他工具来实现这些内容,如JDK、grizzly、apache http等等。
- 对于组件(资源映射类等),默认情况下Jersey将为每次请求创建一个新的对象(与Struts相似)。
资源映射
在RESTful WebService中,资源是核心,整个应用围绕资源的映射和处理。与WebMVC所对应的便是请求的映射即RequestMapping,两者只是解释的入口点不同。对应的示例代码如下:
1 | // Spring MVC |
简单列一下主要注解:
JAX-RS | 描述 | SpringMVC |
---|---|---|
@Path | 配置路径,可以置于类和方法上 | @RequestMapping |
@PathParam | 路径变量的获取,@Path指定的路径同样可以配置{param} | @PathVariable |
@GET、@POST等 | 描述请求方法,相同功能的还有@HttpMethod | @GetMapping等 |
@CookieParam、@FormParam等 | 快速绑定不同类型的参数 | |
@DefaultValue | 参数的默认值 | @RequestParam(default = “…”) |
@Consumes、@Produces | 定义Http接收、返回类型 |
对于表单请求,Jersey额外提供了@BeanParam注解,可以将@FormParam注解在Bean的属性上,然后直接使用@BeanParam接收参数。也可以直接通过MultiValuedMap<String, String>来接收参数。
相对特殊的@MatrixParam可以用于接收/resource;pageSize=10;currentPage=2`这个请求中的pageSize和currentPage,这样使用的意图是将每一个分页看做一个资源,分页信息不是参数而是资源的一部分。
应用配置
JAX-RS中提供了应用类javax.ws.rs.core.Application,定义了应用的基本组件信息,为了方便配置,Jersey提供了一系列继承了Application的ResourceConfig类,可以使用此类来快速配置。如果需要配置Jersey的根路径,在Servlet3.0以上的环境下可以使用注解@ApplicationPath("BasePath")
注解在配置类上。
信息转换
任何Web框架都会涉及信息的转换,用于将请求信息转换成可编程的对象,例如SpringMVC的MessageConverter。在JAX-RS中定义了两个接口:MessageBodyWriter和MessageBodyReader,通过实现这两个接口来处理输入输出的转换,查看继承关系可以看到Jersey默认提供的一些转换器。
过滤拦截
JAX-RS定义了两个面向切面的工具Filter和Interceptor,注意这个Filter和Servlet的Filter并不相同,行为上也不一样,JAX-RS规范下的Filter和Interceptor都是单向的,请求归请求,响应归响应。过滤器一般用来处理头部信息且分为客户端过滤器和服务端过滤器,服务端核心接口为ContainerRequestFilter和ContainerResponseFilter;而拦截器主要用于修改实体的内容,比如编码解码等,核心接口为WriterInterceptor和ReaderInterceptor。
需要注意如果请求不存在的地址,则RequestFilter不会执行,而只要有响应ResponseFilter就会执行,这实际上涉及到Filter的匹配时机,匹配时机分为PreMatch和PostMatch,默认为PostMatch。整个请求在服务端的执行顺序如下:
- 收到请求
- 标记PreMatch的ContainerRequestFilter执行,并完成方法映射
- 标记PostMatch的ContainerRequestFilter执行
- ReaderInterceptor执行
- MessageBodyReader执行
- 具体资源方法执行
- ContainerResponseFilters执行
- WriterInterceptor执行
- MessageBodyWriter执行
- 返回响应
辅助对象
支持使用注解@Context注入一些特定的Web对象来辅助处理,包括:
- UriInfo:封装了每次请求的相关信息
- HttpHeader:请求头信息
- Request:辅助类,并非请求本身
- ServletContext、ServletRequest、ServletResponse
可以置于方法参数上,也可以置于类成员上,同样是通过动态代理来实现。
上传下载
为支持上传和下载需要引入jersey-media-multipart包,并且在配置类中注册MultiPartFeature。
上传时可以使用@FormDataParam,可注解在InputStream和FormDataContentDisposition上,用于获取文件数据和文件描述。
下载时可以使用javax.ws.rs.core.Response直接携带文件构建出适合的响应。
1 | // 上传 |
异常处理
JAX-RS提供了一个标准的异常类WebApplicationException(继承RuntimeException)可以在资源方法、provider、StreamingOutput中抛出这个异常让Jersey统一处理。WebApplicationException有很多构造方法来满足指定异常信息,但是在实际使用过程中比较难满足定制的需求。
此外还提供了ExceptionMapper接口,该接口只有一个方法:Response toResponse(E exception)
即针对指定类型的异常如何生成Response对象,这样就完成了异常类型和返回对象的映射。
1 |
|
整合SpringBoot
整合Spring
自Jersey2.x以来,Jersey的DI(依赖注入)默认由HK2这个符合J2EE标准的框架来实现。如果要整合SpringBoot,首先需要整合Spring容器。这一步同样已经提供好了解决方案,引入jersey-spring包即可,本质上是由HK2提供的Spring-Bridge来完成转换的。
整合Spring后,值得注意的是Jersey本身会扫包,但为了让Spring来管理相关的组件仍然需要在组件上增加注解@Component(纯注解配置模式)。此外在Spring管理下组件的默认行为变为单例。
Boot自动配置
SpringBoot官方提供了Jersey的自动配置类JerseyAutoConfiguration和依赖包spring-boot-starter-jersey。注意只需要引入spring-boot-starter-jersey而不需要再额外引入spring-boot-starter-web,Jersey和SpringMVC不需要同时存在,spring-boot-starter-jersey已经引入了足够的依赖项来启动一个基本的应用服务。

可以看到额外包含了Json序列化和参数校验特性。
JerseyAutoConfiguration这自动配置类中的内容也很简洁,主要包含3个部分:
- 构造函数,包括3个参数:JerseyProperties、ResourceConfig、ResourceConfigCustomizer(接口),用于项目的定制
- FilterRegistrationBean和ServletRegistrationBean,说明整合SpringBoot时,Jersey的入口是Servlet(默认)或者Filter
- 定制化的Json序列化组件,通过Jackson来实现,注册JAX-RS组件、适配JAXB。
搭建完工程后,可以选择直接定制注入一个ResourceConfig,也可以让组件实现ResourceConfigCustomizer来分散配置。
Jersey获取组件是通过扫包或者手动注册,SpringBoot有额外提醒不推荐使用Jersey的扫包功能,因为在war包环境下有可能出现扫包失败,详见原文地址。
然后整合就基本上完成了,可配的属性不是特别多,主要包括选择filter还是servlet、servlet启动时加载(load-on-startup)、以及初始化参数init。
整合Swagger和Swagger-UI
虽然说WebService有自描述的功能,可以配合客户端来使用WSDL、WADL,但是可读性不好,如果是用来编写应用的接口,就更加额外需要编写相关的接口文档。Swagger的动态特性非常不错,同时对RESTful风格的接口支持尤其出色,因此使用它作为接口文档解决方案。
配置Swagger
不同于与SpringMVC整合时有方便的spring-fox来便捷处理,与Jersey整合时需要额外进行一些操作,但也不麻烦。目前Swagger已经发展到3系列,不过应用还不是特别广,所以仍选用了swagger2。引入依赖:
1 | <dependency> |
然后在Jersey的配置类中注册swagger组件,并且定制swagger内容
1 |
|
查看ApiListingResource这个类,可以看到swagger注册了一个接口,请求/swagger.json
或者/swagger.yaml
就可以获取到API文档的信息数据。
可以看到真正起作用的是swagger-jaxrs包下的内容,因此如果需要使用swagger3,引入swagger-jaxrs2即可。
配置Swagger-UI
拥有文档信息后还需要使用Swagger-UI来进行可视化展现。为了方便,采用webjars技术来引入相关的静态网页资源。引入依赖:
1 | <dependency> |
在这个jar包内包含了Swagger-UI网页的各种资源,查看index.html可以发现其中调用获取接口信息的地址默认是https://petstore.swagger.io/v2/swagger.json
,因此需要将其拷贝出来复制在类路径下,修改其中静态资源的引用路径和接口信息的路径。
此时就需要处理静态资源的访问,由于不使用SpringMVC,那么也就没办法直接使用其提供的ResourceHttpRequestHandler
,需要额外配置Web容器的静态资源访问。首先配置Jersey的根路径,不要配置为/
防止冲突,然后配置容器或者Servlet。
对于webjars资源的访问,SpringBoot已经做了默认配置,获取webjar资源路径的方法位于org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory#getUrlsOfJarsWithMetaInfResources
,查看AbstractServletWebServerFactory的子类中使用该方法的位置就可以看到对webjars资源的配置操作。
Jetty容器
如果使用Jetty作为容器,那么可以选择使用Jetty提供的ResourceHandler
1 |
|
Tomcat容器
如果使用嵌入式Tomcat容器,则会稍微麻烦一些,嵌入式Tomcat的文档很少,最后参看SpringBoot对Webjars资源的配置找到了解决方案
1 |
|
样例
例如将index.html拷贝至如下的位置,并且配置Jersey的根路径为/api

修改其中的内容
1 | <!-- HTML for static distribution bundle build --> |
访问host:port/api-doc/index.html
就可以看到自动生成的API文档。
总结
说实话Jersey来整合SpringBoot个人感觉意义并不是特别大,SpringBoot讲求一个便捷快速,Jersey本身同样也很轻巧,且SpringMVC摆在这里,颇有些食之无味,弃之可惜的感觉。整合SpringBoot后最大的好处还是IOC、AOP以及Spring生态的支持以及其他工具的快速整合,由此可见生态的重要性。
总体来说JAX-RS这套API挺不错的,该有的都有,对比SpringMVC可以发现Web开发的共同之处,确有裨益。