如何打造一个Dubbo网关--文档插件

文档做为前后端联调的核心是不可或缺的,一个好的文档能降低大量沟通成本

对于Swagger我们也使用过好几年了,调试还可以但是做为联调文档仍然不是那么方便。所以把目标放在了每个Javaer都必须写的一样东西上了:方法注释

对!我们要通过接口的注释来生成API文档,看上去和javadoc有点像,思路也确实来自于javadoc,但是有以下几个问题:

  1. 不支持自定义的注解,如上文提到过的@WhitelistInjection
  2. 对于展示:方法和类在一起,方法和引用的实体是分开的,查看很麻烦
  3. 一般API文档的大致包括这几个:接口说明、入参列表、返回列表。不能说没有,只能说很分散
  4. 生成的文件再次处理很麻烦,展示可以,不能提供给网关使用

本篇内容主要讲述JDTMaven插件。以下代码片段全部来自于plume

注释信息足够吗

一般来说文档里只要有两部分即可:接口有什么用和字段有什么用。这两部分都可以简单通过方法注释和字段注释获取到

类和方法信息信息生成

图1的类目时Java的类,子类目时选择的类中方法

实体的注释信息生成

  1. 通常类注释的描述可能会很长,这部分对于后端来说是必要的,但是对前端是无所谓,所以添加了一个自定义标签@title
  2. 对于类和方法:在有@title标签时文档上使用这个标签;当没有时使用描述信息
  3. 对于字段:如果方法上有@param标签,则标签内容放于文档的说明,字段注释描述放于文档的详情;否则字段注释描述放于文档的说明

下面是一个Java代码示例,生成的文档信息如上图所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 主要用于提供基础的详细服务
* 使用方式如下......
* 不提供对外调用
*
* @author Dash
* @title 消息相关接口
*/
public interface MessageFacade {
/**
* 消息列表根据消息类型
*/
ListResult<MessageSimpleDTO> messageList(PageEntry page);
}

......

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class PageEntry extends BaseEntry {
/**
* 每页大小
*/
private Integer pageSize = 10;

/**
* 要获取的页数
*/
private Integer pageNum = 1;

/**
* 排序方式,1正序,-1倒序
*/
private Integer sortBy = 1;
}

JDT和访问者

JDT是由eclipse开源的静态代码分析工具,可以从Java源码生成一颗AST

JDT主要包含三个部分:

  • ASTParser:AST分析器,接收一段Java代码,输出ASTNode
  • ASTNode:AST节点,解析后的数据都保存在节点;节点有多种类型,可以通过ASTView查看
  • ASTVisitor:专门用来遍历ASTNode的访问者,使用者自己编写,这里使用了访问者模式

我们可以从AST中获取所有需要的信息:导入、方法名、参数名、字段、注解、注释等。使用流程如下:

1
2
3
4
5
final ASTParser parser = ASTParser.newParser(AST.JLS8);
parser.setKind(ASTParser.K_COMPILATION_UNIT);
parser.setSource(StandardCharsets.UTF_8.decode(ByteBuffer.wrap(Files.readAllBytes(one))).array());
CompilationUnit comp = (CompilationUnit) (parser.createAST(null));
comp.accept(visitor);

目前只需要使用AST.JLS8=8,支持JDK8及以下。kind是指需解析的代码的类型,包括如下几种

  • K_COMPILATION_UNIT:默认。包含import、package及声明的类信息
  • K_CLASS_BODY_DECLARATIONS:相比上面,只有声明的类信息
  • K_EXPRESSION:输入会被解析成一个表达式。如果存在非表达式定义,会报错
  • K_STATEMENTS:输入会被解析成一个语句块序列

CompilationUnit是解析Java类(kind=K_COMPILATION_UNIT)后的入口,也就是AST的根节点

Visitor也是一种常见的设计模式,是通过编程语言的动态分派来实现的。表现就是:你可以将一组重载的方法放在一个Visitor类里,而无需去继承子类重写方法。如果想了解更多可以搜索一下双分派

Maven插件之Mojo

具体如何创建一个Maven插件项目,网上有很多这里就不赘述了。我们只讨论下Mojo

Mojo在Maven的官方定义里就是一个简单对象,从名字也能看出来:就是将Pojo里的plain换成了maven。既然是简单对象,那么一个插件当然可以定义多个了

任何自定义的Mojo都需要继承AbstractMojo,并使用@Mojo标注,这个注解有一个name字段就是运行时的goal。此插件定义为name=parse,并且只要在package之前的阶段都可以;因为必须在打包前生成好文件,否则jar内没有生成的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
@Mojo(name = "parse", defaultPhase = LifecyclePhase.PROCESS_SOURCES)
public class ParseMojo extends AbstractMojo {
private Log log = getLog();

......

@Override
public void execute() throws MojoExecutionException {

......

}
}

插件参数定义

如果想传递参数给插件,需要通过@Parameter来标注Mojo里的字段,下面给出几个示例

  • property:使用插件时configuration标签里的子标签名,也就是配置名
  • required:是否必须,一般给定默认值时可以忽略
  • defaultValue:默认值,可以是字符串,也可以pom里可以使用的变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Parameter(property = "sourceDir", required = true, defaultValue = "${project.build.sourceDirectory}")
private String sourceDirectory;

@Parameter(property = "targetDir", defaultValue = "${project.build.directory}")
private String targetDirectory;

@Parameter(property = "systemId", required = true)
private String systemId;

@Parameter(property = "systemName", required = true)
private String systemName;

@Parameter(property = "packageName", required = true)
private String packageName;

@Parameter(property = "makeJson", defaultValue = "false")
private Boolean makeJson;
  • sourceDir:源码目录,JDT要遍历的目录
  • targetDir:生成文件存放的目录
  • systemId:我们系统自己定义的数字id,因为在使用Apollo配置中心
  • systemName:invokePath的第一个值,每个系统都应该不同
  • packageName:Java的包名对应了路径,其实就是只会解析设置的目录及其子目录
  • makeJson:是否以json格式存储生成后的文件,用于调试。如果是会跳过对文件的压缩

最终我们会完成这样一个插件,在pom里的基本配置如下,这里手动将其绑定在compile阶段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<build>
<plugins>
<plugin>
<groupId>net.dloud</groupId>
<artifactId>platform-maven-plugin</artifactId>
<configuration>
<systemId>110000</systemId>
<systemName>message</systemName>
<packageName>net.dloud.message</packageName>
<makeJson>false</makeJson>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>parse</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

思路总结

  1. 在所有的client-jar的pom里添加platform-maven-plugin插件
  2. 编译时插件遍历client-jar源码里所有符合条件的目录,找到源文件后通过JDT生成解析后的数据
  3. 插件对解析后的数据进行压缩存储,并放于target根目录,跟随jar包
  4. 项目在启动后读取生成后的数据,并通过某些方法(某个项目)存入数据库供随后网关和文档的使用
  5. 有一个项目专门读取存储后的数据,并解析成文档格式,展示在页面上供前端使用

如何打造一个Dubbo网关--文档插件
https://back.pub/post/hh-code-dubbo-gateway-4/
作者
Dash
发布于
2018年11月17日
许可协议