原始博文链接
前言
凑巧有机会用到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,两者只是解释的入口点不同。对应的示例代码如下:
// Spring MVC
@RequestMapping(value = "/comments" ,method = RequestMethod.GET)
@ResponseBody
public JsonResult getComments(@RequestParam(value = "articleid") Long articleid,
@RequestParam(value = "pn",defaultValue = "1") Integer pn){
// some implementation
}
// JAX-RS
@Path("/comments")
@GET
@Produces(MediaType.APPLICATION_JSON)
public JsonResult getComments(@QueryParam(value = "articleid") Long articleid,
@QueryParam("pn") @DefaultValue("1") Integer pn){
// some implementation
}
简略列一下次要注解:
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间接携带文件构建出适宜的响应。
// 上传
@POST
@Path("image1")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.MULTIPART_FORM_DATA)
public String upload(@FormDataParam("file") InputStream fileInputStream,
@FormDataParam("file") FormDataContentDisposition disposition) {
File upload = new File(ctx.getRealPath("/upload"), disposition.getFileName());
try {
// handle input stream...
} catch (IOException e) {
e.printStackTrace();
}
return "success";
}
// 下载
@GET
@Path("/images2/{name}")
public Response showImg(@PathParam("name") String imageName) throws IOException {
File f ;
// find and get file...
if (!f.exists()) {
return Response.status(Status.NOT_FOUND).build();
} else {
return Response.ok(f).header("Content-disposition", "attachment;filename=" + imageName)
.header("Cache-Control", "no-cache").build();
}
}
异样解决
JAX-RS提供了一个规范的异样类WebApplicationException(继承RuntimeException)能够在资源办法、provider、StreamingOutput中抛出这个异样让Jersey对立解决。WebApplicationException有很多构造方法来满足指定异样信息,然而在理论应用过程中比拟难满足定制的需要。
此外还提供了ExceptionMapper接口,该接口只有一个办法:Response toResponse(E exception)
即针对指定类型的异样如何生成Response对象,这样就实现了异样类型和返回对象的映射。
@Provider
public class ErrorHandler implements ExceptionMapper<Exception> {
@Override
public Response toResponse(Exception exception) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(ResponseEntity.status(500).body(exception.getMessage()+"-"+this))
.type(MediaType.APPLICATION_JSON)
.build();
}
}
整合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。引入依赖:
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-jersey-jaxrs</artifactId>
<version>1.5.22</version>
</dependency>
而后在Jersey的配置类中注册swagger组件,并且定制swagger内容
@Component
@ApplicationPath("your_base_path")
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
this.packages("com.demo.jerseyboot");
this.register(MultiPartFeature.class);
this.property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
this.configureSwagger();
}
private void configureSwagger(){
// 注册两个JAX-RS组件
this.register(ApiListingResource.class);
this.register(SwaggerSerializers.class);
// swagger定制
BeanConfig config = new BeanConfig();
config.setConfigId("数据服务接口文档");
config.setTitle("数据服务接口文档");
config.setVersion("v1");
config.setContact("tony");
config.setSchemes(new String[] { "http" });
config.setBasePath("your_base_path");
config.setResourcePackage("com.demo.jerseyboot");
config.setPrettyPrint(true);
config.setScan(true);
}
}
查看ApiListingResource这个类,能够看到swagger注册了一个接口,申请/swagger.json
或者/swagger.yaml
就能够获取到API文档的信息数据。
能够看到真正起作用的是swagger-jaxrs包下的内容,因而如果须要应用swagger3,引入swagger-jaxrs2即可。
配置Swagger-UI
领有文档信息后还须要应用Swagger-UI来进行可视化展示。为了不便,采纳webjars技术来引入相干的动态网页资源。引入依赖:
<dependency>
<groupId>org.webjars</groupId>
<artifactId>swagger-ui</artifactId>
<version>3.22.0</version>
</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
@Component
public class JettyResourceCustomizer implements WebServerFactoryCustomizer<ConfigurableJettyWebServerFactory>, Ordered {
@Override
public void customize(ConfigurableJettyWebServerFactory factory) {
factory.addServerCustomizers(new JettyServerCustomizer() {
@Override
public void customize(Server server) {
ResourceHandler staticHandler = new ResourceHandler();
staticHandler.setDirectoriesListed(true);
staticHandler.setWelcomeFiles(new String[]{"index.html"});
ContextHandler context0 = new ContextHandler();
context0.setContextPath("/api-doc");
context0.setBaseResource(Resource.newClassPathResource("static"));
context0.setHandler(staticHandler);
// 留神不要笼罩原有的Handler
Handler[] handlers = server.getHandlers();
Handler[] newHandlers = Arrays.copyOf(handlers, handlers.length + 1);
newHandlers[handlers.length] = context0;
ContextHandlerCollection contexts = new ContextHandlerCollection();
contexts.setHandlers(newHandlers);
server.setHandler(contexts);
}
});
}
@Override
public int getOrder() {
// 确保在原生handler配置之后执行
return 100;
}
}
Tomcat容器
如果应用嵌入式Tomcat容器,则会略微麻烦一些,嵌入式Tomcat的文档很少,最初参看SpringBoot对Webjars资源的配置找到了解决方案
@Component
public class TomcatStaticResourceCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addContextCustomizers(new TomcatContextCustomizer() {
@Override
public void customize(Context context) {
WebResourceRoot root = context.getResources();
// 此时资源root为null
System.out.println(root);
// 应用tomcat的listener进行配置
context.addLifecycleListener(new LifecycleListener() {
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
context.getResources().createWebResourceSet(
WebResourceRoot.ResourceSetType.PRE,"/api-doc", this.getClass().getResource("/"),"/static"
);
}
}
});
}
});
}
}
样例
例如将index.html拷贝至如下的地位,并且配置Jersey的根门路为/api
批改其中的内容
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="../webjars/swagger-ui/3.22.0/swagger-ui.css" >
<link rel="icon" type="image/png" href="../webjars/swagger-ui/3.22.0/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="../webjars/swagger-ui/3.22.0/favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="../webjars/swagger-ui/3.22.0/swagger-ui-bundle.js"> </script>
<script src="../webjars/swagger-ui/3.22.0/swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "../api/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
// End Swagger UI call region
window.ui = ui
}
</script>
</body>
</html>
拜访host:port/api-doc/index.html
就能够看到主动生成的API文档。
总结
说实话Jersey来整合SpringBoot个人感觉意义并不是特地大,SpringBoot讲求一个便捷疾速,Jersey自身同样也很笨重,且SpringMVC摆在这里,颇有些食之无味,弃之可惜的感觉。整合SpringBoot后最大的益处还是IOC、AOP以及Spring生态的反对以及其余工具的疾速整合,由此可见生态的重要性。
总体来说JAX-RS这套API挺不错的,该有的都有,比照SpringMVC能够发现Web开发的共同之处,确有裨益。
发表回复