1. Introduction to Swagger
In the previous article, we introduced Spring Boot's support for Restful. In this article, we continue to discuss this topic. However, we will no longer discuss how the Restful API is implemented, but rather discuss the maintenance of Restful API documentation.
In daily work, we often need to provide interfaces to the front-end (WEB end, IOS, Android) or third parties. At this time, we need to provide them with a detailed API documentation. But maintaining a detailed document is not an easy task. First of all, writing a detailed document is a time-consuming and laborious task. On the other hand, since the code and the document are separated, it is easy to cause inconsistencies between the document and the code. In this article, we will share a way to maintain API documents, that is, to automatically generate Restuful API documents through Swagger.
So what is Swagger? We can directly read the official description:
THE WORLD'S MOST POPULAR API TOOLINGSwagger is the world's largest framework of API developer tools for the OpenAPI Specification(OAS),enable development across the entire API lifecycle, from design and documentation, to test and deployment.
This passage first tells you that Swagger is the most popular API tool in the world, and the purpose of Swagger is to support the development of the entire API life cycle, including design, documentation, testing and deployment. In this article, we will use Swagger's document management and testing functions.
After gaining a basic understanding of the role of Swagger, let’s take a look at how to use it.
2. Swagger and Spring boot integration
Step 1: Introduce the corresponding jar package:
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.0</version></dependency><dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.0</version></dependency>
Step 2: Basic information configuration:
@Configuration@EnableSwagger2public class Swagger2Config { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.pandy.blog.rest")) .paths(PathSelectors.regex("/rest/.*")) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("Blog System Restful API") .description("Blog System Restful API") .termsOfServiceUrl("http://127.0.0.1:8080/") .contact("liuxiaopeng") .version("1.0") .build(); }}The basic configuration is a description of the entire API document and some global configurations, which work for all interfaces. There are two annotations involved here:
@Configuration means that this is a configuration class, annotation provided by JDK, and has been explained in the previous article.
@EnableSwagger2's function is to enable Swagger2-related functions.
In this configuration class, I instantiated a Docket object, which mainly includes three aspects of information:
(1) The description information of the entire API, that is, the information included in the ApiInfo object, this part of the information will be displayed on the page.
(2) Specify the package name to generate the API document.
(3) Specify the path to generate the API. The path-based API can support four modes, which can be used to refer to its source code:
public class PathSelectors { private PathSelectors() { throw new UnsupportedOperationException(); } public static Predicate<String> any() { return Predicates.alwaysTrue(); } public static Predicate<String> none() { return Predicates.alwaysFalse(); } public static Predicate<String> regex(final String pathRegex) { return new Predicate<String>() { public boolean apply(String input) { return input.matches(pathRegex); } }; } public static Predicate<String> ant(final String antPattern) { return new Predicate<String>() { public boolean apply(String input) { AntPathMatcher matcher = new AntPathMatcher(); return matcher.match(antPattern, input); } }; }}As can be seen from the source code, Swagger supports four ways: generation of any path, non-generating of any path, and regular matching and ant pattern matching. You may be more familiar with the first three types, the last one of ant matching. If you are not familiar with ant, just ignore it. The first three types should be enough for everyone to use in daily work.
With the above configuration, we can see the effect. I have an ArticleRestController class under the com.pandy.blog.rest package. The source code is as follows:
Start Spring boot, and then visit: http://127.0.0.1:8080/swagger-ui.html to see the following results:
You can see on this page that except for the last interface /test/{id}, the other interfaces generate corresponding documents. Because the last interface does not meet the path we configured - "/rest/.*", no document is generated.
We can also click in to see each specific interface. Let’s take the “POST /rest/article” interface as an example:
As you can see, Swagger generates examples of return results and request parameters for each interface, and can directly access the interface through the "try it out" below. In terms of the interface, everyone tests the interface. Overall, Swagger is still very powerful and the configuration is relatively simple.
@RestControllerpublic class ArticleRestController { @Autowired private ArticleService articleService; @RequestMapping(value = "/rest/article", method = POST, produces = "application/json") public WebResponse<Map<String, Object>> saveArticle(@RequestBody Article article) { article.setUserId(1L); articleService.saveArticle(article); Map<String, Object> ret = new HashMap<>(); ret.put("id", article.getId()); WebResponse<Map<String, Object>> response = WebResponse.getSuccessResponse(ret); return response; } @RequestMapping(value = "/rest/article/{id}", method = DELETE, produces = "application/json") public WebResponse<?> deleteArticle(@PathVariable Long id) { Article article = articleService.getById(id); article.setStatus(-1); articleService.updateArticle(article); WebResponse<Object> response = WebResponse.getSuccessResponse(null); return response; } @RequestMapping(value = "/rest/article/{id}", method = PUT, produces = "application/json") public WebResponse<Object> updateArticle(@PathVariable Long id, @RequestBody Article article) { article.setId(id); articleService.updateArticle(article); WebResponse<Object> response = WebResponse.getSuccessResponse(null); return response; } @RequestMapping(value = "/rest/article/{id}", method = GET, produces = "application/json") public WebResponse<Article> getArticle(@PathVariable Long id) { Article article = articleService.getById(id); WebResponse<Article> response = WebResponse.getSuccessResponse(article); return response; } @RequestMapping(value = "/test/{id}", method = GET, produces = "application/json") public WebResponse<?> getNoApi(){ WebResponse<?> response = WebResponse.getSuccessResponse(null); return response; }}3. Detailed configuration of Swagger API
But you will definitely have some questions when you see this:
The first question: There is no literal description of the return result and the request parameters. Can this be configured?
The second question: This request parameter should be directly reflected based on the object, but not every property of the object is required, and the value of the parameter may not meet our needs. Can this be configured?
The answer is certainly OK. Now let’s solve these two problems and look directly at the configuration code:
package com.pandy.blog.rest;import com.pandy.blog.dto.WebResponse;import com.pandy.blog.po.Article;import com.pandy.blog.service.ArticleService;import io.swagger.annotations.ApiImplicitParam;import io.swagger.annotations.ApiImplicitParams;import io.swagger.annotations.ApiOperation;import io.swagger.annotations.ApiResponse;import io.swagger.annotations.ApiResponse;import io.swagger.annotations.ApiResponse;import io.swagger.annotations.ApiResponse;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Profile;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;import java.util.List;import java.util.Map;import static org.springframework.web.bind.annotation.RequestMethod.DELETE;import static org.springframework.web.bind.annotation.RequestMethod.GET;import static org.springframework.web.bind.annotation.RequestMethod.POST;import static org.springframework.web.bind.annotation.RequestMethod.PUT;@RestController@RequestMapping("/rest")public class ArticleRestController { @Autowired private ArticleService articleService; @RequestMapping(value = "/article", method = POST, produces = "application/json") @ApiOperation(value = "Add article", notes = "Add new article", tags = "Article",httpMethod = "POST") @ApiImplicitParams({ @ApiImplicitParam(name = "title", value = "Article title", required = true, dataType = "String"), @ApiImplicitParam(name = "summary", value = "Article summary", required = true, dataType = "String"), @ApiImplicitParam(name = "status", value = "Publish status", required = true, dataType = "Integer") }) @ApiResponses({ @ApiResponse(code=200,message="Success",response=WebResponse.class), }) public WebResponse<Map<String,Object>> saveArticle(@RequestBody Article article){ articleService.saveArticle(article); Map<String,Object> ret = new HashMap<>(); ret.put("id",article.getId()); WebResponse<Map<String,Object>> response = WebResponse.getSuccessResponse(ret); return response; } @ApiOperation(value = "Delete article", notes = "Delete article by ID", tags = "Article",httpMethod = "DELETE") @ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "Article ID", required = true, dataType = "Long") }) @RequestMapping(value = "/{id}",method = DELETE,produces = "application/json") public WebResponse<?> deleteArticle(@PathVariable Long id){ Article article = articleService.getById(id); article.setStatus(-1); articleService.saveArticle(article); return WebResponse.getSuccessResponse(new HashMap<>()); } @ApiOperation(value = "Get the article list", notes = "Full query according to the title", tags = "Article",httpMethod = "GET") @ApiImplicitParams({ @ApiImplicitParam(name = "title", value = "Article title", required = false, dataType = "String"), @ApiImplicitParam(name = "pageSize", value = "Number of articles per page", required = false, dataType = "Integer"), @ApiImplicitParam(name = "pageNum", value = "PagePage Number", required = false, dataType = "Integer") }) @RequestMapping(value = "/article/list", method = GET, produces = "application/json") public WebResponse<?> listArticles(String title, Integer pageSize, Integer pageNum) { if (pageSize == null) { pageSize = 10; } if (pageNum == null) { pageNum = 1; } int offset = (pageNum - 1) * pageSize; List<Article> articles = articleService.getArticles(title, 1L, offset, pageSize); return WebResponse.getSuccessResponse(articles); } @ApiOperation(value = "Update article", notes = "Update article content", tags = "Article",httpMethod = "PUT") @ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "Article ID", required = true, dataType = "Long"), @ApiImplicitParam(name = "title", value = "Article title", required = false, dataType = "String"), @ApiImplicitParam(name = "summary", value = "Article summary", required = false, dataType = "String"), @ApiImplicitParam(name = "status", value = "Publish status", required = false, dataType = "Integer") }) @RequestMapping(value = "/article/{id}", method = PUT, produces = "application/json") public WebResponse<?> updateArticle(@PathVariable Long id,@RequestBody Article article){ article.setId(id); articleService.updateArticle(article); return WebResponse.getSuccessResponse(new HashMap<>()); }}Let's explain the specific functions of several annotations and related attributes in the code:
@ApiOperation, the entire interface attribute configuration:
value: Interface description, displayed in the interface list.
notes: Detailed description of the interface, displayed on the interface details page.
tags: The tag of the interface. The interface with the same tag will be displayed under a tab page.
httpMethod: Supported HTTP method.
@ApiImplicitParams, a container for @ApiImplicitParam, can contain multiple @ApiImplicitParam annotations
@ApiImplicitParam, request parameter attribute configuration:
name: parameter name
value: parameter description
required: Is it necessary
dataType: data type
@ApiResponses, @ApiResponse container, can contain multiple @ApiResponse annotations
@ApiResponse, return the result attribute configuration:
code: Returns the encoding of the result.
message: Returns the description of the result.
response: Returns the corresponding class of the result.
After completing the above configuration, let’s look at the page effect:
List page:
As you can see, the interfaces are now located under the Article tag, and there are also instructions for our configuration behind the interface. Let's look at the details page of the "POST /rest/article" interface:
The picture is too large, and only the display of the title attribute is intercepted, and the other parameters are similar. We can see from the page that there are instructions for request parameters, but this is not the effect we expected. If our parameters are just simple types, this method should be fine, but the problem now is that our request parameters are an object, so how to configure them? This involves two other annotations: @ApiModel and @ApiModelProperty. Let's look at the code first and then explain it, which is easier to understand:
@ApiModel(value="article object", description="Add & update article object description") public class Article { @Id @GeneratedValue @ApiModelProperty(name = "id",value = "Article ID", required = false,example = "1") private Long id; @ApiModelProperty(name = "title",value = "Article title", required = true,example = "Test article title") private String title; @ApiModelProperty(name = "summary",value = "Article Summary", required = true,example = "Test Article Summary") private String summary; @ApiModelProperty(hidden = true) private Date createTime; @ApiModelProperty(hidden = true) private Date publicTime; @ApiModelProperty(hidden = true) private Date updateTime; @ApiModelProperty(hidden = true) private Long userId; @ApiModelProperty(name = "status",value = "Article Release Status", required = true,example = "1") private Integer status; @ApiModelProperty(name = "type",value = "Article category", required = true,example = "1") private Integer type;}@ApiModel is the configuration of the properties of the entire class:
value: description of class
Description: Detailed description
@ApiModelProperty is the configuration of the properties of each field in detail:
name: field name
value: field description
required: Is it necessary
example: Example value
hidden: Whether to display
After completing the above configuration, let’s look at the effect:
Now we can see that the field description has been shown, and the value of the field in the example has also become the corresponding value of the example property we configured. In this way, a complete API document is generated and the document is closely linked to the code, rather than the two isolated parts. In addition, we can also test it easily through this document. We just need to click on the yellow box under Example Value, and the contents inside will be automatically copied to the corresponding value box of the article, and then click "Try it out" to initiate an http request.
After clicking Try it out, we can see the returned result:
The operation is still very convenient. Compared with Junit and postman, testing through Swagger is more convenient. Of course, Swagger's testing cannot replace unit testing, but it still has a very important effect in joint debugging.
4. Summary
Overall, Swagger's configuration is relatively simple, and Swagger's ability to automatically generate documents has indeed saved us a lot of work and provided great help to subsequent maintenance. In addition, Swagger can automatically generate test data for us according to the configuration and provide corresponding HTTP methods, which is also helpful for our self-test and joint debugging work. Therefore, I still recommend that you use Swagger in daily development, which should help you improve your work efficiency to a certain extent. Finally, let me leave a question for you to think about, that is, the document can be accessed directly through the page, so we can't expose the interface directly to the production environment, especially systems that need to provide external services. So how can we turn off this function in the production process? There are many methods, you can try it yourself.
The above is the actual combat of the Spring Boot integrated Swagger2 project introduced to you by the editor. I hope it will be helpful to you. If you have any questions, please leave me a message and the editor will reply to you in time. Thank you very much for your support to Wulin.com website!