乐趣区

关于后端: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,两者只是解释的入口点不同。对应的示例代码如下:

// 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。整个申请在服务端的执行程序如下:

  1. 收到申请
  2. 标记 PreMatch 的 ContainerRequestFilter 执行,并实现办法映射
  3. 标记 PostMatch 的 ContainerRequestFilter 执行
  4. ReaderInterceptor 执行
  5. MessageBodyReader 执行
  6. 具体资源办法执行
  7. ContainerResponseFilters 执行
  8. WriterInterceptor 执行
  9. MessageBodyWriter 执行
  10. 返回响应

辅助对象

反对应用注解 @Context 注入一些特定的 Web 对象来辅助解决,包含:

  1. UriInfo:封装了每次申请的相干信息
  2. HttpHeader:申请头信息
  3. Request:辅助类,并非申请自身
  4. 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 开发的共同之处,确有裨益。

退出移动版