sayi.github.com icon indicating copy to clipboard operation
sayi.github.com copied to clipboard

探讨Dubbo与Swagger的集成

Open Sayi opened this issue 6 years ago • 20 comments

GitHub项目地址:https://github.com/Sayi/swagger-dubbo

Dubbo是一种透明化的RPC调用方案和服务治理方案,对外暴露服务接口Provider。Swagger构建了符合Open Api规范的API文档,通过SwaggerUI提供了模拟HTTP请求的工具。

本文将探讨的是Dubbo服务接口文档化,以及如何通过HTTP请求访问服务接口,便于应用在单机接口测试、服务快速验证、扩展服务方式等场景。

Dubbo服务接口和Http请求映射

服务接口与HTTP的映射,涉及到请求URL、HTTP方法、参数。

请求URL

一个请求URL需要唯一确定一个接口和一个方法,我们先看看一个接口的定义:

package com.deepoove.swagger.dubbo.example.api.service;

import java.util.List;
import com.deepoove.swagger.dubbo.example.api.pojo.User;

public interface UserService {

    List<User> query(String phone);

    List<User> query(int areaCode);

    User get(String id);

    void save(User user);

    User update(User user);

    void delete(String id);

}

我们采用interfaceClass/method来生成地址(当然也必须是interfaceClass,谁会希望在URL看到你的实现类类名)。即访问地址为http://ip:port/context/com.deepoove.swagger.dubbo.example.api.service.UserService/get,但是这种规则无法唯一确定一个重载的方法,比如代码中query方法。解决方案可能有下面两种:

  1. 在http request中附带参数个数、参数类型等信息。
  2. 对重载的方法设法指定一个别名,将请求地址映射为com.xxx.XxService/method/aliasName

方案1的优点是没有变更请求地址,缺点是对请求的信息做了附加,限制了请求的自由性。方案2的优缺点恰恰和方案1相反。

最终,我选择了方案2,我们需要找到一种生成别名的方法。本来考虑过通过参数个数和类型生成别名,但是我希望别名是可记忆性的,目前的方案是依赖swagger,在重载方法加上注解@ApiOperation,设置其nickname作为方法别名,这一步对重载方法来说,目前是必须的。代码可能变成了这样:

    @ApiOperation(nickname = "byPhone", value = "查询用户", notes = "通过phone取用户信息")
    List<User> query(String phone);

    @ApiOperation(nickname = "byArea", value = "查询用户", notes = "通过城市地区取用户信息")
    List<User> query(int areaCode);

HTTP方法

默认所有请求的方法都是POST,除非通过@ApiOperation指定了特别的httpMethod。对于不符合请求方法的HTTP调用,服务器都将返回404。

参数

参数的映射是个很复杂的问题,Spring在参数类型转化时做了很多事。http请求参数的值默认都是string的,如果接口参数恰恰是string类型,这样的映射没有问题。但是对于我们来说,有以下两点需要另处理:

  1. 参数类型为原生类型,参数值为string 我们需要将string类型的参数值转化为原生类型的值。

  2. 参数类型为JavaBean 第一次我会想到用request body去容纳JavaBean,但是当参数为多个JavaBean时,将多个参数值组合容纳在body中会导致无法理解参数的含义(body只有一个)。 如果我们将JavaBean拆解成field作为参数,这样可能会让参数个数泛滥。 最终我们把JavaBean类型的参数定义成表单参数,类型为json string,即将JavaBean序列化成json字符串作为参数。这样就解决了多个JavaBean作为参数的问题,尽管这一方案在模拟参数值时是复杂的。

  3. 参数类型为原生类型,参数值为null Spring不允许null赋值给原生类型的,因为我们不知道null代表原生类型的什么值。所以对于原生类型的参数,都应该为必填。

Swagger扫描Dubbo服务

定义好了映射关系,我们就可以让Swagger扫描Dubbo服务了。有以下几点需要做:

  1. 通过Spring上下文获取到所有注册的Dubbo服务接口和实现类
  2. 根据interfaceClass/method/{aliasName}规则,生成服务URL
  3. 根据方法参数生成请求参数

这里有一个问题,就是Swagger的文档注解是写在接口上,还是实现类上?

个人认为在一个公司内部,可以写在接口上,方便公司内部阅读。如果是对外使用,强烈不建议污染服务接口,应该写在实现类上。swagger-dubbo没有限制注解的位置,既可以写在接口上,也可以写在实现类上。

Swagger和HTTP模拟调用

HTTP模拟调用和Swagger两者之间本身不该有任何依赖和关联。 上文中提到的别名和请求方法是http模拟调用与Swagger的唯一依赖关系,如果不考虑自定义请求方法和重载方法的别名,可以不写任何swagger注解,就可以模拟HTTP调用,因为写swagger注解应该只是为了文档,而不该越权去定义HTTP请求。

但是,现在这些唯一的依赖关系,可能是最简单的实现。

Sayi avatar Sep 05 '17 09:09 Sayi

正好需要,非常感谢作者开源

sakunoo avatar Sep 06 '17 05:09 sakunoo

@sakunon 目前还是alpha版本,欢迎共同来完善。-:)

Sayi avatar Sep 06 '17 06:09 Sayi

作者有没有讨论群?可以一起交流一下

ZoonChen avatar Apr 23 '18 01:04 ZoonChen

