Springboot Series Do you really know Swagger documentation?

Posted by tristanlee85 on Tue, 26 Nov 2019 02:42:03 +0100

Preface

At present, it is quite popular to build a microservice using Springboot in Java. When building a microservice, most of us choose to expose a REST API for invocation.Or the company uses a separate front-end and back-end development model where front-end and back-end work is developed by completely different engineers.Maintaining a complete and up-to-date REST API document, whether it is a micro-service or a separate front-end development, will greatly improve our productivity.However, traditional ways of updating documents, such as writing manually, are difficult to ensure the timeliness of documents, and they often get out of repair and lose their due significance.Therefore, it is necessary to choose a new API document maintenance method, which is also the content of this article.

1. Introduction to OpenAPI specifications

OpenAPI Specification is short for OAS, also known as OpenAPI Description Specification in Chinese. OpenAPI file can describe the whole API. It formulates a set of language-independent RET API description specifications suitable for general use, such as API path specification, request method specification, request parameter specification, return format specification and other related information, so that human and computer can not need access.The source code understands and uses the capabilities of the service.

The following are the recommended API design specifications and basic path design specifications in the OpenAPI specification.

https://api.example.com/v1/users?role=admin&status=active
\________________________/\____/ \______________________/
         server URL       endpoint    query parameters
                            path

There are also specifications for the design of the passed-along parameters, as follows:

There's more to the OpenAPI specification than that. The latest version of the OpenAPI specification is 3.0.2. If you want to know more about the OpenAPI specification, you can visit the links below.
OpenAPI Specification (https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md)

2. Introduction to Swagger

Many people think Swagger is just an interface document generation framework, but it is not.Swagger is a set of open source tools built around the OpenAPI Specification (OAS, also known as the OpenAPI specification in Chinese).It is a huge project to help you provide solutions throughout the API development cycle, from API design to API document output to API testing to final API deployment.Swagger is not only free, but also open source. Whether you are an enterprise user or an individual player, you can use Swagger's solutions to build stunning REST API s.

Swagger has several major products.

  • Swagger Editor - A browser-based Open API specification editor.
  • Swagger UI - A tool to render OpenAPI specifications as interactive online documents.
  • Swagger Codegen - A tool to generate call code from OpenAPI.

If you want more information, visit Swagger's official website https://swagger.io.

3. Introduction to Springfox

Originating from the popularity of the Spring framework in Java, a foreigner named Marrty Pitt got the idea of adding an interface description for Spring MVC, so he created a project called swagger-springmvc, which complies with the OpenAPI specification (OAS), which allows Spring projects to automatically generate OpenAPI documents in JSON format.This framework also uses annotations to configure information, following the Spring project development habits.

The project then evolved into Springfox and then expanded to springfox-swagger2. In order to make the API documents in JSON format better presented, springfox-swagger-ui appeared to show and test the generated OpenAPI.The springfox-swagger-ui here is actually the Wagger-ui described above, but it is packaged into the jar package by webjar and introduced by maven.

As mentioned above, Springfox-swagger2 is also configured with annotations, so how do you use it?Below are some commonly used comments, which are used in the Springboot Integration Swagger below.

annotation Example describe
@ApiModel @ApiModel(value = "user object") Describe an entity object
@ApiModelProperty @ApiModelProperty(value = User ID, required = true, example = 1000) Describe attribute information, perform description, if necessary, give examples
@Api @Api(value = "user action API(v1)", tags = "user action interface") Used on interface classes to add descriptions for interface classes
@ApiOperation @ApiOperation(value = "new user") A method or interface that describes a class
@ApiParam @ApiParam(value = username, required = true) Describe a single parameter

More information about Springfox can be found on the official Springfox website.

