REST 是 Roy Thomas Fielding [[1]](#fn1) 在 2000 年他的博士论文 [[2]](#fn2) “架构风格以及基于网络的软件架构设计” 中提出来的一个概念。REST 是 RESTransfer 的缩写,翻译过来就是 “表现层状态转化”。REST 就是 Roy 在这篇论文中提出的面向互联网的软件所应当具有的架构风格。css
按照 REpresentational State Transfer 的字面意思,能够把应用当作是一个虚拟的状态机,软件提供的不是服务而是一系列的资源统一的操做**来访问,而返回的结果表明了资源状态的一次跃迁。REST 是一种架构风格,若是一个软件架构符合 REST 风格,就能够称之为 RESTful 架构。这个架构应当具有如下一些设计上的约束:资源具备惟一标示、资源之间有关联关系、使用标准的方式来访问、资源有多种表现形式、无状态交互。html
举例来讲,一个简单的静态 HTML 页面的网站就很好的符合了 RESTful 架构风格。访问 http://acme.com/accounts 返回一个包含全部帐号的页面,选取其中一个连接 http://acme.com/accounts/1 又会返回包含用户 1 的详细信息。爬虫软件在这种场景下工做的很好,当知道了某个网站的首页地址后,能够自举发现这个网站上全部关联的网页。更重要的是,这种访问形式不依赖网站提供的任何客户端,而是仅仅经过 HTTP 标准的访问方式完成的。能够说,HTML 这种超媒体文档的组织形式就是资源的表现层状态迁移的一种形式。java
对于一个提供服务的动态网站来讲,能够按照相似的思路将其 RESTful 化:nginx
其中的思路是利用 HTTP 协议的标准方法 POST、DELETE、PUT、GET 来表达对于一个资源的增删改查 (CRUD) 操做,利用 URL 来表示一个资源的惟一标识。对资源访问的错误码也复用 HTTP 协议的状态码。返回结果一般由 json 或 XML 来表示,若是其中包换了对关联资源的访问方式 (所谓的表现层状态迁移) ,这种类型的 RESTful 应用能够进一步的称之为 hypermedia as the engine of application state (HATEOAS) 应用 [[3]](#fn3)。git
source: https://www.nginx.com/wp-content/uploads/2016/04/micro-image.pnggithub
这里须要注意的是,按照 Roy 的定义,RESTful 架构风格与 HTTP 协议并无什么强关联关系。可是,基于 HTTP 的 RESTful 架构风格是实现起来最天然,也是目前使用最普遍的一种实现,咱们称之为 RESTful HTTP。一样的,在下文中将会专一在 HTTP 的场景下介绍如何在 Dubbo 框架中将服务暴露成 Restful 架构。web
随着微服务的流行以及多语言互操做诉求日益增多,在 Dubbo 中暴露 REST 服务变成了一个不容忽视的诉求。为了在 Dubbo 中暴露 REST 服务,一般有两种作法,一种是直接依赖 Sprng REST 或者其余 REST 框架来直接暴露,另外一种是经过 Dubbo 框架内置的 REST 能力暴露。两种作法各有优缺点,主要体如今前者与微服务体系中的服务发现组件可以更好的工做,然后者能够无缝的享受到 Dubbo 体系中的服务发现以及服务治理的能力。本文关注的是如何使用后者来暴露 REST 服务。spring
自 2.6.0
开始,Dubbo 合并了当当网捐献的 DubboX [[4]](#fn4) 中的主要特性,其中就包括了基于 RESTeasy 3.0.19.Final
的 REST 支持,具有 JAXRS 2.0 规范中全部的能力。apache
在如下的例子中,展现了如何经过最传统的 Spring XML 配置的方式来快速的暴露和调用一个 REST 服务。其中底层的 server 使用的是 netty,服务注册发现基于 Zookeeper。编程
注:本章节讨论的示例能够经过 https://github.com/beiwei30/dubbo-rest-samples/tree/master/basic 来得到
首先须要在项目中引入 dubbo all-in-one 的依赖以及 RESTEasy 相关的必要依赖。由于在本例中使用 Zookeeper 做为服务发现,还须要引入 Zookeeper client 相关的依赖。为了方便使用,第三方的依赖能够经过框架提供的 BOM 文件 dubbo-dependencies-bom
来引入。
<properties> <dubbo.version>2.6.5</dubbo.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo-dependencies-bom</artifactId> <version>${dubbo.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>${dubbo.version}</version> </dependency> <!-- REST support dependencies --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxrs</artifactId> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-client</artifactId> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-netty4</artifactId> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jackson-provider</artifactId> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxb-provider</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> </dependency> <!-- zookeeper client dependency --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> </dependency> </dependencies>
定义一个服务接口 UserService
,该接口提供两个功能,一个是获取指定 User 的详细信息,另外一个是新注册一个用户。
@Path("users") // #1 @Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_XML}) // #2 @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_XML}) public interface UserService { @GET // #3 @Path("{id: \\d+}") User getUser(@PathParam("id") Long id); @POST // #4 @Path("register") Long registerUser(User user); }
经过在接口上用 JaxRS 标准的 annotation 来修饰,咱们规定了该服务在 REST 下的访问形式:
@Path("users")
定义了 UserService 经过 '/users' 来访问@Consumers
和 @Produces
来规定参数以及返回值的类型为 XML 和 JSON。在类级别上定义以后,就能够不用在方法级别上进一步定义了@GET
定义了接受的 HTTP 方法为 GET,经过 @Path
来规定参数是来自于 URL 中的 path。'GET /users/1' 等同于调用 'getUser(1)'@POST
定义了接受的 HTTP 方法为 POST,经过将 JSON 或 XML 格式的 User 数据 POST 到 '/users/register' 上来建立一个 User在 Dubbo 中,将 REST 相关的 annotation 定义在接口或者实现上都是能够的。这个在设计上是个权衡问题。Annotation 定义在实现类上能够保证接口的纯净,不然对于不须要经过 REST 方式调用的 Dubbo 调用方来讲将须要强制依赖 JaxRS 的库,可是同时,对于须要经过 REST 方式调用的 Dubbo 调用方来讲,就须要本身来处理 REST 调用相关的细节了。Annotation 定义在接口上,框架会自动处理掉 REST 调用相关的细节,并和 Dubbo 的服务发现以及服务治理功能可以很好的结合起来。在本例中采用了在接口上定义 JaxRS annotation 的形式。
为了简洁,这里给出的接口的实现只是简单的返回了接口须要的类型的示例,在真实的系统中,逻辑可能会比较复杂。
public class UserServiceImpl implements UserService { private final AtomicLong id = new AtomicLong(); public User getUser(Long id) { return new User(id, "username-" + id); } public Long registerUser(User user) { return id.incrementAndGet(); } }
如上所述,本例展现的是如何经过传统的 Spring XML 的方式来装配并暴露 Dubbo 服务。须要指出的是,这里展现了如何同时暴露两种不一样的协议,一种是 REST,另外一种是原生的 Dubbo 协议。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <dubbo:application name="rest-provider"/> <!-- #1 --> <dubbo:registry address="zookeeper://127.0.0.1:2181"/> <!-- #2 --> <dubbo:protocol name="rest" port="8080" server="netty"/> <!-- #3 --> <dubbo:protocol name="dubbo" server="netty4"/> <!-- #4 --> <dubbo:service interface="org.apache.dubbo.samples.rest.api.UserService" protocol="rest,dubbo" ref="userService"/> <!-- #5 --> <bean id="userService" class="org.apache.dubbo.samples.rest.impl.UserServiceImpl"/> </beans> <!-- #6 -->
rest-provider
简单的经过 ClassPathXmlApplicationContext 来加载刚刚配置的 Spring XML 配置 'rest-provider.xml' 便可启动 Dubbo 服务端
public class RestProvider { public static void main(String[] args) throws IOException { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/rest-provider.xml"); context.start(); System.in.read(); } }
因为本例依赖 Zookeeper 作服务注册发现,在启动 RestProvider 以前,须要先启动一个 Zookeeper 服务器。以后就能够直接运行 RestProvider 了。经过如下的输出日志,咱们能够知道 UserService 以两种方式对外暴露了同一个服务,其中:
... [01/01/19 07:18:56:056 CST] main INFO config.AbstractConfig: [DUBBO] Export dubbo service org.apache.dubbo.samples.rest.api.UserService to url rest://192.168.2.132:8080/org.apache.dubbo.samples.rest.api.UserService?anyhost=true&application=rest-provider&bean.name=org.apache.dubbo.samples.rest.api.UserService&bind.ip=192.168.2.132&bind.port=8080&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.samples.rest.api.UserService&methods=getUser,registerUser&pid=27386&server=netty&side=provider×tamp=1546341536194, dubbo version: 2.6.5, current host: 192.168.2.132 ... [01/01/19 07:18:57:057 CST] main INFO config.AbstractConfig: [DUBBO] Export dubbo service org.apache.dubbo.samples.rest.api.UserService to url dubbo://192.168.2.132:20880/org.apache.dubbo.samples.rest.api.UserService?anyhost=true&application=rest-provider&bean.name=org.apache.dubbo.samples.rest.api.UserService&bind.ip=192.168.2.132&bind.port=20880&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.samples.rest.api.UserService&methods=getUser,registerUser&pid=27386&server=netty4&side=provider×tamp=1546341537392, dubbo version: 2.6.5, current host: 192.168.2.132 ...
也能够经过 zkCli 访问 Zookeeper 服务器来验证。'/dubbo/org.apache.dubbo.samples.rest.api.UserService/providers' 路径下返回了一个数组 [dubbo://..., rest:.//...]。数组的第一个元素是 ’dubbo‘ 打头的,而第二个元素是 'rest' 打头的。
[zk: localhost:2181(CONNECTED) 10] ls /dubbo/org.apache.dubbo.samples.rest.api.UserService/providers [dubbo%3A%2F%2F192.168.2.132%3A20880%2Forg.apache.dubbo.samples.rest.api.UserService%3Fanyhost%3Dtrue%26application%3Drest-provider%26bean.name%3Dorg.apache.dubbo.samples.rest.api.UserService%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.samples.rest.api.UserService%26methods%3DgetUser%2CregisterUser%26pid%3D27386%26server%3Dnetty4%26side%3Dprovider%26timestamp%3D1546341537392, rest%3A%2F%2F192.168.2.132%3A8080%2Forg.apache.dubbo.samples.rest.api.UserService%3Fanyhost%3Dtrue%26application%3Drest-provider%26bean.name%3Dorg.apache.dubbo.samples.rest.api.UserService%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.samples.rest.api.UserService%26methods%3DgetUser%2CregisterUser%26pid%3D27386%26server%3Dnetty%26side%3Dprovider%26timestamp%3D1546341536194]
能够简单的经过 'curl' 在命令行验证刚才暴露出来的 REST 服务:
$ curl http://localhost:8080/users/1 {"id":1,"name":"username-1"} $ curl -X POST -H "Content-Type: application/json" -d '{"id":1,"name":"Larry Page"}' http://localhost:8080/users/register 1
Dubbo 调用方只须要依赖服务的接口,经过如下方式装配好 Dubbo Consumer,便可发起调用。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <dubbo:application name="rest-consumer"/> <dubbo:registry address="zookeeper://127.0.0.1:2181"/> <dubbo:reference id="userService" interface="org.apache.dubbo.samples.rest.api.UserService" protocol="rest"/> <!-- #1 --> </beans>
须要特别指出的是,这里显示的指定 protocol="rest" 在一般状况下不是必须的。这里须要显示指定的缘由是咱们例子中服务端同时暴露了多种协议,这里指定使用 rest 是为了确保调用方走 REST 协议。
简单的经过 ClassPathXmlApplicationContext 来加载刚刚配置的 Spring XML 配置 'rest-consumer.xml' 便可发起对 RestProvider 所提供的 UserService 的 REST 服务的调用。
public class RestConsumer { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/rest-consumer.xml"); context.start(); UserService userService = context.getBean("userService", UserService.class); System.out.println(">>> " + userService.getUser(1L)); User user = new User(2L, "Larry Page"); System.out.println(">>> " + userService.registerUser(user)); } }
这里分别展现了对 'getUser' 和 'registerUser' 的调用,输出结果以下:
>>> User{id=1, name='username-1'} >>> 2
在 Dubbo 中使用 annotation 而不是 Spring XML 来暴露和引用服务,对于 REST 协议来讲并无什么不一样。有关如何使用 annotation 更详细的用法,请参阅《在 Dubbo 中使用注解》章节。这里主要展现一下与上面基于 Spring XML 配置的例子不一样之处。
注:本章节讨论的示例能够经过 https://github.com/beiwei30/dubbo-rest-samples/tree/master/annotation 来得到
1. 使用 Java Configuration 来配置服务提供方的 protocol、registry、application
@Configuration @EnableDubbo(scanBasePackages = "org.apache.dubbo.samples.rest.impl") // #1 static class ProviderConfiguration { @Bean // #2 public ProtocolConfig protocolConfig() { ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("rest"); protocolConfig.setPort(8080); protocolConfig.setServer("netty"); return protocolConfig; } @Bean // #3 public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setProtocol("zookeeper"); registryConfig.setAddress("localhost"); registryConfig.setPort(2181); return registryConfig; } @Bean public ApplicationConfig applicationConfig() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("rest-provider"); return applicationConfig; } }
@EnableDubbo
来指定须要扫描 Dubbo 服务的包名,在本例中,UserServiceImpl 在 "org.apache.dubbo.samples.rest.impl" 下2. 使用 Service 来申明 Dubbo 服务
@Service // #1 public class UserServiceImpl implements UserService { ... }
@Service
或者 @Service(protocol = "rest")
修饰 "UserServiceImpl" 来申明一个 Dubbo 服务,这里 protocol = "rest"
不是必须提供的,缘由是经过 Java Configuration 只配置了一个 ProtocolConfig 的示例,在这种状况下,Dubbo 会自动装配该协议到服务中3. 服务提供方启动类
经过使用 ProviderConfiguration
来初始化一个 AnnotationConfigApplicationContext
实例,就能够彻底摆脱 Spring XML 的配置文件,彻底借助 annotation 来装配好一个 Dubbo 的服务提供方。
public class RestProvider { public static void main(String[] args) throws IOException { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class); context.start(); System.in.read(); } }
4. 使用 Java Configuration 来配置服务消费方的 registry、application
@Configuration @EnableDubbo(scanBasePackages = "org.apache.dubbo.samples.rest.comp") // #1 @ComponentScan({"org.apache.dubbo.samples.rest.comp"}) // #2 static class ConsumerConfiguration { @Bean // #3 public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setProtocol("zookeeper"); registryConfig.setAddress("localhost"); registryConfig.setPort(2181); return registryConfig; } @Bean public ApplicationConfig applicationConfig() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("rest-consumer"); return applicationConfig; } }
@EnableDubbo
来指定须要扫描 Dubbo 服务引用 @Reference
的包名。在本例中,UserService 的引用在 "org.apache.dubbo.samples.rest.comp" 下@ComponentScan
来指定须要扫描的 Spring Bean 的包名。在本例中,包含 UserService 引用的类 UserServiceComponent 自己须要是一个 Spring Bean,以方便调用,因此,这里指定的包名也是 "org.apache.dubbo.samples.rest.comp"这里提到的 UserServiceComponent 的 Spring Bean 定义以下:
@Component public class UserServiceComponent implements UserService { // #1 @Reference private UserService userService; @Override public User getUser(Long id) { return userService.getUser(id); } @Override public Long registerUser(User user) { return userService.registerUser(user); } }
UserService
接口,这样在调用的时候也能够面向接口编程5. 服务调用方启动类
经过使用 ConsumerConfiguration
来初始化一个 AnnotationConfigApplicationContext
实例,就能够彻底摆脱 Spring XML 的配置文件,彻底借助 annotation 来装配好一个 Dubbo 的服务消费方。而后就能够经过查找 UserServiceComponent
类型的 Spring Bean 来发起远程调用。
public class RestConsumer { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class); context.start(); UserService userService = context.getBean(UserServiceComponent.class); System.out.println(">>> " + userService.getUser(1L)); User user = new User(2L, "Larry Page"); System.out.println(">>> " + userService.registerUser(user)); } }
目前 REST 协议在 Dubbo 中能够跑在五种不一样的 server 上,分别是:
<dubbo:protocol name="rest" server="netty"/>
来配置<dubbo:protocol name="rest" server="tomcat"/>
来配置<dubbo:protocol name="rest" server="jetty"/>
来配置<dubbo:protocol name="rest" server="sunhttp"/>
来配置,仅推荐在开发环境中使用<dubbo:protocol name="rest" server="servlet"/>
以外,还须要在 web.xml 中作额外的配置因为以上的例子展现了 "netty" 做为 rest server,下面演示一下使用嵌入式 tomcat 的 rest server 的用法。
注:本章节讨论的示例能够经过 https://github.com/beiwei30/dubbo-rest-samples/tree/master/tomcat 来得到
1. 增长 Tomcat 相关的依赖
<dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-logging-juli</artifactId> </dependency>
2. 配置 protocol 使用 tomcat 做为 REST server
<dubbo:protocol name="rest" port="8080" server="tomcat"/>
启动服务提供方以后,在如下的输出将会出现与嵌入式 Tomcat 相关的日志信息:
Jan 01, 2019 10:15:12 PM org.apache.catalina.core.StandardContext setPath WARNING: A context path must either be an empty string or start with a '/' and do not end with a '/'. The path [/] does not meet these criteria and has been changed to [] Jan 01, 2019 10:15:13 PM org.apache.coyote.AbstractProtocol init INFO: Initializing ProtocolHandler ["http-nio-8080"] Jan 01, 2019 10:15:13 PM org.apache.tomcat.util.net.NioSelectorPool getSharedSelector INFO: Using a shared selector for servlet write/read Jan 01, 2019 10:15:13 PM org.apache.catalina.core.StandardService startInternal INFO: Starting service [Tomcat] Jan 01, 2019 10:15:13 PM org.apache.catalina.core.StandardEngine startInternal INFO: Starting Servlet Engine: Apache Tomcat/8.5.31 Jan 01, 2019 10:15:13 PM org.apache.coyote.AbstractProtocol start INFO: Starting ProtocolHandler ["http-nio-8080"]
进一步的,还可使用外部的 servlet 容器来启动 Dubbo 的 REST 服务。
注:本章节讨论的示例能够经过 https://github.com/beiwei30/dubbo-rest-samples/tree/master/servlet 来得到
1. 修改 pom.xml 改变打包方式
由于使用的是外部的 servlet 容器,须要将打包方式修改成 "war"
<packaging>war</packaging>
2. 修改 rest-provider.xml
配置 "server" 为 "servlet" 表示将使用外部的 servlet 容器。并配置 "contextpath" 为 "",缘由是在使用外部 servlet 容器时,Dubbo 的 REST 支持须要知道被托管的 webapp 的 contextpath 是什么。这里咱们计划经过 root context path 来部署应用,因此配置其为 ""。
<dubbo:protocol name="rest" port="8080" server="servlet" contextpath=""/>
3. 配置 WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <context-param> <!-- #1 --> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/classes/spring/rest-provider.xml</param-value> </context-param> <listener> <listener-class>com.alibaba.dubbo.remoting.http.servlet.BootstrapListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <!-- #2 --> <servlet-name>dispatcher</servlet-name> <servlet-class>com.alibaba.dubbo.remoting.http.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/api/*</url-pattern> </servlet-mapping>
这样作以后,再也不须要 RestProvider 来启动 Dubbo 服务,能够将其从工程中删掉。对应的,如今 Dubbo 的服务将会随着 Servlet 容器的启动而启动。启动完毕以后,能够经过相似 "http://localhost:8080/api/users/1" 来访问暴露出的 REST 服务。须要注意的是,这个例子里假定了服务提供方的 WAR 包部署在 root context path 上,因此当该应用经过 IDE 配置的 tomcat server 启动时,须要指定 Application Context 为 "/"。
在上面使用外部 Servlet 容器的例子的基础上,讨论如何暴露 Swagger OpenApi 以及如何继承 Swagger UI。
注:本章节讨论的示例能够经过 https://github.com/beiwei30/dubbo-rest-samples/tree/master/servlet 来得到
1. 暴露 Swagger OpenApi
增长 swagger 相关依赖,以便经过 "http://localhost:8080/openapi.json" 来访问 REST 服务的描述
<properties> <swagger.version>2.0.6</swagger.version> </properties> <dependencies> <dependency> <groupId>io.swagger.core.v3</groupId> <artifactId>swagger-jaxrs2</artifactId> <version>${swagger.version}</version> </dependency> <dependency> <groupId>io.swagger.core.v3</groupId> <artifactId>swagger-jaxrs2-servlet-initializer</artifactId> <version>${swagger.version}</version> </dependency> </dependencies>
修改 WEB-INF/web.xml,增长 openapi servlet 的配置
<web-app> ... <servlet> <!-- #3 --> <servlet-name>openapi</servlet-name> <servlet-class>io.swagger.v3.jaxrs2.integration.OpenApiServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>openapi</servlet-name> <url-pattern>/openapi.json</url-pattern> <url-pattern>/openapi.yaml</url-pattern> </servlet-mapping> </web-app>
从新启动应用以后,能够经过访问 "http://localhost:8080/openapi.json" 或者 "http://localhost:8080/openapi.yaml" 来访问暴露出的 openapi 的契约,如下是 yaml 格式的表述:
openapi: 3.0.1 paths: /api/users/{id}: get: operationId: getUser parameters: - name: id in: path required: true schema: type: integer format: int64 responses: default: description: default response content: application/json: schema: $ref: '#/components/schemas/User' text/xml: schema: $ref: '#/components/schemas/User' /api/users/register: post: operationId: registerUser requestBody: description: a user to register content: application/json: schema: $ref: '#/components/schemas/User' text/xml: schema: $ref: '#/components/schemas/User' responses: default: description: default response content: application/json: schema: type: integer format: int64 text/xml: schema: type: integer format: int64 components: schemas: User: type: object properties: id: type: integer format: int64 name: type: string
2. 集成 Swagger UI
在 pom.xml 中继续增长 swagger-ui 的依赖,这里使用的是 webjars 的版本,从集成的角度来讲更加简洁。webjars 的工做机制能够参阅 webjars 官网 [[5]](#fn5)
<properties> <swagger.webjar.version>3.20.3</swagger.webjar.version> </properties> <dependencies> <dependency> <groupId>org.webjars</groupId> <artifactId>swagger-ui</artifactId> <version>${swagger.webjar.version}</version> </dependency> </dependencies>
在工程的 webapp/WEB-INF 根目录下增长一个 HTML 文件,内容以下。HTML 文件名能够为任何名字,没有硬性要求,若是该文件被命名为 "swagger-ui.html",那么你能够经过访问 “http://localhost:8080/swagger-ui.html" 来访问 swagger UI。本例为了演示方便起见,将其命名为 "index.html",这样当访问 "http://localhost:8080" 时,就能够很方便的获得 swagger UI 的页面。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>API UI</title> <link rel="stylesheet" type="text/css" href="webjars/swagger-ui/3.20.3/swagger-ui.css" > <link rel="icon" type="image/png" href="webjars/swagger-ui/3.20.3/favicon-32x32.png" sizes="32x32" /> <link rel="icon" type="image/png" href="webjars/swagger-ui/3.20.3/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.20.3/swagger-ui-bundle.js"> </script> <script src="webjars/swagger-ui/3.20.3/swagger-ui-standalone-preset.js"> </script> <script> window.onload = function () { window.ui = SwaggerUIBundle({ url: "openapi.json", dom_id: '#swagger-ui', deepLinking: true, presets: [ SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset ], plugins: [ SwaggerUIBundle.plugins.DownloadUrl ], layout: "StandaloneLayout" }); }; </script> </body> </html>
再次重启服务器,并访问 "http://localhost:8080" 时,将会看到 swagger UI 页面的展现:
经过 Swagger UI 能够很方便的浏览当前服务器提供的 REST 服务的文档信息,甚至能够直接调用来作服务测试。以 '/api/users/{id}' 为例,测试结果以下图所示:
本文主要关注了在 Dubbo 中支持 REST 协议的状况。首先探索了 REST 概念的起源,澄清了 REST 是一种适合互联网的软件架构风格,进一步的说明了 REST 风格的架构能够与 HTTP 协议无关,可是 HTTP 协议的确是 REST 风格架构的最经常使用甚至是最佳的组合和搭档。而后讨论了如何在 Dubbo 中开发 REST HTTP 的几种典型用法,其中包括了经过不一样的配置,如传统的 Spring XML,彻底经过 annotation 来配置两种典型的用法,本文中没有涉及到的还有纯 API 编程方式,Spring Boot 配置方式也是彻底能够的,由于篇幅缘由没有说起;还讨论了如何经过不一样的 REST server 来暴露 REST HTTP 服务,包括了 embedded tomcat,netty,以及外置的 servlet 容器等几种用法。最后,在外置的 servlet 容器的基础上,进一步的讨论了如何经过 Swagger 暴露 openAPI 以及集成 Swagger UI 的方法。
本文没有涉及的内容包含但不限于国际化支持、Dubbo REST 更高阶的注入扩展的用法、以及 Dubbo REST 支持将来的规划。其中 Dubbo REST 扩展的支持能够参考 https://github.com/beiwei30/dubbo-rest-samples/tree/master/extensions 中的演示。之后有机会会开专门的篇幅来探讨更高级的 Swagger 的支持、以及对将来的展望。
http://en.wikipedia.org/wiki/Roy_Fielding ↩︎
http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm ↩︎
https://martinfowler.com/articles/richardsonMaturityModel.html ↩︎
https://github.com/dangdangdotcom/dubbox ↩︎
https://www.webjars.org/documentation#servlet3 ↩︎
原文连接 更多技术干货 请关注阿里云云栖社区微信号 :yunqiinsight