https://github.com/zhaojigang/dubbo-springfox
- 一、dubbo-springfox特性簡介
- 二、dubbo-springfox入門案例
- 三、dubbo-springfox基本原理
- 附1、dubbo-springfox注解附表
- 附2、Q&A
一、dubbo-springfox特性簡介
dubbo-springfox的兩個主要特點:
(1)動態文檔
(2)dubbo接口的圖形化調用
1.1 動態文檔
dubbo-springfox基于SpringFox進行開發,將文檔以注解的形式優雅的集成到代碼中;而且,當接口參數發生變化時,也會在UI界面上動態的進行同步。
1.2 dubbo接口的圖形化調用
dubbo-springfox在服務啟動的時候將<dubbo:service>
接口掃描出來,并且動態生成對應的Http接口,并且以漂亮的格式展示在一個UI界面上(包括文檔說明),開發人員與測試人員可以非常方便的進行接口測試。
說明:由于dubbo-springfox是基于SpringFox開發的,所以也天然的支持SpringMVC接口文檔的動態生成與圖形化調用。
二、dubbo-springfox入門案例
項目以maven多模塊形式進行組織:
- dubbo-springfox-core是核心代碼,提供jar包,供使用;
- dubbo-springfox-demo-springboot提供了在使用springboot的情況下怎樣使用dubbo-springfox;
- dubbo-springfox-demo-springmvc提供了在使用springmvc的情況下怎樣使用dubbo-springfox。
以dubbo-springfox-demo-springboot為例來看dubbo-springfox的使用姿勢。
2.1 構建增強版的springfox到nexus或者本地maven倉庫
dubbo-springfox基于增強版的springfox:https://github.com/zhaojigang/springfox。所以需要首先編譯發布增強版的springfox(注意:需要將版本改為6.6.6)
增強版的springfox,相較于原本的springfox:
- 添加了自定義注解@RequestModel(用來打破@RequestBody單體限制)
- 讓springfox內核去支持@RequestModel注解
- 獲取swagger-ui的位置改成了:https://github.com/zhaojigang/swagger-ui,增強版的swagger-ui支持@RequestModel注解的參數解析和構造,支持axios進行ajax調用
可以從 這里 直接獲取的springfox的相關jar包,之后上傳到nexus或者本地maven倉庫即可。
2.2 引入dubbo-springfox jar包
<dependency>
<groupId>io.hulk</groupId>
<artifactId>dubbo-springfox-core</artifactId>
<version>1.0.0</version>
</dependency>
2.3 在啟動類中添加啟動注解與掃描范圍
@SpringBootApplication
@EnableSwagger2
@ImportResource({ "classpath:*.xml" })
@ComponentScan({ "io.hulk.dubbo.springfox.demo.springboot", "io.hulk.dubbo.springfox.core" })
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
說明:
- 添加了
@EnableSwagger2
注解:用于啟動Swagger。 - 在@ComponentScan注解中,添加
"io.hulk.dubbo.springfox.core"
,掃描dubbo-springfox中的相關Bean。(在實際開發中,springboot的啟動類會放在基包下,基包通常會命名為groupId.artifactId,當然,其中的中劃線會去掉;此時,默認@ComponentScan會掃描基包下的所有類,所以不加入@ComponentScan也行,但是此處由于需要掃描dubbo-springfox中的相關Bean,所以這里的@ComponentScan需要掃描兩個包:基包 +"io.hulk.dubbo.springfox.core"
)
對于如果只是想進行在UI界面上調用dubbo和springmvc接口的需要來講,到這里就ok了!!!就是這么簡單。
使用方式總結:引入一個jar包 + 添加一個啟動注解 + 添加一個掃描范圍注解
看一下dubbo-springfox-demo-springboot類組織圖:
說明:
- BookService:Dubbo服務接口;在該接口中,提供了在如下情景下的dubbo-springfox測試用例;
- 測試基礎類型、對象類型、默認值設置、是否必須
- 測試一層泛型
- 測試嵌套泛型
- 測試Map
- 測試方法重載
- 測試入參和返回值為空
- 測試Long型
- 測試接口拋異常
- BookServiceImpl:BookService的實現類;
- UserController:controller接口;
- Book和User:兩個基本的model類。
此處為了簡便,僅以BookService中的一個接口為例來看Dubbo接口的圖像化調用,以UserController中的一個方法為例來看springmvc方法的圖形化調用。
BookService
public interface BookService {
String testGenericField(List<Book> books);
}
BookServiceImpl
public class BookServiceImpl implements BookService {
@Override
public String testGenericField(List<Book> books) {
if (!CollectionUtils.isEmpty(books)) {
return books.get(0).getTitle();
}
return "書籍列表為空";
}
}
Book
public class Book {
private Long id;
private String title;
private String content;
...省略setter和getter
}
UserController
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/testCommonField2", method = RequestMethod.POST)
public String testCommonField2(@RequestParam("name") String name,
@RequestBody Map<String, Book> bookMap) {
return name;
}
}
啟動服務后,訪問:本機ip:port/swagger-ui.html。就可以看到下圖(這里只看BookService的,UserController的同理):
2.3 接口文檔
上述只是完成了接口的圖形化調用功能,下面來看一下接口文檔的生成。
BookService
@Api("Book相關的Dubbo服務")
public interface BookService {
@ApiOperation("測試一層泛型")
String testGenericField(@ApiParam List<Book> books);
}
說明:
- 在接口上添加注解@Api描述該接口作用;
- 在方法上添加注解@ApiOperation描述該接口作用;
- 在參數上添加@ApiParam對參數進行描述;
BookServiceImpl
public class BookServiceImpl implements BookService {
@Override
public String testGenericField(List<Book> books) {
if (!CollectionUtils.isEmpty(books)) {
return books.get(0).getTitle();
}
return "書籍列表為空";
}
}
Book
@ApiModel("書籍")
public class Book {
@ApiModelProperty("ID")
private Long id;
@ApiModelProperty("書標題")
private String title;
@ApiModelProperty("書的內容")
private String content;
...省略setter和getter
}
說明:
- 在模型上添加注解@ApiModel描述該模型作用;
- 在屬性上添加注解@ApiModelProperty描述該屬性作用。
UserController
@Api(tags = "user相關api")
@RestController
@RequestMapping("/user")
public class UserController {
@ApiOperation("普通2")
@RequestMapping(value = "/testCommonField2", method = RequestMethod.POST)
public String testCommonField2(@RequestParam("name") String name,
@RequestBody Map<String, Book> bookMap) {
return name;
}
}
注意:
- 以上的注解都不強制添加,根據需求自行添加。注解相互之間并無影響。
- Dubbo服務的注解只能添加在接口上,不支持添加在實現上:因為對外的model通常會寫在xxx-api中,所以在接口上添加注解是必須支持的;其次接口上添加注解描述,也可以替代javadoc。
- dubbo-springfox支持的注解附表見附錄
之后再啟動服務看一下UI圖:
其中Long型為什么展示為int64而非Long,這與js對Long型的是否支持有關系,有興趣的同學可以自行谷歌。
三、dubbo-springfox基本原理
dubbo-springfox基于SpringFox實現,SpringFox只實現了對springmvc方法的掃描;dubbo-springfox在此基礎上增加了對Dubbo接口的掃描。為了最小化的實現,只需要在服務啟動的時候動態的生成Dubbo接口對應的controller,之后由SpringFox的內核去統一掃描所有的controller即可。
關于SpringFox和Swagger,可自行去谷歌。
流程圖如下所示:
3.1 dubbo-springfox掃描接口的時機
編寫一個流程主控類:ApiDocumentBootstrap
@Configuration
public class ApiDocumentBootstrap implements ApplicationContextAware {
private static final Logger LOGGER = LoggerFactory.getLogger(ApiDocumentBootstrap.class);
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
try {
/** 1 獲取dubboXmlApi掃描器 */
final ApiScanner<ApplicationContext, Map<String, ServiceBean>> dubboXmlApiScanner = new XmlDubboApiScanner();
/** 2 使用dubboXmlApi掃描器從ApplicationContext中獲取ServiceBean(dubbo:service配置)*/
final Map<String, ServiceBean> serviceBeanMap = dubboXmlApiScanner.scanFromSpringContext(context);
/** 3 獲取JdkCompiler實例 */
final JdkCompiler jdkCompiler = new JdkCompiler();
String serviceGroup = "";
for (ServiceBean sb : serviceBeanMap.values()) {
/** 4 根據ServiceBean的相關信息生成dubbo:service對應的controller類字符串 */
final String code = ClassCodeGenerator.generateClassCode(sb);
/** 5 使用JdkCompiler實例編譯加載controller類字符串為Class對象 */
final Class<?> dubboControllerClazz = jdkCompiler.compile(code,
ApiDocumentBootstrap.class.getClassLoader());
/** 6 將生成的controller類Class對象注冊到BeanDefinitionRegistry中,接下來供mvc掃描器進行掃描 */
SpringHelper.registerBeanDefinition(dubboControllerClazz, context);
serviceGroup = serviceGroup == "" ? sb.getGroup() : serviceGroup;
}
/**
* 7 設置系統屬性
*/
System.setProperty(DubboSpringfoxContants.SERVICE_GROUP, serviceGroup == null ? "" : serviceGroup);
} catch (Exception e) {
LOGGER.error("dubbo-springfox generate api-document error, msg:", e);
}
}
}
該類實現了ApplicationContextAware的setApplicationContext(ApplicationContext context)方法,該方法實現流程圖中的所有步驟。setApplicationContext(ApplicationContext context)方法發生在Spring容器創建后 + springmvc容器掃描controller之前 + 可以獲取到ApplicationContext;
而以xml形式進行配置的Dubbo的每一個服務接口都會生成一個ServiceBean,最后放置在ApplicationContext中;這樣,我們就可以通過在setApplicationContext(ApplicationContext context)中獲取到的ApplicationContext中去獲取所有的ServiceBean;
3.2 獲取所有的ServiceBean
定義掃描器接口:方便后需擴展,掃描注解形式的Dubbo服務等。
public interface ApiScanner<T, R> {
R scanFromSpringContext(T t);
}
Xml形式的掃描器:
public class XmlDubboApiScanner implements ApiScanner<ApplicationContext, Map<String, ServiceBean>> {
@Override
public Map<String, ServiceBean> scanFromSpringContext(ApplicationContext context) {
if (context == null) {
return new HashMap<>(0);
}
return context.getBeansOfType(ServiceBean.class);
}
}
從ApplicationContext中獲取所有類型是ServiceBean的Bean。
3.3 創建JdkCompiler編譯器
JdkCompiler繼承于com.alibaba.dubbo.common.compiler.support.AbstractCompiler,Dubbo為該抽象類提供了兩個實現:com.alibaba.dubbo.common.compiler.support.JavassistCompiler和com.alibaba.dubbo.common.compiler.support.JdkCompiler。前者不具有通用性,與Dubbo中構造動態類的寫法強耦合;后者在編譯參數中沒有打印本地變量表,導致無法直接獲取方法參數名。
dubbo-springfox的JdkCompiler與com.alibaba.dubbo.common.compiler.support.JdkCompiler代碼幾乎相同,只是添加了編譯參數"-g",打印本地變量表,后續就可以獲取方法參數名。不使用Javassist的原因是因為Javassist要逐行進行構造代碼或者像com.alibaba.dubbo.common.compiler.support.JavassistCompiler那樣將構造好的完整代碼進行拆解構造,比較麻煩。
具體代碼見:
io.hulk.dubbo.springfox.core.compiler.JdkCompiler
3.4 使用ClassCodeGenerator生成DubboService的controller代碼
public class ClassCodeGenerator {
public static String generateClassCode(ServiceBean sb) throws NoSuchMethodException, ClassNotFoundException {
StringBuilder codeBuilder = new StringBuilder();
/**
* 1 獲取接口類和實現類
*/
final Class<?> dubboInterface = sb.getInterfaceClass();
final Class<?> rawDubboInterfaceImpl = sb.getRef().getClass();
Class<?> dubboInterfaceImpl = rawDubboInterfaceImpl;
String dubboInterfaceImplCanonicalName = rawDubboInterfaceImpl.getCanonicalName();
// 保護性判斷:防止dubbo接口實現類被代理,這里轉換為真實類
if (dubboInterfaceImplCanonicalName.contains("$$")) {
dubboInterfaceImpl = ClassLoader.getSystemClassLoader()
.loadClass(dubboInterfaceImplCanonicalName.substring(0, dubboInterfaceImplCanonicalName.indexOf("$$")));
}
/**
* 2 獲取包名
*/
final String packageName = dubboInterfaceImpl.getPackage().getName();
// 保護性措施:包名尾部添加DubboApi,防止被意外AOP
codeBuilder.append("package ").append(packageName).append("DubboApi;\n");
/**
* 4 獲取重組@Api注解
*/
final Api api = dubboInterface.getAnnotation(Api.class);
if (api != null) {
String apiTag = api.value() != "" ? api.value() : api.tags()[0];
codeBuilder.append("@io.swagger.annotations.Api(tags = \"" + apiTag + "\")").append("\n");
}
/**
* 5 創建@RestController @RequestMapping注解
*/
codeBuilder.append("@org.springframework.web.bind.annotation.RestController\n")
.append("@org.springframework.web.bind.annotation.RequestMapping(\"/dubbo-api\")\n");
/**
* 6 創建類定義
* public class BookServiceImplDubboApi
*/
String classDefinition = dubboInterfaceImpl.toGenericString();
codeBuilder.append(classDefinition.substring(0, classDefinition.lastIndexOf(" ") + 1))
.append(dubboInterfaceImpl.getSimpleName()).append("DubboApi {\n");
/**
* 7 注入實現類
*/
String dubboInterfaceImplName = dubboInterfaceImpl.getSimpleName().substring(0, 1).toLowerCase()
+ dubboInterfaceImpl.getSimpleName().substring(1);
codeBuilder.append("@org.springframework.beans.factory.annotation.Autowired\n").append("private ")
.append(dubboInterfaceImpl.getCanonicalName()).append(" ").append(dubboInterfaceImplName).append(";\n");
/**
* 8 獲取全部方法
*/
for (Method method : dubboInterface.getMethods()) {
/**
* 8.1 獲取重組ApiOperation注解
*/
final ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
if (apiOperation != null) {
codeBuilder.append("@io.swagger.annotations.ApiOperation(value = \"" + apiOperation.value() + "\")\n");
}
/**
* 8.2 創建@RequestMapping注解
* 支持多版本
* 支持方法重寫
*/
String serviceVersion = sb.getVersion() != null && sb.getVersion().length() > 0 ? sb.getVersion() : "0.0.0";
codeBuilder.append("@org.springframework.web.bind.annotation.RequestMapping(value = \"/" + serviceVersion
+ "/" + dubboInterfaceImpl.getCanonicalName() + "/" + method.getName());
if (apiOperation != null && apiOperation.nickname().trim().length() > 0) {
codeBuilder.append("/").append(apiOperation.nickname().trim());
}
codeBuilder.append("\", method = org.springframework.web.bind.annotation.RequestMethod.POST)\n");
/**
* 8.3 創建方法定義
*/
codeBuilder.append("public ").append(method.getGenericReturnType().getTypeName()).append(" ")
.append(method.getName()).append("(");
/**
* 8.4 組建方法參數
*/
final LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
final String[] parameterNames = parameterNameDiscoverer
.getParameterNames(dubboInterfaceImpl.getMethod(method.getName(), method.getParameterTypes()));
final Parameter[] parameters = method.getParameters();
StringBuilder parameterBuilder = new StringBuilder();
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
String parameterName = parameterNames[i];
final ApiParam apiParam = parameter.getAnnotation(ApiParam.class);
if (apiParam != null) {
parameterBuilder.append("@io.swagger.annotations.ApiParam(value = \"").append(apiParam.value())
.append("\"").append(", required = ").append(apiParam.required()).append(", defaultValue = \"")
.append(apiParam.defaultValue()).append("\") ");
}
if (PrimitiveTypeHelper.primitive(parameter.getType())) {
parameterBuilder.append("@org.springframework.web.bind.annotation.RequestParam(value = \"")
.append(parameterName).append("\"");
if (apiParam == null || apiParam.required() == false) {
parameterBuilder.append(", required = false");
} else {
parameterBuilder.append(", required = true");
}
parameterBuilder.append(")");
} else {
parameterBuilder.append("@springfox.documentation.spi.annotations.RequestModel(value = \"")
.append(parameterName).append("\"");
if (apiParam == null || apiParam.required() == false) {
parameterBuilder.append(", required = false");
} else {
parameterBuilder.append(", required = true");
}
parameterBuilder.append(")");
}
parameterBuilder.append(parameter.getParameterizedType().getTypeName()).append(" ");
parameterBuilder.append(parameterName).append(",");
}
if (parameterBuilder != null && parameterBuilder.length() > 0) {
codeBuilder.append(parameterBuilder.substring(0, parameterBuilder.length() - 1));
}
String parameterNamesStr = "";
if (parameterNames != null && parameterNames.length > 0) {
parameterNamesStr = String.join(", ", parameterNames);
}
/**
* 8.5 組建方法異常標識
*/
codeBuilder.append(") ");
final Class<?>[] exceptionTypes = method.getExceptionTypes();
if (exceptionTypes != null && exceptionTypes.length > 0) {
codeBuilder.append("throws ");
for (int i = 0; i < exceptionTypes.length - 1; i++) {
codeBuilder.append(exceptionTypes[i].getCanonicalName()).append(",");
}
codeBuilder.append(exceptionTypes[exceptionTypes.length - 1].getCanonicalName());
}
/**
* 8.6 組建方法內容
*/
codeBuilder.append(" {");
if (!method.getGenericReturnType().getTypeName().equalsIgnoreCase("void")) {
codeBuilder.append("return ");
}
codeBuilder.append(dubboInterfaceImplName).append(".").append(method.getName()).append("(")
.append(parameterNamesStr != "" ? parameterNamesStr : "").append(");");
codeBuilder.append("}");
}
/**
* 9 定義類結尾
*/
codeBuilder.append("}");
return codeBuilder.toString();
}
}
代碼較為簡單,就是使用反射獲取各種類信息、方法信息、注解信息等,最后進行拼接。
假設Dubbo服務代碼如下:
package io.hulk.dubbo.springfox.demo.springboot.api;
...import省略
@Api("Book相關的Dubbo服務")
public interface BookService {
@ApiOperation("測試一層泛型")
String testGenericField(@ApiParam List<Book> books);
}
其實現如下:
package io.hulk.dubbo.springfox.demo.springboot.apiimpl;
...import省略
public class BookServiceImpl implements BookService {
@Override
public String testGenericField(List<Book> books) {
if (!CollectionUtils.isEmpty(books)) {
return books.get(0).getTitle();
}
return "書籍列表為空";
}
}
最后生成的Controller代碼:
package io.hulk.dubbo.springfox.demo.springboot.apiimplDubboApi;
@io.swagger.annotations.Api(tags = "Book相關的Dubbo服務")
@org.springframework.web.bind.annotation.RestController
@org.springframework.web.bind.annotation.RequestMapping("/dubbo-api")
public class BookServiceImplDubboApi {
@org.springframework.beans.factory.annotation.Autowired
private io.hulk.dubbo.springfox.demo.springboot.apiimpl.BookServiceImpl bookServiceImpl;
@io.swagger.annotations.ApiOperation(value = "測試一層泛型")
@org.springframework.web.bind.annotation.RequestMapping(value = "/0.0.0/io.hulk.dubbo.springfox.demo.springboot.apiimpl.BookServiceImpl/testGenericField", method = org.springframework.web.bind.annotation.RequestMethod.POST)
public java.lang.String testGenericField(@io.swagger.annotations.ApiParam(value = "", required = false, defaultValue = "") @springfox.documentation.spi.annotations.RequestModel(value = "books", required = false) java.util.List<io.hulk.dubbo.springfox.demo.springboot.model.Book> books) {
return bookServiceImpl.testGenericField(books);
}
}
說明:
- 包名:Dubbo服務實現類的包名+"DubboApi"后綴;
- 類注解:添加Dubbo接口上配置的@Api注解 + @RestController + 類上下文匹配路徑("
/dubbo-api
"); - 類:Dubbo服務實現類的類名+"DubboApi"后綴;
- 注入Dubbo服務實現類,之后的實現都通過該實現類進行調用;
- 方法注解:添加Dubbo接口上配置的@ApiOperation注解 + 方法匹配路徑("
/{Dubbo服務版本號}/{Dubbo服務實現類的全類名}/{方法名}
");- 對于重載的方法,方法注解需要添加nickname tag進行區分,具體見io.hulk.dubbo.springfox.demo.springboot.api.BookService。
- 方法:與Dubbo服務方法相同;
- 方法參數:添加@ApiParam注解 + 相關的參數接收注解;
- 參數接收注解:對于普通類型的參數,使用@RequestParam;對于對象類型的參數,使用@RequestModel,該注解是自定義的一個注解,用于打破@RequestBody的單體限制,關于單體限制與自定義mvc參數注解的方式見作者的另一篇文章《自定義spring參數注解 - 打破@RequestBody單體限制》
- 方法體:調用注入的Dubbo服務實現類調用同名方法。
3.5 使用JdkCompiler編譯生成的controller并使用classLoader加載其到JVM中
使用JdkCompiler將4.4小節中生成的code進行javac編譯,之后使用ApiDocumentBootstrap的類加載器加載編譯好的class文件,返回Class對象;
Class<?> dubboControllerClazz = jdkCompiler.compile(code, ApiDocumentBootstrap.class.getClassLoader());
3.6 注冊Class對象到BeanDefinitionRegistry中
然后創建Class對象的BeanDefinition對象,最后注冊到BeanDefinitionRegistry中。
/**
* 獲取clazz的BeanDefinition并注冊到BeanDefinitionRegistry中
*
* @param clazz
* @param context
*/
public static void registerBeanDefinition(Class<?> clazz, ApplicationContext context) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) context;
BeanDefinitionRegistry beanDefinitonRegistry = (BeanDefinitionRegistry) configurableApplicationContext.getBeanFactory();
beanDefinitonRegistry.registerBeanDefinition(clazz.getCanonicalName(), beanDefinitionBuilder.getBeanDefinition());
}
這樣之后,springmvc在掃描controller的時候,就會將生成的一并掃描了,SpringFox也會統一掃描所有的controller,生成相關的文檔和圖形化調用界面。
3.7 設置系統屬性
最后,設置系統屬性。
System.setProperty(DubboSpringfoxContants.SERVICE_GROUP, serviceGroup);
將當前Dubbo服務的group讀取出來并存儲到系統屬性中。在構建springfox.documentation.spring.web.plugins.Docket實例的時候使用。通過Docket實例可以配置一系列與SpringFox相關的信息,其中最重要的就是指定圖形化UI界面在何時可以顯示接口信息。
@Bean
public Docket docket() {
final String serviceGroup = System.getProperty(DubboSpringfoxContants.SERVICE_GROUP, "dev");
if (serviceGroup.equals(DubboSpringfoxContants.SERVICE_GROUP_PRODUCT)) {
return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.none()).build();
}
return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any())
// .paths(PathSelectors.regex("^(/error/)"))
.build().useDefaultResponseMessages(false)
.apiInfo(new ApiInfo("", "", "", "", new Contact("", "", ""), "", "", new ArrayList<VendorExtension>()));
}
這里讀取剛剛存放到系統屬性中的Dubbo服務的group信息,然后判斷如果是"product"則不在UI上顯示接口信息,其他的group或者沒有配置group都會顯示。
假設配置為這樣:
<dubbo:service interface="io.hulk.dubbo.springfox.demo.springboot.api.BookService" ref="bookService" group="product"/>
UI界面為:
查看返回結果:http://localhost:8081/v2/api-docs
{
swagger: "2.0",
info: {
description: "Api Documentation",
version: "1.0",
title: "Api Documentation",
termsOfService: "urn:tos",
contact:{},
license: {
name: "Apache 2.0",
url: "http://www.apache.org/licenses/LICENSE-2.0"
}
},
host: "localhost:8081",
basePath: "/"
}
也沒有相關的接口信息,這樣就保證了在生產環境的安全性。
附1、dubbo-springfox注解附表
對于springmvc controller接口來講,支持全部的Swagger注解;對于Dubbo服務接口來講,僅支持如下注解:
@Api("xxx")
用于類上,注解內屬性僅支持value和tags。默認不寫屬性名就是value。
@ApiOperation(value="xxx", nickname ="xxx")
用于方法上,注解內屬性僅支持value和nickname。nickname用于區分重載的方法。默認不寫屬性名就是value。
@ApiParam(value="xxx", required="xxx", defaultValue="xxx")
用于參數上,注解內屬性僅支持value、required和defaultValue。默認不寫屬性名就是value。
附2、Q&A
Q1:dubbo-springfox是否支持注解形式配置的Dubbo服務?
不支持。
Q2:方法重載時是否必須填寫nickname?
方法重載時必須添加@ApiOperation(nickname="xxx")注解和注解屬性,否則會報出springmvc匹配路徑重復的錯誤。
Q3:為什么沒有看到前臺代碼,就可以顯示出UI界面?
SpringFox-UI采用webjar技術,將swagger-ui打包成jar包,是的后端可以以jar包的形式來引入前端代碼。關于webjar技術,參考:https://www.webjars.org/