Introduction to swagger
Swagger is indeed a good thing. It can automatically generate API interface documents based on business code, especially for projects in restful style. Developers can hardly have to maintain the restap API specifically. This framework can automatically generate restfut-style APIs for your business code, and also provides a corresponding test interface to automatically display the response in the json format. It greatly facilitates the communication and coordination costs between backend developers and front-end.
Introduction to springfox-swagger
With the powerful functions of swagger, the Java open source industry big spring framework quickly kept up. It fully utilized its own advantages, integrated swagger into its own project, and integrated a spring-swagger, which later evolved into springfox. Springfox itself only uses its own AOP characteristics and integrates swagger into the plug. Its own generation of business APIs still rely on swagger to achieve it.
There is relatively little information about this framework, and most of them are entry-level simple use. I encountered many pitfalls in the process of integrating this framework into my project. In order to solve these pitfalls, I had to dig out its source code to see what it was. This article describes my understanding of springfox and what needs to be paid attention to during the use of springfox.
The general principle of springfox
The general principle of springfox is that in the process of project startup, during the initialization process of spring context, the framework automatically loads some swagger-related beans into the current context according to the configuration, and automatically scans the classes that may need to generate API documents in the system, and generates corresponding information cache. If the MVC control layer of the project uses springMvc, it will automatically scan all Controller classes to generate corresponding API documents according to the methods in these Controller classes.
Since my project is SpringMvc, this article uses Srping mvc integration springfox as an example to discuss the use and principles of springfox.
Steps to integrate SpringMvc into springfox
First, the project needs to add the following three dependencies:
<!-- sring mvc dependency--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.2.8.RELEASE</version> </dependency><!-- swagger2 core dependency--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.1</version> </dependency> <!-- swagger-ui provides API display and testing interface for projects --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.1</version> </dependency>
The above three dependencies are the most basic dependencies for project integration SpringMvc and Springfox, and other dependencies are omitted here. The first one is the basic dependency of springmvc, the second is the swagger dependency, and the third is the interface-related dependency. This is not necessary. If you don’t want to use the API interface that comes with springfox, you can also not use this, and write a set of interfaces that suit your project. After adding these dependencies, the system will automatically add some jar packages related to springfox and swagger. I took a brief look and found that there are mainly the following:
springfox-swagger2-2.6.1.jar
swagger-annotations-1.5.10.jar
swagger-models-1.5.10.jar
springfox-spi-2.6.1.jar
springfox-core-2.6.1.jar
springfox-schema-2.6.1.jar
springfox-swagger-common-2.6.1.jar
springfox-spring-web-2.6.1.jar
guava-17.0.jar
spring-plugin-core-1.2.0.RELEASE.jar
spring-plug-metadata-1.2.0.RELEASE.jar
spring-swagger-ui-2.6.1.jar
jackson-databind-2.2.3.jar
jackson-annotations-2.2.3.jar
The above is the jars that I visually think springfox may need, and may not fully illustrate all the jars that springfox needs. From the above jar, we can see that in addition to relying on swagger, Pringfox also requires guava, spring-plug, jackson and other dependencies (note that jackson is a necessary jar package for generating json. If this dependency is not added to the project itself, in order to integrate swagger, this dependency must be added).
Simple use of springfox
If you only use the default configuration of springfox, integrating with springmvc is very simple. Just write a class similar to the following code and put it in your project. The code is as follows:
@Configuration@EnableWebMvc@EnableSwagger2publicclass ApiConfig {}Note that above is an empty java class file, the class name can be specified at will, but the three annotations marked @Configuration, @EnableWebMvc, and @EnableSwagger2 in the above class must be added. This completes the basic integration of springmvc and springfox. With three annotations, after the project starts, you can directly use an address similar to the following to view the API list: http://127.0.0.1:8080/jadDemo/swagger-ui.html
This is indeed a very magical effect. With three simple annotations, the system will automatically display all the APIs of all Controller classes in the project. Now, let’s start with this configuration class and simply analyze its principles. There is no code in this class, and it is obvious that the three annotations play a crucial role. Among them, the @Configuration annotation is already available in the spring framework. It is an annotation identified by the @Component meta annotation. Therefore, with this annotation, spring will automatically instantiate the class into a bean and register it into the spring context. The second annotation @EnableWebMvc therefore means that srpingmvc is enabled. Click this annotation in Eclipse for a brief look. It is to stuff a bean of type DelegatingWebMvcConfiguration into the spring context through the meta annotation @Import(DelegatingWebMvcConfiguration.class). I think the purpose of this class should be to provide swagger with some springmvc configuration. The third annotation: @EnableSwagger2. You can think of the name. It is used to integrate swagger 2. Through the meta annotation: @Import({Swagger2DocumentationConfiguration.class}), it introduced a Swagger2DocumentationConfiguration type configuration bean, and this is the core configuration of Swagger. The code inside it is as follows:
@Configuration@Import({ SpringfoxWebMvcConfiguration.class, SwaggerCommonConfiguration.class })@ComponentScan(basePackages = { "springfox.documentation.swagger2.readers.parameter", "springfox.documentation.swagger2.web", "springfox.documentation.swagger2.mappers"})publicclassSwagger2DocumentationConfiguration { @Bean public JacksonModuleRegistrar swagger2Module() { returnnewSwagger2JacksonModule(); }}This class header uses some annotations, and then introduces the SpringfoxWebMvcConfiguration class and the SwaggerCommonConfiguration class, and automatically scans springfox .swagger2-related beans into the spring context through ComponentScan annotation. Here, what I am most interested in is the SpringfoxWebMvcConfiguration class. I guess this class should be the more core configuration of springfox integrated mvc. Click in and see the following code:
@Configuration@Import({ModelsConfiguration.class })@ComponentScan(basePackages = { "springfox.documentation.spring.web.scanners","springfox.documentation.spring.web.readers.operation","springfox.documentation.spring.web.readers.operation","springfox.documentation.spring.parameter","springfox.documentation.spring.web.plugins","springfox.documentation.spring.web.paths"})@EnablePluginRegistries({ DocumentationPlugin.class, ApiListingBuilderPlugin.class, OperationBuilderPlugin.class, ParameterBuilderPlugin.class, ExpandedParameterBuilderPlugin.class, ResourceGroupingStrategy.class, OperationModelsProviderPlugin.class, DefaultsProviderPlugin.class, PathDecorator.class})publicclassSpringfoxWebMvcConfiguration {}The following code in this class is nothing more than adding some new beans through the @Bean annotation. I am not very interested in it. What I am most interested in is the things added to the head through @EnablePluginRegistries. springfox is based on the spring-plug mechanism to integrate swagger. How does spring-plug be implemented? I don’t have time to study the principle of spring-plug yet. But below, I will mention that I write a plug plugin to extend the functionality of swagger. The plug added above through @EnablePluginRegistries is not available yet. The codes I have seen mainly include ApiListingBuilderPlugin.class, OperationBuilderPlugin.class, ParameterBuilderPlugin.class, ExpandedParameterBuilderPlugin.class, ExpandedParameterBuilderPlugin.class,
The first ApiListingBuilderPlugin, which has two implementation classes, namely ApiListingReader and SwaggerApiListingReader. Among them, the ApiListingReader will automatically generate an API list according to the Controller type, and the SwaggerApiListingReader will generate an API list according to the class identified by the @Api annotation. The OperationBuilderPlugin plugin is used to generate specific API documents. This type of plugin has many implementation classes. They each divide their labor and do their own things. I didn't look at the details carefully, but only focused on one of the implementation classes: OperationParameterReader. This class is a Plugin used to read API parameters. It relies on the ModelAttributeParameterExpander tool class, which can automatically parse command objects of non-simple type in the interface method parameters in the Controller to obtain a parameter list containing all attributes (there is a pit here that may cause infinite recursion, which is introduced below). The ExpandedParameterBuilderPlugin plug-in is mainly used to extend some functions of interface parameters, such as determining the data type of this parameter and whether it is a necessary parameter for this interface, etc. Overall, the entire springfox-swagger is actually transported by this series of plugs. When the system starts, they are tuned up, some are used to scan out the interface list, some are used to read interface parameters, etc. Their common purpose is to scan out all API interfaces in the system and cache them for users to view. So, how are this series of table plugs tuned and where are their execution entrances?
We put our attention point on the ComponentScan annotation content at the code header of the SpringfoxWebMvcConfiguration class above. In this annotation, a package called springfox.documentation.spring.web.plugins is scanned. This package can be found in springfox-spring-web-2.6.1.jar. Under this package, we found that there are two very core classes, namely DocumentationPluginsManager and DocumentationPluginsBootstrapper. For the first DocumentationPluginsManager, it is a bean that does not implement any interface, but it has many properties of PluginRegistry type, and all of them are injected into the property value through the @Autowired annotation. Combining its class name, it is easy to think that this is a manager that manages all plugs. It is easy to understand, because of the configuration of ComponentScan annotation, all plug instances will be instantiated into a bean by spring, and then injected into this DocumentationPluginsManager instance and managed uniformly. Another important class in this package DocumentationPluginsBootstrapper, you can guess by looking at the name, it may be the startup class of plug. When you click in and look at the specifics, you will find that it is indeed a component identified by @Component, and its construction method injects the DocumentationPluginsManager instance just described, and the most critical thing is that it also implements the SmartLifecycle interface. Anyone who knows the life cycle of spring beans knows that when this component is instantiated into a bean and is managed in the srping context, its start() method will be automatically called. When you click on start() to look at the code, you will find that it has a line of code scanDocumentation(buildContext(each)); which is used to scan API documents. By further tracking the code of this method, you can find that this method will eventually use its DocumentationPluginsManager property to adjust all plugs together to scan the entire system and generate API documents. The scan results are cached in a map property of the DocumentationCache class.
The above is the general principle of srpingMvc integrating springfox. It mainly injects a series of beans into the srping context through the EnableSwagger2 annotation, and automatically scans the system's Controller class when the system starts, generates corresponding API information and caches it. In addition, it injects some Controller classes identified by @Controller annotation as the entry for the ui module to access the API list. For example, the Swagger2Controller class in the springfox-swagger2-2.6.1.jar package. This Controller is the interface address used in the ui module to access the API list. When you visit the address http://127.0.0.1:8080/jadDemo/swagger-ui.html to view the API list, you can see through the browser that it is asynchronously obtaining the API information (Json format) through an address similar to http://127.0.0.1:8080/jadDemo/v2/api-docs?group=sysGroup and displaying it on the interface. The Controller entry corresponding to the background of this address is the Swagger2Controller class above. After receiving the request, this class directly fetches the API information from the cache that was initialized in advance to generate a json string return.
After understanding the principles of springfox, let’s take a look at what pitfalls I encountered during the use of springfox.
Springfox's first big pit: the bean generated by the configuration class must share the same context as spring mvc.
As described above, in the springmvc project, integrating springfox is just to write a simple configuration class as follows without any business code in the project.
@Configuration@EnableWebMvc@EnableSwagger2publicclass ApiConfig {}Because of the @Configuration annotation, spring will automatically instantiate it into a bean and inject it into the context. But one pitfall to be noted is that the context in which this bean must be in the same context as spring mvc. How to understand? Because in actual spring mvc projects, there are usually two contexts, one is following context and the other is spring mvc (it is a subcontext that follows context). The context is the org.springframework.web.context.request.RequestContextListener listener related to spring in the web.xml file. The loaded context is usually written as a configuration file called spring-contet.xml. The beans here will eventually be initialized into the context. It mainly includes service, dao and other beans in the system, as well as data sources, things, etc. Another context is spring mvc, which is loaded through the org.springframework.web.servlet.DispatcherServlet related to spring mvc in web.xml. It usually has a configuration file called spring-mvc.xml. When writing the ApiConfig class, if we decide to load it with the @Configuration annotation, we must ensure that the path of this class is within the base-package scope of the component-scan configuration in springmvc. Because when ApiConfig is loaded by spring, a series of beans will be injected. In these beans, in order to automatically scan out all Controller classes, some beans need to rely on some beans in SpringMvc. If the project separates the context of Srpingmvc from the context as a subcontext of the context. If you accidentally let this ApiConfig type bean be loaded with the previous text, because there are no configuration classes in the spring mvc context in the root context.
In fact, I do not agree with configuring Swagger through the @Configuration annotation, because I think Swagger's api functionality is optional for production projects. Our Swagger is often used to test environments for project front-end team development or for other systems to integrate interfaces. Once the system is online, it is likely that these API lists will be hidden on the production system. But if the configuration is written in the java code through the @Configuration annotation, then when you want to remove this function when you go online, it will be embarrassing and you have to modify the java code to recompile. Based on this, I recommend a method to configure the most traditional XML file by spring. The specific method is to remove the @Configuration annotation, and then it writes a bean configuration similar to <bean/> into the spring XML configuration file. In a project where the root context is separated from the mvc context, it is directly configured into spring-mvc.xml, which ensures that it must be in the same context as the springmvc context.
The second biggest pit of springfox: the parameters of the Controller class, pay attention to prevent infinite recursion.
Spring mvc has a powerful parameter binding mechanism, which can automatically bind request parameters into a custom command object. Therefore, in order to be lazy, many developers directly use an entity object as a parameter of the Controller method when writing a Controller. For example, the following example code:
@RequestMapping(value = "update")public String update(MenuVomenuVo, Model model){}This is the code that most programmers like to write in Controller to modify an entity. When integrating with swagger, there is a big pit here. If all the properties in MenuVo are basic types, then it's fine, nothing goes wrong. But if there are some other custom type attributes in this class, and this attribute directly or indirectly exists attributes of its own type, then there will be problems. For example: If the MenuVo class is a menu class, it also contains a property parent of the MenuVo type that represents its parent menu. In this way, the swagger module will directly report an error when the system starts because it cannot load the API. The reason for the error is that when loading this method, the parameters of the update method will be parsed. When the parameter MenuVo is not a simple type, all its class attributes will be automatically interpreted recursively. This makes it easy to fall into a dead loop of infinite recursion.
In order to solve this problem, I have just written an OperationParameterReader plug-in implementation class and the ModelAttributeParameterExpander tool class it depends on. It replaces the original two classes of srpingfox through configuration, replaces the logic of parsing the parameter analysis like a column, and avoids infinite recursion. Of course, this is equivalent to a way to modify the source code level. I have not found a more perfect solution to this problem yet, so I can only recommend that you try to avoid this infinite recursion when using spring-fox Swagger. After all, this does not comply with the specifications of springmvc command objects. The command objects with springmvc parameter are preferably only simple basic type attributes.
Springfox's third major pit: API grouping related, Docket instances cannot be loaded latently
Springfox will divide all APIs into a group by default. When accessed through an address similar to http://127.0.0.1:8080/jadDemo/swagger-ui.html, all API lists will be loaded on the same page. In this way, if the system is a little larger and the API is a little more, the page will be faked to death, so it is very necessary to group the API. Api grouping is defined by the @Bean annotation in the ApiConf configuration file. Common configurations on the Internet are as follows:
@EnableWebMvc@EnableSwagger2publicclass ApiConfig {@Bean public Docket customDocket() { return newDocket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()); }}In the above code, a Docket is injected through @Bean. This configuration is not necessary. If this configuration is not available, the framework will generate a default Docket instance by itself. The purpose of this Docket instance is to specify the public information of all APIs it can manage, such as basic information such as API version, author, etc., and to specify which APIs are listed only (filtered by API addresses or annotations).
There can be multiple Docket instances, such as the following code:
@EnableWebMvc@EnableSwagger2publicclass ApiConfig {@Bean public Docket customDocket1() { return newDocket(DocumentationType.SWAGGER_2).groupName("apiGroup1").apiInfo(apiInfo()).select().paths(PathSelectors.ant("/sys/**")); }@Bean public Docket customDocket2() { return newDocket(DocumentationType.SWAGGER_2).groupName("apiGroup2").apiInfo(apiInfo()).select().paths(PathSelectors.ant("/shop/**")); }}When multiple Docket instances are configured in the project, the api can be grouped, for example, the above code divides the api into two groups. In this case, each group must be assigned a different name, such as "apiGroup1" and "apiGroup2" in the above code. Each group can use paths to specify which group to manage which APIs through ant-style address expression. For example, in the above configuration, the first group of management addresses are APIs with the beginning of /sys/. The second group of management APIs with the beginning of /shop/. Of course, there are many other filtering methods, such as class annotation, method annotation, address regular expressions, etc. After grouping, you can select different api groups in the drop-down option in the upper right corner of the api list interface. This will disperse the project's API list to different pages. This will facilitate management without pretending to be dead because the page needs to load too many APIs.
However, like using @Configuration, I do not agree with using @Bean to configure Docket instances to group APIs. Because of this, the code will also be written to death. So, I recommend configuring your own Docket instance in the XML file to implement these similar functions. Of course, considering the many attributes in the Docket, it is more troublesome to configure beans directly. You can write a FactoryBean for the Docket yourself and then configure FactoryBean in the XML file. However, when configuring Docket into XML. You will encounter another big pit, that is, the loading method of spring on beans is lazy loaded by default. After directly configuring these Docket instance beans in XML. You will find that there is no effect, and there is no grouping item in the drop-down list in the upper left corner of the page.
This problem has troubled me for several hours. Later, based on experience, it was speculated that it might be because the spring bean is lazy loading by default, and this Docket instance has not been loaded into the spring context. As it turns out, my guess is correct. I don't know if this is a bug in springfox, or if I shouldn't have moved the Docket configuration from the original java code to the XML configuration file.
Other pitfalls in springfox: There are some other pitfalls in springfox. For example, in the @ApiOperation annotation, if the httpMethod attribute is not specified as a certain get or post method, all methods such as get, post, delete, put will be listed in the API list, so that the API list is duplicated too much, which is very ugly. In addition, during testing, I encountered login permission issues, etc. These piles of small pits that are easier to solve, because of the limited space, I won’t say much. There are also the usage of annotations such as @Api, @ApiOperation and @ApiParam. I won’t repeat many documents on this online.
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.