作者: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,因此您可以指向本地或远程文件。如果您托管定义,只需指定 http
或 https
协议,Swagger Parser 将会检索它。此功能有一些非常有趣的用例,我们将在以后的博客文章中介绍。
现在让我们添加 Inflector 依赖项。在运行时,Inflector 简单地使用 Jersey 2.6 作为 REST 框架,因此生产集成有很多选项。现在,让我们使用 Jetty Maven Plugin 并将 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>
<!-- your other plugins -->
</plugins>
</build>
<dependencies>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-inflector</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<!-- your other dependencies -->
查看包含您所需所有内容的示例 pom.xml。
请注意,我们在插件配置中配置了两个扫描目标。这将告诉 Jetty,如果 ./inflector.yaml
或 src/main/swagger/swagger.yaml
描述文件发生更改,则重新启动应用程序。您将看到这如何使使用 Inflector 进行开发变得轻而易举。
最后,让我们添加一个 web.xml
。这允许我们将 Inflector 应用程序安装到 Jersey 上下文的服务的根目录。我们还将添加一个 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: Find pet by ID
description: Returns a single pet
operationId: getPetById
consumes
- application/x-www-form-urlencoded
produces
- application/xml
- application/json
parameters
- name: petId
in: path
description: ID of pet to return
required: true
type: integer
format: int64
responses
"200":
description: successful operation
schema
$ref: "#/definitions/Pet"
"400":
description: Invalid ID supplied
"404":
description: Pet not found
如果您访问此端点(https://:8080/v2/pet/1
),Inflector 将根据您的 Accepts
标头提供 #/definitions/Pet
模型的一个示例有效载荷,可以是 JSON 或 XML 格式。无需代码!Inflector 将根据 swagger schema 构建示例。
{
"id": 0,
"category": {
"id": 0,
"name": "string"
},
"name": "doggie",
"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: Add a new pet to the store
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
对象。这为您提供了一个 Map
或 Array
类型对象,可将其视为您的域模型(XML 也是如此)。
自动定位控制器 + 包
对于大胆的用户,您可以配置 Inflector 扫描 controllerPackage
。
# inflector.yaml
controllerPackage: io.swagger.sample.controllers
Inflector 将始终在此处查找。您还可以使用 controllerPackage
设置默认包查找,并设置 x-swagger-router-controller: SampleController
而不是使用完全限定名。
然后,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 角色。