使用 Swagger Inflector 编写 API

  2015 年 8 月 13 日

作者:Tony Tam

Swagger Inflector 是 Swagger 团队的一个新项目,用于在 JVM 上编写 API。它目前处于预览状态,但正在积极开发和支持,作为在 Java 中编码 API 的替代方案,即设计优先的方法。让我们来看看如何使用 Inflector 项目。

[embed]http://www.slideshare.net/Swagger-API/the-inflector-project[/embed]

入门

首先,我们从 Swagger 规范开始。您可以使用您喜欢的文本编辑器以 JSON 或 YAML 格式创建规范,或者使用 http://editor.swagger.io 上精彩的在线编辑器来构建 API 的定义。不用担心进行更改!我们将介绍 Inflector 如何帮助您快速迭代您的设计,而无需编写任何代码。

在本演示中,我们假设您已从 此处复制了 Swagger 描述。

接下来,我们创建一个 YAML 文件来配置 Inflector。这允许使用许多选项,但现在,我们只放置 Swagger 描述的位置

# inflector.yaml

swaggerUrl: ./src/main/swagger/swagger.yaml

由于 Inflector 在底层使用了 Swagger Parser,因此您可以指向本地或远程文件。如果您托管您的定义,只需指定 httphttps 协议,Swagger Parser 将检索它。我们将在以后的博客文章中介绍此功能的一些非常有趣的用例。

现在让我们添加 Inflector 依赖项。在运行时,Inflector 只是使用 Jersey 2.6 作为 REST 框架,因此有很多用于生产集成的选项。现在,让我们使用 Jetty Maven 插件并将 Inflector 添加为依赖项

    <build>

<plugins>

<plugin>

<groupId>org.eclipse.jetty</groupId>

<artifactId>jetty-maven-plugin</artifactId>

<version>9.2.9.v20150224</version>

<configuration>

<monitoredDirName>.</monitoredDirName>

<scanTargets>

<scanTarget>inflector.yaml</scanTarget>

<scanTarget>src/main/swagger/swagger.yaml</scanTarget>

</scanTargets>

<scanIntervalSeconds>1</scanIntervalSeconds>

<webApp>

<contextPath>/</contextPath>

</webApp>

<httpConnector>

<port>8080</port>

<idleTimeout>60000</idleTimeout>

</httpConnector>

</configuration>

</plugin>

<!-- 您的其他插件 -->

</plugins>

</build>

<dependencies>

<dependency>

<groupId>io.swagger</groupId>

<artifactId>swagger-inflector</artifactId>

<version>1.0.0-SNAPSHOT</version>

</dependency>

<!-- 您的其他依赖项 -->

查看示例 pom.xml,其中包含您需要的一切。

请注意,我们在插件配置中配置了两个扫描目标。如果 ./inflector.yamlsrc/main/swagger/swagger.yaml 描述文件发生更改,这将告诉 Jetty 重新启动应用程序。您将看到这如何使 Inflector 的开发变得轻而易举。

最后,让我们添加一个 web.xml。这使我们可以在 Jersey 上下文中,在服务的根目录中挂载 Inflector 应用程序。我们还将添加一个 CORS 过滤器,以便我们可以轻松地从 Swagger UI 读取我们的 Swagger API。

<!-- from https://github.com/swagger-api/swagger-inflector/blob/master/samples/jetty-webxml/src/main/webapp/WEB-INF/web.xml -->

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<servlet>

<servlet-name>swagger-inflector</servlet-name>

<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>

<init-param>

<param-name>javax.ws.rs.Application</param-name>

<param-value>io.swagger.inflector.SwaggerInflector</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>swagger-inflector</servlet-name>