HttpMatch 中的 findRefMethods(Method[] interfaceMethods, String operationId,String requestMethod) 方法 Collections.sort(ret, new Comparator<Method>() { @Override public int compare(Method o1, Method o2) { return o2.getParameterCount() - o1.getParameterCount(); } }); getParameterCount()方法 没有定义 可不可以提交fix一下这个问题

ZoonChen avatar Apr 23 '18 01:04 ZoonChen

@ZoonChen 基于JDK8开发的

Sayi avatar Apr 23 '18 02:04 Sayi

能否支持springfox

hfq-yangyang-yangyang avatar May 18 '18 06:05 hfq-yangyang-yangyang

用的 docker swaggerapi/swagger-ui 输入input框 http://10.10.11.180:12102/swagger-dubbo/swagger.json 点击 explore按钮。 发现一个不能跨域的问题, 加了 @CrossOrigin(origins = "*") 问题解决

@Controller @CrossOrigin(origins = "*") @RequestMapping("${swagger.dubbo.doc:swagger-dubbo}") @Api(hidden = true) public class SwaggerDubboController {

hfq-yangyang-yangyang avatar May 18 '18 07:05 hfq-yangyang-yangyang

跨域解决后就是下面问题

https://github.com/Sayi/swagger-dubbo/issues/13

hfq-yangyang-yangyang avatar May 18 '18 07:05 hfq-yangyang-yangyang

swagger-dubbo V2.0.1 更新日志:

  • 支持最新的dubbo注解方式(@dubbocomponentscan)的服务发现
  • 修复某些情况下Swagger数据以JSON格式暴露,而不是String
  • Spring boot 示例
  • 跨域支持的一种解决方案
  • 修复若干BUG

Sayi avatar Jun 06 '18 17:06 Sayi

你好,支持dubbo2.8.4吗?我这边启动报错~

Redick01 avatar Jul 04 '18 03:07 Redick01

您好,说下我这变报错的具体信息吧,是创建DubboPropertyConfig这个bean的时候报错,大概是DubboPropertyConfig注入ServletContext的时候找不到要注入的Class,我这边是使用的dubbo2.8.4,并且使用的是tomcat插件通过dubbo发布http服务。

Redick01 avatar Jul 04 '18 04:07 Redick01

@Redick01 目前官方dubbo最新版本是2.6.2。你的项目应该缺少这个pom:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.0.1</version>
</dependency>

Sayi avatar Jul 04 '18 04:07 Sayi

很高兴您能回复我,我在这个之前也试过添加这个依赖包,依然报错。以下是我报错的具体信息,麻烦您抽空看下~ Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dubboPropertyConfig': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private javax.servlet.ServletContext com.deepoove.swagger.dubbo.config.DubboPropertyConfig.servletContext; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [javax.servlet.ServletContext] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1210) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:755) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480) at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:139) at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:93) at com.ruubypay.MainClass.main(MainClass.java:27) Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private javax.servlet.ServletContext com.deepoove.swagger.dubbo.config.DubboPropertyConfig.servletContext; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [javax.servlet.ServletContext] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:561) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331) ... 13 more Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [javax.servlet.ServletContext] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1301) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1047) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533) ... 15 more

Redick01 avatar Jul 04 '18 04:07 Redick01

@Redick01 你是用Main方法启动的,缺少web容器。

HTTP模拟请求需要Web容器。

Sayi avatar Jul 05 '18 15:07 Sayi

我有个疑问,我目前很多项目上都有dubbo服务,那么我现在想把这些dubbo服务都统一放到swagger-dubbo这个webapp中进行统一展现,这该如何优雅的实现

xujunhua555 avatar Jul 10 '18 14:07 xujunhua555

@xujunhua555 我的想法是通过Zookeeper去发现这些服务,然后通过swagger-dubbo暴露API文档。

Sayi avatar Jul 11 '18 01:07 Sayi

您好! 是的,这是个办法,但是目前你github上的代码还没实现这样的需求吧?能否加个qq探讨下呢?我qq1139273516 我也是杭州上班的

原始邮件 发件人:卅一[email protected] 收件人:Sayi/[email protected] 抄送:[email protected]; [email protected] 发送时间:2018年7月11日(周三) 09:42 主题:Re: [Sayi/sayi.github.com] 探讨Dubbo与Swagger的集成 (#15)

@xujunhua555 我的想法是通过Zookeeper去发现这些服务,然后通过swagger-dubbo暴露API文档。 — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

xujunhua555 avatar Jul 11 '18 01:07 xujunhua555

在使用dubbo2.5.3(公司硬性要求)和Spring boot整合时,发现ReferenceManager中从ServiceBean时,取不到,有遇到这种情况吗

kclizhiwei avatar Aug 20 '18 09:08 kclizhiwei

为什么http://localhost:8080/项目名称/swagger-dubbo/swagger.json 这个接口返回的是404,地址错了吗 这个是swagger-dubbo.properties的配置 swagger.dubbo.doc=swagger-dubbo swagger.dubbo.http=h

swagger.dubbo.application.version = 2.0 swagger.dubbo.application.groupId = com.myhexin.yyzt swagger.dubbo.application.artifactId = yyzt-cbas-api

#rpc or local swagger.dubbo.cluster = rpc

swagger.dubbo.enable = true

@Sayi

finduuu avatar Sep 27 '18 07:09 finduuu

Java零注入 DUBBO+SWAGGER纯前端集成方案,只要会写OAS2,就能消费服务

TheNorthMemory avatar Oct 30 '18 13:10 TheNorthMemory