springmvc与swagger的集成

来源:互联网 发布:lua for mac 编辑:程序博客网 时间:2024/05/21 10:29

引子

由于我们公司在尝试着做前后端分离,那么后端的api文档是必须的,因此研究了一下swagger。swagger的2.0版本已经升级为springfox, 看这名字就知道springfox只能与springmvc做集成。

swagger介绍

  1. Swagger-Core,swagger最核心的部分,能够根据注解来生成swagger definition(json或者yaml格式)
  2. Swagger Codegen,顾名思义,根据swagger definition生成代码
  3. Swagger UI,用来展示swagger definition的web项目
  4. Swagger Editor,用来编辑swagger definition

本文主要涉及swagger-core,swagger-ui方面的内容

配置

  1. 首先添加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会展现如下页面

swagger-ui

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

原创粉丝点击