<url-pattern>/*</url-pattern>

</servlet-mapping>

<filter>

<filter-name>CORSFilter</filter-name>

<filter-class>io.swagger.inflector.utils.CORSFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>CORSFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

</web-app>

请注意我们如何添加 CORS 过滤器。这就像任何标准的 web.xml,您可以添加过滤器、静态 servlet 等。这里没有魔法!

这就是我们的项目设置。现在让我们试驾一下。

启动服务器

mvn package jetty:run

并在端口 8080 (在 jetty 插件中配置)上读取 swagger 列表。根据您在 Swagger 描述中配置的 basePath,您现在可以在 https://#:8080/{basePath}/swagger.json 打开 Swagger 描述(当然,根据规范,可以省略 basePath)。这将以 JSON 格式显示您一直在编辑的规范。我知道,没什么大不了的,但我们才刚刚开始。

请注意,根据我们的 Jetty 插件配置,您可以将更改保存到您的 swagger.yaml 文件并进行自动重新加载(它每秒扫描一次)。一个重要提示!如果您编写了无效的规范,服务器将拒绝提供它。使用 Swagger Editor 获取有关编写有效 Swagger 描述的上下文相关帮助!

现在是精彩的部分。让我们从我们的描述中取出一个路由

  /pet/{petId}:

get

tags

- pet

summary: 通过 ID 查找宠物

description: 返回单个宠物

operationId: getPetById

consumes

- application/x-www-form-urlencoded

produces

- application/xml

- application/json

parameters

- name: petId

in: path

description: 要返回的宠物的 ID

required: true

type: integer

format: int64

responses

"200":

description: 操作成功

schema

$ref: "#/definitions/Pet"

"400":

description: 提供的 ID 无效

"404":

description: 未找到宠物

如果您访问此端点(https://#:8080/v2/pet/1),Inflector 将根据您的 Accepts 标头,以 JSON 或 XML 格式提供 #/definitions/Pet 模型的示例有效负载。无需代码!Inflector 将根据 swagger 模式构建示例

{

"id": 0,

"category": {

"id": 0,

"name": "string"

},

"name": "小狗",

"photoUrls": [

"string"

],

"tags": [

{

"id": 0,

"name": "string"

}

],

"status": "string"

}

这很有趣!您可以对 Swagger 描述中的每个操作尝试相同的方法。Inflector 将使模拟服务变得简单。同样,当您修改服务描述并进行更改时,Jetty 插件将重新加载并立即显示您的更改。您甚至可以加载 swagger-ui 并指向我们的 Swagger 描述以查看真实内容。

现在,让我们超越 Hello World,让它更真实。假设您不想要模拟响应,而是想实现一个控制器并放入一些业务逻辑。Inflector 将通过将请求直接路由到控制器来简化此过程。

这通过多种技术进行配置。让我们来看看这些选项。

显式命名控制器 + 包

您可以使用 Swagger Extension 在您的描述中添加其他元数据。对于此用例,我们将添加我们要将请求发送到的控制器类的名称

  /pet:

post

tags

- pet

summary: 向商店添加一只新宠物

operationId: addPet

# 我们正在配置 Inflector 以在此类中查找我们的控制器!

x-swagger-router-controller: io.swagger.sample.SampleController

现在,在启动时,Inflector 将在 io.swagger.sample.SampleController 类中查找与此方法匹配的方法。但是我们的签名是什么???

Inflector 将使用 operationId 作为方法名。如果它不存在,则会使用一种算法来创建一个(您应该指定它以简化操作)。然后,它会查找与操作的输入参数匹配的参数签名,外加一个额外的上下文参数。实际上,如果您配置应用程序以启动调试日志记录,您会看到它正在尝试查找的内容。

DEBUG i.s.i.SwaggerOperationController - looking for operation: addPet(io.swagger.inflector.models.RequestContext request, com.fasterxml.jackson.databind.JsonNode body)

请注意,RequestContext 作为第一个参数——它包含有关标头、媒体类型等的宝贵信息。查看源代码,了解它为您提供了什么。

请注意,body 直接映射到 JsonNode 对象。这为您提供了一个 MapArray 类型的对象,您可以将其视为您的领域模型(这对于 XML 也成立)。

自动定位命名控制器 + 包

对于勇敢者,您可以配置 Inflector 来扫描 controllerPackage

# inflector.yaml

controllerPackage: io.swagger.sample.controllers

Inflector 将始终在此处查找。您还可以使用 controllerPackage 设置默认包查找,并设置 x-swagger-router-controller: SampleController 而不是使用 FQ 名称。

然后,inflector 将使用第一个标签来构造类名。如果您有一个标记为以下内容的操作

tags:

- pet

Inflector 现在将 controllerPackage + ‘.’ + Capitalize(tags[0]) + Controller 作为预期的类名

io.swagger.sample.controllers.PetController

实现业务逻辑

实现业务逻辑就像在我们的控制器中创建该方法一样简单

package io.swagger.sample;

import io.swagger.inflector.models.RequestContext;

import io.swagger.inflector.models.ResponseContext;

import com.fasterxml.jackson.databind.JsonNode;

import javax.ws.rs.core.Response.Status;

public class SampleController {

// ResponseContext 是可选的,但它可以提供关于写入客户端的额外控制

public ResponseContext addPet(RequestContext request, JsonNode body) {

return new ResponseContext()

.status(Status.OK)

.entity(body);

}

}

这很无聊,是的,但这表明我们现在可以完全控制响应对象,以及 Inflector 如何直接映射到我们的控制器!当您不用忙于处理所有管道时,您会发现编写 REST API 的速度非常惊人!

映射 POJO

假设我们想要一个具体的对象,而不是使用 JsonNode。这很容易做到——您有两种选择。

首先,您可以使用 modelMappings 语法在 inflector.yaml 中直接映射模型

modelMappings:

Pet: io.swagger.sample.models.Pet

您猜对了,当我们在 Swagger 描述中看到 Pet 定义时,我们将使用 io.swagger.sample.models.Pet 实现。

其次,您可以配置 modelPackage,Inflector 将尝试查找它

modelPackage: io.swagger.sample.models

这将导致 Inflector 从类加载器加载 io.swagger.sample.models.Pet。如果在两种情况下都存在,则您的方法签名将看起来更加友好

DEBUG i.s.i.SwaggerOperationController - looking for operation: addPet(io.swagger.inflector.models.RequestContext request, io.swagger.sample.models.Pet body)

现在您可以开始使用更熟悉的代码了

  public ResponseContext addPet(RequestContext request, Pet body) {

/* 所有业务逻辑 */

}

结论

这是一篇相当长的文章,但希望 Inflector 的设计优先目标是明确的。一旦您可以在不担心所有管道的情况下开始编写 API,您会发现您将创建更好的 API,并且速度更快。Inflector 消除了生成 Swagger 描述所需的注解和其他连线,并将 Swagger 本身置于 REST API 的 DSL 角色中。