springmvc与swagger的集成
引子
由于我们公司在尝试着做前后端分离,那么后端的api文档是必须的,因此研究了一下swagger。swagger的2.0版本已经升级为springfox, 看这名字就知道springfox只能与springmvc做集成。
swagger介绍
- Swagger-Core,swagger最核心的部分,能够根据注解来生成swagger definition(json或者yaml格式)
- Swagger Codegen,顾名思义,根据swagger definition生成代码
- Swagger UI,用来展示swagger definition的web项目
- Swagger Editor,用来编辑swagger definition
本文主要涉及swagger-core,swagger-ui方面的内容
配置
- 首先添加maven依赖,通过maven repository可以知道兼容的spring的最低版本是4.1.8.RELEASE
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.6.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.6.4</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.4.0</version>
</dependency>
- 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
2.swagger config配置
package com.xxx.swagger;
import static com.google.common.collect.Lists.newArrayList;
import static springfox.documentation.builders.PathSelectors.ant;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ImplicitGrantBuilder;
import springfox.documentation.builders.OAuthBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.GrantType;
import springfox.documentation.service.LoginEndpoint;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.ApiKeyVehicle;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableWebMvc
@EnableSwagger2
@ComponentScan("com.xxx")
public class SpringfoxDocConfig {
@Bean
public Docket petApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.build()
.securitySchemes(newArrayList(oauth()))
.securityContexts(newArrayList(securityContext()));
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Springfox petstore API")
.description("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum " +
"has been the industry's standard dummy text ever since the 1500s, when an unknown printer "
+ "took a " +
"galley of type and scrambled it to make a type specimen book. It has survived not only five " +
"centuries, but also the leap into electronic typesetting, remaining essentially unchanged. " +
"It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum " +
"passages, and more recently with desktop publishing software like Aldus PageMaker including " +
"versions of Lorem Ipsum.")
.termsOfServiceUrl("http://springfox.io")
.contact("springfox")
.license("Apache License Version 2.0")
.licenseUrl("https://github.com/springfox/springfox/blob/master/LICENSE")
.version("2.0")
.build();
}
@Bean
SecurityContext securityContext() {
AuthorizationScope readScope = new AuthorizationScope("read:pets", "read your pets");
AuthorizationScope[] scopes = new AuthorizationScope[1];
scopes[0] = readScope;
SecurityReference securityReference = SecurityReference.builder()
.reference("petstore_auth")
.scopes(scopes)
.build();
return SecurityContext.builder()
.securityReferences(newArrayList(securityReference))
.forPaths(ant("/api/pet.*"))
.build();
}
@Bean
SecurityScheme oauth() {
return new OAuthBuilder()
.name("petstore_auth")
.grantTypes(grantTypes())
.scopes(scopes())
.build();
}
@Bean
SecurityScheme apiKey() {
return new ApiKey("api_key", "api_key", "header");
}
List<AuthorizationScope> scopes() {
return newArrayList(
new AuthorizationScope("write:pets", "modify pets in your account"),
new AuthorizationScope("read:pets", "read your pets"));
}
List<GrantType> grantTypes() {
GrantType grantType = new ImplicitGrantBuilder()
.loginEndpoint(new LoginEndpoint("http://petstore.swagger.io/api/oauth/dialog"))
.build();
return newArrayList(grantType);
}
@Bean
public SecurityConfiguration securityInfo() {
return new SecurityConfiguration("abc", "123", "pets", "petstore", "123", ApiKeyVehicle.HEADER, "", ",");
}
}
- 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
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
3.配置controller,这里仅举一例,详细完整的例子在springfox项目中的springfox-petstore模块里面
@Controller
@RequestMapping(value = "/api/user", produces = MediaType.APPLICATION_JSON_VALUE)
@Api(value = "/user", description = "Operations about user")
public class UserController {
UserRepository userRepository = new UserRepository();
static class UserRepository extends MapBackedRepository<String, User> {
}
@RequestMapping(value = "/{username}", method = GET)
@ApiOperation(value = "Get user by user name", response = User.class)
@ApiResponses(value = {
@ApiResponse(code = 400, message = "Invalid username supplied"),
@ApiResponse(code = 404, message = "User not found")})
public ResponseEntity<User> getUserByName(
@ApiParam(value = "The name that needs to be fetched. Use user1 for testing. ", required = true)
@PathVariable("username") String username) {
User user = userRepository.get(username);
if (null != user) {
return new ResponseEntity<User>(user, HttpStatus.OK);
} else {
throw new NotFoundException(404, "User not found");
}
}
}
- 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
4.下载swagger-ui, 将dist目录copy到web项目中
5.测试一下,访问http://host:port/v2/api-docs应该可以下载swagger definition,访问http://host:port/swagger-ui/index.html会展现如下页面
6.在有些项目中spring mvc的请求是以某一后缀(url-pattern, 例如 .do)结尾的, 这样就要做一些必要的修改,在swagger-ui.js中,修改如下
opts.responseContentType = $('div select[name=responseContentType]', $(this.el)).val();
opts.requestContentType = $('div select[name=parameterContentType]', $(this.el)).val();
$('.response_throbber', $(this.el)).show();
// here, add suffix
var suffix = ".do";
if (!this.model.path.endsWith(suffix)) {
this.model.path = this.model.path + suffix;
}
if (isFileUpload) {
$('.request_url', $(this.el)).html('<pre></pre>');
$('.request_url pre', $(this.el)).text(this.invocationUrl);
opts.useJQuery = true;
map.parameterContentType = 'multipart/form-data';
this.map = map;
return this.model.execute(map, opts, this.showCompleteStatus, this.showErrorStatus, this);
} else {
this.map = map;
return this.model.execute(map, opts, this.showCompleteStatus, this.showErrorStatus, this);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
这样在swagger里面try it out的时候,即能发出正确的请求了
springfox源码走读
我们请求的地址是http://host:port/v2/api-docs,这个请求是Swagger2Controller
来处理的
@ApiIgnore
@RequestMapping(value = "${springfox.documentation.swagger.v2.path:" + DEFAULT_URL + "}",
method = RequestMethod.GET, produces = { APPLICATION_JSON_VALUE, HAL_MEDIA_TYPE })
public
@ResponseBody
ResponseEntity<Json> getDocumentation(
@RequestParam(value = "group", required = false) String swaggerGroup,
HttpServletRequest servletRequest) {
String groupName = Optional.fromNullable(swaggerGroup).or(Docket.DEFAULT_GROUP_NAME);
Documentation documentation = documentationCache.documentationByGroup(groupName);
if (documentation == null) {
return new ResponseEntity<Json>(HttpStatus.NOT_FOUND);
}
Swagger swagger = mapper.mapDocumentation(documentation);
if (isNullOrEmpty(swagger.getHost())) {
swagger.host(hostName(servletRequest));
}
return new ResponseEntity<Json>(jsonSerializer.toJson(swagger), HttpStatus.OK);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
文档的数据是从documentationCache
里面拿的,那么documentationCache
的数据又是怎么添加进去的呢?在DocumentationPluginsBootstrapper
中,其实是一个监听器,接收到ContextRefreshedEvent
事件后会扫描所有的文档插件
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
if (initialized.compareAndSet(false, true)) {
log.info("Context refreshed");
List<DocumentationPlugin> plugins = pluginOrdering()
.sortedCopy(documentationPluginsManager.documentationPlugins());
log.info("Found {} custom documentation plugin(s)", plugins.size());
for (DocumentationPlugin each : plugins) {
DocumentationType documentationType = each.getDocumentationType();
if (each.isEnabled()) {
scanDocumentation(buildContext(each));
} else {
log.info("Skipping initializing disabled plugin bean {} v{}",
documentationType.getName(), documentationType.getVersion());
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
那么scan的具体细节又是什么呢?在ApiDocumentationScanner
中我们看到DocumentationBuilder
先组装context的全局配置,然后apiListingScanner.scan(listingContext)
会扫描所有的controller的api信息,具体操作在ApiListingScanner
的scan方法中
public Documentation scan(DocumentationContext context) {
ApiListingReferenceScanResult result = apiListingReferenceScanner.scan(context);
ApiListingScanningContext listingContext = new ApiListingScanningContext(context,
result.getResourceGroupRequestMappings());
Multimap<String, ApiListing> apiListings = apiListingScanner.scan(listingContext);
DocumentationBuilder group = new DocumentationBuilder()
.name(context.getGroupName())
.apiListingsByResourceGroupName(apiListings)
.produces(context.getProduces())
.consumes(context.getConsumes())
.host(context.getHost())
.schemes(context.getProtocols())
.basePath(context.getPathProvider().getApplicationBasePath())
.tags(toTags(apiListings));
Set<ApiListingReference> apiReferenceSet = newTreeSet(listingReferencePathComparator());
apiReferenceSet.addAll(apiListingReferences(apiListings, context));
ResourceListing resourceListing = new ResourceListingBuilder()
.apiVersion(context.getApiInfo().getVersion())
.apis(from(apiReferenceSet).toSortedList(context.getListingReferenceOrdering()))
.securitySchemes(context.getSecuritySchemes())
.info(context.getApiInfo())
.build();
group.resourceListing(resourceListing);
return group.build();
}
- 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
总的来说springfox的原理比较简单,但是在文档格式的规定上比较严格
参考
http://swagger.io/
https://github.com/swagger-api/swagger-core/wiki/Annotations
上一篇: Android百度地图应用之图层展示
下一篇: JBrowse使用说明:参考基因组准备