Springfox Reference Documentation (http://springfox.github.io)

4. Springboot Integration Swagger

At present, the Springboot framework is a very popular framework for microservices, under which many times we directly provide REST APIs.If there is no documentation for the REST API, users will have a headache.But don't worry, as mentioned above, a foreign man named Marrty Pitt has created a project that has evolved into Springfox to easily provide OpenAPI specification and documentation support in JSON format.springfox-swagger-ui is extended for page display.

It is important to note that the so-called Swagger used here is not really one thing, it is the Swagger implementation provided by Springfox.They are all API builds based on OpenAPI specifications.So you can also use Swagger-ui for page rendering of the API.

4.1. Create a project

How to create a Springboot project is not mentioned here. You can create a Springboot project directly from Official Springboot Download a standard project, use idea to quickly create a Springboot project, or copy a Springboot project to test it. In short, there are many ways to choose from.

The following demonstrates how to use swagger2 in a Springboot project.

4.2. Introducing dependencies

This is mainly the introduction of springfox-swagger2, which can be annotated to generate a JSON-formatted OpenAPI interface document, then introduced because Springfox relies on jackson.springfox-swagger-ui displays the resulting OpenAPI interface document as a page.Lombok's introduction can generate get/set methods for entity classes through annotations.

<dependencies> 
    <!-- Spring Boot web Development Integration -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <artifactId>spring-boot-starter-json</artifactId>
                <groupId>org.springframework.boot</groupId>
            </exclusion>
        </exclusions>
    </dependency>

    <!-- Introduce swagger2 Dependency on-->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.9.2</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.9.2</version>
    </dependency>
    
    <!-- jackson Related Dependencies -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.5.4</version>
    </dependency>

    <!-- Lombok tool -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

4.3. Configure Springfox-swagger

The Springfox-swagger configuration is wrapped in a Docket, where the apiInfo method passes in descriptive information about the overall interface.The apis method specifies the specific path of the package to be scanned.Add @Configuration to the class to declare that it is a configuration class and finally open Springfox-swagger2 using @EnableSwagger2.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * <p>
 * Springfox-swagger2 To configure
 *
 * @Author niujinpeng
 * @Date 2019/11/19 23:17
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("net.codingme.boot.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Unread Code API")
                .description("Public number: unread code(weidudaima) springboot-swagger2 Online Excuse Documentation")
                .termsOfServiceUrl("https://www.codingme.net")
                .contact("Darcy")
                .version("1.0")
                .build();
    }
}

4.4. Coding

The article won't list all the code, which doesn't make much sense, so just post the main code, and the complete code will be uploaded to Github with a Github link at the bottom of the article.

Parameter entity class User.java, uses @ApiModel and @ApiModelProperty to describe parameter objects, uses @NotNull for data validation, and uses @Data to automatically generate get/set methods for parameter entity classes.

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;

import javax.validation.constraints.NotNull;
import java.util.Date;

/**
 * <p>
 * User Entity Class
 *
 * @Author niujinpeng
 * @Date 2018/12/19 17:13
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "User Objects")
public class User {

    /**
     * User ID
     *
     * @Id Primary key
     * @GeneratedValue Self-adding Primary Key
     */
    @NotNull(message = "user ID Cannot be empty")
    @ApiModelProperty(value = "user ID", required = true, example = "1000")
    private Integer id;

    /**
     * User name
     */
    @NotNull(message = "User name cannot be empty")
    @ApiModelProperty(value = "User name", required = true)
    private String username;
    /**
     * Password
     */
    @NotNull(message = "Password cannot be empty")
    @ApiModelProperty(value = "User Password", required = true)
    private String password;
    /**
     * Age
     */
    @ApiModelProperty(value = "User Age", example = "18")
    private Integer age;
    /**
     * Birthday
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")
    @ApiModelProperty(value = "User Birthday")
    private Date birthday;
    /**
     * Skill
     */
    @ApiModelProperty(value = "User Skills")
    private String skills;
}

Write the Controller layer, describe the interface class with @Api, describe the interface with @ApiOperation, and describe the interface parameters with @ApiParam.The code adds tags = "user query" tags to both interfaces that query user information, so the two methods are grouped together into a common tag group when generating Swagger interface documents.

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import net.codingme.boot.domain.Response;
import net.codingme.boot.domain.User;
import net.codingme.boot.enums.ResponseEnum;
import net.codingme.boot.utils.ResponseUtill;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * User Action
 *
 * @Author niujinpeng
 * @Date 2019/11/19 23:17
 */

@Slf4j
@RestController(value = "/v1")
@Api(value = "User Action API(v1)", tags = "User Operational Interface")
public class UserController {

    @ApiOperation(value = "New Users")
    @PostMapping(value = "/user")
    public Response create(@Valid User user, BindingResult bindingResult) throws Exception {
        if (bindingResult.hasErrors()) {
            String message = bindingResult.getFieldError().getDefaultMessage();
            log.info(message);
            return ResponseUtill.error(ResponseEnum.ERROR.getCode(), message);
        } else {
            // Add user information do something
            return ResponseUtill.success("user[" + user.getUsername() + "]Information added");
        }
    }

