整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Jersey with SpringBoot

Jersey with SpringBoot

ttps://www.tony-bro.com/posts/890461404/

前言

恰巧有机会用到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开发的共同之处,确有裨益。

pring Boot Web容器

Web应用开发是企业开发的重要领域,Spring Boot 1.X的Web容器管理方式基于Servlet容器技术栈。Servlet容器主要基于同步阻塞I/O架构,HTTP请求和线程是一对一的关系,主要是TPR模型,即一个请求对应一个线程。主要的业务逻辑也是基于命令式的编程模式。以Spring MVC框架为主,Web容器方面以Tomcat为主,也可以通过自动配置功能改为Jetty/UnderTow容器。

Spring Boot 2.X主要基于异步非阻塞I/O架构,HTTP请求基于收敛的线程模型,网络层使用基于Reactor的I/O多路复用模式,业务逻辑基于函数式编程模式,以Spring WebFlux为主要框架。在Web容器方面可以基于Servlet 3.0的异步模式,默认情况下使用Netty作为容器。本节我们主要以Spring Boot 1.X讲解嵌入式Web容器的启动和加载原理,在进阶篇的响应式编程中将介绍Spring 5及Spring Boot 2.X的响应式框架WebFlux对Web应用服务的支持。

Spring Boot Web容器配置

Spring Boot对Web项目的支持主要是Spring Boot对Spring MVC框架的继承。Spring MVC框架是一个基于Servlet容器标准的Web容器框架实现,Spring Boot向Spring MVC提供开箱即用的Starter:springboot-starter-web。

Spring Boot应用中利用自动配置功能,只需要在pom.xml文件中加入下面的Web依赖,就可以直接启动一个Web服务:

Spring Web MVC 框 架 使 用 特 定 的 @Controller 或 者@RestController 注 解 的 Bean 作 为 处 理 HTTP 请 求 的 端 点 , 通 过@RequestMapping注解将控制器中的方法与HTTP请求进行映射,示例如下:

Spring Boot为Spring MVC提供了自动配置功能,包含如下主要配置特性。

● 自动配置ViewResolver引入ContentNegotiatingViewResolver组件功能。示例:在应用中添加ViewResolver组件用来匹配HTML静态页面,如果没有匹配成功,则返回false,由其他ViewResolver继续尝试匹配。ContentNegotiatingViewResolver会组合所有的视图解析器,代码如下。

● 自动注册Converter、GenericConverter、Formatter Bean。

示例:将页面提交数据转化为后台数据,实现格式化,代码如下。

● 对HttpMessageConverters的支持。

示例:Spring Boot可以为HttpMessageConverters类添加自定义转换类,通过这种方式可以将所有的HttpMessageConverters的Bean添加到Converter列表,覆盖默认的转换器列表,代码如下。

● 自动注册MessageCodeResolver。

● 自动使用ConfigurableWebBindingInitializer Bean。

● 使用WebMvcConfigurerAdapter类型的Bean来定制化配置。

默认情况下,Spring Boot会以/src/main/resources/static作为查找静态资源的文件路径,如果想自定义静态资源映射目录,需要重写addResourceHandlers来添加指定路径,重写addResourceLocations来指定静态资源路径。

总之,我们可以根据自己的意愿,对默认的Spring MVC的组件配置加以修改,方法也很简单,通过在IoC容器中注册新的同类型Bean来替换即可。如果你希望完全接管Spring MVC的所有相关配置,可以添加自己的@Configuration,并使用@EnableWebMvc注解实现定制化配置。

JAX-RS和Jersey框架

如果你喜欢JAX-RS和REST风格的编程模型,可以使用下面的Starter替代Spring MVC框架,Spring支持Jersey 1.X和Jersey 2.X等技术框架。这里我们只介绍Spring Boot对Jersey 2.X的支持,在pom.xml文件中加入下面的依赖:

Spring Boot对Jersey的配置有三种主要方式。在开始不同的配置方式前,我们注册一个端点对象资源,示例代码如下:

● 第一种方式,创建一个自定义的ResourceConfig:

● 第二种方式,返回一个ResourceConfig类型的@Bean:

● 第三种方式,配置一组ResourceConfigCustomizer对象。

Spring Boot提供了ResourceConfigCustomizer接口,让我们更灵活地对ResourceConfig对象进行配置。要使用该接口,我们需要先注释掉前面两节中提到的相关代码,然后创建一个类:

默 认 情 况 下 , Jersey 将 以 Servlet 的 形 式 注 册 一 个

ServletRegistrationBean 类 型 的 @Bean 。 它 的 名 字 为

jerseyServletRegistration,该Servlet默认会延迟初始化。

你可以通过spring.jersey.servlet.load-on-startup自定义配置

Jersey组件。通过创建相同名字的Bean,可以禁用或覆盖框架默认的

Bean。设置spring.jersey.type=filter可以使用Filter的形式代替

Servlet , 相 应 的 @Bean 类 型 变 为 jerseyFilter-Registration , 该

Filter有一个@Order属性,你可以通过spring.jersey.filter.order

设 置 该 属 性 。 Servlet 和 Filter 在 注 册 时 都 可 以 使 用

spring.jersey.init.*定义一个属性集合并将其传递给init参数进行

初始化。

内嵌容器的配置

Spring Boot 的 另 一 大 特 性 就 是 支 持 内 嵌 的 Web 容 器 , 包 括Tomcat、Jetty和UnderTow服务器,大多数开发者只需要使用合适的Starter来获取一个完全配置好的实例即可,内嵌服务器默认监听8080端口的HTTP请求。spring-boot-starter-web默认使用Tomcat作为Web容器,你可以在pom.xml中去除spring-boot-starter-tomcat依赖,然后 引 入 spring-boot-starter-jetty 或 者 spring-boot-starterundertow模块作为替代Web容器方案。Starter还提供了以“server.”为前缀的配置项对嵌入式容器配置进行修改。配置项的加载和定制化钩子加载过程如下。

1.自动化配置嵌入式容器

2.初始化TomcatEmbeddedServletContainerFactory的Bean对象

3.定制化Bean扩展逻辑

EmbeddedServletContainerCustomizerBeanPostProcessor在加载Bean后开始初始化配置项PostProcessor的处理逻辑:

4.配置文件加载

从配置文件中,你可以加载配置文件对象的配置值。如果配置文件中没有相关配置项,将使用默认代码设定配置。

5.Web容器定制化

如果你需要对Web容器进行更深入的定制,可以使用对应的Factory自动化配置Tomcat容器,它是初始化的关键流程和步骤,代码示例如下:

下图是Spring Boot启动过程中Tomcat容器完成自动配置的类图结构。我们在最新的Spring Boot下查看Tomcat的相关配置,发现有两个自动装配类,分别包含了三个定制器,还有一个工厂类。

本文给大家讲解的内容是SpringBootWeb容器配置:JAX-RS和Jersey框架、内嵌容器的配置

  1. 下篇文章给大家讲解的是Spring Boot嵌入式Web容器原理
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!
  1. aven swagger依赖: