
一、配置多个接口路径
1.1 分析
单个接口路径的配置是:
**.apis(RequestHandlerSelectors.basePackage("cn.van.swagger.group.web.controller"))
那我们就来参考一下RequestHandlerSelectors.java
源码
public class RequestHandlerSelectors {private RequestHandlerSelectors() {throw new UnsupportedOperationException();}public static Predicate<RequestHandler> any() {return Predicates.alwaysTrue();}public static Predicate<RequestHandler> none() {return Predicates.alwaysFalse();}public static Predicate<RequestHandler> withMethodAnnotation(final Class<? extends Annotation> annotation) {return new Predicate<RequestHandler>() {public boolean apply(RequestHandler input) {return input.isAnnotatedWith(annotation);}};}public static Predicate<RequestHandler> withClassAnnotation(final Class<? extends Annotation> annotation) {return new Predicate<RequestHandler>() {public boolean apply(RequestHandler input) {return (Boolean)RequestHandlerSelectors.declaringClass(input).transform(RequestHandlerSelectors.annotationPresent(annotation)).or(false);}};}private static Function<Class<?>, Boolean> annotationPresent(final Class<? extends Annotation> annotation) {return new Function<Class<?>, Boolean>() {public Boolean apply(Class<?> input) {return input.isAnnotationPresent(annotation);}};}private static Function<Class<?>, Boolean> handlerPackage(final String basePackage) {return new Function<Class<?>, Boolean>() {public Boolean apply(Class<?> input) {return ClassUtils.getPackageName(input).startsWith(basePackage);}};}/*** Predicate 匹配RequestHandler,并为处理程序方法的类提供基本包名.* predicate 包括与所提供的basePackage匹配的所有请求处理程序** @param basePackage - base package of the classes* @return this*/public static Predicate<RequestHandler> basePackage(final String basePackage) {return new Predicate<RequestHandler>() {public boolean apply(RequestHandler input) {return (Boolean)RequestHandlerSelectors.declaringClass(input).transform(RequestHandlerSelectors.handlerPackage(basePackage)).or(true);}};}private static Optional<? extends Class<?>> declaringClass(RequestHandler input) {return Optional.fromNullable(input.declaringClass());}}
我们看到 Swagger
是通过 Predicate
的apply()
方法的返回值来判断是非匹配的,所以我们的方案是:通过改造basePackage()
方法来实现多包扫描。
1.2 方案测试
•Swagger2Config.java
配置多包扫描
@EnableSwagger2@Configurationpublic class Swagger2Config {/*** 定义分隔符*/private static final String SEPARATE = ";";@Beanpublic Docket createRestApi() {return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()// 指定controller存放的目录路径单个路径.apis(RequestHandlerSelectors.basePackage("cn.van.swagger.group.web.controller"))多个路径.apis(basePackage("cn.van.swagger.group.web.controller" + SEPARATE +"cn.van.swagger.group.api")).paths(PathSelectors.any()).build();}private ApiInfo apiInfo() {return new ApiInfoBuilder().title("这里是Swagger2构建Restful APIs").description("这里是文档描述").termsOfServiceUrl("https://www.dustyblog.cn").version("v1.0").build();}public static Predicate<RequestHandler> basePackage(final String basePackage) {return input -> declaringClass(input).transform(handlerPackage(basePackage)).or(true);}private static Function<Class<?>, Boolean> handlerPackage(final String basePackage) {return input -> {for (String strPackage : basePackage.split(SEPARATE)) {boolean isMatch = input.getPackage().getName().startsWith(strPackage);if (isMatch) {return true;}}return false;};}private static Optional<? extends Class<?>> declaringClass(RequestHandler input) {return Optional.fromNullable(input.declaringClass());}}
1.3 测试
•在api
包下建立另一个接口类:MultipleController.java
@RestController@RequestMapping("/swagger2")@Api(tags = "第二个包的Swagger接口")public class MultipleController {/*** 无参方法* @return*/@GetMapping("/sayHello")public String sayHello(){return "hello MultipleController!";}}
•启动项目,查看Swagger
接口

由上图可知扫描到两个路径的接口,说明配置多个接口路径成功!
二、接口分组
2.1 背景
我们在 Spring Boot
中定义各个接口是以Controller
作为第一级维度来进行组织的,Controller
与具体接口之间的关系是一对多的关系。我们通常将同属一个模块的接口定义在一个Controller
里。
默认情况下,Swagger
是以Controller
为单位,对接口进行分组管理的,这个分组的元素在Swagger
中称为Tag
。但是这里的Tag
与接口的关系并不是一对多的,它支持更丰富的多对多关系。
2.2 默认分组
默认情况下,Swagger
是如何根据Controller
来组织Tag
与接口关系的。例如:
•TeacherController.java
@Api(tags = "教师工作")@RestController@RequestMapping(value = "/teacher")public class TeacherController {@PostMapping("/teaching")@ApiOperation(value = "教书")public String teaching() {return "teaching....";}@PostMapping("/preparing")@ApiOperation(value = "备课")public String preparing() {return "preparing....";}}
•StudentController.java
@Api(tags = "学生任务")@RestController@RequestMapping(value = "/student")public class StudentController {@ApiOperation("学习")@GetMapping("/study")public String study() {return "study....";}}
启动项目,查看此时Swagger
接口

到这里,我们还都只是Tag
与Controller
一一对应。
2.3 合并分组
Swagger
中还支持更灵活的分组,查看@Api
源码,tags
属性其实是个数组类型
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inheritedpublic @interface Api {String value() default "";String[] tags() default {""};...}
由此可以推测:可以通过定义同名的Tag
来汇总Controller
中的接口。
上手验证
•定义一个Tag
为“教学管理”,让这个分组同时包含教师工作和学生任务的所有接口。•TeacherController.java
修改为:
@Api(tags = {"教师工作", "教学管理"})@RestController@RequestMapping(value = "/teacher")public class TeacherController {@PostMapping("/teaching")@ApiOperation(value = "教书")public String teaching() {return "teaching....";}@PostMapping("/preparing")@ApiOperation(value = "备课")public String preparing() {return "preparing....";}}
•StudentController.java
修改为:
@Api(tags = {"学生任务", "教学管理"})@RestController@RequestMapping(value = "/student")public class StudentController {@ApiOperation("学习")@GetMapping("/study")public String study() {return "study....";}}
•启动项目,Swagger
接口如下:

此时,学生和教师的接口均在教学管理之下,说明该方案下合并分组成功。
2.4 精准分组
通过
@Api
可以实现将指定Controller
中的所有接口合并到一个Tag
中,但是如果产品希望:教学管理接口下包含学生任务中所有接口以及教师工作管理中的接口teaching
(不包含接口preparing
)。
•通过@ApiOperation
中的tags
属性做更细粒度的接口划分,修改TeacherController.java
为:
@Api(tags = "教师工作")@RestController@RequestMapping(value = "/teacher")public class TeacherController {@PostMapping("/teaching")@ApiOperation(value = "教书", tags = "教学管理")public String teaching() {return "teaching....";}@PostMapping("/preparing")@ApiOperation(value = "备课")public String preparing() {return "preparing....";}}
•启动项目,Swagger
接口如下:

此时,教师工作中的接口preparing
不在教学管理中,说明粒度更细的精准分组生效。
三、总结及示例代码
本文讲解的Swagger
的多接口路径扫描和分组在博主开发的进程中,场景不多,但确实用到了,所以稍微整理了下,以防万一。




