添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
io.springfox springfox-swagger2 2.9.2 io.springfox springfox-swagger-ui 2.9.2
import java.util.Collections;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
  @Value("${spring.swagger.enable:true}")
  private String swaggerEnable;
   * swagger配置
  @Bean
  public Docket api() {
    return new Docket(DocumentationType.SWAGGER_2)
        .select()
        .apis(RequestHandlerSelectors.basePackage("com.imooc.cnblogs.web")) //扫描包路径
        .paths(PathSelectors.any())
        .build()
        .apiInfo(apiInfo())
        .enable("true".equals(swaggerEnable)); // 是否启用,我们可以使用这个属性关闭生产环境的swagger文档
  // 文档的一些描述信息
  private ApiInfo apiInfo() {
    return new ApiInfo(
        "接口文档", "", "1.0", "",
        new Contact("strongmore", "", "xxx@163.com"),
        "strongmore", "", Collections.emptyList());

配置开启swagger注解的扫描

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@ToString
@ApiModel("订单信息模型") //注意,多个@ApiModel的value不能重复
public class OrderInfo {
  @ApiModelProperty("订单号")
  private String orderId;
  @ApiModelProperty("订单创建时间")
  private Long createDate;

在模型类及属性上添加描述注解供swagger扫描处理,主要有@ApiModel和@ApiModelProperty

import com.imooc.cnblogs.model.OrderInfo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/order")
@Api(tags = "订单接口")
public class OrderController {
  @GetMapping("/order/detail")
  @ResponseBody
  @ApiOperation("订单详情查询")
  public OrderInfo queryOrderDetail(@RequestParam @ApiParam("订单号") String orderId) {
    OrderInfo orderInfo = new OrderInfo();
    orderInfo.setOrderId(orderId);
    orderInfo.setCreateDate(System.currentTimeMillis());
    return orderInfo;

在Controller类及方法上添加描述注解供swagger扫描处理,主要有@Api,@ApiOperation,@ApiParam

固定的请求路径为/swagger-ui.html

@EnableSwagger2

通过此注解开启swagger注解的扫描,它会导入Swagger2DocumentationConfiguration配置类。

@Configuration
@Import({ SpringfoxWebMvcConfiguration.class, SwaggerCommonConfiguration.class })
@ComponentScan(basePackages = {
    "springfox.documentation.swagger2.mappers"
@ConditionalOnWebApplication
public class Swagger2DocumentationConfiguration {
  // 用来自定义Jackson这个JSON解析器
  @Bean
  public JacksonModuleRegistrar swagger2Module() {
    return new Swagger2JacksonModule();
  // 处理器映射器,用来处理Swagger2Controller(我们项目中定义的所有接口及Model信息都是此类响应到swagger-ui.html页面的)
  @Bean
  public HandlerMapping swagger2ControllerMapping(
      Environment environment,
      DocumentationCache documentationCache,
      ServiceModelToSwagger2Mapper mapper,
      JsonSerializer jsonSerializer) {
    return new PropertySourcedRequestMappingHandlerMapping(
        environment,
        // 注意,Swagger2Controller没有被扫描到,所以不是一个Bean对象,通过new的方式来创建实例
        new Swagger2Controller(environment, documentationCache, mapper, jsonSerializer));

此配置类又导入了SpringfoxWebMvcConfiguration配置类(重要)和SwaggerCommonConfiguration配置类(不重要)。
SpringfoxWebMvcConfiguration配置类的作用:

  • 指定扫描包路径,扫描很多类型为Plugin的Bean。
  • 通过Spring-Plugin组件向容器注册很多PluginRegistry对象。关于Spring-Plugin组件原理,可以查看Spring Plugin插件系统入门
  • 其中很重要的两个Bean为DocumentationPluginsBootstrapper和DocumentationPluginsManager,
    两者配合通过管理Plugin对象将我们项目中标注了swagger注解的接口和Model收集整理并存储起来,
    在这个过程中就使用到了上面所说的PluginRegistry对象。

    DocumentationPluginsBootstrapper

    此类可以看做一个插件引导器,它实现了SmartLifecycle接口,所以会在ApplicationContext的refresh()流程最后被执行,这也是Spring提供的一个扩展点。

    @Override
    public void start() {
        if (initialized.compareAndSet(false, true)) {
          // 这里的DocumentationPlugin实现类其实就是我们在SwaggerConfig配置类中定义的Docket对象
          List<DocumentationPlugin> plugins = pluginOrdering()
              .sortedCopy(documentationPluginsManager.documentationPlugins());
          for (DocumentationPlugin each : plugins) {
            DocumentationType documentationType = each.getDocumentationType();
            // 是否启用
            if (each.isEnabled()) {
              // 开启文档扫描
              scanDocumentation(buildContext(each));
            } else {
    

    继续跟进去

    private DocumentationContext buildContext(DocumentationPlugin each) {
        // 创建文档上下文
        return each.configure(defaultContextBuilder(each));
    

    其实通过defaultContextBuilder()方法这一步已经获取到了所有的处理器方法(包含@RequestMapping注解的方法),具体来说是通过WebMvcRequestHandlerProvider类,
    它内部会依赖所有的RequestMappingInfoHandlerMapping对象,SpringMVC定义的RequestMappingHandlerMapping处理器映射器就是此类型,其中包含所有的处理器方法。
    关于RequestMappingHandlerMapping的原理,可以查看SpringMVC源码分析之一个请求的处理
    继续分析scanDocumentation()方法

    private void scanDocumentation(DocumentationContext context) {
        try {
          // resourceListing在这里是ApiDocumentationScanner类型,scanned为DocumentationCache(保存所有文档信息)
          scanned.addDocumentation(resourceListing.scan(context));
        } catch (Exception e) {
          log.error(String.format("Unable to scan documentation context %s", context.getGroupName()), e);
    

    ApiDocumentationScanner,从名称就可以看出来,是一个文档扫描器

    public Documentation scan(DocumentationContext context) {
        // 根据所有的处理器方法获取所有的Controller
        ApiListingReferenceScanResult result = apiListingReferenceScanner.scan(context);
        ApiListingScanningContext listingContext = new ApiListingScanningContext(context,
            result.getResourceGroupRequestMappings());
        // 扫描出所有Controller的文档及其中所有的方法文档,包括返回值及参数的文档,这里的apiListingScanner类型为ApiListingScanner
        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())
            .extensions(context.getVendorExtentions())
            .tags(tags);
        return group.build();
    

    继续跟进去ApiListingScanner

    public Multimap<String, ApiListing> scan(ApiListingScanningContext context) {
        final Multimap<String, ApiListing> apiListingMap = LinkedListMultimap.create();
        int position = 0;
        Map<ResourceGroup, List<RequestMappingContext>> requestMappingsByResourceGroup
            = context.getRequestMappingsByResourceGroup();
        Collection<ApiDescription> additionalListings = pluginsManager.additionalListings(context);
        Set<ResourceGroup> allResourceGroups = FluentIterable.from(collectResourceGroups(additionalListings))
            .append(requestMappingsByResourceGroup.keySet())
            .toSet();
        // 这里的allResourceGroups可以看做就是所有的Controller
        for (final ResourceGroup resourceGroup : sortedByName(allResourceGroups)) {
          DocumentationContext documentationContext = context.getDocumentationContext();
          Set<ApiDescription> apiDescriptions = newHashSet();
          Map<String, Model> models = new LinkedHashMap<String, Model>();
          List<RequestMappingContext> requestMappings = nullToEmptyList(requestMappingsByResourceGroup.get(resourceGroup));
          // 扫描Controller下每一个包含@RequestMapping注解的方法
          for (RequestMappingContext each : sortedByMethods(requestMappings)) {
            // 扫描Model,包括方法返回值和参数,主要就是@ApiModel注解和属性上的@ApiModelProperty注解
            models.putAll(apiModelReader.read(each.withKnownModels(models)));
            // 扫描方法上的@ApiOperation注解和参数中的@ApiParam注解
            apiDescriptions.addAll(apiDescriptionReader.read(each));
          List<ApiDescription> sortedApis = FluentIterable.from(apiDescriptions)
              .toSortedList(documentationContext.getApiDescriptionOrdering());
          String resourcePath = new ResourcePathProvider(resourceGroup)
              .resourcePath()
              .or(longestCommonPath(sortedApis))
              .orNull();
          PathProvider pathProvider = documentationContext.getPathProvider();
          String basePath = pathProvider.getApplicationBasePath();
          PathAdjuster adjuster = new PathMappingAdjuster(documentationContext);
          ApiListingBuilder apiListingBuilder = new ApiListingBuilder(context.apiDescriptionOrdering())
              .apiVersion(documentationContext.getApiInfo().getVersion())
              .basePath(adjuster.adjustedPath(basePath))
              .resourcePath(resourcePath)
              .produces(produces)
              .consumes(consumes)
              .host(host)
              .protocols(protocols)
              .securityReferences(securityReferences)
              .apis(sortedApis)
              .models(models)
              .position(position++)
              .availableTags(documentationContext.getTags());
          ApiListingContext apiListingContext = new ApiListingContext(
              context.getDocumentationType(),
              resourceGroup,
              apiListingBuilder);
          // 扫描Controller类上的@Api注解
          apiListingMap.put(resourceGroup.getGroupName(), pluginsManager.apiListing(apiListingContext));
        return apiListingMap;
    

    至此所有文档信息已经收集完成了,接下来就是显示到页面上。

    Swagger2Controller

    http://localhost:8081/cnblogs/swagger-ui.html请求路径开始,这个swagger-ui.html是swagger框架提供的

    SpringMVC通过SimpleUrlHandlerMapping处理器映射器根据swagger-ui.html查找到对应处理器为ResourceHttpRequestHandler,
    对应的处理器适配器为HttpRequestHandlerAdapter。

    swagger-ui.html会请求/v2/api-docs这个接口来获取文档信息,Swagger2Controller提供了此接口,对Swagger2Controller的配置有疑问的话,可以看上面的
    @EnableSwagger2原理

    @Controller
    @ApiIgnore
    public class Swagger2Controller {
      public static final String DEFAULT_URL = "/v2/api-docs";
      private final DocumentationCache documentationCache;
      private final ServiceModelToSwagger2Mapper mapper;
      private final JsonSerializer jsonSerializer;
      // 请求路径为 /v2/api-docs
      @RequestMapping(
          value = DEFAULT_URL,
          method = RequestMethod.GET,
          produces = { APPLICATION_JSON_VALUE, HAL_MEDIA_TYPE })
      @PropertySourcedMapping(
          value = "${springfox.documentation.swagger.v2.path}",
          propertyKey = "springfox.documentation.swagger.v2.path")
      @ResponseBody
      public ResponseEntity<Json> getDocumentation(
          @RequestParam(value = "group", required = false) String swaggerGroup,
          HttpServletRequest servletRequest) {
        // 组名为default
        String groupName = Optional.fromNullable(swaggerGroup).or(Docket.DEFAULT_GROUP_NAME);
        // documentationCache存储着所有的文档信息
        Documentation documentation = documentationCache.documentationByGroup(groupName);
        if (documentation == null) {
          return new ResponseEntity<Json>(HttpStatus.NOT_FOUND);
        // 将文档对象转换成Swagger对象
        Swagger swagger = mapper.mapDocumentation(documentation);
        // 转成JSON字符串并响应给页面
        return new ResponseEntity<Json>(jsonSerializer.toJson(swagger), HttpStatus.OK);
    

    最终的文档数据为

    "swagger": "2.0", "info": { "version": "1.0", "title": "接口文档", "contact": { "name": "strongmore", "email": "xxx@163.com" "license": { "name": "strongmore" "host": "localhost:8081", "basePath": "/cnblogs", "tags": [ "name": "cnblogs-back-up-controller", "description": "Cnblogs Back Up Controller" "name": "订单接口", "description": "Order Controller" "paths": { "/cnblogs/backup": { "get": { "tags": [ "cnblogs-back-up-controller" "summary": "backUp", "operationId": "backUpUsingGET", "produces": [ "*/*" "responses": { "200": { "description": "OK" "401": { "description": "Unauthorized" "403": { "description": "Forbidden" "404": { "description": "Not Found" "deprecated": false "/cnblogs/index": { "get": { "tags": [ "cnblogs-back-up-controller" "summary": "index", "operationId": "indexUsingGET", "produces": [ "*/*" "responses": { "200": { "description": "OK", "schema": { "type": "string" "401": { "description": "Unauthorized" "403": { "description": "Forbidden" "404": { "description": "Not Found" "deprecated": false "/cnblogs/swagger/index": { "get": { "tags": [ "cnblogs-back-up-controller" "summary": "swaggerIndex", "operationId": "swaggerIndexUsingGET", "produces": [ "*/*" "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/ModelAndView" "401": { "description": "Unauthorized" "403": { "description": "Forbidden" "404": { "description": "Not Found" "deprecated": false "/cnblogs/testSendMqtt": { "get": { "tags": [ "cnblogs-back-up-controller" "summary": "testSendMqtt", "operationId": "testSendMqttUsingGET", "produces": [ "*/*" "responses": { "200": { "description": "OK" "401": { "description": "Unauthorized" "403": { "description": "Forbidden" "404": { "description": "Not Found" "deprecated": false "/order/order/detail": { "get": { "tags": [ "订单接口" "summary": "订单详情查询", "operationId": "queryOrderDetailUsingGET", "produces": [ "*/*" "parameters": [ "name": "orderId", "in": "query", "description": "订单号", "required": false, "type": "string", "allowEmptyValue": false "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/订单信息模型" "401": { "description": "Unauthorized" "403": { "description": "Forbidden" "404": { "description": "Not Found" "deprecated": false "definitions": { "ModelAndView": { "type": "object", "properties": { "empty": { "type": "boolean" "model": { "type": "object" "modelMap": { "type": "object", "additionalProperties": { "type": "object" "reference": { "type": "boolean" "status": { "type": "string", "enum": [ "100 CONTINUE", "101 SWITCHING_PROTOCOLS", "102 PROCESSING", "103 CHECKPOINT", "200 OK", "201 CREATED", "202 ACCEPTED", "203 NON_AUTHORITATIVE_INFORMATION", "204 NO_CONTENT", "205 RESET_CONTENT", "206 PARTIAL_CONTENT", "207 MULTI_STATUS", "208 ALREADY_REPORTED", "226 IM_USED", "300 MULTIPLE_CHOICES", "301 MOVED_PERMANENTLY", "302 FOUND", "302 MOVED_TEMPORARILY", "303 SEE_OTHER", "304 NOT_MODIFIED", "305 USE_PROXY", "307 TEMPORARY_REDIRECT", "308 PERMANENT_REDIRECT", "400 BAD_REQUEST", "401 UNAUTHORIZED", "402 PAYMENT_REQUIRED", "403 FORBIDDEN", "404 NOT_FOUND", "405 METHOD_NOT_ALLOWED", "406 NOT_ACCEPTABLE", "407 PROXY_AUTHENTICATION_REQUIRED", "408 REQUEST_TIMEOUT", "409 CONFLICT", "410 GONE", "411 LENGTH_REQUIRED", "412 PRECONDITION_FAILED", "413 PAYLOAD_TOO_LARGE", "413 REQUEST_ENTITY_TOO_LARGE", "414 URI_TOO_LONG", "414 REQUEST_URI_TOO_LONG", "415 UNSUPPORTED_MEDIA_TYPE", "416 REQUESTED_RANGE_NOT_SATISFIABLE", "417 EXPECTATION_FAILED", "418 I_AM_A_TEAPOT", "419 INSUFFICIENT_SPACE_ON_RESOURCE", "420 METHOD_FAILURE", "421 DESTINATION_LOCKED", "422 UNPROCESSABLE_ENTITY", "423 LOCKED", "424 FAILED_DEPENDENCY", "425 TOO_EARLY", "426 UPGRADE_REQUIRED", "428 PRECONDITION_REQUIRED", "429 TOO_MANY_REQUESTS", "431 REQUEST_HEADER_FIELDS_TOO_LARGE", "451 UNAVAILABLE_FOR_LEGAL_REASONS", "500 INTERNAL_SERVER_ERROR", "501 NOT_IMPLEMENTED", "502 BAD_GATEWAY", "503 SERVICE_UNAVAILABLE", "504 GATEWAY_TIMEOUT", "505 HTTP_VERSION_NOT_SUPPORTED", "506 VARIANT_ALSO_NEGOTIATES", "507 INSUFFICIENT_STORAGE", "508 LOOP_DETECTED", "509 BANDWIDTH_LIMIT_EXCEEDED", "510 NOT_EXTENDED", "511 NETWORK_AUTHENTICATION_REQUIRED" "view": { "$ref": "#/definitions/View" "viewName": { "type": "string" "title": "ModelAndView" "View": { "type": "object", "properties": { "contentType": { "type": "string" "title": "View" "订单信息模型": { "type": "object", "properties": { "createDate": { "type": "integer", "format": "int64", "description": "订单创建时间" "orderId": { "type": "string", "description": "订单号" "title": "订单信息模型"
  • 通过@EnableSwagger2注解将swagger中各种扫描器及插件注册到容器中。
  • DocumentationPluginsBootstrapper使用各种扫描器收集各种文档信息,最终保存到DocumentationCache对象中。
  • Swagger2Controller提供一个接口,可以查询DocumentationCache中的文档信息。
  • swagger-ui.html请求Swagger2Controller的接口获取到文档信息,展示到页面上。
  • Swagger官网