    @ApiOperation(value = "delete user")
    @DeleteMapping(value = "/user/{username}")
    public Response delete(@PathVariable("username")
                           @ApiParam(value = "User name", required = true) String name) throws Exception {
        // Delete user information do something
        return ResponseUtill.success("user[" + name + "]Information deleted");
    }

    @ApiOperation(value = "Modify User")
    @PutMapping(value = "/user")
    public Response update(@Valid User user, BindingResult bindingResult) throws Exception {
        if (bindingResult.hasErrors()) {
            String message = bindingResult.getFieldError().getDefaultMessage();
            log.info(message);
            return ResponseUtill.error(ResponseEnum.ERROR.getCode(), message);
        } else {
            String username = user.getUsername();
            return ResponseUtill.success("user[" + username + "]Information modified");
        }
    }

    @ApiOperation(value = "Get individual user information", tags = "User Query")
    @GetMapping(value = "/user/{username}")
    public Response get(@PathVariable("username")
                        @NotNull(message = "User name cannot be empty")
                        @ApiParam(value = "User name", required = true) String username) throws Exception {
        // Query user information do something
        User user = new User();
        user.setId(10000);
        user.setUsername(username);
        user.setAge(99);
        user.setSkills("cnp");
        return ResponseUtill.success(user);
    }

    @ApiOperation(value = "Get User List", tags = "User Query")
    @GetMapping(value = "/user")
    public Response selectAll() throws Exception {
        // Query User Information List do something
        User user = new User();
        user.setId(10000);
        user.setUsername("Unread Code");
        user.setAge(99);
        user.setSkills("cnp");
        List<User> userList = new ArrayList<>();
        userList.add(user);
        return ResponseUtill.success(userList);
    }
}

Finally, to make the code more canonical and useful, use a uniform class for interface response.

@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "Response information")
public class Response {
    /**
     * Response Code
     */
    @ApiModelProperty(value = "Response Code")
    private String code;
    /**
     * Response information
     */
    @ApiModelProperty(value = "Response information")
    private String message;

    /**
     * Response data
     */
    @ApiModelProperty(value = "Response data")
    private Collection content;
}

4.5. Run Access

Start the Springboog project directly, and you can see the access paths for each interface scanned by the console output, including / 2/api-docs.

This is also the description of the JSON access path for the generated OpenAPI specification, which you can see.

As we introduced dependencies above, we also introduced the springfox-swagger-ui package, so we can also access the page documentation for the API.The access path is/swagger-ui.html, and you can see what you see when you visit it.

You can also see that the two methods of user queries come together because they use the same tag attribute in their annotations.

4.7. Call tests

springfox-swagger-ui not only generates API documentation, but also provides the ability to call tests.The following is a test on the page to get individual user information.

  1. Click on the interface [/user/{username}] to get individual user information.
  2. Click **Try it out** to enter the test pass-through page.
  3. Enter the parameters and click the Execute blue button to execute the call.
  4. View the return information.

Below is a screenshot of the response during the test.

5. Common Errors

If you often find errors like the one below while running the program.

java.lang.NumberFormatException: For input string: ""
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[na:1.8.0_111]
    at java.lang.Long.parseLong(Long.java:601) ~[na:1.8.0_111]
    at java.lang.Long.valueOf(Long.java:803) ~[na:1.8.0_111]
    at io.swagger.models.parameters.AbstractSerializableParameter.getExample(AbstractSerializableParameter.java:412) ~[swagger-models-1.5.20.jar:1.5.20]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_111]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_111]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_111]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_111]
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:536) [jackson-databind-2.5.4.jar:2.5.4]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:666) [jackson-databind-2.5.4.jar:2.5.4]
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:156) [jackson-databind-2.5.4.jar:2.5.4]
    at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:113) [jackson-databind-2.5.4.jar:2.5.4]

Then you need to check if the @ApiModelProperty annotation sets an example value on a property that uses the @ApiModelProperty annotation and has a field type of numeric type. If not, you need to set it, as shown below.

@NotNull(message = "user ID Cannot be empty")
@ApiModelProperty(value = "user ID", required = true, example = "1000")
private Integer id;

The code in the text has been uploaded to https://github.com/niumoo/springboot

Reference Documents


Personal website: https://www.codingme.net
If you like this article, you can follow the Public Number and grow up together.
Focus on Public Number Reply Resources to get the hottest Java Core Knowledge compilation on the web without routine access - Interview Core Materials.

Topics: Java SpringBoot Spring Lombok